@@ -745,6 +745,137 @@ def is_disconnect(
745
745
746
746
return any (indicator in error_msg for indicator in disconnect_indicators )
747
747
748
+ def get_table_options (
749
+ self , connection : Any , table_name : str , schema : Optional [str ] = None , ** kw : Any ,
750
+ ) -> Dict [str , Any ]:
751
+ """Reflect table options including SingleStore-specific features."""
752
+ options = super ().get_table_options (
753
+ connection , table_name , schema = schema , ** kw ,
754
+ )
755
+
756
+ # Parse the CREATE TABLE statement to extract SingleStore features
757
+ parsed_state = self ._parsed_state_or_create (
758
+ connection , table_name , schema , ** kw ,
759
+ )
760
+
761
+ # Convert parsed SingleStore features back to dialect options
762
+ if hasattr (parsed_state , 'singlestore_features' ):
763
+ from sqlalchemy_singlestoredb .ddlelement import (
764
+ ShardKey , SortKey , VectorKey , RowStore , ColumnStore ,
765
+ )
766
+
767
+ for feature_type , spec in parsed_state .singlestore_features .items ():
768
+ if feature_type == 'shard_key' :
769
+ # Convert parsed spec back to ShardKey object
770
+ # Handle multiple formats:
771
+ # 1. New SingleStore format: [(col_name, direction), ...]
772
+ # 2. MySQL fallback format: [(col_name, direction, extra), ...]
773
+ # 3. Legacy format: [col_name, ...]
774
+ columns = spec ['columns' ]
775
+ column_specs = []
776
+
777
+ if columns and isinstance (columns [0 ], tuple ):
778
+ # Check if this is our new format or MySQL format
779
+ first_tuple = columns [0 ]
780
+ if (
781
+ len (first_tuple ) == 2 and isinstance (first_tuple [1 ], str ) and
782
+ first_tuple [1 ] in ('ASC' , 'DESC' )
783
+ ):
784
+ # New SingleStore format: [(col_name, direction), ...]
785
+ column_specs = columns
786
+ else :
787
+ # MySQL parser format: [(col_name, direction, extra), ...]
788
+ # Extract just the column names
789
+ column_specs = [(col [0 ], 'ASC' ) for col in columns if col [0 ]]
790
+ else :
791
+ # Legacy SingleStore parser format: [col_name, ...]
792
+ column_specs = [(col , 'ASC' ) for col in columns ]
793
+
794
+ # Check for metadata_only flag
795
+ metadata_only = spec .get ('metadata_only' , False )
796
+ shard_key = ShardKey (* column_specs , metadata_only = metadata_only )
797
+ options ['singlestoredb_shard_key' ] = shard_key
798
+
799
+ elif feature_type == 'sort_key' :
800
+ # Convert parsed spec back to SortKey object
801
+ # Handle multiple formats (same logic as shard_key)
802
+ columns = spec ['columns' ]
803
+ column_specs = []
804
+
805
+ if columns and isinstance (columns [0 ], tuple ):
806
+ # Check if this is our new format or MySQL format
807
+ first_tuple = columns [0 ]
808
+ if (
809
+ len (first_tuple ) == 2 and isinstance (first_tuple [1 ], str ) and
810
+ first_tuple [1 ] in ('ASC' , 'DESC' )
811
+ ):
812
+ # New SingleStore format: [(col_name, direction), ...]
813
+ column_specs = columns
814
+ else :
815
+ # MySQL parser format: [(col_name, direction, extra), ...]
816
+ # Extract just the column names
817
+ column_specs = [(col [0 ], 'ASC' ) for col in columns if col [0 ]]
818
+ else :
819
+ # Legacy SingleStore parser format: [col_name, ...]
820
+ column_specs = [(col , 'ASC' ) for col in columns ]
821
+
822
+ sort_key = SortKey (* column_specs )
823
+ options ['singlestoredb_sort_key' ] = sort_key
824
+
825
+ elif feature_type == 'vector_key' :
826
+ # Convert parsed spec back to VectorKey object
827
+ # Handle both SingleStore format (list of strings) and MySQL
828
+ # fallback format (list of tuples)
829
+ columns = spec ['columns' ]
830
+ if columns and isinstance (columns [0 ], tuple ):
831
+ # MySQL parser format: [(col_name, direction, extra), ...]
832
+ # Extract just the column names
833
+ column_names = [col [0 ] for col in columns if col [0 ]]
834
+ else :
835
+ # SingleStore parser format: [col_name, ...]
836
+ column_names = columns
837
+
838
+ vector_key = VectorKey (
839
+ * column_names ,
840
+ name = spec .get ('name' ),
841
+ index_options = spec .get ('index_options' ),
842
+ )
843
+ # For vector keys, store as list if multiple exist
844
+ existing = options .get ('singlestoredb_vector_key' )
845
+ if existing :
846
+ if isinstance (existing , list ):
847
+ existing .append (vector_key )
848
+ else :
849
+ options ['singlestoredb_vector_key' ] = [existing , vector_key ]
850
+ else :
851
+ options ['singlestoredb_vector_key' ] = vector_key
852
+
853
+ elif feature_type == 'table_type' :
854
+ # Convert parsed table type spec back to TableType object
855
+ is_rowstore = spec .get ('is_rowstore' , False )
856
+ is_temporary = spec .get ('is_temporary' , False )
857
+ is_global_temporary = spec .get ('is_global_temporary' , False )
858
+ is_reference = spec .get ('is_reference' , False )
859
+
860
+ if is_rowstore :
861
+ # Create RowStore with appropriate modifiers
862
+ table_type = RowStore (
863
+ temporary = is_temporary ,
864
+ global_temporary = is_global_temporary ,
865
+ reference = is_reference ,
866
+ )
867
+ else :
868
+ # Default to ColumnStore (handles CREATE TABLE without ROWSTORE)
869
+ # Note: ColumnStore doesn't support global_temporary
870
+ table_type = ColumnStore (
871
+ temporary = is_temporary ,
872
+ reference = is_reference ,
873
+ )
874
+
875
+ options ['singlestoredb_table_type' ] = table_type
876
+
877
+ return options
878
+
748
879
def on_connect (self ) -> Optional [Callable [[Any ], None ]]:
749
880
"""Return a callable that will be executed on new connections."""
750
881
def connect (dbapi_connection : Any ) -> None :
0 commit comments