diff --git a/test/test2498.js b/test/test2498.js new file mode 100644 index 0000000000..7c8dae98a4 --- /dev/null +++ b/test/test2498.js @@ -0,0 +1,55 @@ +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 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, + 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, + }; + + 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)); + const expected = { + 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'}}, + op: '<', + right: {value: 0}, + }, + }; + + assert.deepStrictEqual(actual, expected); + }); +}); diff --git a/types/alasql.d.ts b/types/alasql.d.ts index 2183b3256c..8f7904ece2 100644 --- a/types/alasql.d.ts +++ b/types/alasql.d.ts @@ -39,8 +39,67 @@ 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 = Op | Column | ParamValue | NumValue | StringValue | LogicValue | UniOp; + + interface ExpressionWrapper extends AlaSQLExpression { + expression: ExpressionNode; + reduced?: boolean; + } + + interface Statement { + compile(databaseid: string): AlaSQLStatement; + where?: ExpressionWrapper; + [key: string]: unknown; + } + // abstract Syntax Tree interface AlaSQLAST { + statements: Statement[]; compile(databaseid: string): AlaSQLStatement; }