From 00bea838b5a7c5b35cd3d76c6739f5fc6347e39e Mon Sep 17 00:00:00 2001 From: pratyushsinghal7 Date: Wed, 29 Jul 2026 17:04:46 -0700 Subject: [PATCH] Fix UNION with nested SELECT --- src/40select.js | 10 ++++ src/420from.js | 11 ++++- test/test1264.js | 116 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 136 insertions(+), 1 deletion(-) create mode 100644 test/test1264.js diff --git a/src/40select.js b/src/40select.js index e55a49474d..ce224c0cd0 100755 --- a/src/40select.js +++ b/src/40select.js @@ -593,6 +593,16 @@ yy.Select = class Select { } return nq; }); + + // Subquery indices (queriesidx) are assigned against the statement-level + // queries list, so a subquery that references another subquery + // (e.g. a scalar subquery nested inside a subquery's WHERE clause) + // needs access to the same compiled list + query.queriesfn.forEach(function (nq) { + if (!nq.query.queriesfn) { + nq.query.queriesfn = query.queriesfn; + } + }); } }; diff --git a/src/420from.js b/src/420from.js index 7fd67f9185..2ede1457f1 100755 --- a/src/420from.js +++ b/src/420from.js @@ -92,9 +92,18 @@ yy.Select.prototype.compileFrom = function (query) { if (typeof source.subquery.query.modifier === 'undefined') { source.subquery.query.modifier = 'RECORDSET'; } - source.columns = source.subquery.query.columns; + // If the subquery could not resolve all its columns at compile time + // (e.g. SELECT * over parameter data), its columns list is incomplete, + // so leave the columns unknown and let SELECT * expand from the data + source.columns = source.subquery.query.dirtyColumns ? [] : source.subquery.query.columns; source.datafn = (query, params, cb, idx, alasql) => { + // Subqueries parsed inside this FROM subselect (e.g. a scalar + // subquery in its column list) are hoisted to the statement-level + // queries list, so share the compiled list with the subselect + if (!source.subquery.query.queriesfn && query.queriesfn) { + source.subquery.query.queriesfn = query.queriesfn; + } let res; source.subquery(query.params, data => { res = data.data; diff --git a/test/test1264.js b/test/test1264.js new file mode 100644 index 0000000000..3b5dffe3e7 --- /dev/null +++ b/test/test1264.js @@ -0,0 +1,116 @@ +if (typeof exports === 'object') { + var assert = require('assert'); + var alasql = require('..'); +} + +let testId = '1264'; + +describe(`Test ${testId} - UNION with nested SELECT`, function () { + before(function () { + alasql('create database test' + testId); + alasql('use test' + testId); + }); + + after(function () { + alasql('drop database test' + testId); + }); + + var t1 = [ + {id: '1', a: 'one'}, + {id: '2', a: 'two'}, + {id: '3', a: 'three'}, + {id: '4', a: 'four'}, + ]; + var t2 = [ + {id: '1', b: 'A'}, + {id: '2', b: 'B'}, + {id: '5', b: 'E'}, + {id: '6', b: 'F'}, + ]; + + var expected = [ + {id: '1', b: 'A'}, + {id: '2', b: 'B'}, + {id: '5', b: 'E'}, + {id: '6', b: 'F'}, + {id: '1', a: 'one', c: 4}, + {id: '2', a: 'two', c: 4}, + {id: '3', a: 'three', c: 4}, + {id: '4', a: 'four', c: 4}, + ]; + + it('A) UNION CORRESPONDING with a scalar subquery column', function () { + var res = alasql( + 'SELECT *, (SELECT COUNT(*) FROM ?) AS c FROM ? T1 UNION CORRESPONDING SELECT * FROM ?', + [t1, t1, t2] + ); + assert.deepEqual(res, expected); + }); + + it('B) UNION CORRESPONDING with a scalar subquery column, nested in FROM', function () { + var res = alasql( + 'SELECT * FROM (SELECT *, (SELECT COUNT(*) FROM ?) AS c FROM ? T1 UNION CORRESPONDING SELECT * FROM ?)', + [t1, t1, t2] + ); + assert.deepEqual(res, expected); + }); + + it('C) UNION ALL CORRESPONDING with a scalar subquery column, nested in FROM', function () { + var res = alasql( + 'SELECT * FROM (SELECT *, (SELECT COUNT(*) FROM ?) AS c FROM ? T1 UNION ALL CORRESPONDING SELECT * FROM ?)', + [t1, t1, t2] + ); + assert.deepEqual(res, [ + {id: '1', a: 'one', c: 4}, + {id: '2', a: 'two', c: 4}, + {id: '3', a: 'three', c: 4}, + {id: '4', a: 'four', c: 4}, + {id: '1', b: 'A'}, + {id: '2', b: 'B'}, + {id: '5', b: 'E'}, + {id: '6', b: 'F'}, + ]); + }); + + it('D) scalar subquery column on the right side of the UNION', function () { + var res = alasql( + 'SELECT * FROM (SELECT * FROM ? UNION CORRESPONDING SELECT *, (SELECT COUNT(*) FROM ?) AS c FROM ?)', + [t2, t1, t1] + ); + assert.deepEqual(res, [ + {id: '1', a: 'one', c: 4}, + {id: '2', a: 'two', c: 4}, + {id: '3', a: 'three', c: 4}, + {id: '4', a: 'four', c: 4}, + {id: '1', b: 'A'}, + {id: '2', b: 'B'}, + {id: '5', b: 'E'}, + {id: '6', b: 'F'}, + ]); + }); + + it('E) scalar subquery column in a FROM subselect without UNION', function () { + var res = alasql('SELECT * FROM (SELECT *, (SELECT COUNT(*) FROM ?) AS c FROM ?)', [t1, t1]); + assert.deepEqual(res, [ + {id: '1', a: 'one', c: 4}, + {id: '2', a: 'two', c: 4}, + {id: '3', a: 'three', c: 4}, + {id: '4', a: 'four', c: 4}, + ]); + }); + + it('F) scalar subquery nested inside another scalar subquery', function () { + var data = [ + {TYPE: 'CAUSE', PARENT: 'FL1', FAILURECODE: '999', FAILURELIST: 'FLX'}, + {TYPE: 'PROBLEM', PARENT: 'FL0', FAILURECODE: '123', FAILURELIST: 'FL1'}, + {TYPE: '', PARENT: '', FAILURECODE: '234', FAILURELIST: 'FL0'}, + ]; + var res = alasql( + "SELECT * FROM ? WHERE TYPE = 'CAUSE' AND PARENT = " + + "(SELECT FAILURELIST FROM ? WHERE FAILURECODE = '123' AND TYPE = 'PROBLEM' AND PARENT = " + + "(SELECT FAILURELIST FROM ? WHERE FAILURECODE = '234' AND TYPE = '' AND PARENT = ''))", + [data, data, data] + ); + assert.deepEqual(res, [{TYPE: 'CAUSE', PARENT: 'FL1', FAILURECODE: '999', FAILURELIST: 'FLX'}]); + }); +});