Skip to content
Open
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
11 changes: 10 additions & 1 deletion src/15utility.js
Original file line number Diff line number Diff line change
Expand Up @@ -1196,6 +1196,15 @@ var domEmptyChildren = (utils.domEmptyChildren = function (container) {
*/
var patternCache = {};
var like = (utils.like = function (pattern, value, 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 = '\\';
}

if (!patternCache[pattern]) {
var regexStr = '^'; // Start regex pattern to match from the beginning.
var i = 0; // Index for traversing the pattern string.
Expand All @@ -1205,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.
}
Expand Down
98 changes: 98 additions & 0 deletions test/test1978.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
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) {
// Single backslash escape (the correct runtime value)
assert(alasql.utils.like('%\\%%', '100%', '\\'));
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 cities1978 (city string, population string)');

alasql(
"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 cities1978 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'},
]);

// 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();
});
});
Loading