fix(editor): 修复 Kingbase 反引号 SQL 诊断误报

This commit is contained in:
zipg 2026-07-20 11:33:25 +08:00 committed by GitHub
parent 71ce46e5fd
commit 1e76d60f16
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 31 additions and 1 deletions

View File

@ -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));

View File

@ -137,6 +137,11 @@ export const SQL_SEMANTIC_DIALECTS: Record<string, SqlSemanticDialectAdapter> =
},
};
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) {

View File

@ -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();

View File

@ -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 = [
{