diff --git a/apps/desktop/src/lib/__tests__/sql/sqlCompletion.snippet.spec.ts b/apps/desktop/src/lib/__tests__/sql/sqlCompletion.snippet.spec.ts index bea9ab4867..2fbf51e084 100644 --- a/apps/desktop/src/lib/__tests__/sql/sqlCompletion.snippet.spec.ts +++ b/apps/desktop/src/lib/__tests__/sql/sqlCompletion.snippet.spec.ts @@ -104,6 +104,41 @@ describe("buildSnippetItems", () => { expect(snippet?.apply).toBe("SELECT *\nFROM ${table}\nWHERE ROWNUM <= 100;"); }); + it("ranks an exact snippet prefix ahead of an exact function name", () => { + const items = buildSqlCompletionItems("sf", 2, { + databaseType: "oracle", + snippets: [{ id: "custom-sf", label: "select * from table", prefix: "sf", body: "SELECT *\nFROM table;" }], + objects: [{ name: "SF", type: "function" }], + tables: [], + columnsByTable: new Map(), + }); + + expect(items[0]).toEqual(expect.objectContaining({ label: "select * from table", type: "snippet", exactMatch: true })); + expect(items.findIndex((item) => item.label === "SF" && item.type === "function")).toBeGreaterThan(0); + }); + + it("ranks an exact snippet prefix ahead of an exact keyword", () => { + const items = buildSqlCompletionItems("select", 6, { + snippets: [{ id: "custom-select", label: "select all rows", prefix: "select", body: "SELECT *\nFROM table;" }], + tables: [], + columnsByTable: new Map(), + }); + + expect(items[0]).toEqual(expect.objectContaining({ label: "select all rows", type: "snippet", exactMatch: true })); + expect(items.findIndex((item) => item.label === "SELECT" && item.type === "keyword")).toBeGreaterThan(0); + }); + + it("keeps the real keyword first after typing past a snippet prefix", () => { + const items = buildSqlCompletionItems("sele", 4, { + snippets: [{ id: "custom-sel", label: "select all rows", prefix: "sel", body: "SELECT *\nFROM table;" }], + tables: [], + columnsByTable: new Map(), + }); + + expect(items[0]).toEqual(expect.objectContaining({ label: "SELECT", type: "keyword" })); + expect(items.find((item) => item.label === "select all rows" && item.type === "snippet")?.exactMatch).not.toBe(true); + }); + it("preserves a customized built-in select body instead of replacing it with a dialect default", () => { const customized = { ...BUILTIN_SELECT, body: "SELECT *\nFROM table\nLIMIT 7;" }; const items = buildSnippetItemsForTest("sel", [customized], undefined, "oracle"); diff --git a/apps/desktop/src/lib/sql/sqlCompletion.ts b/apps/desktop/src/lib/sql/sqlCompletion.ts index 996f6f72d3..6e387b48ac 100644 --- a/apps/desktop/src/lib/sql/sqlCompletion.ts +++ b/apps/desktop/src/lib/sql/sqlCompletion.ts @@ -1443,7 +1443,9 @@ class SqlCompletionProvider { for (const item of this.items) { // Alias snippets reuse the prefix as a label while applying alias SQL, so they are not exact name matches. const isAliasSnippet = item.type === "snippet" && item.apply === formatAliasCompletionApply(item.label, this.databaseType); - if (!isAliasSnippet && item.label.toLowerCase() === context.prefix.toLowerCase()) { + const isExactLabelMatch = !isAliasSnippet && item.label.toLowerCase() === context.prefix.toLowerCase(); + const isExactSnippetPrefixMatch = item.type === "snippet" && item.filterText?.toLowerCase() === context.prefix.toLowerCase(); + if (isExactLabelMatch || isExactSnippetPrefixMatch) { item.exactMatch = true; item.boost += EXACT_LABEL_MATCH_BOOST; }