Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions apps/dbagent/src/lib/targetdb/explain.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ describe('isSingleStatement', () => {
expect(isSingleStatement('SELECT * FROM schema_name.table_name;')).toBe(true);
});

test('complex SELECT with CASE, JOIN, UNION ALL and parameterized queries', () => {
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")`;
expect(isSingleStatement(complexQuery)).toBe(true);
});

test('complex multi-line statement with comments and quotes', () => {
const complexQuery = `
/* multi-line comment */
Expand Down
37 changes: 36 additions & 1 deletion apps/dbagent/src/lib/targetdb/explain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,11 @@ class SQLParser {
} else if (char === '"') {
this.parseDoubleQuotedString();
} else if (char === '$') {
this.parseDollarQuotedString();
if (this.isDollarParameter()) {
this.parseDollarParameter();
} else {
this.parseDollarQuotedString();
}
} else if (char === '/' && this.pos + 1 < this.len && this.input[this.pos + 1] === '*') {
this.parseBlockComment();
} else if (char === '-' && this.pos + 1 < this.len && this.input[this.pos + 1] === '-') {
Expand Down Expand Up @@ -195,6 +199,37 @@ class SQLParser {
// Don't consume the newline, let skipWhitespace handle it
}

private isDollarParameter(): boolean {
// Check if this looks like $1, $2, etc. (parameter placeholder)
// vs $tag$content$tag$ (dollar-quoted string)
if (this.pos + 1 >= this.len) {
return false;
}

let i = this.pos + 1; // Skip the $

// Check if the character after $ is a digit
if (!/\d/.test(this.input[i]!)) {
return false;
}

// Check if it's all digits until we hit a non-digit
while (i < this.len && /\d/.test(this.input[i]!)) {
i++;
}

// If the next character after digits is not $, then it's a parameter placeholder
return i >= this.len || this.input[i] !== '$';
}

private parseDollarParameter(): void {
this.pos++; // Skip $
// Skip the digits
while (this.pos < this.len && /\d/.test(this.input[this.pos]!)) {
this.pos++;
}
}

private skipWhitespace(): void {
while (this.pos < this.len && /\s/.test(this.input[this.pos]!)) {
this.pos++;
Expand Down
Loading