mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-05-06 20:03:05 +08:00
- 抽象 OceanBase 协议解析与运行态参数注入 - 复用 OracleDB 实现 OceanBase Oracle 租户连接能力 - 调整 DDL、schema、SQL 方言和数据源能力判断 - 补充协议优先级、缓存隔离和 RPC 参数测试 - 支持按指定 driver 自动生成 agent revision
351 lines
12 KiB
Go
351 lines
12 KiB
Go
package app
|
|
|
|
import (
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
|
|
"GoNavi-Wails/internal/connection"
|
|
)
|
|
|
|
type fakeCreateStatementDB struct {
|
|
createSQL string
|
|
createErr error
|
|
columns []connection.ColumnDefinition
|
|
columnsErr error
|
|
queryRows []map[string]interface{}
|
|
queryErr error
|
|
|
|
createSchema string
|
|
createTable string
|
|
colsSchema string
|
|
colsTable string
|
|
columnsCalls int
|
|
queries []string
|
|
}
|
|
|
|
func (f *fakeCreateStatementDB) Connect(config connection.ConnectionConfig) error { return nil }
|
|
func (f *fakeCreateStatementDB) Close() error { return nil }
|
|
func (f *fakeCreateStatementDB) Ping() error { return nil }
|
|
func (f *fakeCreateStatementDB) Query(query string) ([]map[string]interface{}, []string, error) {
|
|
f.queries = append(f.queries, query)
|
|
return f.queryRows, []string{"ddl"}, f.queryErr
|
|
}
|
|
func (f *fakeCreateStatementDB) Exec(query string) (int64, error) { return 0, nil }
|
|
func (f *fakeCreateStatementDB) GetDatabases() ([]string, error) { return nil, nil }
|
|
func (f *fakeCreateStatementDB) GetTables(dbName string) ([]string, error) { return nil, nil }
|
|
func (f *fakeCreateStatementDB) GetCreateStatement(dbName, tableName string) (string, error) {
|
|
f.createSchema = dbName
|
|
f.createTable = tableName
|
|
return f.createSQL, f.createErr
|
|
}
|
|
func (f *fakeCreateStatementDB) GetColumns(dbName, tableName string) ([]connection.ColumnDefinition, error) {
|
|
f.columnsCalls++
|
|
f.colsSchema = dbName
|
|
f.colsTable = tableName
|
|
return f.columns, f.columnsErr
|
|
}
|
|
func (f *fakeCreateStatementDB) GetAllColumns(dbName string) ([]connection.ColumnDefinitionWithTable, error) {
|
|
return nil, nil
|
|
}
|
|
func (f *fakeCreateStatementDB) GetIndexes(dbName, tableName string) ([]connection.IndexDefinition, error) {
|
|
return nil, nil
|
|
}
|
|
func (f *fakeCreateStatementDB) GetForeignKeys(dbName, tableName string) ([]connection.ForeignKeyDefinition, error) {
|
|
return nil, nil
|
|
}
|
|
func (f *fakeCreateStatementDB) GetTriggers(dbName, tableName string) ([]connection.TriggerDefinition, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func TestResolveDDLDBType_CustomDriverAlias(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
testCases := []struct {
|
|
name string
|
|
driver string
|
|
want string
|
|
}{
|
|
{name: "postgresql alias", driver: "postgresql", want: "postgres"},
|
|
{name: "pgx alias", driver: "pgx", want: "postgres"},
|
|
{name: "kingbase8 alias", driver: "kingbase8", want: "kingbase"},
|
|
{name: "kingbase contains alias", driver: "kingbasees", want: "kingbase"},
|
|
{name: "dm alias", driver: "dm8", want: "dameng"},
|
|
{name: "sqlite alias", driver: "sqlite3", want: "sqlite"},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
tc := tc
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
cfg := connection.ConnectionConfig{Type: "custom", Driver: tc.driver}
|
|
if got := resolveDDLDBType(cfg); got != tc.want {
|
|
t.Fatalf("resolveDDLDBType() mismatch, want=%q got=%q", tc.want, got)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestResolveDDLDBType_OceanBaseOracleProtocol(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
cfg := connection.ConnectionConfig{
|
|
Type: "oceanbase",
|
|
ConnectionParams: "protocol=oracle",
|
|
}
|
|
if got := resolveDDLDBType(cfg); got != "oracle" {
|
|
t.Fatalf("expected OceanBase Oracle protocol to use oracle DDL dialect, got %q", got)
|
|
}
|
|
}
|
|
|
|
func TestNormalizeSchemaAndTableByType_PGLikeQuotedQualifiedName(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
dbType string
|
|
tableName string
|
|
wantSchema string
|
|
wantTable string
|
|
}{
|
|
{name: "postgres quoted dots", dbType: "postgres", tableName: `"sales.schema"."order.items"`, wantSchema: "sales.schema", wantTable: "order.items"},
|
|
{name: "highgo escaped quoted", dbType: "highgo", tableName: `\"sales\".\"orders\"`, wantSchema: "sales", wantTable: "orders"},
|
|
{name: "vastbase quoted table only", dbType: "vastbase", tableName: `"order.items"`, wantSchema: "public", wantTable: "order.items"},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
gotSchema, gotTable := normalizeSchemaAndTableByType(tt.dbType, "", tt.tableName)
|
|
if gotSchema != tt.wantSchema || gotTable != tt.wantTable {
|
|
t.Fatalf("normalizeSchemaAndTableByType(%q,%q)=(%q,%q),want=(%q,%q)", tt.dbType, tt.tableName, gotSchema, gotTable, tt.wantSchema, tt.wantTable)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestBuildRunConfigForDDL_CustomHighGoUsesDatabase(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
got := buildRunConfigForDDL(connection.ConnectionConfig{
|
|
Type: "custom",
|
|
Driver: "highgo",
|
|
Database: "default_db",
|
|
}, "highgo", "target_db")
|
|
if got.Database != "target_db" {
|
|
t.Fatalf("expected custom highgo DDL database target_db, got %q", got.Database)
|
|
}
|
|
}
|
|
|
|
func TestResolveCreateStatementWithFallback_CustomKingbaseUsesPublicSchema(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
dbInst := &fakeCreateStatementDB{
|
|
createSQL: "SHOW CREATE TABLE not directly supported in Kingbase/Postgres via SQL",
|
|
columns: []connection.ColumnDefinition{
|
|
{Name: "id", Type: "bigint", Nullable: "NO", Key: "PRI"},
|
|
},
|
|
}
|
|
|
|
ddl, err := resolveCreateStatementWithFallback(dbInst, connection.ConnectionConfig{
|
|
Type: "custom",
|
|
Driver: "kingbase8",
|
|
}, "demo_db", "orders")
|
|
if err != nil {
|
|
t.Fatalf("resolveCreateStatementWithFallback() unexpected error: %v", err)
|
|
}
|
|
if dbInst.createSchema != "public" || dbInst.colsSchema != "public" {
|
|
t.Fatalf("expected fallback schema public, got create=%q columns=%q", dbInst.createSchema, dbInst.colsSchema)
|
|
}
|
|
if !strings.Contains(ddl, `CREATE TABLE "public"."orders"`) {
|
|
t.Fatalf("expected fallback DDL with public schema, got: %s", ddl)
|
|
}
|
|
}
|
|
|
|
func TestResolveCreateStatementWithFallback_KeepQualifiedSchema(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
dbInst := &fakeCreateStatementDB{
|
|
createSQL: "-- SHOW CREATE TABLE not fully supported for PostgreSQL in this MVP.",
|
|
columns: []connection.ColumnDefinition{
|
|
{Name: "id", Type: "integer", Nullable: "NO", Key: "PRI"},
|
|
},
|
|
}
|
|
|
|
ddl, err := resolveCreateStatementWithFallback(dbInst, connection.ConnectionConfig{
|
|
Type: "custom",
|
|
Driver: "postgresql",
|
|
}, "demo_db", "sales.orders")
|
|
if err != nil {
|
|
t.Fatalf("resolveCreateStatementWithFallback() unexpected error: %v", err)
|
|
}
|
|
if dbInst.createSchema != "sales" || dbInst.colsSchema != "sales" {
|
|
t.Fatalf("expected schema sales, got create=%q columns=%q", dbInst.createSchema, dbInst.colsSchema)
|
|
}
|
|
if !strings.Contains(ddl, `CREATE TABLE "sales"."orders"`) {
|
|
t.Fatalf("expected fallback DDL with sales schema, got: %s", ddl)
|
|
}
|
|
}
|
|
|
|
func TestResolveCreateStatementWithFallback_PGLikeQuotedQualifiedName(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
dbInst := &fakeCreateStatementDB{
|
|
createSQL: "-- SHOW CREATE TABLE not fully supported for PostgreSQL in this MVP.",
|
|
columns: []connection.ColumnDefinition{
|
|
{Name: "id", Type: "integer", Nullable: "NO", Key: "PRI"},
|
|
},
|
|
}
|
|
|
|
ddl, err := resolveCreateStatementWithFallback(dbInst, connection.ConnectionConfig{
|
|
Type: "postgres",
|
|
}, "", `"sales.schema"."order.items"`)
|
|
if err != nil {
|
|
t.Fatalf("resolveCreateStatementWithFallback() unexpected error: %v", err)
|
|
}
|
|
if dbInst.createSchema != "sales.schema" || dbInst.createTable != "order.items" {
|
|
t.Fatalf("expected create target sales.schema.order.items, got %q.%q", dbInst.createSchema, dbInst.createTable)
|
|
}
|
|
if dbInst.colsSchema != "sales.schema" || dbInst.colsTable != "order.items" {
|
|
t.Fatalf("expected column target sales.schema.order.items, got %q.%q", dbInst.colsSchema, dbInst.colsTable)
|
|
}
|
|
if !strings.Contains(ddl, `CREATE TABLE "sales.schema"."order.items"`) {
|
|
t.Fatalf("expected fallback DDL with quoted dotted identifiers, got: %s", ddl)
|
|
}
|
|
}
|
|
|
|
func TestResolveCreateStatementWithFallback_ReturnsCreateViewDirectly(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
dbInst := &fakeCreateStatementDB{
|
|
createSQL: "CREATE VIEW sales.orders_v AS SELECT 1;",
|
|
columnsErr: errors.New("should not be called"),
|
|
}
|
|
|
|
ddl, err := resolveCreateStatementWithFallback(dbInst, connection.ConnectionConfig{Type: "postgres"}, "", "sales.orders_v")
|
|
if err != nil {
|
|
t.Fatalf("resolveCreateStatementWithFallback() unexpected error: %v", err)
|
|
}
|
|
if ddl != dbInst.createSQL {
|
|
t.Fatalf("expected original create view DDL, got: %s", ddl)
|
|
}
|
|
if dbInst.columnsCalls != 0 {
|
|
t.Fatalf("CREATE VIEW path should not call GetColumns, calls=%d", dbInst.columnsCalls)
|
|
}
|
|
}
|
|
|
|
func TestResolveCreateStatementWithFallback_PGLikeViewHelperBeforeColumnFallback(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
dbInst := &fakeCreateStatementDB{
|
|
createSQL: "SHOW CREATE TABLE not directly supported in PostgreSQL",
|
|
columnsErr: errors.New("should not be called"),
|
|
queryRows: []map[string]interface{}{
|
|
{"ddl": "SELECT id FROM sales.orders"},
|
|
},
|
|
}
|
|
|
|
ddl, err := resolveCreateStatementWithFallback(dbInst, connection.ConnectionConfig{Type: "postgres"}, "", "sales.orders_v")
|
|
if err != nil {
|
|
t.Fatalf("resolveCreateStatementWithFallback() unexpected error: %v", err)
|
|
}
|
|
if !strings.Contains(ddl, `CREATE VIEW "sales"."orders_v" AS SELECT id FROM sales.orders`) {
|
|
t.Fatalf("expected CREATE VIEW DDL from view helper, got: %s", ddl)
|
|
}
|
|
if dbInst.columnsCalls != 0 {
|
|
t.Fatalf("view helper path should not call GetColumns, calls=%d", dbInst.columnsCalls)
|
|
}
|
|
if len(dbInst.queries) == 0 || !strings.Contains(dbInst.queries[0], "pg_get_viewdef") {
|
|
t.Fatalf("expected pg_get_viewdef query, got: %v", dbInst.queries)
|
|
}
|
|
}
|
|
|
|
func TestResolveCreateStatementWithFallback_PGLikeViewHelperKeepsQuotedDottedName(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
dbInst := &fakeCreateStatementDB{
|
|
createSQL: "SHOW CREATE TABLE not directly supported in PostgreSQL",
|
|
columnsErr: errors.New("should not be called"),
|
|
queryRows: []map[string]interface{}{
|
|
{"ddl": "SELECT 1"},
|
|
},
|
|
}
|
|
|
|
ddl, err := resolveCreateStatementWithFallback(dbInst, connection.ConnectionConfig{Type: "postgres"}, "", `"sales.schema"."order.items"`)
|
|
if err != nil {
|
|
t.Fatalf("resolveCreateStatementWithFallback() unexpected error: %v", err)
|
|
}
|
|
if !strings.Contains(ddl, `CREATE VIEW "sales.schema"."order.items" AS SELECT 1`) {
|
|
t.Fatalf("expected CREATE VIEW DDL to keep quoted dotted identifiers, got: %s", ddl)
|
|
}
|
|
if dbInst.columnsCalls != 0 {
|
|
t.Fatalf("view helper path should not call GetColumns, calls=%d", dbInst.columnsCalls)
|
|
}
|
|
}
|
|
|
|
func TestResolveCreateStatementWithFallback_PGLikeViewHelperMissFallsBackToColumns(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
dbInst := &fakeCreateStatementDB{
|
|
createSQL: "SHOW CREATE TABLE not directly supported in PostgreSQL",
|
|
columns: []connection.ColumnDefinition{
|
|
{Name: "id", Type: "bigint", Nullable: "NO", Key: "PRI"},
|
|
},
|
|
}
|
|
|
|
ddl, err := resolveCreateStatementWithFallback(dbInst, connection.ConnectionConfig{Type: "postgres"}, "", "sales.orders")
|
|
if err != nil {
|
|
t.Fatalf("resolveCreateStatementWithFallback() unexpected error: %v", err)
|
|
}
|
|
if !strings.Contains(ddl, `CREATE TABLE "sales"."orders"`) {
|
|
t.Fatalf("expected CREATE TABLE fallback after view helper miss, got: %s", ddl)
|
|
}
|
|
if dbInst.columnsCalls != 1 {
|
|
t.Fatalf("expected one GetColumns call after view helper miss, calls=%d", dbInst.columnsCalls)
|
|
}
|
|
}
|
|
|
|
func TestResolveCreateStatementWithFallback_NoFallbackForMySQL(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
dbInst := &fakeCreateStatementDB{
|
|
createSQL: "SHOW CREATE TABLE not directly supported in Kingbase/Postgres via SQL",
|
|
columnsErr: errors.New("should not be called"),
|
|
}
|
|
|
|
ddl, err := resolveCreateStatementWithFallback(dbInst, connection.ConnectionConfig{
|
|
Type: "mysql",
|
|
}, "demo_db", "orders")
|
|
if err != nil {
|
|
t.Fatalf("resolveCreateStatementWithFallback() unexpected error: %v", err)
|
|
}
|
|
if ddl != dbInst.createSQL {
|
|
t.Fatalf("expected original ddl for mysql, got: %s", ddl)
|
|
}
|
|
if dbInst.colsTable != "" {
|
|
t.Fatalf("mysql path should not call GetColumns, got table=%q", dbInst.colsTable)
|
|
}
|
|
}
|
|
|
|
func TestResolveCreateStatementWithFallback_FallbackWhenCreateStatementError(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
dbInst := &fakeCreateStatementDB{
|
|
createErr: errors.New("statement unsupported"),
|
|
columns: []connection.ColumnDefinition{
|
|
{Name: "id", Type: "bigint", Nullable: "NO", Key: "PRI"},
|
|
},
|
|
}
|
|
|
|
ddl, err := resolveCreateStatementWithFallback(dbInst, connection.ConnectionConfig{
|
|
Type: "postgres",
|
|
}, "demo_db", "orders")
|
|
if err != nil {
|
|
t.Fatalf("resolveCreateStatementWithFallback() unexpected error: %v", err)
|
|
}
|
|
if !strings.Contains(ddl, `CREATE TABLE "public"."orders"`) {
|
|
t.Fatalf("expected fallback DDL for postgres error path, got: %s", ddl)
|
|
}
|
|
}
|