diff --git a/apps/desktop/src/lib/sql/sqlCompletion.ts b/apps/desktop/src/lib/sql/sqlCompletion.ts index 542a0c99b..b7ad1de58 100644 --- a/apps/desktop/src/lib/sql/sqlCompletion.ts +++ b/apps/desktop/src/lib/sql/sqlCompletion.ts @@ -2512,9 +2512,10 @@ function extractReferencedTables(sql: string, databaseType?: DatabaseType): SqlC ]); // STRAIGHT_JOIN is a standalone MySQL table introducer, not a modifier followed by JOIN. - const identifier = '(?:"[^"]+"|`[^`]+`|\\[[^\\]]+\\]|[A-Za-z_][\\w$@#]*)'; + const unquotedIdentifier = databaseType === "sqlserver" ? "[_\\p{ID_Start}][$@#_\\u200c\\u200d\\p{ID_Continue}]*" : "[A-Za-z_][\\w$@#]*"; + const identifier = `(?:"[^"]+"|\`[^\`]+\`|\\[[^\\]]+\\]|${unquotedIdentifier})`; const qualifiedSeparator = databaseType === "sqlserver" ? `\\.(?:${identifier}|\\.${identifier})` : `\\.${identifier}`; - const pattern = new RegExp(`\\b(?:from|join|straight_join|update|apply)\\s+(${identifier}(?:${qualifiedSeparator}){0,3})(?:\\s+(?:as\\s+)?([A-Za-z_][\\w$]*))?`, "gi"); + const pattern = new RegExp(`\\b(?:from|join|straight_join|update|apply)\\s+(${identifier}(?:${qualifiedSeparator}){0,3})(?:\\s+(?:as\\s+)?([A-Za-z_][\\w$]*))?`, databaseType === "sqlserver" ? "giu" : "gi"); const referenced: SqlCompletionReferencedTable[] = []; let match: RegExpExecArray | null; while ((match = pattern.exec(sql)) !== null) { diff --git a/packages/app-tests/sqlCompletion.test.ts b/packages/app-tests/sqlCompletion.test.ts index e3730c2d0..50265a2b7 100644 --- a/packages/app-tests/sqlCompletion.test.ts +++ b/packages/app-tests/sqlCompletion.test.ts @@ -2,6 +2,7 @@ import { strict as assert } from "node:assert"; import { test } from "vitest"; import { buildSqlCompletionItems, + buildSqlCompletionItemsFromContext, getSqlFunctionSignatureHelp, getSqlCompletionResultValidFor, isSqlCommentContext, @@ -496,6 +497,40 @@ test("suggests SQL Server tables for unquoted Chinese prefixes", () => { assert.equal(shouldAutoOpenSqlCompletion(sql, sql.length), true); }); +test("suggests SQL Server columns for an aliased unquoted Chinese table", () => { + const sql = "SELECT * FROM 大客户报废物资 AS tb2 WHERE "; + const options = { databaseType: "sqlserver" as const, dialect: "sqlserver" as const }; + const legacy = getSqlCompletionContext(sql, sql.length, options); + const semantic = buildSqlSemanticModel(sql, sql.length, options); + const context = sqlCompletionContextFromSemantic(semantic, legacy); + const items = buildSqlCompletionItemsFromContext(context, { + ...options, + tables: [{ name: "大客户报废物资", schema: "dbo", type: "table" }], + columnsByTable: new Map([ + [ + "dbo.大客户报废物资", + [ + { name: "物资编号", table: "大客户报废物资", schema: "dbo" }, + { name: "客户名称", table: "大客户报废物资", schema: "dbo" }, + ], + ], + ]), + }); + + assert.equal(context.referencedTables.length, 1); + assert.equal(context.referencedTables[0]?.name, "大客户报废物资"); + assert.equal(context.referencedTables[0]?.alias, "tb2"); + assert.equal(context.suggestColumns, true); + assert.equal(shouldAutoOpenSqlCompletion(sql, sql.length, options), true); + assert.deepEqual( + items + .filter((item) => item.type === "column") + .map((item) => item.label) + .sort(), + ["客户名称", "物资编号"], + ); +}); + test("preserves Unicode prefixes through the semantic completion context", () => { const sql = "select * from dbo.客户"; const legacy = getSqlCompletionContext(sql, sql.length);