@@ -711,3 +711,250 @@ func (m *Manager) ExecuteQuery(cfg *ConnectionConfig, database, query string) (*
711711 result .Count = len (result .Rows )
712712 return result , nil
713713}
714+
715+ type ColumnSchema struct {
716+ Name string `json:"name"`
717+ Type string `json:"type"`
718+ Nullable bool `json:"nullable"`
719+ Default interface {} `json:"default"`
720+ Key string `json:"key"`
721+ Extra string `json:"extra"`
722+ }
723+
724+ type IndexSchema struct {
725+ Name string `json:"name"`
726+ Columns []string `json:"columns"`
727+ Unique bool `json:"unique"`
728+ Primary bool `json:"primary"`
729+ }
730+
731+ type TableSchema struct {
732+ Columns []ColumnSchema `json:"columns"`
733+ Indexes []IndexSchema `json:"indexes"`
734+ }
735+
736+ func (m * Manager ) DescribeTable (cfg * ConnectionConfig , database , table string ) (* TableSchema , error ) {
737+ driver := m .getDriver (cfg .Type )
738+ if driver == "" {
739+ return nil , fmt .Errorf ("unsupported database type: %s" , cfg .Type )
740+ }
741+
742+ cfgCopy := * cfg
743+ cfgCopy .Database = database
744+
745+ dsn , err := m .buildDSN (& cfgCopy )
746+ if err != nil {
747+ return nil , err
748+ }
749+
750+ db , err := sql .Open (driver , dsn )
751+ if err != nil {
752+ return nil , err
753+ }
754+ defer db .Close ()
755+
756+ table = strings .ReplaceAll (table , "`" , "" )
757+ table = strings .ReplaceAll (table , "'" , "" )
758+ table = strings .ReplaceAll (table , "\" " , "" )
759+ table = strings .ReplaceAll (table , ";" , "" )
760+
761+ schema := & TableSchema {
762+ Columns : []ColumnSchema {},
763+ Indexes : []IndexSchema {},
764+ }
765+
766+ switch cfg .Type {
767+ case "mysql" , "mariadb" :
768+ if err := m .describeMySQLTable (db , table , schema ); err != nil {
769+ return nil , err
770+ }
771+ case "postgresql" :
772+ if err := m .describePostgresTable (db , table , schema ); err != nil {
773+ return nil , err
774+ }
775+ default :
776+ return nil , fmt .Errorf ("unsupported database type: %s" , cfg .Type )
777+ }
778+
779+ return schema , nil
780+ }
781+
782+ func (m * Manager ) describeMySQLTable (db * sql.DB , table string , schema * TableSchema ) error {
783+ rows , err := db .Query (fmt .Sprintf ("DESCRIBE `%s`" , table ))
784+ if err != nil {
785+ return err
786+ }
787+ defer rows .Close ()
788+
789+ for rows .Next () {
790+ var field , colType , null , key string
791+ var defaultVal , extra sql.NullString
792+
793+ if err := rows .Scan (& field , & colType , & null , & key , & defaultVal , & extra ); err != nil {
794+ continue
795+ }
796+
797+ col := ColumnSchema {
798+ Name : field ,
799+ Type : colType ,
800+ Nullable : null == "YES" ,
801+ Key : key ,
802+ Extra : extra .String ,
803+ }
804+ if defaultVal .Valid {
805+ col .Default = defaultVal .String
806+ }
807+ schema .Columns = append (schema .Columns , col )
808+ }
809+
810+ indexRows , err := db .Query (fmt .Sprintf ("SHOW INDEX FROM `%s`" , table ))
811+ if err != nil {
812+ return nil
813+ }
814+ defer indexRows .Close ()
815+
816+ indexMap := make (map [string ]* IndexSchema )
817+ for indexRows .Next () {
818+ var tableName , keyName , columnName string
819+ var nonUnique int
820+ var seqInIndex , cardinality sql.NullInt64
821+ var collation , subPart , packed , null , indexType , comment , indexComment sql.NullString
822+ var visible sql.NullString
823+
824+ cols , _ := indexRows .Columns ()
825+ var scanArgs []interface {}
826+ if len (cols ) >= 15 {
827+ scanArgs = []interface {}{& tableName , & nonUnique , & keyName , & seqInIndex , & columnName ,
828+ & collation , & cardinality , & subPart , & packed , & null , & indexType , & comment , & indexComment , & visible }
829+ if len (cols ) > 14 {
830+ var extra sql.NullString
831+ scanArgs = append (scanArgs , & extra )
832+ }
833+ } else {
834+ scanArgs = []interface {}{& tableName , & nonUnique , & keyName , & seqInIndex , & columnName ,
835+ & collation , & cardinality , & subPart , & packed , & null , & indexType , & comment , & indexComment }
836+ }
837+
838+ if err := indexRows .Scan (scanArgs [:len (cols )]... ); err != nil {
839+ continue
840+ }
841+
842+ if _ , exists := indexMap [keyName ]; ! exists {
843+ indexMap [keyName ] = & IndexSchema {
844+ Name : keyName ,
845+ Columns : []string {},
846+ Unique : nonUnique == 0 ,
847+ Primary : keyName == "PRIMARY" ,
848+ }
849+ }
850+ indexMap [keyName ].Columns = append (indexMap [keyName ].Columns , columnName )
851+ }
852+
853+ for _ , idx := range indexMap {
854+ schema .Indexes = append (schema .Indexes , * idx )
855+ }
856+
857+ return nil
858+ }
859+
860+ func (m * Manager ) describePostgresTable (db * sql.DB , table string , schema * TableSchema ) error {
861+ query := `
862+ SELECT
863+ c.column_name,
864+ c.data_type || COALESCE('(' || c.character_maximum_length::text || ')', '') as full_type,
865+ c.is_nullable,
866+ c.column_default,
867+ CASE
868+ WHEN pk.column_name IS NOT NULL THEN 'PRI'
869+ WHEN uq.column_name IS NOT NULL THEN 'UNI'
870+ ELSE ''
871+ END as key_type
872+ FROM information_schema.columns c
873+ LEFT JOIN (
874+ SELECT kcu.column_name
875+ FROM information_schema.table_constraints tc
876+ JOIN information_schema.key_column_usage kcu
877+ ON tc.constraint_name = kcu.constraint_name
878+ AND tc.table_schema = kcu.table_schema
879+ WHERE tc.constraint_type = 'PRIMARY KEY'
880+ AND tc.table_name = $1
881+ ) pk ON c.column_name = pk.column_name
882+ LEFT JOIN (
883+ SELECT kcu.column_name
884+ FROM information_schema.table_constraints tc
885+ JOIN information_schema.key_column_usage kcu
886+ ON tc.constraint_name = kcu.constraint_name
887+ AND tc.table_schema = kcu.table_schema
888+ WHERE tc.constraint_type = 'UNIQUE'
889+ AND tc.table_name = $1
890+ ) uq ON c.column_name = uq.column_name
891+ WHERE c.table_name = $1
892+ ORDER BY c.ordinal_position
893+ `
894+
895+ rows , err := db .Query (query , table )
896+ if err != nil {
897+ return err
898+ }
899+ defer rows .Close ()
900+
901+ for rows .Next () {
902+ var name , colType , nullable , key string
903+ var defaultVal sql.NullString
904+
905+ if err := rows .Scan (& name , & colType , & nullable , & defaultVal , & key ); err != nil {
906+ continue
907+ }
908+
909+ col := ColumnSchema {
910+ Name : name ,
911+ Type : colType ,
912+ Nullable : nullable == "YES" ,
913+ Key : key ,
914+ Extra : "" ,
915+ }
916+ if defaultVal .Valid {
917+ col .Default = defaultVal .String
918+ }
919+ schema .Columns = append (schema .Columns , col )
920+ }
921+
922+ indexQuery := `
923+ SELECT indexname, indexdef
924+ FROM pg_indexes
925+ WHERE tablename = $1
926+ `
927+ indexRows , err := db .Query (indexQuery , table )
928+ if err != nil {
929+ return nil
930+ }
931+ defer indexRows .Close ()
932+
933+ for indexRows .Next () {
934+ var indexName , indexDef string
935+ if err := indexRows .Scan (& indexName , & indexDef ); err != nil {
936+ continue
937+ }
938+
939+ idx := IndexSchema {
940+ Name : indexName ,
941+ Columns : []string {},
942+ Unique : strings .Contains (indexDef , "UNIQUE" ),
943+ Primary : strings .HasSuffix (indexName , "_pkey" ),
944+ }
945+
946+ start := strings .Index (indexDef , "(" )
947+ end := strings .LastIndex (indexDef , ")" )
948+ if start != - 1 && end != - 1 && end > start {
949+ colStr := indexDef [start + 1 : end ]
950+ cols := strings .Split (colStr , "," )
951+ for _ , c := range cols {
952+ idx .Columns = append (idx .Columns , strings .TrimSpace (c ))
953+ }
954+ }
955+
956+ schema .Indexes = append (schema .Indexes , idx )
957+ }
958+
959+ return nil
960+ }
0 commit comments