diff --git a/internal/completer/candidates.go b/internal/completer/candidates.go index c512a7cc..fe47ade8 100644 --- a/internal/completer/candidates.go +++ b/internal/completer/candidates.go @@ -68,7 +68,17 @@ func (c *Completer) columnCandidates(targetTables []*parseutil.TableInfo, parent if table.Name != parent.Name && table.Alias != parent.Name { continue } - columns, ok := c.DBCache.ColumnDescs(table.Name) + + var columns []*database.ColumnDesc + var ok bool + + if table.DatabaseSchema != "" { + columns, ok = c.DBCache.ColumnDatabase(table.DatabaseSchema, table.Name) + + } else { + columns, ok = c.DBCache.ColumnDescs(table.Name) + } + if !ok { continue } @@ -113,7 +123,13 @@ func (c *Completer) ReferencedTableCandidates(targetTables []*parseutil.TableInf for _, targetTable := range targetTables { includeTables := []*parseutil.TableInfo{} - for _, table := range c.DBCache.SortedTables() { + var schemaTables []string + if (targetTable.DatabaseSchema != "") { + schemaTables, _ = c.DBCache.SortedTablesByDBName(targetTable.DatabaseSchema) + } else { + schemaTables = c.DBCache.SortedTables() + } + for _, table := range schemaTables { if table == targetTable.Name { includeTables = append(includeTables, targetTable) } @@ -146,7 +162,7 @@ func (c *Completer) TableCandidates(parent *completionParent, targetTables []*pa case ParentTypeSchema: tables, ok := c.DBCache.SortedTablesByDBName(parent.Name) if ok { - candidates = append(candidates, generateTableCandidates(tables, c.DBCache)...) + candidates = append(candidates, generateTableCandidatesBySchema(parent.Name, tables, c.DBCache)...) } case ParentTypeTable: // pass @@ -339,6 +355,26 @@ func generateTableCandidates(tables []string, dbCache *database.DBCache) []lsp.C return candidates } +func generateTableCandidatesBySchema(schemaName string, tables []string, dbCache *database.DBCache) []lsp.CompletionItem { + candidates := []lsp.CompletionItem{} + for _, tableName := range tables { + candidate := lsp.CompletionItem{ + Label: tableName, + Kind: lsp.ClassCompletion, + Detail: "table", + } + cols, ok := dbCache.ColumnDatabase(schemaName, tableName) + if ok { + candidate.Documentation = lsp.MarkupContent{ + Kind: lsp.Markdown, + Value: database.TableDoc(tableName, cols), + } + } + candidates = append(candidates, candidate) + } + return candidates +} + func generateTableCandidatesByInfos(tables []*parseutil.TableInfo, dbCache *database.DBCache) []lsp.CompletionItem { candidates := []lsp.CompletionItem{} for _, table := range tables { @@ -353,7 +389,14 @@ func generateTableCandidatesByInfos(tables []*parseutil.TableInfo, dbCache *data Kind: lsp.ClassCompletion, Detail: detail, } - cols, ok := dbCache.ColumnDescs(table.Name) + var cols []*database.ColumnDesc + var ok bool + + if table.DatabaseSchema != "" { + cols, ok = dbCache.ColumnDatabase(table.DatabaseSchema, table.Name) + } else { + cols, ok = dbCache.ColumnDescs(table.Name) + } if ok { candidate.Documentation = lsp.MarkupContent{ Kind: lsp.Markdown, diff --git a/internal/database/postgresql.go b/internal/database/postgresql.go index 2d08c2fb..158148c1 100644 --- a/internal/database/postgresql.go +++ b/internal/database/postgresql.go @@ -221,43 +221,60 @@ func (db *PostgreSQLDBRepository) Tables(ctx context.Context) ([]string, error) } func (db *PostgreSQLDBRepository) DescribeDatabaseTable(ctx context.Context) ([]*ColumnDesc, error) { + log.Println("repository: describing all database tables") rows, err := db.Conn.QueryContext( ctx, ` - SELECT - c.table_schema, - c.table_name, - c.column_name, - c.data_type, - c.is_nullable, - CASE t.constraint_type - WHEN 'PRIMARY KEY' THEN 'YES' - ELSE 'NO' - END, - c.column_default, - '' - FROM - information_schema.columns c - LEFT JOIN ( - SELECT - ccu.table_schema as table_schema, - ccu.table_name as table_name, - ccu.column_name as column_name, - tc.constraint_type as constraint_type - FROM information_schema.constraint_column_usage ccu - LEFT JOIN information_schema.table_constraints tc ON - tc.table_schema = ccu.table_schema - AND tc.table_name = ccu.table_name - AND tc.constraint_name = ccu.constraint_name - WHERE - tc.constraint_type = 'PRIMARY KEY' - ) as t - ON c.table_schema = t.table_schema - AND c.table_name = t.table_name - AND c.column_name = t.column_name - ORDER BY - c.table_name, - c.ordinal_position + SELECT DISTINCT ON (table_name, a.attnum) + c1.relnamespace::regnamespace AS table_schema, c1.relname AS table_name, a.attname AS column_name, CASE WHEN + t.typtype = 'd'::"char" THEN + CASE WHEN bt.typelem <> 0::oid + AND bt.typlen = '-1'::integer THEN + 'ARRAY'::text + WHEN nbt.nspname = 'pg_catalog'::name THEN + format_type(t.typbasetype, NULL::integer) + ELSE + 'USER-DEFINED'::text + END + ELSE + CASE WHEN t.typelem <> 0::oid + AND t.typlen = '-1'::integer THEN + 'ARRAY'::text + WHEN nt.nspname = 'pg_catalog'::name THEN + format_type(a.atttypid, NULL::integer) + ELSE + 'USER-DEFINED'::text + END + END::information_schema.character_data AS data_type, CASE WHEN a.attnotnull + OR t.typtype = 'd'::"char" + AND t.typnotnull THEN + 'NO'::text + ELSE + 'YES'::text + END::information_schema.yes_or_no AS is_nullable, CASE WHEN conn.contype = 'p' THEN + 'YES' + ELSE + 'NO' + END AS is_primary_key, CASE WHEN a.attgenerated = ''::"char" THEN + pg_get_expr(ad.adbin, ad.adrelid) + ELSE + NULL::text + END::information_schema.character_data AS column_default, + '' + FROM pg_catalog.pg_class c1 + JOIN pg_catalog.pg_attribute a ON a.attrelid = c1.oid + LEFT JOIN (pg_type t + JOIN pg_namespace nt ON t.typnamespace = nt.oid) ON a.atttypid = t.oid + LEFT JOIN (pg_type bt + JOIN pg_namespace nbt ON bt.typnamespace = nbt.oid) ON t.typtype = 'd'::char + AND t.typbasetype = bt.oid + LEFT JOIN pg_catalog.pg_constraint conn ON conn.conrelid = c1.oid + AND a.attnum = ANY (conn.conkey) + AND conn.contype = 'p' + LEFT JOIN pg_attrdef ad ON a.attrelid = ad.adrelid + AND a.attnum = ad.adnum + WHERE a.attnum > 0 + ORDER BY table_name, a.attnum `) if err != nil { log.Fatal(err) @@ -285,47 +302,63 @@ func (db *PostgreSQLDBRepository) DescribeDatabaseTable(ctx context.Context) ([] } func (db *PostgreSQLDBRepository) DescribeDatabaseTableBySchema(ctx context.Context, schemaName string) ([]*ColumnDesc, error) { + log.Printf("repository: describing database tables in schema %s", schemaName) + rows, err := db.Conn.QueryContext( ctx, ` - SELECT - c.table_schema, - c.table_name, - c.column_name, - c.data_type, - c.is_nullable, - CASE t.constraint_type - WHEN 'PRIMARY KEY' THEN 'YES' - ELSE 'NO' - END, - c.column_default, - '' - FROM - information_schema.columns c - LEFT JOIN ( - SELECT - ccu.table_schema as table_schema, - ccu.table_name as table_name, - ccu.column_name as column_name, - tc.constraint_type as constraint_type - FROM information_schema.constraint_column_usage ccu - LEFT JOIN information_schema.table_constraints tc ON - tc.table_schema = ccu.table_schema - AND tc.table_name = ccu.table_name - AND tc.constraint_name = ccu.constraint_name - WHERE - ccu.table_schema = $1 - AND tc.constraint_type = 'PRIMARY KEY' - ) as t - ON c.table_schema = t.table_schema - AND c.table_name = t.table_name - AND c.column_name = t.column_name - WHERE - c.table_schema = $2 - ORDER BY - c.table_name, - c.ordinal_position - `, schemaName, schemaName) + SELECT DISTINCT ON (table_name, a.attnum) + c1.relnamespace::regnamespace AS table_schema, c1.relname AS table_name, a.attname AS column_name, CASE WHEN + t.typtype = 'd'::"char" THEN + CASE WHEN bt.typelem <> 0::oid + AND bt.typlen = '-1'::integer THEN + 'ARRAY'::text + WHEN nbt.nspname = 'pg_catalog'::name THEN + format_type(t.typbasetype, NULL::integer) + ELSE + 'USER-DEFINED'::text + END + ELSE + CASE WHEN t.typelem <> 0::oid + AND t.typlen = '-1'::integer THEN + 'ARRAY'::text + WHEN nt.nspname = 'pg_catalog'::name THEN + format_type(a.atttypid, NULL::integer) + ELSE + 'USER-DEFINED'::text + END + END::information_schema.character_data AS data_type, CASE WHEN a.attnotnull + OR t.typtype = 'd'::"char" + AND t.typnotnull THEN + 'NO'::text + ELSE + 'YES'::text + END::information_schema.yes_or_no AS is_nullable, CASE WHEN conn.contype = 'p' THEN + 'YES' + ELSE + 'NO' + END AS is_primary_key, CASE WHEN a.attgenerated = ''::"char" THEN + pg_get_expr(ad.adbin, ad.adrelid) + ELSE + NULL::text + END::information_schema.character_data AS column_default, + '' + FROM pg_catalog.pg_class c1 + JOIN pg_catalog.pg_attribute a ON a.attrelid = c1.oid + LEFT JOIN (pg_type t + JOIN pg_namespace nt ON t.typnamespace = nt.oid) ON a.atttypid = t.oid + LEFT JOIN (pg_type bt + JOIN pg_namespace nbt ON bt.typnamespace = nbt.oid) ON t.typtype = 'd'::char + AND t.typbasetype = bt.oid + LEFT JOIN pg_catalog.pg_constraint conn ON conn.conrelid = c1.oid + AND a.attnum = ANY (conn.conkey) + AND conn.contype = 'p' + LEFT JOIN pg_attrdef ad ON a.attrelid = ad.adrelid + AND a.attnum = ad.adnum + WHERE c1.relnamespace = $1::regnamespace::oid + AND a.attnum > 0 + ORDER BY table_name, a.attnum + `, schemaName) if err != nil { log.Fatal(err) } @@ -352,29 +385,23 @@ func (db *PostgreSQLDBRepository) DescribeDatabaseTableBySchema(ctx context.Cont } func (db *PostgreSQLDBRepository) DescribeForeignKeysBySchema(ctx context.Context, schemaName string) ([]*ForeignKey, error) { + log.Printf("repository: describing foreign keys in schema %s", schemaName) + rows, err := db.Conn.QueryContext( ctx, ` - select kcu.CONSTRAINT_NAME, - kcu.TABLE_NAME, - kcu.COLUMN_NAME, - rel_kcu.TABLE_NAME, - rel_kcu.COLUMN_NAME - from INFORMATION_SCHEMA.TABLE_CONSTRAINTS tco - join INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu - on tco.CONSTRAINT_SCHEMA = kcu.CONSTRAINT_SCHEMA - and tco.CONSTRAINT_NAME = kcu.CONSTRAINT_NAME - join INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS rco - on tco.CONSTRAINT_SCHEMA = rco.CONSTRAINT_SCHEMA - and tco.CONSTRAINT_NAME = rco.CONSTRAINT_NAME - join INFORMATION_SCHEMA.KEY_COLUMN_USAGE rel_kcu - on rco.UNIQUE_CONSTRAINT_SCHEMA = rel_kcu.CONSTRAINT_SCHEMA - and rco.UNIQUE_CONSTRAINT_NAME = rel_kcu.CONSTRAINT_NAME - and kcu.ORDINAL_POSITION = rel_kcu.ORDINAL_POSITION - where tco.CONSTRAINT_TYPE = 'FOREIGN KEY' - and tco.CONSTRAINT_SCHEMA = $1 - order by kcu.CONSTRAINT_NAME, - kcu.ORDINAL_POSITION + SELECT fk.conname AS constraint_name, c1.relname AS table_name, a1.attname AS column_name, c2.relname AS + foreign_table_name, a2.attname AS foreign_column_name + FROM pg_catalog.pg_constraint fk + JOIN pg_catalog.pg_class c1 ON c1.oid = fk.conrelid + JOIN pg_catalog.pg_attribute a1 ON a1.attrelid = c1.oid + AND a1.attnum = ANY (fk.conkey) + JOIN pg_catalog.pg_class c2 ON c2.oid = fk.confrelid + JOIN pg_catalog.pg_attribute a2 ON a2.attrelid = c2.oid + AND a2.attnum = ANY (fk.confkey) + WHERE fk.contype = 'f' + AND fk.connamespace = $1::regnamespace::oid + ORDER BY constraint_name, a1.attnum; `, schemaName) if err != nil { log.Fatal(err) diff --git a/main.go b/main.go index 8acf1cfe..be3a2d23 100644 --- a/main.go +++ b/main.go @@ -109,6 +109,7 @@ func serve(c *cli.Context) error { logWriter = io.MultiWriter(os.Stderr) } log.SetOutput(logWriter) + log.SetFlags(log.LstdFlags | log.Lmicroseconds) // Initialize language server server := handler.NewServer()