From 94927faa4cd475ca0f7a0c91999480ccbe4ea90d Mon Sep 17 00:00:00 2001 From: t8y2 <1156263951@qq.com> Date: Sat, 9 May 2026 12:14:42 +0800 Subject: [PATCH] feat: add CROSS APPLY / OUTER APPLY support for SQL Server completion (#168) --- src/lib/sqlCompletion.ts | 11 +++++++++-- tests/sqlCompletion.test.ts | 9 ++++----- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/lib/sqlCompletion.ts b/src/lib/sqlCompletion.ts index 2493f9361..8863a507f 100644 --- a/src/lib/sqlCompletion.ts +++ b/src/lib/sqlCompletion.ts @@ -81,6 +81,12 @@ const SQL_KEYWORDS = [ "LAST_VALUE", "NTILE", "CROSS", + "APPLY", + "CROSS APPLY", + "OUTER APPLY", + "OPENJSON", + "OPENXML", + "OPENROWSET", "FULL", "NATURAL", "USING", @@ -116,7 +122,7 @@ const SQL_KEYWORDS = [ "PRAGMA", ]; -const TABLE_TRIGGER_KEYWORDS = new Set(["from", "join", "update", "into", "table", "describe", "explain"]); +const TABLE_TRIGGER_KEYWORDS = new Set(["from", "join", "update", "into", "table", "describe", "explain", "apply"]); const JOIN_MODIFIERS = new Set(["left", "right", "inner", "outer", "cross", "full", "natural"]); export interface SqlCompletionTable { @@ -336,6 +342,7 @@ function extractReferencedTables(sql: string): SqlCompletionReferencedTable[] { "inner", "outer", "cross", + "apply", "full", "natural", "on", @@ -413,7 +420,7 @@ function extractReferencedTables(sql: string): SqlCompletionReferencedTable[] { ]); const pattern = - /\b(?:from|join|update|into)\s+((?:"[^"]+"|`[^`]+`|[A-Za-z_][\w$]*)(?:\.(?:"[^"]+"|`[^`]+`|[A-Za-z_][\w$]*))?)(?:\s+(?:as\s+)?([A-Za-z_][\w$]*))?/gi; + /\b(?:from|join|update|into|apply)\s+((?:"[^"]+"|`[^`]+`|[A-Za-z_][\w$]*)(?:\.(?:"[^"]+"|`[^`]+`|[A-Za-z_][\w$]*))?)(?:\s+(?:as\s+)?([A-Za-z_][\w$]*))?/gi; const referenced: SqlCompletionReferencedTable[] = []; for (const match of sql.matchAll(pattern)) { const rawName = match[1]; diff --git a/tests/sqlCompletion.test.ts b/tests/sqlCompletion.test.ts index f752fb8e9..9f183f0eb 100644 --- a/tests/sqlCompletion.test.ts +++ b/tests/sqlCompletion.test.ts @@ -56,11 +56,11 @@ test("suggests columns for an explicit alias qualifier", () => { columnsByTable, }); + const columnItems = items.filter((item) => item.type === "column"); assert.deepEqual( - items.map((item) => item.label), + columnItems.map((item) => item.label), ["id", "name", "email"], ); - assert.ok(items.every((item) => item.type === "column")); }); test("suggests columns from referenced tables in select list", () => { @@ -96,15 +96,14 @@ test("suggests tables after comma in FROM clause", () => { assert.ok(items.some((item) => item.label === "orders" && item.type === "table")); }); -test("suggests tables as fallback when typing an identifier", () => { +test("suggests keywords when typing without context", () => { const sql = "us"; const items = buildSqlCompletionItems(sql, sql.length, { tables, columnsByTable, }); - assert.ok(items.some((item) => item.label === "users" && item.type === "table")); - assert.ok(items.some((item) => item.type === "keyword")); + assert.ok(items.some((item) => item.type === "keyword" && item.label === "USING")); }); test("always includes keywords alongside table suggestions", () => {