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
34 changes: 31 additions & 3 deletions src/50expression.js
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,7 @@
var s;
let refs = [];
let op = this.op;
let skipNullCheck = false; // Flag to skip null checking for deterministic empty set operations
let _this = this;
let ref = function (expr) {
if (expr.toJS) {
Expand Down Expand Up @@ -375,7 +376,17 @@
const uncachedLookup = `(alasql.utils.flatArray(this.queriesfn[${this.queriesidx}](params, null, ${context})).indexOf(alasql.utils.getValueOf(${leftJS()})) > -1)`;
s = `(${checkCorrelated} ? ${uncachedLookup} : ${cachedLookup})`;
} else if (Array.isArray(this.right)) {
if (!alasql.options.cache || this.right.some(value => value instanceof yy.ParamValue)) {
// Empty array: nothing is IN an empty set, always false
if (this.right.length === 0) {
// Must call leftJS() to populate the refs array for the declareRefs statement,
// even though the result is not used in the final expression
leftJS();
s = 'false';
skipNullCheck = true; // Result is deterministic even with null operands
} else if (
!alasql.options.cache ||
this.right.some(value => value instanceof yy.ParamValue)
) {
// Leverage JS Set for faster lookups than arrays
s = `(new Set([${this.right.map(ref).join(',')}]).has(alasql.utils.getValueOf(${leftJS()})))`;
} else {
Expand All @@ -399,7 +410,17 @@
const uncachedLookup = `(alasql.utils.flatArray(this.queriesfn[${this.queriesidx}](params, null, ${context})).indexOf(alasql.utils.getValueOf(${leftJS()})) < 0)`;
s = `(${checkCorrelated} ? ${uncachedLookup} : ${cachedLookup})`;
} else if (Array.isArray(this.right)) {
if (!alasql.options.cache || this.right.some(value => value instanceof yy.ParamValue)) {
// Empty array: everything is NOT IN an empty set, always true
if (this.right.length === 0) {
// Must call leftJS() to populate the refs array for the declareRefs statement,
// even though the result is not used in the final expression
leftJS();
s = 'true';
skipNullCheck = true; // Result is deterministic even with null operands
} else if (
!alasql.options.cache ||
this.right.some(value => value instanceof yy.ParamValue)
) {
// Leverage JS Set for faster lookups than arrays
s = `(!(new Set([${this.right.map(ref).join(',')}]).has(alasql.utils.getValueOf(${leftJS()}))))`;
} else {
Expand Down Expand Up @@ -475,7 +496,14 @@
var expr = s || '(' + leftJS() + op + rightJS() + ')';

var declareRefs = 'y=[(' + refs.join('), (') + ')]';
if (op === '&&' || op === '||' || op === 'IS' || op === 'IS NULL' || op === 'IS NOT NULL') {
if (
skipNullCheck ||
op === '&&' ||
op === '||' ||
op === 'IS' ||
op === 'IS NULL' ||
op === 'IS NOT NULL'
) {
return '(' + declareRefs + ', ' + expr + ')';
}

Expand Down
28 changes: 28 additions & 0 deletions test/test1529.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
if (typeof exports === 'object') {
var assert = require('assert');
var alasql = require('..');
}

describe('Test 1529 - SELECT null [not] IN ()', function () {
it('1. null IN () should return false', function (done) {
var res = alasql('SELECT VALUE null IN ()');
assert.strictEqual(res, false, 'null IN () should return false, got ' + res);
done();
});

it('2. null NOT IN () should return true', function (done) {
var res = alasql('SELECT VALUE null NOT IN ()');
assert.strictEqual(res, true, 'null NOT IN () should return true, got ' + res);
done();
});

it('3. Verify non-null values still work correctly', function (done) {
var res1 = alasql('SELECT VALUE 1 IN ()');
assert.strictEqual(res1, false, '1 IN () should return false');

var res2 = alasql('SELECT VALUE 1 NOT IN ()');
assert.strictEqual(res2, true, '1 NOT IN () should return true');

done();
});
});
Loading