From d574e6b396a20a9b4b9a997c1fac019ebf825fa3 Mon Sep 17 00:00:00 2001 From: Syngnat <92659908+Syngnat@users.noreply.github.com> Date: Tue, 7 Jul 2026 15:40:36 +0800 Subject: [PATCH 1/2] fix(oceanbase): set Oracle current schema for selected tenant --- internal/app/db_context.go | 58 +++++++++++++++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/internal/app/db_context.go b/internal/app/db_context.go index a4408527..e47f3fb6 100644 --- a/internal/app/db_context.go +++ b/internal/app/db_context.go @@ -1,6 +1,7 @@ package app import ( + "net/url" "strconv" "strings" @@ -23,7 +24,9 @@ func normalizeRunConfig(config connection.ConnectionConfig, dbName string) conne case "kafka", "apache-kafka", "apache_kafka": // Kafka 的 Database 字段表示默认 Topic,不能被树上的 synthetic database(topics) 覆盖。 case "oceanbase": - if !isOceanBaseOracleProtocol(config) { + if isOceanBaseOracleProtocol(config) { + runConfig = applyOceanBaseOracleCurrentSchemaInit(runConfig, name) + } else { runConfig.Database = name } case "mysql", "mariadb", "goldendb", "greatdb", "gdb", "diros", "starrocks", "sphinx", "postgres", "kingbase", "highgo", "vastbase", "opengauss", "gaussdb", "sqlserver", "iris", "intersystems", "intersystemsiris", "inter-systems", "inter-systems-iris", "mongodb", "tdengine", "iotdb", "clickhouse", "trino", "rabbitmq", "rabbit-mq", "rabbit_mq": @@ -46,6 +49,59 @@ func normalizeRunConfig(config connection.ConnectionConfig, dbName string) conne return runConfig } +func applyOceanBaseOracleCurrentSchemaInit(config connection.ConnectionConfig, schema string) connection.ConnectionConfig { + normalizedSchema := strings.TrimSpace(schema) + if normalizedSchema == "" { + return config + } + values, err := url.ParseQuery(strings.TrimSpace(config.ConnectionParams)) + if err != nil { + return config + } + statement := "ALTER SESSION SET CURRENT_SCHEMA = " + quoteOracleCurrentSchemaIdentifier(normalizedSchema) + for _, existing := range values["init"] { + if strings.EqualFold(strings.TrimSpace(existing), statement) { + return config + } + } + values.Add("init", statement) + config.ConnectionParams = values.Encode() + return config +} + +func quoteOracleCurrentSchemaIdentifier(schema string) string { + normalized := strings.TrimSpace(schema) + if normalized == "" { + return normalized + } + if isSimpleOracleIdentifier(normalized) { + return strings.ToUpper(normalized) + } + return `"` + strings.ReplaceAll(normalized, `"`, `""`) + `"` +} + +func isSimpleOracleIdentifier(value string) bool { + text := strings.TrimSpace(value) + if text == "" { + return false + } + for index, r := range text { + isLetter := (r >= 'A' && r <= 'Z') || (r >= 'a' && r <= 'z') + isDigit := r >= '0' && r <= '9' + isSpecial := r == '_' || r == '$' || r == '#' + if index == 0 { + if !isLetter && r != '_' { + return false + } + continue + } + if !isLetter && !isDigit && !isSpecial { + return false + } + } + return true +} + func normalizeSchemaAndTable(config connection.ConnectionConfig, dbName string, tableName string) (string, string) { rawTable := strings.TrimSpace(tableName) rawDB := strings.TrimSpace(dbName) From 61b62fe5af2ab66d49ecc26430133b2456c86ac1 Mon Sep 17 00:00:00 2001 From: Syngnat <92659908+Syngnat@users.noreply.github.com> Date: Tue, 7 Jul 2026 15:41:22 +0800 Subject: [PATCH 2/2] test(oceanbase): cover Oracle current schema init --- .../app/db_context_oceanbase_oracle_test.go | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 internal/app/db_context_oceanbase_oracle_test.go diff --git a/internal/app/db_context_oceanbase_oracle_test.go b/internal/app/db_context_oceanbase_oracle_test.go new file mode 100644 index 00000000..bd831bac --- /dev/null +++ b/internal/app/db_context_oceanbase_oracle_test.go @@ -0,0 +1,85 @@ +package app + +import ( + "net/url" + "testing" + + "GoNavi-Wails/internal/connection" +) + +func TestNormalizeRunConfig_OceanBaseOracleAddsCurrentSchemaInit(t *testing.T) { + t.Parallel() + + config := connection.ConnectionConfig{ + Type: "oceanbase", + Database: "OBORCL", + OceanBaseProtocol: "oracle", + } + runConfig := normalizeRunConfig(config, "sbdev") + + if runConfig.Database != "OBORCL" { + t.Fatalf("expected OceanBase Oracle service name to stay OBORCL, got %q", runConfig.Database) + } + values, err := url.ParseQuery(runConfig.ConnectionParams) + if err != nil { + t.Fatalf("unexpected connection params parse error: %v", err) + } + initValues := values["init"] + if len(initValues) != 1 || initValues[0] != "ALTER SESSION SET CURRENT_SCHEMA = SBDEV" { + t.Fatalf("expected current schema init for selected schema, got %#v", initValues) + } +} + +func TestNormalizeRunConfig_OceanBaseOraclePreservesExistingInit(t *testing.T) { + t.Parallel() + + config := connection.ConnectionConfig{ + Type: "oceanbase", + Database: "OBORCL", + OceanBaseProtocol: "oracle", + ConnectionParams: "init=ALTER+SESSION+SET+NLS_DATE_FORMAT%3D%27YYYY-MM-DD%27&timeout=10s", + } + runConfig := normalizeRunConfig(config, "sbdev") + + values, err := url.ParseQuery(runConfig.ConnectionParams) + if err != nil { + t.Fatalf("unexpected connection params parse error: %v", err) + } + initValues := values["init"] + if len(initValues) != 2 { + t.Fatalf("expected existing init plus current schema init, got %#v", initValues) + } + if initValues[0] != "ALTER SESSION SET NLS_DATE_FORMAT='YYYY-MM-DD'" { + t.Fatalf("expected existing init to stay first, got %#v", initValues) + } + if initValues[1] != "ALTER SESSION SET CURRENT_SCHEMA = SBDEV" { + t.Fatalf("expected current schema init appended, got %#v", initValues) + } + if values.Get("timeout") != "10s" { + t.Fatalf("expected non-init connection param preserved, got %q", values.Get("timeout")) + } +} + +func TestQuoteOracleCurrentSchemaIdentifier(t *testing.T) { + t.Parallel() + + cases := []struct { + name string + raw string + want string + }{ + {name: "lower simple", raw: "sbdev", want: "SBDEV"}, + {name: "normal uppercase", raw: "SBDEV", want: "SBDEV"}, + {name: "quoted mixed case required", raw: "Sb Dev", want: `"Sb Dev"`}, + {name: "quote escaping", raw: `A"B`, want: `"A""B"`}, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + if got := quoteOracleCurrentSchemaIdentifier(tc.raw); got != tc.want { + t.Fatalf("expected %q, got %q", tc.want, got) + } + }) + } +}