From 36bc9891f77a147fb5bff7d9c146de40e8b3b56c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 20 Aug 2026 11:35:58 +0000 Subject: [PATCH 1/7] Initial plan From 80b0a03594234d786894034e957c4a7f2ad1237c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 20 Aug 2026 11:38:39 +0000 Subject: [PATCH 2/7] Expose parser AST statements and expression node typings --- test/test2498.js | 45 +++++++++++++++++++++++++++++++ types/alasql.d.ts | 67 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+) create mode 100644 test/test2498.js diff --git a/test/test2498.js b/test/test2498.js new file mode 100644 index 0000000000..5de55572d3 --- /dev/null +++ b/test/test2498.js @@ -0,0 +1,45 @@ +if (typeof exports === 'object') { + var assert = require('assert'); + var alasql = require('..'); +} + +let testId = '2498'; + +describe(`Test ${testId} - parser AST surface`, function () { + it('A) exposes statements[] and where.toJS() for parsed statements', function () { + const ast = alasql.parse('SELECT 1 FROM ? WHERE x < $y'); + assert.ok(Array.isArray(ast.statements)); + assert.strictEqual(ast.statements.length, 1); + + const where = ast.statements[0].where; + assert.strictEqual(typeof where.toJS, 'function'); + + const js = where.toJS('p', '', null); + assert.strictEqual(typeof js, 'string'); + + const fn = new Function('alasql', 'p', 'params', `return Boolean(${js});`); + assert.strictEqual(fn(alasql, {x: 1}, {y: 2}), true); + assert.strictEqual(fn(alasql, {x: 3}, {y: 2}), false); + }); + + it('B) preserves stable expression node shapes used by parser consumers', function () { + const paramWhere = alasql.parse('SELECT 1 FROM ? WHERE x < $y').statements[0].where.expression; + assert.strictEqual(paramWhere.left.columnid, 'x'); + assert.strictEqual(paramWhere.left.tableid, undefined); + assert.strictEqual(paramWhere.op, '<'); + assert.strictEqual(paramWhere.right.param, 'y'); + + const numWhere = alasql.parse('SELECT 1 FROM ? WHERE x < 10').statements[0].where.expression; + assert.strictEqual(numWhere.right.value, 10); + + const strWhere = alasql.parse("SELECT 1 FROM ? WHERE x = 'abc'").statements[0].where.expression; + assert.strictEqual(strWhere.right.value, 'abc'); + + const boolWhere = alasql.parse('SELECT 1 FROM ? WHERE x = TRUE').statements[0].where.expression; + assert.strictEqual(boolWhere.right.value, true); + + const uniWhere = alasql.parse('SELECT 1 FROM ? WHERE -x < 0').statements[0].where.expression; + assert.strictEqual(uniWhere.left.op, '-'); + assert.strictEqual(uniWhere.left.right.columnid, 'x'); + }); +}); diff --git a/types/alasql.d.ts b/types/alasql.d.ts index 2183b3256c..e3b1ca0442 100644 --- a/types/alasql.d.ts +++ b/types/alasql.d.ts @@ -39,8 +39,75 @@ declare module 'alasql' { (params?: any, cb?: AlaSQLCallback, scope?: unknown): T; } + type AlaSQLDefaultColumns = {[columnName: string]: string}; + + /** + * Base parsed expression node. + * Node shapes below are part of the public parser AST surface and follow semver. + */ + interface AlaSQLExpression { + toJS(context: string, tableid: string | number, defcols: AlaSQLDefaultColumns | null): string; + } + + interface Op extends AlaSQLExpression { + left: ExpressionNode; + op: string; + right?: ExpressionNode; + right1?: ExpressionNode; + right2?: ExpressionNode; + } + + interface Column extends AlaSQLExpression { + columnid: string; + tableid?: string; + databaseid?: string; + } + + interface ParamValue extends AlaSQLExpression { + param: string | number; + } + + interface NumValue extends AlaSQLExpression { + value: number; + } + + interface StringValue extends AlaSQLExpression { + value: string; + } + + interface LogicValue extends AlaSQLExpression { + value: boolean; + } + + interface UniOp extends AlaSQLExpression { + op: string | null; + right: ExpressionNode; + } + + type ExpressionNode = + | AlaSQLExpression + | Op + | Column + | ParamValue + | NumValue + | StringValue + | LogicValue + | UniOp; + + interface Expression extends AlaSQLExpression { + expression: ExpressionNode; + reduced?: boolean; + } + + interface Statement { + compile(databaseid: string): AlaSQLStatement; + where?: Expression; + [key: string]: unknown; + } + // abstract Syntax Tree interface AlaSQLAST { + statements: Statement[]; compile(databaseid: string): AlaSQLStatement; } From c9af0c3b8e0633a3ce6de03993826845781ec184 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 20 Aug 2026 11:39:48 +0000 Subject: [PATCH 3/7] Refine AST declaration union and wrapper naming --- types/alasql.d.ts | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/types/alasql.d.ts b/types/alasql.d.ts index e3b1ca0442..8f7904ece2 100644 --- a/types/alasql.d.ts +++ b/types/alasql.d.ts @@ -84,24 +84,16 @@ declare module 'alasql' { right: ExpressionNode; } - type ExpressionNode = - | AlaSQLExpression - | Op - | Column - | ParamValue - | NumValue - | StringValue - | LogicValue - | UniOp; - - interface Expression extends AlaSQLExpression { + type ExpressionNode = Op | Column | ParamValue | NumValue | StringValue | LogicValue | UniOp; + + interface ExpressionWrapper extends AlaSQLExpression { expression: ExpressionNode; reduced?: boolean; } interface Statement { compile(databaseid: string): AlaSQLStatement; - where?: Expression; + where?: ExpressionWrapper; [key: string]: unknown; } From 381ad22df6b3a68287e05010b8b6d5a528b6a708 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 25 Aug 2026 09:08:51 +0000 Subject: [PATCH 4/7] Strengthen test2498 with full AST shape assertions --- test/test2498.js | 73 +++++++++++++++++++++++++++++++++++++----------- 1 file changed, 56 insertions(+), 17 deletions(-) diff --git a/test/test2498.js b/test/test2498.js index 5de55572d3..f2234a6890 100644 --- a/test/test2498.js +++ b/test/test2498.js @@ -23,23 +23,62 @@ describe(`Test ${testId} - parser AST surface`, function () { }); it('B) preserves stable expression node shapes used by parser consumers', function () { - const paramWhere = alasql.parse('SELECT 1 FROM ? WHERE x < $y').statements[0].where.expression; - assert.strictEqual(paramWhere.left.columnid, 'x'); - assert.strictEqual(paramWhere.left.tableid, undefined); - assert.strictEqual(paramWhere.op, '<'); - assert.strictEqual(paramWhere.right.param, 'y'); + const actual = { + paramWhere: alasql.parse('SELECT 1 FROM ? WHERE x < $y').statements[0].where.expression, + numWhere: alasql.parse('SELECT 1 FROM ? WHERE x < 10').statements[0].where.expression, + strWhere: alasql.parse("SELECT 1 FROM ? WHERE x = 'abc'").statements[0].where.expression, + boolWhere: alasql.parse('SELECT 1 FROM ? WHERE x = TRUE').statements[0].where.expression, + uniWhere: alasql.parse('SELECT 1 FROM ? WHERE -x < 0').statements[0].where.expression, + }; - const numWhere = alasql.parse('SELECT 1 FROM ? WHERE x < 10').statements[0].where.expression; - assert.strictEqual(numWhere.right.value, 10); - - const strWhere = alasql.parse("SELECT 1 FROM ? WHERE x = 'abc'").statements[0].where.expression; - assert.strictEqual(strWhere.right.value, 'abc'); - - const boolWhere = alasql.parse('SELECT 1 FROM ? WHERE x = TRUE').statements[0].where.expression; - assert.strictEqual(boolWhere.right.value, true); - - const uniWhere = alasql.parse('SELECT 1 FROM ? WHERE -x < 0').statements[0].where.expression; - assert.strictEqual(uniWhere.left.op, '-'); - assert.strictEqual(uniWhere.left.right.columnid, 'x'); + assert.deepStrictEqual( + { + paramWhere: { + left: { + columnid: actual.paramWhere.left.columnid, + tableid: actual.paramWhere.left.tableid, + }, + op: actual.paramWhere.op, + right: {param: actual.paramWhere.right.param}, + }, + numWhere: { + left: {columnid: actual.numWhere.left.columnid, tableid: actual.numWhere.left.tableid}, + op: actual.numWhere.op, + right: {value: actual.numWhere.right.value}, + }, + strWhere: { + left: {columnid: actual.strWhere.left.columnid, tableid: actual.strWhere.left.tableid}, + op: actual.strWhere.op, + right: {value: actual.strWhere.right.value}, + }, + boolWhere: { + left: {columnid: actual.boolWhere.left.columnid, tableid: actual.boolWhere.left.tableid}, + op: actual.boolWhere.op, + right: {value: actual.boolWhere.right.value}, + }, + uniWhere: { + left: { + op: actual.uniWhere.left.op, + right: { + columnid: actual.uniWhere.left.right.columnid, + tableid: actual.uniWhere.left.right.tableid, + }, + }, + op: actual.uniWhere.op, + right: {value: actual.uniWhere.right.value}, + }, + }, + { + paramWhere: {left: {columnid: 'x', tableid: undefined}, op: '<', right: {param: 'y'}}, + numWhere: {left: {columnid: 'x', tableid: undefined}, op: '<', right: {value: 10}}, + strWhere: {left: {columnid: 'x', tableid: undefined}, op: '=', right: {value: 'abc'}}, + boolWhere: {left: {columnid: 'x', tableid: undefined}, op: '=', right: {value: true}}, + uniWhere: { + left: {op: '-', right: {columnid: 'x', tableid: undefined}}, + op: '<', + right: {value: 0}, + }, + } + ); }); }); From 166c9288e5ef8e8fd45ab58d33beb963cd4fbfb5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 25 Aug 2026 09:09:56 +0000 Subject: [PATCH 5/7] Refactor test2498 to explicit normalized-vs-expected object assertions --- test/test2498.js | 90 ++++++++++++++++++++++++------------------------ 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/test/test2498.js b/test/test2498.js index f2234a6890..2ef77e80a2 100644 --- a/test/test2498.js +++ b/test/test2498.js @@ -31,54 +31,54 @@ describe(`Test ${testId} - parser AST surface`, function () { uniWhere: alasql.parse('SELECT 1 FROM ? WHERE -x < 0').statements[0].where.expression, }; - assert.deepStrictEqual( - { - paramWhere: { - left: { - columnid: actual.paramWhere.left.columnid, - tableid: actual.paramWhere.left.tableid, - }, - op: actual.paramWhere.op, - right: {param: actual.paramWhere.right.param}, - }, - numWhere: { - left: {columnid: actual.numWhere.left.columnid, tableid: actual.numWhere.left.tableid}, - op: actual.numWhere.op, - right: {value: actual.numWhere.right.value}, - }, - strWhere: { - left: {columnid: actual.strWhere.left.columnid, tableid: actual.strWhere.left.tableid}, - op: actual.strWhere.op, - right: {value: actual.strWhere.right.value}, - }, - boolWhere: { - left: {columnid: actual.boolWhere.left.columnid, tableid: actual.boolWhere.left.tableid}, - op: actual.boolWhere.op, - right: {value: actual.boolWhere.right.value}, + const normalized = { + paramWhere: { + left: { + columnid: actual.paramWhere.left.columnid, + tableid: actual.paramWhere.left.tableid, }, - uniWhere: { - left: { - op: actual.uniWhere.left.op, - right: { - columnid: actual.uniWhere.left.right.columnid, - tableid: actual.uniWhere.left.right.tableid, - }, + op: actual.paramWhere.op, + right: {param: actual.paramWhere.right.param}, + }, + numWhere: { + left: {columnid: actual.numWhere.left.columnid, tableid: actual.numWhere.left.tableid}, + op: actual.numWhere.op, + right: {value: actual.numWhere.right.value}, + }, + strWhere: { + left: {columnid: actual.strWhere.left.columnid, tableid: actual.strWhere.left.tableid}, + op: actual.strWhere.op, + right: {value: actual.strWhere.right.value}, + }, + boolWhere: { + left: {columnid: actual.boolWhere.left.columnid, tableid: actual.boolWhere.left.tableid}, + op: actual.boolWhere.op, + right: {value: actual.boolWhere.right.value}, + }, + uniWhere: { + left: { + op: actual.uniWhere.left.op, + right: { + columnid: actual.uniWhere.left.right.columnid, + tableid: actual.uniWhere.left.right.tableid, }, - op: actual.uniWhere.op, - right: {value: actual.uniWhere.right.value}, }, + op: actual.uniWhere.op, + right: {value: actual.uniWhere.right.value}, }, - { - paramWhere: {left: {columnid: 'x', tableid: undefined}, op: '<', right: {param: 'y'}}, - numWhere: {left: {columnid: 'x', tableid: undefined}, op: '<', right: {value: 10}}, - strWhere: {left: {columnid: 'x', tableid: undefined}, op: '=', right: {value: 'abc'}}, - boolWhere: {left: {columnid: 'x', tableid: undefined}, op: '=', right: {value: true}}, - uniWhere: { - left: {op: '-', right: {columnid: 'x', tableid: undefined}}, - op: '<', - right: {value: 0}, - }, - } - ); + }; + const expected = { + paramWhere: {left: {columnid: 'x', tableid: undefined}, op: '<', right: {param: 'y'}}, + numWhere: {left: {columnid: 'x', tableid: undefined}, op: '<', right: {value: 10}}, + strWhere: {left: {columnid: 'x', tableid: undefined}, op: '=', right: {value: 'abc'}}, + boolWhere: {left: {columnid: 'x', tableid: undefined}, op: '=', right: {value: true}}, + uniWhere: { + left: {op: '-', right: {columnid: 'x', tableid: undefined}}, + op: '<', + right: {value: 0}, + }, + }; + + assert.deepStrictEqual(normalized, expected); }); }); From 85fe594c2dba65fee9ba17645d654c9345c6ec52 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 25 Aug 2026 09:11:27 +0000 Subject: [PATCH 6/7] Assert complete AST objects with explicit undefined tableid checks --- test/test2498.js | 54 ++++++++++-------------------------------------- 1 file changed, 11 insertions(+), 43 deletions(-) diff --git a/test/test2498.js b/test/test2498.js index 2ef77e80a2..b7814afc6d 100644 --- a/test/test2498.js +++ b/test/test2498.js @@ -23,7 +23,7 @@ describe(`Test ${testId} - parser AST surface`, function () { }); it('B) preserves stable expression node shapes used by parser consumers', function () { - const actual = { + const parsed = { paramWhere: alasql.parse('SELECT 1 FROM ? WHERE x < $y').statements[0].where.expression, numWhere: alasql.parse('SELECT 1 FROM ? WHERE x < 10').statements[0].where.expression, strWhere: alasql.parse("SELECT 1 FROM ? WHERE x = 'abc'").statements[0].where.expression, @@ -31,54 +31,22 @@ describe(`Test ${testId} - parser AST surface`, function () { uniWhere: alasql.parse('SELECT 1 FROM ? WHERE -x < 0').statements[0].where.expression, }; - const normalized = { - paramWhere: { - left: { - columnid: actual.paramWhere.left.columnid, - tableid: actual.paramWhere.left.tableid, - }, - op: actual.paramWhere.op, - right: {param: actual.paramWhere.right.param}, - }, - numWhere: { - left: {columnid: actual.numWhere.left.columnid, tableid: actual.numWhere.left.tableid}, - op: actual.numWhere.op, - right: {value: actual.numWhere.right.value}, - }, - strWhere: { - left: {columnid: actual.strWhere.left.columnid, tableid: actual.strWhere.left.tableid}, - op: actual.strWhere.op, - right: {value: actual.strWhere.right.value}, - }, - boolWhere: { - left: {columnid: actual.boolWhere.left.columnid, tableid: actual.boolWhere.left.tableid}, - op: actual.boolWhere.op, - right: {value: actual.boolWhere.right.value}, - }, - uniWhere: { - left: { - op: actual.uniWhere.left.op, - right: { - columnid: actual.uniWhere.left.right.columnid, - tableid: actual.uniWhere.left.right.tableid, - }, - }, - op: actual.uniWhere.op, - right: {value: actual.uniWhere.right.value}, - }, - }; + assert.strictEqual(parsed.paramWhere.left.tableid, undefined); + assert.strictEqual(parsed.uniWhere.left.right.tableid, undefined); + + const actual = JSON.parse(JSON.stringify(parsed)); const expected = { - paramWhere: {left: {columnid: 'x', tableid: undefined}, op: '<', right: {param: 'y'}}, - numWhere: {left: {columnid: 'x', tableid: undefined}, op: '<', right: {value: 10}}, - strWhere: {left: {columnid: 'x', tableid: undefined}, op: '=', right: {value: 'abc'}}, - boolWhere: {left: {columnid: 'x', tableid: undefined}, op: '=', right: {value: true}}, + paramWhere: {left: {columnid: 'x'}, op: '<', right: {param: 'y'}}, + numWhere: {left: {columnid: 'x'}, op: '<', right: {value: 10}}, + strWhere: {left: {columnid: 'x'}, op: '=', right: {value: 'abc'}}, + boolWhere: {left: {columnid: 'x'}, op: '=', right: {value: true}}, uniWhere: { - left: {op: '-', right: {columnid: 'x', tableid: undefined}}, + left: {op: '-', right: {columnid: 'x'}}, op: '<', right: {value: 0}, }, }; - assert.deepStrictEqual(normalized, expected); + assert.deepStrictEqual(actual, expected); }); }); From 9806e81cc12568154811ee1e3bb053ed632ab8b6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 25 Aug 2026 09:12:03 +0000 Subject: [PATCH 7/7] Add explicit undefined tableid checks for all unqualified nodes --- test/test2498.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/test2498.js b/test/test2498.js index b7814afc6d..7c8dae98a4 100644 --- a/test/test2498.js +++ b/test/test2498.js @@ -32,6 +32,9 @@ describe(`Test ${testId} - parser AST surface`, function () { }; assert.strictEqual(parsed.paramWhere.left.tableid, undefined); + assert.strictEqual(parsed.numWhere.left.tableid, undefined); + assert.strictEqual(parsed.strWhere.left.tableid, undefined); + assert.strictEqual(parsed.boolWhere.left.tableid, undefined); assert.strictEqual(parsed.uniWhere.left.right.tableid, undefined); const actual = JSON.parse(JSON.stringify(parsed));