Skip to content

Commit 0bcf780

Browse files
authored
fix: isSingleStatement add test for failing case (#228)
1 parent bb395a0 commit 0bcf780

File tree

2 files changed

+41
-1
lines changed

2 files changed

+41
-1
lines changed

apps/dbagent/src/lib/targetdb/explain.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ describe('isSingleStatement', () => {
3737
expect(isSingleStatement('SELECT * FROM schema_name.table_name;')).toBe(true);
3838
});
3939

40+
test('complex SELECT with CASE, JOIN, UNION ALL and parameterized queries', () => {
41+
const complexQuery = `SELECT CASE WHEN $3 < LENGTH(CAST("public"."Post"."geoJson" AS TEXT)) THEN $4 ELSE "public"."Post"."geoJson" END AS "geoJson", CASE WHEN $5 < LENGTH(CAST("public"."Post"."runs" AS TEXT)) THEN $6 ELSE "public"."Post"."runs" END AS "runs", CASE WHEN $7 < LENGTH(CAST("public"."Post"."sprints" AS TEXT)) THEN $8 ELSE "public"."Post"."sprints" END AS "sprints" FROM "public"."Post" INNER JOIN ( (SELECT "public"."Post"."id" FROM "public"."Post" ORDER BY "public"."Post"."id" ASC LIMIT $1) UNION ALL (SELECT "public"."Post"."id" FROM "public"."Post" ORDER BY "public"."Post"."id" DESC LIMIT $2) ) AS "result" ON ("result"."id" = "public"."Post"."id")`;
42+
expect(isSingleStatement(complexQuery)).toBe(true);
43+
});
44+
4045
test('complex multi-line statement with comments and quotes', () => {
4146
const complexQuery = `
4247
/* multi-line comment */

apps/dbagent/src/lib/targetdb/explain.ts

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,11 @@ class SQLParser {
104104
} else if (char === '"') {
105105
this.parseDoubleQuotedString();
106106
} else if (char === '$') {
107-
this.parseDollarQuotedString();
107+
if (this.isDollarParameter()) {
108+
this.parseDollarParameter();
109+
} else {
110+
this.parseDollarQuotedString();
111+
}
108112
} else if (char === '/' && this.pos + 1 < this.len && this.input[this.pos + 1] === '*') {
109113
this.parseBlockComment();
110114
} else if (char === '-' && this.pos + 1 < this.len && this.input[this.pos + 1] === '-') {
@@ -195,6 +199,37 @@ class SQLParser {
195199
// Don't consume the newline, let skipWhitespace handle it
196200
}
197201

202+
private isDollarParameter(): boolean {
203+
// Check if this looks like $1, $2, etc. (parameter placeholder)
204+
// vs $tag$content$tag$ (dollar-quoted string)
205+
if (this.pos + 1 >= this.len) {
206+
return false;
207+
}
208+
209+
let i = this.pos + 1; // Skip the $
210+
211+
// Check if the character after $ is a digit
212+
if (!/\d/.test(this.input[i]!)) {
213+
return false;
214+
}
215+
216+
// Check if it's all digits until we hit a non-digit
217+
while (i < this.len && /\d/.test(this.input[i]!)) {
218+
i++;
219+
}
220+
221+
// If the next character after digits is not $, then it's a parameter placeholder
222+
return i >= this.len || this.input[i] !== '$';
223+
}
224+
225+
private parseDollarParameter(): void {
226+
this.pos++; // Skip $
227+
// Skip the digits
228+
while (this.pos < this.len && /\d/.test(this.input[this.pos]!)) {
229+
this.pos++;
230+
}
231+
}
232+
198233
private skipWhitespace(): void {
199234
while (this.pos < this.len && /\s/.test(this.input[this.pos]!)) {
200235
this.pos++;

0 commit comments

Comments
 (0)