mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-06-16 03:29:55 +08:00
- 修复 DuckDB qualified table 在查询结果页丢失 schema 导致无法识别主键的问题 - 打开对象修改前强制刷新最新定义,并避免切换对象失败时沿用旧定义 - 为 DuckDB 元数据链路补充前后端回归测试,并给 app 层真实 runtime 测试增加环境门槛
153 lines
5.1 KiB
Go
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
|
|
}
|