diff --git a/package.json b/package.json index 18726dd..a794f72 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sqlparser-devexpress", - "version": "2.3.16", + "version": "2.3.17", "main": "src/index.js", "type": "module", "scripts": { diff --git a/src/@types/core/converter.d.ts b/src/@types/core/converter.d.ts index a19ce0e..faf6d2b 100644 --- a/src/@types/core/converter.d.ts +++ b/src/@types/core/converter.d.ts @@ -10,6 +10,7 @@ export interface ConvertOptions { ast: ASTNode; resultObject?: ResultObject; enableShortCircuit?: boolean; + isValueNullShortCircuit?: boolean; } /** diff --git a/src/@types/default.d.ts b/src/@types/default.d.ts index c8a58a1..5cede39 100644 --- a/src/@types/default.d.ts +++ b/src/@types/default.d.ts @@ -32,10 +32,12 @@ export function convertSQLToAst(filterString: string, enableConsoleLogs?: boolea * @param ast - The parsed AST from `convertSQLToAst`. * @param state - An optional result object to resolve placeholders to actual values. * @param enableShortCircuit - Whether to apply short-circuit evaluation. + * @param isValueNullShortCircuit - Whether to treat null values as short-circuit conditions. * @returns DevExpressFilter - The DevExpress-compatible filter array or null. */ export function convertAstToDevextreme( ast: ASTNode, state?: ResultObject | null, enableShortCircuit?: boolean, + isValueNullShortCircuit?: boolean ): DevExpressFilter; \ No newline at end of file diff --git a/src/core/converter.js b/src/core/converter.js index 0bddabe..5ca3651 100644 --- a/src/core/converter.js +++ b/src/core/converter.js @@ -8,6 +8,7 @@ function DevExpressConverter() { // Global variables accessible throughout the converter let resultObject = null; let EnableShortCircuit = true; + let IsValueNullShortCircuit = false; // Flag to enable/disable null short-circuiting /** * Main conversion function that sets up the global context @@ -16,10 +17,11 @@ function DevExpressConverter() { * @param {boolean} enableShortCircuit - Optional enabling and disabling the shortcircuit ie evaluating value = value scenario * @returns {Array|null} DevExpress format filter */ - function convert(ast, ResultObject = null, enableShortCircuit = true) { + function convert(ast, ResultObject = null, enableShortCircuit = true, isValueNullShortCircuit = false) { // Set up global context resultObject = ResultObject; EnableShortCircuit = enableShortCircuit; + IsValueNullShortCircuit = isValueNullShortCircuit; // Process the AST let result = processAstNode(ast); @@ -89,6 +91,7 @@ function DevExpressConverter() { const left = processAstNode(ast.left, operator); const right = processAstNode(ast.right, operator); + if (EnableShortCircuit) { // Short-circuit: always-true conditions if (left === true || right === true) { @@ -100,7 +103,6 @@ function DevExpressConverter() { if (left === false || right === false) { return left === false ? right : left; } - } // Detect and flatten nested logical expressions @@ -165,6 +167,10 @@ function DevExpressConverter() { } // Apply short-circuit evaluation if enabled + if (EnableShortCircuit && IsValueNullShortCircuit && (left == null || right == null)) { + return true; // If either value is null, return true for short-circuit evaluation + } + if (EnableShortCircuit) { if (isAlwaysTrue(comparison, leftDefault, rightDefault)) return true; if (isAlwaysFalse(comparison, leftDefault, rightDefault)) return false; @@ -242,6 +248,10 @@ function DevExpressConverter() { } } + if (EnableShortCircuit && IsValueNullShortCircuit && (ast.field?.type === "placeholder" || ast.value?.type === "placeholder" || ast.value === null) && resolvedValue === null) { + return true; + } + let operatorToken = operator === "IN" ? '=' : operator === "NOT IN" ? '!=' : operator; let joinOperatorToken = operator === "IN" ? 'or' : operator === "NOT IN" ? 'and' : operator; let field = convertValue(ast.field); @@ -443,8 +453,9 @@ const devExpressConverter = DevExpressConverter(); * @param {Object} ast - The abstract syntax tree * @param {Object} resultObject - Optional object for placeholder resolution * @param {string} enableShortCircuit - Optional enabling and disabling the shortcircuit ie evaluating value = value scenario + * @param {boolean} isValueNullShortCircuit - Optional enabling and disabling the null shortcircuit ie evaluating value = null scenario * @returns {Array|null} DevExpress format filter */ -export function convertToDevExpressFormat({ ast, resultObject = null, enableShortCircuit = true }) { - return devExpressConverter.init(ast, resultObject, enableShortCircuit); +export function convertToDevExpressFormat({ ast, resultObject = null, enableShortCircuit = true, isValueNullShortCircuit = false }) { + return devExpressConverter.init(ast, resultObject, enableShortCircuit, isValueNullShortCircuit); } \ No newline at end of file diff --git a/src/debug.js b/src/debug.js index c1cca18..9f6cc69 100644 --- a/src/debug.js +++ b/src/debug.js @@ -25,7 +25,7 @@ // const astTree = parsedResult.ast; // console.log("AST Tree:", JSON.stringify(astTree, null, 2), "\n"); -// return convertToDevExpressFormat({ ast: astTree, resultObject: sampleData }); +// return convertToDevExpressFormat({ ast: astTree, resultObject: sampleData, isValueNullShortCircuit: true }); // } // const devexpress = parseFilterString("(ISNULL(TicketID, 0) = ISNULL({CustomerOrders.OrderID}, 0))", sampleData); diff --git a/src/index.js b/src/index.js index d28bef0..f5fa1c6 100644 --- a/src/index.js +++ b/src/index.js @@ -16,8 +16,8 @@ export function convertSQLToAst(filterString, enableConsoleLogs = false) { return parsedResult; } -export function convertAstToDevextreme(ast, state = null, enableShortCircuit = true) { - return convertToDevExpressFormat({ ast, resultObject: state, enableShortCircuit }) +export function convertAstToDevextreme(ast, state = null, enableShortCircuit = true, isValueNullShortCircuit = false) { + return convertToDevExpressFormat({ ast, resultObject: state, enableShortCircuit, isValueNullShortCircuit }) } diff --git a/tests/parser.test.js b/tests/parser.test.js index de3b053..80cdc43 100644 --- a/tests/parser.test.js +++ b/tests/parser.test.js @@ -260,6 +260,14 @@ describe("Parser SQL to dx Filter Builder", () => { { input: "{LeadDocument.AllowSubDealer} != null", expected: [] + }, + { + input: "ID = {SaleOrderStatusStmtGlobalRpt.RegionID}", + expected: [] + }, + { + input: "ID IN ({SaleOrderStatusStmtGlobalRpt.RegionID})", + expected: [] } ]; @@ -282,7 +290,7 @@ describe("Parser SQL to dx Filter Builder", () => { const variables = astwithVariables.variables; const ast = astwithVariables.ast; - const result = convertAstToDevextreme(ast, sampleData); + const result = convertAstToDevextreme(ast, sampleData, true, true); if (result == null || result == true || result == false) { expect([]).toEqual(expected);