fix(sql): respect PostgreSQL ARRAY brackets
This commit is contained in:
1 parent
11179676ed
commit
f274a599ed
6 files changed
+282
-57
No files matched your search
@@ -11,6 +11,8 @@
|
||||
// reference is only expanded when a matching `@set` declaration exists; any
|
||||
// unresolved `${name}` or `@name` remains available to the parameter dialog.
|
||||
|
||||
import type { DatabaseType } from "@/types/database";
|
||||
|
||||
const VARIABLE_NAME_START_RE = /[\p{L}_]/u;
|
||||
const VARIABLE_NAME_CHAR_RE = /[\p{L}\p{N}_]/u;
|
||||
|
||||
@@ -21,6 +23,7 @@ export interface SqlVariableExpansion {
|
||||
|
||||
export interface SqlVariableExpansionOptions {
|
||||
declarationSql?: string;
|
||||
databaseType?: DatabaseType;
|
||||
}
|
||||
|
||||
interface DeclarationSpan {
|
||||
@@ -39,7 +42,7 @@ interface DeclarationSpan {
|
||||
*/
|
||||
export function expandSqlVariables(sql: string, options: SqlVariableExpansionOptions = {}): SqlVariableExpansion {
|
||||
const declarationSql = options.declarationSql ?? sql;
|
||||
const declarations = collectDeclarations(declarationSql);
|
||||
const declarations = collectDeclarations(declarationSql, options.databaseType);
|
||||
if (!declarations.length) return { sql, expanded: false };
|
||||
|
||||
const values = new Map<string, string>();
|
||||
@@ -49,18 +52,18 @@ export function expandSqlVariables(sql: string, options: SqlVariableExpansionOpt
|
||||
|
||||
// Only spans inside the execution target are removed. Declarations supplied
|
||||
// by the surrounding document are context and must not affect target offsets.
|
||||
const targetDeclarations = declarationSql === sql ? declarations : collectDeclarations(sql);
|
||||
const targetDeclarations = declarationSql === sql ? declarations : collectDeclarations(sql, options.databaseType);
|
||||
let result = sql;
|
||||
for (let i = targetDeclarations.length - 1; i >= 0; i -= 1) {
|
||||
const declaration = targetDeclarations[i];
|
||||
result = stripDeclaration(result, declaration.start, declaration.end);
|
||||
}
|
||||
|
||||
result = replaceReferences(result, values);
|
||||
result = replaceReferences(result, values, options.databaseType);
|
||||
return { sql: result, expanded: result !== sql };
|
||||
}
|
||||
|
||||
function collectDeclarations(sql: string): DeclarationSpan[] {
|
||||
function collectDeclarations(sql: string, databaseType?: DatabaseType): DeclarationSpan[] {
|
||||
const declarations: DeclarationSpan[] = [];
|
||||
let i = 0;
|
||||
let dollarQuoteEnd = "";
|
||||
@@ -82,7 +85,7 @@ function collectDeclarations(sql: string): DeclarationSpan[] {
|
||||
continue;
|
||||
}
|
||||
if (ch === "[") {
|
||||
i = skipBracketIdentifier(sql, i);
|
||||
i = skipBracketIdentifier(sql, i, databaseType);
|
||||
continue;
|
||||
}
|
||||
if (ch === "-" && next === "-") {
|
||||
@@ -102,7 +105,7 @@ function collectDeclarations(sql: string): DeclarationSpan[] {
|
||||
}
|
||||
}
|
||||
if (ch === "@" && matchesWord(sql, i + 1, "set") && isStatementStart(sql, i)) {
|
||||
const declaration = readDeclaration(sql, i);
|
||||
const declaration = readDeclaration(sql, i, databaseType);
|
||||
if (declaration) {
|
||||
declarations.push(declaration);
|
||||
i = declaration.end;
|
||||
@@ -118,7 +121,7 @@ function collectDeclarations(sql: string): DeclarationSpan[] {
|
||||
// Parse `@set name = value` starting at `start` (the `@`). The value runs until
|
||||
// the terminating `;` or end of input, honouring nested quotes, comments and
|
||||
// parentheses so that `IN (...)` lists and quoted strings survive intact.
|
||||
function readDeclaration(sql: string, start: number): DeclarationSpan | null {
|
||||
function readDeclaration(sql: string, start: number, databaseType?: DatabaseType): DeclarationSpan | null {
|
||||
let i = start + 1 + "set".length;
|
||||
i = skipInlineWhitespace(sql, i);
|
||||
|
||||
@@ -132,7 +135,7 @@ function readDeclaration(sql: string, start: number): DeclarationSpan | null {
|
||||
i = skipInlineWhitespace(sql, i);
|
||||
|
||||
const valueStart = i;
|
||||
const valueEnd = readValueEnd(sql, i);
|
||||
const valueEnd = readValueEnd(sql, i, databaseType);
|
||||
const value = sql.slice(valueStart, valueEnd).trim();
|
||||
if (!value) return null;
|
||||
|
||||
@@ -143,9 +146,10 @@ function readDeclaration(sql: string, start: number): DeclarationSpan | null {
|
||||
return { name, value, start, end };
|
||||
}
|
||||
|
||||
function readValueEnd(sql: string, start: number): number {
|
||||
function readValueEnd(sql: string, start: number, databaseType?: DatabaseType): number {
|
||||
let i = start;
|
||||
let depth = 0;
|
||||
let bracketDepth = 0;
|
||||
while (i < sql.length) {
|
||||
const ch = sql[i];
|
||||
const next = sql[i + 1];
|
||||
@@ -154,9 +158,15 @@ function readValueEnd(sql: string, start: number): number {
|
||||
continue;
|
||||
}
|
||||
if (ch === "[") {
|
||||
i = skipBracketIdentifier(sql, i);
|
||||
if (databaseType === "postgres") {
|
||||
bracketDepth += 1;
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
i = skipBracketIdentifier(sql, i, databaseType);
|
||||
continue;
|
||||
}
|
||||
if (databaseType === "postgres" && ch === "]") bracketDepth = Math.max(0, bracketDepth - 1);
|
||||
if (ch === "-" && next === "-") return i;
|
||||
if (ch === "/" && next === "*") return i;
|
||||
if (ch === "$") {
|
||||
@@ -169,13 +179,13 @@ function readValueEnd(sql: string, start: number): number {
|
||||
}
|
||||
if (ch === "(") depth += 1;
|
||||
else if (ch === ")") depth = Math.max(0, depth - 1);
|
||||
else if ((ch === ";" || ch === "\n") && depth === 0) return i;
|
||||
else if ((ch === ";" || ch === "\n") && depth === 0 && bracketDepth === 0) return i;
|
||||
i += 1;
|
||||
}
|
||||
return sql.length;
|
||||
}
|
||||
|
||||
function replaceReferences(sql: string, values: Map<string, string>): string {
|
||||
function replaceReferences(sql: string, values: Map<string, string>, databaseType?: DatabaseType): string {
|
||||
let result = "";
|
||||
let i = 0;
|
||||
let dollarQuoteEnd = "";
|
||||
@@ -200,7 +210,7 @@ function replaceReferences(sql: string, values: Map<string, string>): string {
|
||||
continue;
|
||||
}
|
||||
if (ch === "[") {
|
||||
const end = skipBracketIdentifier(sql, i);
|
||||
const end = skipBracketIdentifier(sql, i, databaseType);
|
||||
result += sql.slice(i, end);
|
||||
i = end;
|
||||
continue;
|
||||
@@ -317,7 +327,11 @@ function skipQuoted(sql: string, start: number, quote: string): number {
|
||||
return sql.length;
|
||||
}
|
||||
|
||||
function skipBracketIdentifier(sql: string, start: number): number {
|
||||
function skipBracketIdentifier(sql: string, start: number, databaseType?: DatabaseType): number {
|
||||
// PostgreSQL uses square brackets for ARRAY constructors and subscripts, not
|
||||
// for delimited identifiers. Leave the opening bracket in the lexical stream.
|
||||
if (databaseType === "postgres") return start + 1;
|
||||
|
||||
let i = start + 1;
|
||||
while (i < sql.length) {
|
||||
if (sql[i] === "]") {
|
||||
|
||||
Reference in new issue
Block a user