Skip to content

Commit 99dee5f

Browse files
authored
Merge pull request #147 from constructive-io/devin/1775609815-fix-scan-json-escaping
fix: escape control characters in build_scan_json() for multi-line tokens
2 parents 8ad9a92 + c09d3b8 commit 99dee5f

2 files changed

Lines changed: 63 additions & 2 deletions

File tree

full/src/wasm_wrapper.c

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -379,8 +379,19 @@ static char* build_scan_json(PgQuery__ScanResult *scan_result, const char* origi
379379
char c = token_text[j];
380380
if (c == '"' || c == '\\') {
381381
escaped_text[escaped_pos++] = '\\';
382+
escaped_text[escaped_pos++] = c;
383+
} else if (c == '\n') {
384+
escaped_text[escaped_pos++] = '\\';
385+
escaped_text[escaped_pos++] = 'n';
386+
} else if (c == '\r') {
387+
escaped_text[escaped_pos++] = '\\';
388+
escaped_text[escaped_pos++] = 'r';
389+
} else if (c == '\t') {
390+
escaped_text[escaped_pos++] = '\\';
391+
escaped_text[escaped_pos++] = 't';
392+
} else {
393+
escaped_text[escaped_pos++] = c;
382394
}
383-
escaped_text[escaped_pos++] = c;
384395
}
385396
escaped_text[escaped_pos] = '\0';
386397

full/test/scan.test.js

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,5 +225,55 @@ describe("Query Scanning", () => {
225225
assert.equal(typeof result1.version, "number");
226226
assert.ok(result1.version > 0);
227227
});
228+
229+
it("should handle multi-line dollar-quoted strings without JSON errors", () => {
230+
// Without the fix, scanSync throws:
231+
// "Bad control character in string literal"
232+
// because build_scan_json() doesn't escape \n in the token text.
233+
const sql = `CREATE FUNCTION test() RETURNS void AS $$
234+
BEGIN
235+
RAISE NOTICE 'hello';
236+
END;
237+
$$ LANGUAGE plpgsql`;
238+
239+
const result = query.scanSync(sql);
240+
assert.equal(typeof result, "object");
241+
assert.ok(Array.isArray(result.tokens));
242+
assert.ok(result.tokens.length > 0);
243+
244+
// The dollar-quoted body spans multiple lines
245+
const dollarToken = result.tokens.find(t => t.text.includes('BEGIN'));
246+
assert.ok(dollarToken, "should have a token containing the function body");
247+
assert.ok(dollarToken.text.includes('\n'), "token text should preserve newlines");
248+
});
249+
250+
it("should handle dollar-quoted tokens with tabs", () => {
251+
// Tab characters also break JSON.parse when unescaped.
252+
const sql = `SELECT $$line1
253+
indented
254+
line3$$`;
255+
256+
const result = query.scanSync(sql);
257+
assert.equal(typeof result, "object");
258+
assert.ok(Array.isArray(result.tokens));
259+
260+
const dollarToken = result.tokens.find(t => t.text.includes('indented'));
261+
assert.ok(dollarToken, "should have a token containing the tabbed content");
262+
});
263+
264+
it("should handle multi-line block comments", () => {
265+
// C-style block comments spanning multiple lines hit the same bug.
266+
const sql = `SELECT 1; /* multi
267+
line
268+
comment */ SELECT 2`;
269+
270+
const result = query.scanSync(sql);
271+
assert.equal(typeof result, "object");
272+
assert.ok(Array.isArray(result.tokens));
273+
274+
const commentToken = result.tokens.find(t => t.tokenName === "C_COMMENT");
275+
assert.ok(commentToken, "should have a C_COMMENT token");
276+
assert.ok(commentToken.text.includes('\n'), "comment text should preserve newlines");
277+
});
228278
});
229-
});
279+
});

0 commit comments

Comments
 (0)