diff --git a/apps/desktop/src/lib/__tests__/sql/sqlParameters.spec.ts b/apps/desktop/src/lib/__tests__/sql/sqlParameters.spec.ts index 746c6354b..74068fff5 100644 --- a/apps/desktop/src/lib/__tests__/sql/sqlParameters.spec.ts +++ b/apps/desktop/src/lib/__tests__/sql/sqlParameters.spec.ts @@ -321,6 +321,30 @@ describe("extractSqlParameters", () => { expect(extractSqlParameters(sql, { databaseType: "duckdb" })).toEqual(["column", "nested_column", "later"]); }); + it("ignores compact DuckDB prefix alias separators", () => { + const sql = 'select total:price * quantity, "order":sum(amount) from sales'; + + expect(extractSqlParameters(sql, { databaseType: "duckdb" })).toEqual([]); + expect(substituteSqlParameters(sql, {}, { databaseType: "duckdb" })).toBe(sql); + expect(extractSqlParameters(sql, { databaseType: "postgres" })).toEqual(["price", "sum"]); + }); + + it("keeps named parameters inside DuckDB prefix alias expressions", () => { + const sql = "from r:range(:row_count) select total:r.range + :offset"; + + expect(extractSqlParameters(sql, { databaseType: "duckdb" })).toEqual(["row_count", "offset"]); + expect( + substituteSqlParameters( + sql, + { + row_count: { kind: "number", value: "3" }, + offset: { kind: "number", value: "10" }, + }, + { databaseType: "duckdb" }, + ), + ).toBe("from r:range(3) select total:r.range + 10"); + }); + it("ignores Doris VARIANT field type separators", () => { const sql = ` create table \`events\` ( diff --git a/apps/desktop/src/lib/sql/sqlParameters.ts b/apps/desktop/src/lib/sql/sqlParameters.ts index 428588c6b..48aa42316 100644 --- a/apps/desktop/src/lib/sql/sqlParameters.ts +++ b/apps/desktop/src/lib/sql/sqlParameters.ts @@ -176,7 +176,7 @@ function findSqlParameterOccurrences(sql: string, options?: SqlParameterOptions) } if (ch === ":" && supportsNamedParameters && isSyntaxEnabled("named")) { const name = readParameterName(sql, i + 1); - if (name && sql[i - 1] !== ":" && sql[i + 1] !== "=" && !complexTypeFieldSeparators.has(i) && !duckDbStructFieldSeparators.has(i) && !triggerPseudoRecordFieldStarts.has(i)) { + if (name && sql[i - 1] !== ":" && sql[i + 1] !== "=" && !complexTypeFieldSeparators.has(i) && !duckDbStructFieldSeparators.has(i) && !isDuckDbCompactPrefixAliasSeparator(sql, i, options?.databaseType) && !triggerPseudoRecordFieldStarts.has(i)) { occurrences.push({ key: name, name, @@ -230,6 +230,12 @@ function findSqlParameterOccurrences(sql: string, options?: SqlParameterOptions) return occurrences; } +function isDuckDbCompactPrefixAliasSeparator(sql: string, index: number, databaseType?: DatabaseType): boolean { + if (databaseType !== "duckdb") return false; + const previous = sql[index - 1] ?? ""; + return PARAMETER_NAME_CHAR_RE.test(previous) || previous === '"'; +} + function isOracleDatabaseLinkMarker(sql: string, index: number, databaseType: DatabaseType | undefined): boolean { if (databaseType !== "oracle" || index === 0) return false; const previous = sql[index - 1];