Files
MyGoNavi/internal/db/duckdb_metadata_integration_test.go
Syngnat 274c32ebdd 🐛 fix(frontend): 修复 DuckDB 对象编辑与安全修改回归
- 修复 DuckDB qualified table 在查询结果页丢失 schema 导致无法识别主键的问题

- 打开对象修改前强制刷新最新定义,并避免切换对象失败时沿用旧定义

- 为 DuckDB 元数据链路补充前后端回归测试,并给 app 层真实 runtime 测试增加环境门槛
2026-06-04 22:00:55 +08:00

153 lines
5.1 KiB
Go

//go:build gonavi_duckdb_driver
package db
import (
"path/filepath"
"strings"
"testing"
"GoNavi-Wails/internal/connection"
)
func TestDuckDBMetadataDetectsPrimaryAndUniqueIndexes(t *testing.T) {
t.Parallel()
dbPath := filepath.Join(t.TempDir(), "metadata.duckdb")
client := &DuckDB{}
if err := client.Connect(connection.ConnectionConfig{Type: "duckdb", Host: dbPath}); err != nil {
t.Fatalf("Connect failed: %v", err)
}
t.Cleanup(func() {
_ = client.Close()
})
if _, err := client.Exec(`
CREATE TABLE events (
id BIGINT PRIMARY KEY,
email VARCHAR UNIQUE,
name VARCHAR
);
CREATE UNIQUE INDEX idx_events_name ON events(name);
`); err != nil {
t.Fatalf("create test table failed: %v", err)
}
columns, err := client.GetColumns("main", "main.events")
if err != nil {
t.Fatalf("GetColumns failed: %v", err)
}
if len(columns) != 3 {
t.Fatalf("unexpected column count: %d, columns=%+v", len(columns), columns)
}
keysByName := map[string]string{}
for _, column := range columns {
keysByName[column.Name] = column.Key
}
if keysByName["id"] != "PRI" {
t.Fatalf("primary key metadata missing: columns=%+v", columns)
}
if keysByName["email"] != "UNI" {
t.Fatalf("unique constraint metadata missing: columns=%+v", columns)
}
indexes, err := client.GetIndexes("main", "main.events")
if err != nil {
t.Fatalf("GetIndexes failed: %v", err)
}
if !duckDBTestHasUniqueIndexColumn(indexes, "id") {
t.Fatalf("primary key index metadata missing: indexes=%+v", indexes)
}
if !duckDBTestHasUniqueIndexColumn(indexes, "email") {
t.Fatalf("unique constraint index metadata missing: indexes=%+v", indexes)
}
if !duckDBTestHasUniqueIndexColumn(indexes, "name") {
t.Fatalf("unique index metadata missing: indexes=%+v", indexes)
}
}
func TestDuckDBDefinitionReloadReflectsLatestDDL(t *testing.T) {
t.Parallel()
dbPath := filepath.Join(t.TempDir(), "definition-reload.duckdb")
client := &DuckDB{}
if err := client.Connect(connection.ConnectionConfig{Type: "duckdb", Host: dbPath}); err != nil {
t.Fatalf("Connect failed: %v", err)
}
t.Cleanup(func() {
_ = client.Close()
})
if _, err := client.Exec(`
CREATE VIEW active_users AS
SELECT id FROM (VALUES (1), (2)) AS users(id);
CREATE OR REPLACE MACRO add_one(x) AS x + 1;
`); err != nil {
t.Fatalf("create initial objects failed: %v", err)
}
viewDefinitionBefore, _, err := client.Query(`SELECT view_definition FROM information_schema.views WHERE table_schema = 'main' AND table_name = 'active_users' LIMIT 1`)
if err != nil {
t.Fatalf("query initial view definition failed: %v", err)
}
if len(viewDefinitionBefore) != 1 {
t.Fatalf("expected one initial view definition row, got %+v", viewDefinitionBefore)
}
if got := duckDBRowString(viewDefinitionBefore[0], "view_definition"); !strings.Contains(got, "SELECT id FROM") || !strings.Contains(got, "VALUES (1), (2)") {
t.Fatalf("unexpected initial view definition: %q", got)
}
routineDefinitionBefore, _, err := client.Query(`SELECT macro_definition FROM duckdb_functions() WHERE internal = false AND lower(function_type) = 'macro' AND schema_name = 'main' AND function_name = 'add_one' LIMIT 1`)
if err != nil {
t.Fatalf("query initial macro definition failed: %v", err)
}
if len(routineDefinitionBefore) != 1 {
t.Fatalf("expected one initial macro definition row, got %+v", routineDefinitionBefore)
}
if got := duckDBRowString(routineDefinitionBefore[0], "macro_definition"); !strings.Contains(got, "x + 1") {
t.Fatalf("unexpected initial macro definition: %q", got)
}
if _, err := client.Exec(`
CREATE OR REPLACE VIEW active_users AS
SELECT id, id * 10 AS score FROM (VALUES (1), (2)) AS users(id);
CREATE OR REPLACE MACRO add_one(x) AS x + 2;
`); err != nil {
t.Fatalf("replace latest objects failed: %v", err)
}
viewDefinitionAfter, _, err := client.Query(`SELECT view_definition FROM information_schema.views WHERE table_schema = 'main' AND table_name = 'active_users' LIMIT 1`)
if err != nil {
t.Fatalf("query latest view definition failed: %v", err)
}
if len(viewDefinitionAfter) != 1 {
t.Fatalf("expected one latest view definition row, got %+v", viewDefinitionAfter)
}
if got := duckDBRowString(viewDefinitionAfter[0], "view_definition"); !strings.Contains(got, "SELECT id") || !strings.Contains(got, "score") || !strings.Contains(got, "10") {
t.Fatalf("expected latest view definition, got %q", got)
}
routineDefinitionAfter, _, err := client.Query(`SELECT macro_definition FROM duckdb_functions() WHERE internal = false AND lower(function_type) = 'macro' AND schema_name = 'main' AND function_name = 'add_one' LIMIT 1`)
if err != nil {
t.Fatalf("query latest macro definition failed: %v", err)
}
if len(routineDefinitionAfter) != 1 {
t.Fatalf("expected one latest macro definition row, got %+v", routineDefinitionAfter)
}
if got := duckDBRowString(routineDefinitionAfter[0], "macro_definition"); !strings.Contains(got, "x + 2") {
t.Fatalf("expected latest macro definition, got %q", got)
}
}
func duckDBTestHasUniqueIndexColumn(indexes []connection.IndexDefinition, columnName string) bool {
for _, index := range indexes {
if index.ColumnName == columnName && index.NonUnique == 0 {
return true
}
}
return false
}