From 69bc48626242ed75fe30493c924c88dc7e73b906 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 7 Dec 2025 06:42:59 +0000 Subject: [PATCH 1/3] Initial plan From 25a532239268c7495f1d0eebf68c9ede839b5ba9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 7 Dec 2025 06:55:14 +0000 Subject: [PATCH 2/3] Add escape sequence support to AlaSQL strings (\t, \n, \r, etc.) Co-authored-by: mathiasrw <1063454+mathiasrw@users.noreply.github.com> --- src/58json.js | 4 +-- src/alasqlparser.jison | 38 ++++++++++++++++++++-- src/alasqlparser.js | 38 ++++++++++++++++++++-- test/test125.js | 11 ++++--- test/test134-B.js | 74 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 154 insertions(+), 11 deletions(-) create mode 100644 test/test134-B.js diff --git a/src/58json.js b/src/58json.js index 0308999ed3..8bc6039ec8 100755 --- a/src/58json.js +++ b/src/58json.js @@ -46,7 +46,7 @@ const JSONtoString = (alasql.utils.JSONtoString = function (obj) { function JSONtoJS(obj, context, tableid, defcols) { var s = ''; - if (typeof obj == 'string') s = '"' + obj + '"'; + if (typeof obj == 'string') s = '"' + escapeq(obj) + '"'; else if (typeof obj == 'number') s = '(' + obj + ')'; else if (typeof obj == 'boolean') s = obj; else if (typeof obj === 'bigint') s = obj.toString() + 'n'; @@ -56,7 +56,7 @@ function JSONtoJS(obj, context, tableid, defcols) { } else if (!obj.toJS || obj instanceof yy.Json) { let ss = []; for (const k in obj) { - let keyStr = typeof k === 'string' ? `"${k}"` : k.toString(); + let keyStr = typeof k === 'string' ? `"${escapeq(k)}"` : k.toString(); let valueStr = JSONtoJS(obj[k], context, tableid, defcols); ss.push(`${keyStr}:${valueStr}`); } diff --git a/src/alasqlparser.jison b/src/alasqlparser.jison index 40093f13cc..b62eb16959 100755 --- a/src/alasqlparser.jison +++ b/src/alasqlparser.jison @@ -1460,9 +1460,43 @@ LogicValue StringValue : STRING - { $$ = new yy.StringValue({value: $1.substr(1,$1.length-2).replace(/(\\\')/g,"'").replace(/(\'\')/g,"'")}); } + { + var str = $1.substr(1,$1.length-2).replace(/(\'\')/g,"'"); + str = str.replace(/\\(.)/g, function(match, char) { + switch(char) { + case 'n': return '\n'; + case 't': return '\t'; + case 'r': return '\r'; + case 'b': return '\b'; + case 'f': return '\f'; + case 'v': return '\v'; + case '\\': return '\\'; + case "'": return "'"; + case '"': return '"'; + default: return char; + } + }); + $$ = new yy.StringValue({value: str}); + } | NSTRING - { $$ = new yy.StringValue({value: $1.substr(2,$1.length-3).replace(/(\\\')/g,"'").replace(/(\'\')/g,"'")}); } + { + var str = $1.substr(2,$1.length-3).replace(/(\'\')/g,"'"); + str = str.replace(/\\(.)/g, function(match, char) { + switch(char) { + case 'n': return '\n'; + case 't': return '\t'; + case 'r': return '\r'; + case 'b': return '\b'; + case 'f': return '\f'; + case 'v': return '\v'; + case '\\': return '\\'; + case "'": return "'"; + case '"': return '"'; + default: return char; + } + }); + $$ = new yy.StringValue({value: str}); + } ; NullValue diff --git a/src/alasqlparser.js b/src/alasqlparser.js index 4bcacc5f94..32b5315057 100755 --- a/src/alasqlparser.js +++ b/src/alasqlparser.js @@ -904,10 +904,44 @@ case 378: this.$ = new yy.LogicValue({value:false}); break; case 379: - this.$ = new yy.StringValue({value: $$[$0].substr(1,$$[$0].length-2).replace(/(\\\')/g,"'").replace(/(\'\')/g,"'")}); + + var str = $$[$0].substr(1,$$[$0].length-2).replace(/(\'\')/g,"'"); + str = str.replace(/\\(.)/g, function(match, char) { + switch(char) { + case 'n': return '\n'; + case 't': return '\t'; + case 'r': return '\r'; + case 'b': return '\b'; + case 'f': return '\f'; + case 'v': return '\v'; + case '\\': return '\\'; + case "'": return "'"; + case '"': return '"'; + default: return char; + } + }); + this.$ = new yy.StringValue({value: str}); + break; case 380: - this.$ = new yy.StringValue({value: $$[$0].substr(2,$$[$0].length-3).replace(/(\\\')/g,"'").replace(/(\'\')/g,"'")}); + + var str = $$[$0].substr(2,$$[$0].length-3).replace(/(\'\')/g,"'"); + str = str.replace(/\\(.)/g, function(match, char) { + switch(char) { + case 'n': return '\n'; + case 't': return '\t'; + case 'r': return '\r'; + case 'b': return '\b'; + case 'f': return '\f'; + case 'v': return '\v'; + case '\\': return '\\'; + case "'": return "'"; + case '"': return '"'; + default: return char; + } + }); + this.$ = new yy.StringValue({value: str}); + break; case 381: this.$ = new yy.NullValue({value:undefined}); diff --git a/test/test125.js b/test/test125.js index 60b37f1044..c6c5bdfd29 100644 --- a/test/test125.js +++ b/test/test125.js @@ -34,11 +34,12 @@ describe('Test 125 - remove comments', function () { var res = alasql("select 'Cote d''Ivoir'"); assert.deepEqual(res, [{"'Cote d'Ivoir'": "Cote d'Ivoir"}]); var res = alasql('select "Cote d\\"Ivoir"'); - assert.deepEqual(res, [{"'Cote d\\\"Ivoir'": 'Cote d\\"Ivoir'}]); - var res = alasql('select "\\r"'); - assert.deepEqual(res, [{"'\\r'": '\\r'}]); - var res = alasql('select "\\n"'); - assert.deepEqual(res, [{"'\\n'": '\\n'}]); + assert.deepEqual(res, [{"'Cote d\"Ivoir'": 'Cote d"Ivoir'}]); + // Test escape sequences with aliases to make expectations clearer + var res = alasql('select "\\r" as col'); + assert.deepEqual(res, [{col: '\r'}]); + var res = alasql('select "\\n" as col'); + assert.deepEqual(res, [{col: '\n'}]); alasql('drop database test125'); done(); diff --git a/test/test134-B.js b/test/test134-B.js new file mode 100644 index 0000000000..6105ca72a9 --- /dev/null +++ b/test/test134-B.js @@ -0,0 +1,74 @@ +if (typeof exports === 'object') { + var assert = require('assert'); + var alasql = require('..'); +} + +describe('Test 134-B - Escape sequences in strings', function () { + const test = '134B'; + + before(function () { + alasql('create database test' + test); + alasql('use test' + test); + }); + + after(function () { + alasql('drop database test' + test); + }); + + it('A) Tab escape sequence', function () { + var res = alasql("SELECT 'hello\\tworld' AS result"); + assert.deepEqual(res, [{result: 'hello\tworld'}]); + }); + + it('B) Newline escape sequence', function () { + var res = alasql("SELECT 'line1\\nline2' AS result"); + assert.deepEqual(res, [{result: 'line1\nline2'}]); + }); + + it('C) Carriage return escape sequence', function () { + var res = alasql("SELECT 'text\\rmore' AS result"); + assert.deepEqual(res, [{result: 'text\rmore'}]); + }); + + it('D) Backslash escape sequence', function () { + var res = alasql("SELECT 'back\\\\slash' AS result"); + assert.deepEqual(res, [{result: 'back\\slash'}]); + }); + + it('E) Form feed escape sequence', function () { + var res = alasql("SELECT 'page\\fbreak' AS result"); + assert.deepEqual(res, [{result: 'page\fbreak'}]); + }); + + it('F) Backspace escape sequence', function () { + var res = alasql("SELECT 'back\\bspace' AS result"); + assert.deepEqual(res, [{result: 'back\bspace'}]); + }); + + it('G) Double quote escape sequence', function () { + var res = alasql('SELECT "quote\\"here" AS result'); + assert.deepEqual(res, [{result: 'quote"here'}]); + }); + + it('H) Mixed escape sequences', function () { + var res = alasql("SELECT 'Line 1\\nLine 2\\tTabbed\\rCarriage' AS result"); + assert.deepEqual(res, [{result: 'Line 1\nLine 2\tTabbed\rCarriage'}]); + }); + + it('I) Escape sequence in WHERE clause', function () { + alasql('CREATE TABLE test_escapes (id INT, text STRING)'); + alasql("INSERT INTO test_escapes VALUES (1, 'hello\\tworld')"); + alasql("INSERT INTO test_escapes VALUES (2, 'no tabs here')"); + + var res = alasql("SELECT * FROM test_escapes WHERE text = 'hello\\tworld'"); + assert.deepEqual(res, [{id: 1, text: 'hello\tworld'}]); + }); + + it('J) Single quote still works', function () { + var res = alasql("SELECT 'Cote d\\'Ivoir' AS result"); + assert.deepEqual(res, [{result: "Cote d'Ivoir"}]); + + var res2 = alasql("SELECT 'Cote d''Ivoir' AS result"); + assert.deepEqual(res2, [{result: "Cote d'Ivoir"}]); + }); +}); From 57cfff3cf12d3421cc3846444a6d29d5b3c8e4d9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 7 Dec 2025 10:50:22 +0000 Subject: [PATCH 3/3] Address PR feedback: Remove \b \f \v support, fix regex, update tests - Removed support for \b, \f, \v escape sequences as requested - Fixed regex to only match specific escape sequences: \n \t \r \\ \' \" - Removed default case that was accepting any character after backslash - Updated test134-B to remove tests for \b, \f, \v - Updated test125 escape sequence assertions to work with new behavior Co-authored-by: mathiasrw <1063454+mathiasrw@users.noreply.github.com> --- src/alasqlparser.jison | 14 ++++---------- src/alasqlparser.js | 14 ++++---------- test/test125.js | 2 +- test/test134-B.js | 18 ++++-------------- 4 files changed, 13 insertions(+), 35 deletions(-) diff --git a/src/alasqlparser.jison b/src/alasqlparser.jison index b62eb16959..f99d970f11 100755 --- a/src/alasqlparser.jison +++ b/src/alasqlparser.jison @@ -1462,18 +1462,15 @@ StringValue : STRING { var str = $1.substr(1,$1.length-2).replace(/(\'\')/g,"'"); - str = str.replace(/\\(.)/g, function(match, char) { + // Process escape sequences: \n \t \r \\ \' \" + str = str.replace(/\\(n|t|r|\\|'|")/g, function(match, char) { switch(char) { case 'n': return '\n'; case 't': return '\t'; case 'r': return '\r'; - case 'b': return '\b'; - case 'f': return '\f'; - case 'v': return '\v'; case '\\': return '\\'; case "'": return "'"; case '"': return '"'; - default: return char; } }); $$ = new yy.StringValue({value: str}); @@ -1481,18 +1478,15 @@ StringValue | NSTRING { var str = $1.substr(2,$1.length-3).replace(/(\'\')/g,"'"); - str = str.replace(/\\(.)/g, function(match, char) { + // Process escape sequences: \n \t \r \\ \' \" + str = str.replace(/\\(n|t|r|\\|'|")/g, function(match, char) { switch(char) { case 'n': return '\n'; case 't': return '\t'; case 'r': return '\r'; - case 'b': return '\b'; - case 'f': return '\f'; - case 'v': return '\v'; case '\\': return '\\'; case "'": return "'"; case '"': return '"'; - default: return char; } }); $$ = new yy.StringValue({value: str}); diff --git a/src/alasqlparser.js b/src/alasqlparser.js index 32b5315057..c9b2c0b369 100755 --- a/src/alasqlparser.js +++ b/src/alasqlparser.js @@ -906,18 +906,15 @@ break; case 379: var str = $$[$0].substr(1,$$[$0].length-2).replace(/(\'\')/g,"'"); - str = str.replace(/\\(.)/g, function(match, char) { + // Process escape sequences: \n \t \r \\ \' \" + str = str.replace(/\\(n|t|r|\\|'|")/g, function(match, char) { switch(char) { case 'n': return '\n'; case 't': return '\t'; case 'r': return '\r'; - case 'b': return '\b'; - case 'f': return '\f'; - case 'v': return '\v'; case '\\': return '\\'; case "'": return "'"; case '"': return '"'; - default: return char; } }); this.$ = new yy.StringValue({value: str}); @@ -926,18 +923,15 @@ break; case 380: var str = $$[$0].substr(2,$$[$0].length-3).replace(/(\'\')/g,"'"); - str = str.replace(/\\(.)/g, function(match, char) { + // Process escape sequences: \n \t \r \\ \' \" + str = str.replace(/\\(n|t|r|\\|'|")/g, function(match, char) { switch(char) { case 'n': return '\n'; case 't': return '\t'; case 'r': return '\r'; - case 'b': return '\b'; - case 'f': return '\f'; - case 'v': return '\v'; case '\\': return '\\'; case "'": return "'"; case '"': return '"'; - default: return char; } }); this.$ = new yy.StringValue({value: str}); diff --git a/test/test125.js b/test/test125.js index c6c5bdfd29..b021adfc4e 100644 --- a/test/test125.js +++ b/test/test125.js @@ -35,7 +35,7 @@ describe('Test 125 - remove comments', function () { assert.deepEqual(res, [{"'Cote d'Ivoir'": "Cote d'Ivoir"}]); var res = alasql('select "Cote d\\"Ivoir"'); assert.deepEqual(res, [{"'Cote d\"Ivoir'": 'Cote d"Ivoir'}]); - // Test escape sequences with aliases to make expectations clearer + // Escape sequences now work as of issue #134 var res = alasql('select "\\r" as col'); assert.deepEqual(res, [{col: '\r'}]); var res = alasql('select "\\n" as col'); diff --git a/test/test134-B.js b/test/test134-B.js index 6105ca72a9..6b32e6c8a8 100644 --- a/test/test134-B.js +++ b/test/test134-B.js @@ -35,27 +35,17 @@ describe('Test 134-B - Escape sequences in strings', function () { assert.deepEqual(res, [{result: 'back\\slash'}]); }); - it('E) Form feed escape sequence', function () { - var res = alasql("SELECT 'page\\fbreak' AS result"); - assert.deepEqual(res, [{result: 'page\fbreak'}]); - }); - - it('F) Backspace escape sequence', function () { - var res = alasql("SELECT 'back\\bspace' AS result"); - assert.deepEqual(res, [{result: 'back\bspace'}]); - }); - - it('G) Double quote escape sequence', function () { + it('E) Double quote escape sequence', function () { var res = alasql('SELECT "quote\\"here" AS result'); assert.deepEqual(res, [{result: 'quote"here'}]); }); - it('H) Mixed escape sequences', function () { + it('F) Mixed escape sequences', function () { var res = alasql("SELECT 'Line 1\\nLine 2\\tTabbed\\rCarriage' AS result"); assert.deepEqual(res, [{result: 'Line 1\nLine 2\tTabbed\rCarriage'}]); }); - it('I) Escape sequence in WHERE clause', function () { + it('G) Escape sequence in WHERE clause', function () { alasql('CREATE TABLE test_escapes (id INT, text STRING)'); alasql("INSERT INTO test_escapes VALUES (1, 'hello\\tworld')"); alasql("INSERT INTO test_escapes VALUES (2, 'no tabs here')"); @@ -64,7 +54,7 @@ describe('Test 134-B - Escape sequences in strings', function () { assert.deepEqual(res, [{id: 1, text: 'hello\tworld'}]); }); - it('J) Single quote still works', function () { + it('H) Single quote still works', function () { var res = alasql("SELECT 'Cote d\\'Ivoir' AS result"); assert.deepEqual(res, [{result: "Cote d'Ivoir"}]);