Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial support for other schemas #141

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 47 additions & 4 deletions internal/completer/candidates.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand All @@ -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,
Expand Down
211 changes: 119 additions & 92 deletions internal/database/postgresql.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
}
Expand All @@ -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)
Expand Down
1 change: 1 addition & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down