From ccd22cedf04e77e29cea2e98cc8dddd7e24fa545 Mon Sep 17 00:00:00 2001 From: Aaryav Gangrade Date: Fri, 2 Oct 2026 00:26:06 +0530 Subject: [PATCH 1/2] fix LIKE ESCAPE with backslash --- src/15utility.js | 4 ++++ test/test1978.js | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 test/test1978.js diff --git a/src/15utility.js b/src/15utility.js index 5f2c40559b..bf84ee64ce 100755 --- a/src/15utility.js +++ b/src/15utility.js @@ -1196,6 +1196,10 @@ var domEmptyChildren = (utils.domEmptyChildren = function (container) { */ var patternCache = {}; var like = (utils.like = function (pattern, value, escape = '') { + if (escape === '\\\\') { + escape = '\\'; + } + if (!patternCache[pattern]) { var regexStr = '^'; // Start regex pattern to match from the beginning. var i = 0; // Index for traversing the pattern string. diff --git a/test/test1978.js b/test/test1978.js new file mode 100644 index 0000000000..f0ed9d379c --- /dev/null +++ b/test/test1978.js @@ -0,0 +1,44 @@ +if (typeof exports === 'object') { + var assert = require('assert'); + var alasql = require('..'); +} else { + __dirname = '.'; +} + +describe('Test 1978 - LIKE ESCAPE with backslash', function () { + it('supports backslash as ESCAPE character in utils.like', function (done) { + assert(alasql.utils.like('%\\%%', '100%', '\\')); + assert(!alasql.utils.like('%\\%%', '1000', '\\')); + + assert(alasql.utils.like('%\\%%', '100%', '\\\\')); + assert(!alasql.utils.like('%\\%%', '1000', '\\\\')); + + assert(alasql.utils.like('%\\_%', '100_', '\\')); + assert(!alasql.utils.like('%\\_%', '1000', '\\')); + + done(); + }); + + it('supports backslash ESCAPE in SQL queries', function (done) { + alasql('CREATE TABLE cities (city string, population string)'); + + alasql( + "INSERT INTO cities VALUES ('%','1'), ('1%','2'), ('%1','3'), ('1%1','4'), ('_1','5'), ('1^1','6'), ('1!1','7')" + ); + + var res = alasql( + "SELECT * FROM cities WHERE (city LIKE '%\\%%' ESCAPE '\\\\') OR (population LIKE '2\\%' ESCAPE '\\\\') ORDER BY population DESC" + ); + + assert.deepStrictEqual(res, [ + {city: '1%1', population: '4'}, + {city: '%1', population: '3'}, + {city: '1%', population: '2'}, + {city: '%', population: '1'}, + ]); + + alasql('DROP TABLE cities'); + + done(); + }); +}); From 00f029d78ef535a00ea2bcf673981e68d43f8bd1 Mon Sep 17 00:00:00 2001 From: Aaryav Gangrade Date: Sun, 11 Oct 2026 00:22:21 +0530 Subject: [PATCH 2/2] fix: generically handle repeated backslashes as LIKE ESCAPE character --- src/15utility.js | 9 +++++-- test/test1978.js | 64 ++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 66 insertions(+), 7 deletions(-) diff --git a/src/15utility.js b/src/15utility.js index bf84ee64ce..ff6f860046 100755 --- a/src/15utility.js +++ b/src/15utility.js @@ -1196,7 +1196,12 @@ var domEmptyChildren = (utils.domEmptyChildren = function (container) { */ var patternCache = {}; var like = (utils.like = function (pattern, value, escape = '') { - if (escape === '\\\\') { + // Normalize the escape character: the SQL ESCAPE clause specifies a single + // character, but due to JavaScript string escaping layers in the parser + // pipeline, a backslash escape can arrive as multiple backslash characters + // (e.g. '\\\\' for '\\', etc.). If the escape string consists entirely + // of backslashes, collapse it to a single '\\'. + if (escape.length > 1 && /^\\+$/.test(escape)) { escape = '\\'; } @@ -1209,7 +1214,7 @@ var like = (utils.like = function (pattern, value, escape = '') { var nextChar = i < pattern.length - 1 ? pattern[i + 1] : ''; // Handle escape character. - if (currentChar === escape) { + if (escape && currentChar === escape) { regexStr += '\\' + nextChar; i++; // Skip next character as it's escaped. } diff --git a/test/test1978.js b/test/test1978.js index f0ed9d379c..a5df50b981 100644 --- a/test/test1978.js +++ b/test/test1978.js @@ -7,27 +7,57 @@ if (typeof exports === 'object') { describe('Test 1978 - LIKE ESCAPE with backslash', function () { it('supports backslash as ESCAPE character in utils.like', function (done) { + // Single backslash escape (the correct runtime value) assert(alasql.utils.like('%\\%%', '100%', '\\')); - assert(!alasql.utils.like('%\\%%', '1000', '\\')); + assert(!alasql.utils.like('%\\%%', '100', '\\')); + // Double backslash (arrives from parser escaping layer) assert(alasql.utils.like('%\\%%', '100%', '\\\\')); assert(!alasql.utils.like('%\\%%', '1000', '\\\\')); + // Triple and quadruple backslashes (additional escaping layers) + assert(alasql.utils.like('%\\%%', '100%', '\\\\\\')); + assert(!alasql.utils.like('%\\%%', '1000', '\\\\\\')); + + assert(alasql.utils.like('%\\%%', '100%', '\\\\\\\\')); + assert(!alasql.utils.like('%\\%%', '1000', '\\\\\\\\')); + + // Underscore wildcard escaping with backslash assert(alasql.utils.like('%\\_%', '100_', '\\')); assert(!alasql.utils.like('%\\_%', '1000', '\\')); + assert(alasql.utils.like('%\\_%', '100_', '\\\\')); + assert(!alasql.utils.like('%\\_%', '1000', '\\\\')); + + done(); + }); + + it('supports non-backslash ESCAPE characters in utils.like', function (done) { + // Caret escape + assert(alasql.utils.like('%^%%', '100%', '^')); + assert(!alasql.utils.like('%^%%', '100', '^')); + + // Exclamation mark escape + assert(alasql.utils.like('%!%%', '100%', '!')); + assert(!alasql.utils.like('%!%%', '100', '!')); + + // Tilde escape + assert(alasql.utils.like('%~_%', '100_', '~')); + assert(!alasql.utils.like('%~_%', '1000', '~')); + done(); }); it('supports backslash ESCAPE in SQL queries', function (done) { - alasql('CREATE TABLE cities (city string, population string)'); + alasql('CREATE TABLE cities1978 (city string, population string)'); alasql( - "INSERT INTO cities VALUES ('%','1'), ('1%','2'), ('%1','3'), ('1%1','4'), ('_1','5'), ('1^1','6'), ('1!1','7')" + "INSERT INTO cities1978 VALUES ('%','1'), ('1%','2'), ('%1','3'), ('1%1','4'), ('_1','5'), ('1^1','6'), ('1!1','7')" ); + // Using backslash ESCAPE in SQL with two LIKE columns (the original issue) var res = alasql( - "SELECT * FROM cities WHERE (city LIKE '%\\%%' ESCAPE '\\\\') OR (population LIKE '2\\%' ESCAPE '\\\\') ORDER BY population DESC" + "SELECT * FROM cities1978 WHERE (city LIKE '%\\%%' ESCAPE '\\\\') OR (population LIKE '2\\%' ESCAPE '\\\\') ORDER BY population DESC" ); assert.deepStrictEqual(res, [ @@ -37,7 +67,31 @@ describe('Test 1978 - LIKE ESCAPE with backslash', function () { {city: '%', population: '1'}, ]); - alasql('DROP TABLE cities'); + // Also verify with quadruple backslashes in SQL ESCAPE + var resQuad = alasql( + "SELECT * FROM cities1978 WHERE (city LIKE '%\\%%' ESCAPE '\\\\\\\\') OR (population LIKE '2\\%' ESCAPE '\\\\\\\\') ORDER BY population DESC" + ); + + assert.deepStrictEqual(resQuad, [ + {city: '1%1', population: '4'}, + {city: '%1', population: '3'}, + {city: '1%', population: '2'}, + {city: '%', population: '1'}, + ]); + + // Using caret ESCAPE (should also work with multiple LIKE columns) + var res2 = alasql( + "SELECT * FROM cities1978 WHERE (city LIKE '%^%%' ESCAPE '^') OR (population LIKE '2^%' ESCAPE '^') ORDER BY population DESC" + ); + + assert.deepStrictEqual(res2, [ + {city: '1%1', population: '4'}, + {city: '%1', population: '3'}, + {city: '1%', population: '2'}, + {city: '%', population: '1'}, + ]); + + alasql('DROP TABLE cities1978'); done(); });