From 1e76d60f16b98c8acebd718d336aedff5d3ca4c9 Mon Sep 17 00:00:00 2001 From: zipg Date: Mon, 20 Jul 2026 11:33:25 +0800 Subject: [PATCH] =?UTF-8?q?fix(editor):=20=E4=BF=AE=E5=A4=8D=20Kingbase=20?= =?UTF-8?q?=E5=8F=8D=E5=BC=95=E5=8F=B7=20SQL=20=E8=AF=8A=E6=96=AD=E8=AF=AF?= =?UTF-8?q?=E6=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/desktop/src/components/editor/QueryEditor.vue | 10 +++++++++- apps/desktop/src/lib/sql/semantic/dialect.ts | 5 +++++ crates/dbx-core/tests/sql_analysis.rs | 9 +++++++++ packages/app-tests/sqlSemanticDiagnostics.test.ts | 8 ++++++++ 4 files changed, 31 insertions(+), 1 deletion(-) diff --git a/apps/desktop/src/components/editor/QueryEditor.vue b/apps/desktop/src/components/editor/QueryEditor.vue index 1ad9f4f23..0eff46266 100644 --- a/apps/desktop/src/components/editor/QueryEditor.vue +++ b/apps/desktop/src/components/editor/QueryEditor.vue @@ -73,6 +73,7 @@ import { usesLocalOnlyEditorCompletionMetadata, usesOnDemandOnlyEditorColumnMeta import { queryContextObjectActions, queryContextObjectRoute, queryTableCandidateAtSqlPosition, resolveQueryContextCandidateDatabase, resolveQueryContextObjectTarget, type QueryContextObjectAction } from "@/lib/sql/queryCursorTableTarget"; import * as api from "@/lib/backend/api"; import { areSqlSemanticDiagnosticsEqual, buildSqlParserErrorDiagnostic, buildSqlSemanticDiagnostics, isSqlSemanticDiagnosticInputContext, shouldRunSqlSemanticDiagnostics, sqlSemanticDiagnosticRangesForViewport, tableReferenceKey, type SqlSemanticDiagnostic } from "@/lib/sql/semantic/diagnostics"; +import { sqlReferenceAnalysisDialectFor } from "@/lib/sql/semantic/dialect"; import { buildRedisSyntaxDiagnostics, shouldRunRedisDiagnostics } from "@/lib/redis/redisSyntaxDiagnostics"; import { buildRedisCompletionItemsFromContext, getRedisCompletionContext, getRedisCompletionResultValidFor, shouldAutoOpenRedisCompletion, takesKeyArgument, type RedisCompletionItem } from "@/lib/redis/redisCompletion"; import type { SqlCompletionColumn, SqlCompletionForeignKey, SqlCompletionItem, SqlCompletionObject, SqlCompletionReferencedTable, SqlCompletionTable } from "@/lib/sql/sqlCompletion"; @@ -1860,7 +1861,14 @@ async function refreshSemanticDiagnostics(options: { preserveOutsideRanges?: boo const nextDiagnostics: SqlSemanticDiagnostic[] = []; for (const range of diagnosticRanges) { try { - const analysis = await api.analyzeSqlReferences(range.sql, props.formatDialect ?? props.dialect ?? "generic"); + const analysis = await api.analyzeSqlReferences( + range.sql, + sqlReferenceAnalysisDialectFor({ + databaseType: props.databaseType, + identifierQuote: connectionStore.connectionIdentifierQuote(props.connectionId), + fallbackDialect: props.formatDialect ?? props.dialect ?? "generic", + }), + ); if (runId !== semanticDiagnosticRunId) return; const semanticCursor = Math.max(0, Math.min(currentView.state.selection.main.head - range.from, range.sql.length)); diff --git a/apps/desktop/src/lib/sql/semantic/dialect.ts b/apps/desktop/src/lib/sql/semantic/dialect.ts index 54199676f..6c35fa56e 100644 --- a/apps/desktop/src/lib/sql/semantic/dialect.ts +++ b/apps/desktop/src/lib/sql/semantic/dialect.ts @@ -137,6 +137,11 @@ export const SQL_SEMANTIC_DIALECTS: Record = }, }; +export function sqlReferenceAnalysisDialectFor(options: { databaseType?: DatabaseType; identifierQuote?: string; fallbackDialect: string }): string { + if (options.databaseType === "kingbase" && options.identifierQuote === "`") return "mysql"; + return options.fallbackDialect; +} + export function sqlSemanticDialectFor(options: { databaseType?: DatabaseType; dialect?: "mysql" | "postgres" | "sqlserver" }): SqlSemanticDialectAdapter { if (options.dialect && SQL_SEMANTIC_DIALECTS[options.dialect]) return SQL_SEMANTIC_DIALECTS[options.dialect]; switch (options.databaseType) { diff --git a/crates/dbx-core/tests/sql_analysis.rs b/crates/dbx-core/tests/sql_analysis.rs index f4912e79d..f6beaa8f4 100644 --- a/crates/dbx-core/tests/sql_analysis.rs +++ b/crates/dbx-core/tests/sql_analysis.rs @@ -55,6 +55,15 @@ fn extracts_mysql_quoted_table_references() { assert_eq!(analysis.tables[0].span.end_column, 24); } +#[test] +fn extracts_mysql_qualified_backtick_table_references() { + let analysis = analyze_sql_references("SELECT * FROM `core`.`products` LIMIT 100;", Some("mysql")).unwrap(); + + assert_eq!(analysis.tables.len(), 1); + assert_eq!(analysis.tables[0].schema.as_deref(), Some("core")); + assert_eq!(analysis.tables[0].name, "products"); +} + #[test] fn extracts_mysql_single_quoted_table_references() { let analysis = analyze_sql_references("SELECT * FROM 't_10001' LIMIT 100", Some("mysql")).unwrap(); diff --git a/packages/app-tests/sqlSemanticDiagnostics.test.ts b/packages/app-tests/sqlSemanticDiagnostics.test.ts index e032ef09c..af50ac7ff 100644 --- a/packages/app-tests/sqlSemanticDiagnostics.test.ts +++ b/packages/app-tests/sqlSemanticDiagnostics.test.ts @@ -1,6 +1,7 @@ import { strict as assert } from "node:assert"; import { test } from "vitest"; import { buildSqlParserErrorDiagnostic, buildSqlSemanticDiagnostics, areSqlSemanticDiagnosticsEqual, isSqlSemanticDiagnosticInputContext, shouldRunSqlSemanticDiagnostics, sqlSemanticDiagnosticRangesForViewport } from "../../apps/desktop/src/lib/sql/semantic/diagnostics.ts"; +import { sqlReferenceAnalysisDialectFor } from "../../apps/desktop/src/lib/sql/semantic/dialect.ts"; import type { SqlReferenceAnalysis } from "../../apps/desktop/src/types/database.ts"; const span = (startColumn: number, endColumn: number) => ({ @@ -290,6 +291,13 @@ test("builds a syntax diagnostic from parser errors with line and column", () => assert.deepEqual(diagnostic?.span, span(10, 12)); }); +test("uses MySQL reference parsing only for Kingbase connections that report backtick identifiers", () => { + assert.equal(sqlReferenceAnalysisDialectFor({ databaseType: "kingbase", identifierQuote: "`", fallbackDialect: "postgres" }), "mysql"); + assert.equal(sqlReferenceAnalysisDialectFor({ databaseType: "kingbase", identifierQuote: '"', fallbackDialect: "postgres" }), "postgres"); + assert.equal(sqlReferenceAnalysisDialectFor({ databaseType: "kingbase", fallbackDialect: "postgres" }), "postgres"); + assert.equal(sqlReferenceAnalysisDialectFor({ databaseType: "postgres", identifierQuote: "`", fallbackDialect: "postgres" }), "postgres"); +}); + test("compares diagnostics by severity message and span", () => { const diagnostics = [ {