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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "sqlparser-devexpress",
"version": "2.3.7",
"version": "2.3.10",
"main": "src/index.js",
"type": "module",
"scripts": {
Expand Down
38 changes: 25 additions & 13 deletions src/core/converter.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,27 +135,30 @@ function DevExpressConverter() {
}

const left = ast.left !== undefined ? processAstNode(ast.left) : convertValue(ast.field);
const leftDefault = ast.left?.args[1]?.value;
const right = ast.right !== undefined ? processAstNode(ast.right) : convertValue(ast.value);
const rightDefault = ast.right?.args[1]?.value;
let operatorToken = ast.operator.toLowerCase();

if(operatorToken === "like") {
if (operatorToken === "like") {
operatorToken = "contains";
}else if (operatorToken === "not like") {
} else if (operatorToken === "not like") {
operatorToken = "notcontains";
}

let comparison = [left, operatorToken, right];

// Last null because of special case when using dropdown it https://github.com/DevExpress/DevExtreme/blob/25_1/packages/devextreme/js/__internal/data/m_utils.ts#L18 it takes last value as null
if ((ast.left && isFunctionNullCheck(ast.left, true)) || (ast.value && isFunctionNullCheck(ast.value, false))) {
comparison = [[left, operatorToken, right], 'or', [left, operatorToken, null, {type: "ISNULL", defaultValue: (ast.left ?? ast.value).args[1]?.value}]];
comparison = [[left, operatorToken, right], 'or', [left, operatorToken, null, { type: "ISNULL", defaultValue: (ast.left ?? ast.value).args[1]?.value }, null]];
} else if (ast.right && isFunctionNullCheck(ast.right, true)) {
comparison = [[left, operatorToken, right], 'or', [right, operatorToken, null, {type: "ISNULL", defaultValue: ast.right.args[1]?.value}]];
comparison = [[left, operatorToken, right], 'or', [right, operatorToken, null, { type: "ISNULL", defaultValue: ast.right.args[1]?.value }, null]];
}

// Apply short-circuit evaluation if enabled
if (EnableShortCircuit) {
if (isAlwaysTrue(comparison)) return true;
if (isAlwaysFalse(comparison)) return false;
if (isAlwaysTrue(comparison, leftDefault, rightDefault)) return true;
if (isAlwaysFalse(comparison, leftDefault, rightDefault)) return false;
}

return comparison;
Expand Down Expand Up @@ -229,8 +232,8 @@ function DevExpressConverter() {
if (typeof val === "object") {
if (val.type === "placeholder") {
const placeholderValue = resolvePlaceholderFromResultObject(val.value);
if(val?.dataType === "string"){

if (val?.dataType === "string") {
return placeholderValue?.toString();
}

Expand Down Expand Up @@ -331,29 +334,38 @@ function DevExpressConverter() {
/**
* Checks if a condition is always true.
* @param {Array} condition - The condition to check.
* @param {*} leftDefault - The default value for the left operand.
* @param {*} rightDefault - The default value for the right operand.
* @returns {boolean} True if the condition is always true.
*/
function isAlwaysTrue(condition) {
return Array.isArray(condition) && condition.length >= 3 && evaluateExpression(...condition) == true;
function isAlwaysTrue(condition, leftDefault, rightDefault) {
return Array.isArray(condition) && condition.length >= 3 && evaluateExpression(...condition, leftDefault, rightDefault) == true;
}

/**
* Checks if a condition is always false.
* @param {Array} condition - The condition to check.
* @param {*} leftDefault - The default value for the left operand.
* @param {*} rightDefault - The default value for the right operand.
* @returns {boolean} True if the condition is always false.
*/
function isAlwaysFalse(condition) {
return Array.isArray(condition) && condition.length >= 3 && evaluateExpression(...condition) == false;
function isAlwaysFalse(condition, leftDefault, rightDefault) {
return Array.isArray(condition) && condition.length >= 3 && evaluateExpression(...condition, leftDefault, rightDefault) == false;
}

/**
* Evaluates a simple expression.
* @param {*} left - The left operand.
* @param {string} operator - The operator.
* @param {*} right - The right operand.
* @param {*} leftDefault - The default value for the left operand.
* @param {*} rightDefault - The default value for the right
* @returns {boolean|null} The result of the evaluation or null if not evaluable.
*/
function evaluateExpression(left, operator, right) {
function evaluateExpression(left, operator, right, leftDefault, rightDefault) {
if (left == null && leftDefault != undefined) left = leftDefault;
if (right == null && rightDefault != undefined) right = rightDefault;

if ((left !== null && isNaN(left)) || (right !== null && isNaN(right))) return null;

if (left === null || right === null) {
Expand Down
24 changes: 14 additions & 10 deletions tests/parser.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,11 +137,11 @@ describe("Parser SQL to dx Filter Builder", () => {
expected: [
["SourceID", "=", 2],
"or",
["SourceID", "=", null,{ "defaultValue": 0, "type": "ISNULL"}],
["SourceID", "=", null, { "defaultValue": 0, "type": "ISNULL" }, null],
"or",
["SourceID", "=", 0],
"or",
["SourceID", "=", null,{ "defaultValue": 0, "type": "ISNULL"}]
["SourceID", "=", null, { "defaultValue": 0, "type": "ISNULL" }, null]
]
},
{
Expand All @@ -153,14 +153,14 @@ describe("Parser SQL to dx Filter Builder", () => {
[
["CompanyID", "=", 0],
"or",
["CompanyID", "=", null,{ "defaultValue": 0, "type": "ISNULL"}]
["CompanyID", "=", null, { "defaultValue": 0, "type": "ISNULL" }, null]
]
],
"and",
[
["IsSubdealer", "=", true],
"or",
["IsSubdealer", "=", null,{ "defaultValue": 0, "type": "ISNULL"}]
["IsSubdealer", "=", null, { "defaultValue": 0, "type": "ISNULL" }, null]
]
]
},
Expand All @@ -187,22 +187,20 @@ describe("Parser SQL to dx Filter Builder", () => {
expected: [
["TicketID", "=", 123],
"or",
["TicketID", "=", null,{ "defaultValue": 0, "type": "ISNULL"}]
["TicketID", "=", null, { "defaultValue": 0, "type": "ISNULL" }, null]
]
},
{
input: "CompanyID = ISNULL({LeadDocument.CompanyID},0) OR (ISNULL(CompanyID,0) = 0))",
expected: [
["CompanyID", "=", 7],
"or",
["CompanyID", "=", null,{ "defaultValue": 0, "type": "ISNULL"}],
["CompanyID", "=", null, { "defaultValue": 0, "type": "ISNULL" }, null],
"or",
["CompanyID", "=", 0],
"or",
["CompanyID", "=", null,{ "defaultValue": 0, "type": "ISNULL"},
["CompanyID", "=", null, { "defaultValue": 0, "type": "ISNULL" }, null]

]

]
},
{
Expand All @@ -212,6 +210,10 @@ describe("Parser SQL to dx Filter Builder", () => {
"and",
["BranchName", "notcontains", "42"]
]
},
{
input: "(RS2ID in ({SaleOrderStatusStmtGlobalRpt.StateID}) Or (ISNULL({SaleOrderStatusStmtGlobalRpt.StateID},0) =0)) And (RS3ID in (0,{SaleOrderStatusStmtGlobalRpt.RegionID}) Or ISNULL({SaleOrderStatusStmtGlobalRpt.RegionID},0) =0 )",
expected: []
}
];

Expand Down Expand Up @@ -272,5 +274,7 @@ const sampleData = {
"LeadDocument.CompanyID": 7,
"ServiceOrderDocument.SourceID": 2,
"LeadDocument.AllowSubDealer": true,
"SupportResolution.TicketID": 123
"SupportResolution.TicketID": 123,
"SaleOrderStatusStmtGlobalRpt.StateID": null,
"SaleOrderStatusStmtGlobalRpt.RegionID": null,
};