mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-06-14 18:39:54 +08:00
- 统一 PG-like 数据源字段和索引元数据查询,支持 search_path 可见表 - 兼容 snake_case、布尔别名和字符串唯一索引标记 - 修复 DuckDB main/memory 路径解析,避免误判外部 catalog - 补充前后端回归测试,覆盖可编辑结果定位和元数据重试路径
160 lines
4.8 KiB
Go
160 lines
4.8 KiB
Go
package db
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"GoNavi-Wails/internal/connection"
|
|
)
|
|
|
|
func normalizePGLikeMetadataTable(schemaName, tableName string) (string, string) {
|
|
schema := strings.TrimSpace(schemaName)
|
|
table := strings.TrimSpace(tableName)
|
|
if parsedSchema, parsedTable := SplitSQLQualifiedName(table); parsedSchema != "" && parsedTable != "" {
|
|
schema = parsedSchema
|
|
table = parsedTable
|
|
}
|
|
schema = strings.TrimSpace(normalizeSQLIdentifierEscapes(schema))
|
|
table = strings.TrimSpace(normalizeSQLIdentifierEscapes(table))
|
|
schema = strings.Trim(schema, `"`)
|
|
table = strings.Trim(table, `"`)
|
|
return schema, table
|
|
}
|
|
|
|
func escapePGLikeMetadataLiteral(raw string) string {
|
|
text := strings.TrimSpace(raw)
|
|
text = strings.Trim(text, `"`)
|
|
return strings.ReplaceAll(text, "'", "''")
|
|
}
|
|
|
|
func buildPGLikeVisibleRelationPredicate(alias string, schemaName string) string {
|
|
relAlias := strings.TrimSpace(alias)
|
|
if relAlias == "" {
|
|
relAlias = "c"
|
|
}
|
|
if strings.TrimSpace(schemaName) == "" {
|
|
return fmt.Sprintf("pg_catalog.pg_table_is_visible(%s.oid)", relAlias)
|
|
}
|
|
return fmt.Sprintf("n.nspname = '%s'", escapePGLikeMetadataLiteral(schemaName))
|
|
}
|
|
|
|
func buildPGLikeColumnsMetadataQuery(schemaName, tableName string) string {
|
|
return fmt.Sprintf(`
|
|
SELECT
|
|
a.attname AS column_name,
|
|
pg_catalog.format_type(a.atttypid, a.atttypmod) AS data_type,
|
|
CASE WHEN a.attnotnull THEN 'NO' ELSE 'YES' END AS is_nullable,
|
|
pg_get_expr(ad.adbin, ad.adrelid) AS column_default,
|
|
col_description(a.attrelid, a.attnum) AS comment,
|
|
CASE WHEN pk.attname IS NOT NULL THEN 'PRI' ELSE '' END AS column_key
|
|
FROM pg_class c
|
|
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
JOIN pg_attribute a ON a.attrelid = c.oid
|
|
LEFT JOIN pg_attrdef ad ON ad.adrelid = c.oid AND ad.adnum = a.attnum
|
|
LEFT JOIN (
|
|
SELECT i.indrelid, a3.attname
|
|
FROM pg_index i
|
|
JOIN pg_attribute a3 ON a3.attrelid = i.indrelid AND a3.attnum = ANY(i.indkey)
|
|
WHERE i.indisprimary
|
|
) pk ON pk.indrelid = c.oid AND pk.attname = a.attname
|
|
WHERE c.relkind IN ('r', 'p')
|
|
AND %s
|
|
AND c.relname = '%s'
|
|
AND a.attnum > 0
|
|
AND NOT a.attisdropped
|
|
ORDER BY a.attnum`, buildPGLikeVisibleRelationPredicate("c", schemaName), escapePGLikeMetadataLiteral(tableName))
|
|
}
|
|
|
|
func buildPGLikeIndexesMetadataQuery(schemaName, tableName string) string {
|
|
return fmt.Sprintf(`
|
|
SELECT
|
|
i.relname AS index_name,
|
|
a.attname AS column_name,
|
|
ix.indisunique AS is_unique,
|
|
x.ordinality AS seq_in_index,
|
|
am.amname AS index_type
|
|
FROM pg_class t
|
|
JOIN pg_namespace n ON n.oid = t.relnamespace
|
|
JOIN pg_index ix ON t.oid = ix.indrelid
|
|
JOIN pg_class i ON i.oid = ix.indexrelid
|
|
JOIN pg_am am ON i.relam = am.oid
|
|
JOIN unnest(ix.indkey) WITH ORDINALITY AS x(attnum, ordinality) ON TRUE
|
|
JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = x.attnum
|
|
WHERE t.relkind IN ('r', 'p')
|
|
AND t.relname = '%s'
|
|
AND %s
|
|
AND ix.indisvalid
|
|
AND ix.indpred IS NULL
|
|
AND x.ordinality <= ix.indnkeyatts
|
|
AND NOT EXISTS (
|
|
SELECT 1 FROM unnest(ix.indkey) AS expr_key(attnum) WHERE expr_key.attnum <= 0
|
|
)
|
|
ORDER BY i.relname, x.ordinality`, escapePGLikeMetadataLiteral(tableName), buildPGLikeVisibleRelationPredicate("t", schemaName))
|
|
}
|
|
|
|
func buildPGLikeColumnDefinitions(data []map[string]interface{}) []connection.ColumnDefinition {
|
|
columns := make([]connection.ColumnDefinition, 0, len(data))
|
|
for _, row := range data {
|
|
col := connection.ColumnDefinition{
|
|
Name: fmt.Sprintf("%v", row["column_name"]),
|
|
Type: fmt.Sprintf("%v", row["data_type"]),
|
|
Nullable: fmt.Sprintf("%v", row["is_nullable"]),
|
|
Key: fmt.Sprintf("%v", row["column_key"]),
|
|
Extra: "",
|
|
Comment: "",
|
|
}
|
|
|
|
if v, ok := row["comment"]; ok && v != nil {
|
|
col.Comment = fmt.Sprintf("%v", v)
|
|
}
|
|
|
|
if v, ok := row["column_default"]; ok && v != nil {
|
|
def := fmt.Sprintf("%v", v)
|
|
col.Default = &def
|
|
if strings.HasPrefix(strings.ToLower(strings.TrimSpace(def)), "nextval(") {
|
|
col.Extra = "auto_increment"
|
|
}
|
|
}
|
|
|
|
columns = append(columns, col)
|
|
}
|
|
return columns
|
|
}
|
|
|
|
func buildPGLikeIndexDefinitions(data []map[string]interface{}) []connection.IndexDefinition {
|
|
indexes := make([]connection.IndexDefinition, 0, len(data))
|
|
for _, row := range data {
|
|
isUnique := false
|
|
if v, ok := row["is_unique"]; ok && v != nil {
|
|
isUnique = parseMetadataBool(v)
|
|
}
|
|
|
|
nonUnique := 1
|
|
if isUnique {
|
|
nonUnique = 0
|
|
}
|
|
|
|
seq := 0
|
|
if v, ok := row["seq_in_index"]; ok && v != nil {
|
|
seq = parseMetadataInt(v)
|
|
}
|
|
|
|
indexType := ""
|
|
if v, ok := row["index_type"]; ok && v != nil {
|
|
indexType = strings.ToUpper(fmt.Sprintf("%v", v))
|
|
}
|
|
if indexType == "" {
|
|
indexType = "BTREE"
|
|
}
|
|
|
|
indexes = append(indexes, connection.IndexDefinition{
|
|
Name: fmt.Sprintf("%v", row["index_name"]),
|
|
ColumnName: fmt.Sprintf("%v", row["column_name"]),
|
|
NonUnique: nonUnique,
|
|
SeqInIndex: seq,
|
|
IndexType: indexType,
|
|
})
|
|
}
|
|
return indexes
|
|
}
|