diff --git a/agents/drivers/kingbase-go/kingbase_metadata.go b/agents/drivers/kingbase-go/kingbase_metadata.go index 4446859fb..b389f8677 100644 --- a/agents/drivers/kingbase-go/kingbase_metadata.go +++ b/agents/drivers/kingbase-go/kingbase_metadata.go @@ -139,17 +139,17 @@ func detectMySQLCompatMode(db *sql.DB) bool { // sql_mode for MySQL syntax toggles such as ANSI_QUOTES. return strings.EqualFold(strings.TrimSpace(databaseMode), "mysql") case errors.Is(err, sql.ErrNoRows): - // Older Kingbase versions may not expose database_mode. Fall back to the - // legacy sql_mode existence probe in that case. + // Older Kingbase versions may not expose database_mode. Probe the syntax + // directly because non-MySQL modes can still expose a sql_mode setting. default: - // Ignore metadata errors and fall back to the legacy probe below. + // Ignore metadata errors and fall back to the syntax probe below. } - return mysqlSQLModeExists(db) + return supportsBacktickIdentifiers(db) } -func mysqlSQLModeExists(db *sql.DB) bool { +func supportsBacktickIdentifiers(db *sql.DB) bool { var value int - return db.QueryRow("SELECT 1 FROM sys_catalog.sys_settings WHERE LOWER(name) = 'sql_mode'").Scan(&value) == nil + return db.QueryRow("SELECT 1 AS `dbx_identifier_probe`").Scan(&value) == nil } func (s *server) identifierQuote() string { diff --git a/agents/drivers/kingbase-go/main_test.go b/agents/drivers/kingbase-go/main_test.go index 783399810..a0bbc6faf 100644 --- a/agents/drivers/kingbase-go/main_test.go +++ b/agents/drivers/kingbase-go/main_test.go @@ -52,11 +52,11 @@ type fallbackConn struct { } type modeDetectionDriverState struct { - mu sync.Mutex - queries []string - databaseMode *string - sqlModeExists bool - databaseErr error + mu sync.Mutex + queries []string + databaseMode *string + backtickIdentifiers bool + databaseErr error } type modeDetectionDriver struct{} @@ -192,12 +192,11 @@ func (connection *modeDetectionConn) QueryContext(_ context.Context, query strin rows = append(rows, []driver.Value{*connection.state.databaseMode}) } return &valueRows{columns: []string{"setting"}, rows: rows}, nil - case strings.Contains(query, "LOWER(name) = 'sql_mode'"): - rows := [][]driver.Value{} - if connection.state.sqlModeExists { - rows = append(rows, []driver.Value{int64(1)}) + case strings.Contains(query, "AS `dbx_identifier_probe`"): + if !connection.state.backtickIdentifiers { + return nil, &gokb.Error{Code: gokb.ErrorCode("42601"), Message: "syntax error at or near `"} } - return &valueRows{columns: []string{"value"}, rows: rows}, nil + return &valueRows{columns: []string{"dbx_identifier_probe"}, rows: [][]driver.Value{{int64(1)}}}, nil default: return nil, errors.New("unexpected query: " + query) } @@ -706,11 +705,11 @@ func TestGetTableCommentUsesPostgresCatalogComment(t *testing.T) { } } -func TestDetectMySQLCompatModePrefersDatabaseModeOverSQLModePresence(t *testing.T) { +func TestDetectMySQLCompatModePrefersDatabaseModeOverSyntaxProbe(t *testing.T) { oracle := "oracle" state := &modeDetectionDriverState{ - databaseMode: &oracle, - sqlModeExists: true, + databaseMode: &oracle, + backtickIdentifiers: true, } db := openModeDetectionDB(t, state) @@ -725,6 +724,22 @@ func TestDetectMySQLCompatModePrefersDatabaseModeOverSQLModePresence(t *testing. } } +func TestDetectMySQLCompatModeAcceptsExplicitMySQLMode(t *testing.T) { + mysql := "mysql" + state := &modeDetectionDriverState{databaseMode: &mysql} + db := openModeDetectionDB(t, state) + + if !detectMySQLCompatMode(db) { + t.Fatal("database_mode=mysql should use MySQL compatibility") + } + + state.mu.Lock() + defer state.mu.Unlock() + if len(state.queries) != 1 || !strings.Contains(state.queries[0], "database_mode") { + t.Fatalf("explicit MySQL mode should not need the syntax probe: %v", state.queries) + } +} + func TestConnectionInfoReportsCompatibilityIdentifierQuote(t *testing.T) { for _, testCase := range []struct { name string @@ -751,18 +766,27 @@ func TestConnectionInfoReportsCompatibilityIdentifierQuote(t *testing.T) { } } -func TestDetectMySQLCompatModeFallsBackToSQLModeWhenDatabaseModeMissing(t *testing.T) { - state := &modeDetectionDriverState{sqlModeExists: true} +func TestDetectMySQLCompatModeProbesBacktickSyntaxWhenDatabaseModeMissing(t *testing.T) { + state := &modeDetectionDriverState{backtickIdentifiers: true} db := openModeDetectionDB(t, state) if !detectMySQLCompatMode(db) { - t.Fatal("legacy server with sql_mode but no database_mode should still use MySQL compatibility") + t.Fatal("legacy server accepting backtick identifiers should use MySQL compatibility") } state.mu.Lock() defer state.mu.Unlock() - if len(state.queries) != 2 { - t.Fatalf("expected database_mode probe followed by sql_mode fallback, got: %v", state.queries) + if len(state.queries) != 2 || !strings.Contains(state.queries[1], "dbx_identifier_probe") { + t.Fatalf("expected database_mode probe followed by backtick syntax probe, got: %v", state.queries) + } +} + +func TestDetectMySQLCompatModeRejectsSQLModeWithoutBacktickSyntax(t *testing.T) { + state := &modeDetectionDriverState{} + db := openModeDetectionDB(t, state) + + if detectMySQLCompatMode(db) { + t.Fatal("legacy server rejecting backtick identifiers must not use MySQL compatibility") } }