sqlparser-devexpress 2.4.2 → 2.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sqlparser-devexpress",
3
- "version": "2.4.2",
3
+ "version": "2.5.1",
4
4
  "main": "src/index.js",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -33,7 +33,10 @@ function DevExpressConverter() {
33
33
  // Handle special cases for short circuit
34
34
  if (result === true || result === false || result === null) return [];
35
35
 
36
- return processAstNode(ast);
36
+ if (result.length == 1) {
37
+ return result[0];
38
+ }
39
+ return result;
37
40
  }
38
41
 
39
42
  /**
@@ -164,27 +167,60 @@ function DevExpressConverter() {
164
167
  if (includeExtradata)
165
168
  comparison = [left, operatorToken, right, { type: originalOperator }, right];
166
169
 
167
- // 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
168
- if ((ast.left && isFunctionNullCheck(ast.left, true)) || (ast.value && isFunctionNullCheck(ast.value, false))) {
170
+ let isLeftNullCheck = isFunctionNullCheck(ast.left, true);
171
+ let isRightNullCheck = isFunctionNullCheck(ast.right, false) || (ast.value && isFunctionNullCheck(ast.value, false));
172
+ let isBothNullChecks = isLeftNullCheck && isRightNullCheck;
173
+ let isdestructured = false;
174
+ // 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.
175
+ if (!isBothNullChecks && isLeftNullCheck) {
169
176
  const nullCheckArg = (ast.left ?? ast.value).args[1]?.value;
177
+ let valueRight = null
178
+
170
179
  let baseComparison = comparison;
171
180
 
172
181
  if (Array.isArray(right) && (right.includes("or") || right.includes("and"))) {
173
- const valueRight = right.shift();
182
+ isdestructured = true;
183
+ valueRight = right.shift();
174
184
  baseComparison = [[left, operatorToken, valueRight], ...right];
175
185
  }
186
+ const _val = !isdestructured ? baseComparison[2] : valueRight;
187
+ if (normalizeBool(_val) == normalizeBool(nullCheckArg))
188
+ comparison = [[...baseComparison], 'or', [left, operatorToken, null, { type: "ISNULL", position: "column", defaultValue: nullCheckArg }, null]];
189
+ else
190
+ comparison = [...baseComparison, { type: "ISNULL", position: "column", defaultValue: nullCheckArg }, _val];
176
191
 
177
- comparison = [baseComparison, 'or', [left, operatorToken, null, { type: "ISNULL", defaultValue: nullCheckArg }, null]];
178
- } else if (ast.right && isFunctionNullCheck(ast.right, true)) {
179
- const nullCheckArg = ast.right.args[1]?.value;
192
+
193
+ } else if (!isBothNullChecks && isRightNullCheck) {
194
+ const nullCheckArg = (ast.right ?? ast.value).args[1]?.value;
195
+ let valueLeft = null
180
196
  let baseComparison = comparison;
181
197
 
182
- if (Array.isArray(right) && (right.includes("or") || right.includes("and"))) {
183
- const valueRight = right.shift();
184
- baseComparison = [[left, operatorToken, valueRight], ...right];
198
+ if (Array.isArray(left) && (left.includes("or") || left.includes("and"))) {
199
+ isdestructured = true;
200
+ valueLeft = left.shift();
201
+ baseComparison = [[valueLeft, operatorToken, right], ...left];
185
202
  }
203
+ const _val = !isdestructured ? baseComparison[2] : valueRight;
204
+
205
+ comparison = [...baseComparison, { type: "ISNULL", position: "value", defaultValue: nullCheckArg }, _val];
206
+ } else if (isBothNullChecks) {
207
+ const nullCheckArgleft = (ast.left ?? ast.value).args[1]?.value;
208
+ const nullCheckArgright = (ast.right ?? ast.value).args[1]?.value;
209
+ let valueLeft = null;
210
+ let baseComparison = comparison;
186
211
 
187
- comparison = [baseComparison, 'or', [right, operatorToken, null, { type: "ISNULL", defaultValue: nullCheckArg }, null]];
212
+ if (Array.isArray(left) && (left.includes("or") || left.includes("and"))) {
213
+ isdestructured = true;
214
+ valueLeft = left.shift();
215
+ baseComparison = [[valueLeft, operatorToken, right], ...left];
216
+ }
217
+
218
+ const _val = !isdestructured ? baseComparison[2] : valueLeft;
219
+
220
+ if (normalizeBool(nullCheckArgleft) == normalizeBool(nullCheckArgright))
221
+ comparison = [[...baseComparison, { type: "ISNULL", position: "both", defaultValue: nullCheckArgleft, defaultValueRight: nullCheckArgright }, _val], 'or', [left, operatorToken, null]];
222
+ else
223
+ comparison = [[...baseComparison, { type: "ISNULL", position: "both", defaultValue: nullCheckArgleft, defaultValueRight: nullCheckArgright }, _val]];
188
224
  }
189
225
 
190
226
  // Apply short-circuit evaluation if enabled
@@ -217,6 +253,20 @@ function DevExpressConverter() {
217
253
  return comparison;
218
254
  }
219
255
 
256
+ /**
257
+ * Normalizes numeric boolean-like values to actual booleans.
258
+ *
259
+ * Converts the number `0` to `false` and `1` to `true`.
260
+ * If the input is not exactly `0` or `1`, it returns the value unchanged.
261
+ *
262
+ * @param {*} value - The value to normalize. Can be of any type.
263
+ * @returns {*} - Returns `false` if `value` is `0`, `true` if `value` is `1`, otherwise returns the original value.
264
+ */
265
+ function normalizeBool(value) {
266
+ return value === 0 || value === 1 ? Boolean(value) : value;
267
+ }
268
+
269
+
220
270
  /**
221
271
  * Handles function calls, focusing on ISNULL.
222
272
  * @param {Object} ast - The function AST node.
@@ -106,6 +106,21 @@ export function parse(input, variables = []) {
106
106
  return { type: "logical", operator, left: { type: "function", name: functionName, args: functionArgs }, right: rightOperand };
107
107
  }
108
108
 
109
+ if (rightOperand.type === "logical" && nodeType === "comparison") {
110
+ return {
111
+ type: "logical",
112
+ left: {
113
+ type: "comparison",
114
+ left: { type: "function", name: functionName, args: functionArgs },
115
+ operator: operator,
116
+ right: rightOperand.left
117
+ },
118
+ operator: rightOperand.operator,
119
+ right: rightOperand.right
120
+
121
+ }
122
+ }
123
+
109
124
  return {
110
125
  type: "comparison",
111
126
  left: { type: "function", name: functionName, args: functionArgs },
@@ -134,11 +149,10 @@ export function parse(input, variables = []) {
134
149
  operator: operator,
135
150
  value: rightList
136
151
  };
137
- }
138
-
139
- if (LOGICAL_OPERATORS.includes(operator.toLowerCase())) {
152
+ } else if (LOGICAL_OPERATORS.includes(operator.toLowerCase())) {
140
153
  // Recursively parse the right-hand expression with adjusted precedence
141
- const right = parseExpression(OPERATOR_PRECEDENCE[operator]);
154
+ // Add 1 to ensure left-associativity for operators of the same precedence
155
+ const right = parseExpression(OPERATOR_PRECEDENCE[operator] + 1);
142
156
  left = { type: "logical", operator, left, right };
143
157
  } else if (currentToken?.type == "identifier") {
144
158
  const right = parseValue(operator);
@@ -173,7 +187,32 @@ export function parse(input, variables = []) {
173
187
  }
174
188
 
175
189
  // Handle function calls like ISNULL(field)
176
- if (currentToken.type === "function") return parseFunction();
190
+ if (currentToken.type === "function") {
191
+ const functionNode = parseFunction();
192
+
193
+ // Check if the function is followed by a comparison operator
194
+ if (currentToken && currentToken.type === "operator" && !LOGICAL_OPERATORS.includes(currentToken.value.toLowerCase())) {
195
+ const operator = currentToken.value.toLowerCase();
196
+ const originalOperator = currentToken.originalValue;
197
+ next();
198
+
199
+ if (operator === "between") {
200
+ return parseBetweenComparison(functionNode, operator);
201
+ }
202
+
203
+ const value = parseValue(operator);
204
+
205
+ return {
206
+ type: "comparison",
207
+ field: functionNode,
208
+ operator,
209
+ value,
210
+ originalOperator
211
+ };
212
+ }
213
+
214
+ return functionNode;
215
+ }
177
216
 
178
217
  // Handle literal values (numbers, strings, null)
179
218
  if (LITERALS.includes(currentToken.type)) {
@@ -186,7 +225,7 @@ export function parse(input, variables = []) {
186
225
  const field = parseValue();
187
226
 
188
227
  // Check if it's part of a comparison expression
189
- if (currentToken && currentToken.type === "operator") {
228
+ if (currentToken && currentToken.type === "operator" && !LOGICAL_OPERATORS.includes(currentToken.value.toLowerCase())) {
190
229
  const operator = currentToken.value.toLowerCase();
191
230
  const originalOperator = currentToken.originalValue;
192
231
  next();
package/src/debug.js CHANGED
@@ -11,7 +11,8 @@
11
11
  // 'LeadStatementGlobalRpt.StateID': null,
12
12
  // 'LeadStatementGlobalRpt.RegionID': null,
13
13
  // 'ServiceOrderDocument.SourceID': 2,
14
- // 'CustomerOrders.OrderID': 76548
14
+ // 'CustomerOrders.OrderID': 76548,
15
+ // "TransferOutwardDocument.CompanyID": 7,
15
16
  // }
16
17
 
17
18
  // export function parseFilterString(filterString, sampleData = null) {
@@ -25,10 +26,10 @@
25
26
  // const astTree = parsedResult.ast;
26
27
  // console.log("AST Tree:", JSON.stringify(astTree, null, 2), "\n");
27
28
 
28
- // return convertToDevExpressFormat({ ast: astTree, resultObject: sampleData, isValueNullShortCircuit: true });
29
+ // return convertToDevExpressFormat({ ast: astTree, resultObject: sampleData, options: { isValueNullShortCircuit: true, treatNumberAsNullableBit: true } });
29
30
  // }
30
31
 
31
- // const devexpress = parseFilterString("ISNULL(CompanyID,0) = ISNULL({CustomerOrders.OrderID},0) OR (ISNULL(CompanyID,0) = 0)", sampleData);
32
+ // const devexpress = parseFilterString("ISNULL(CompanyID,0) = ISNULL({TransferOutwardDocument.CompanyID},0) OR ISNULL(CompanyID,0) = 0", sampleData);
32
33
  // console.log("DevExpress Filter:", JSON.stringify(devexpress, null, 2));
33
34
  // // const devexpress = parseFilterString("(RS2ID in ({LeadStatementGlobalRpt.StateID}) Or ({LeadStatementGlobalRpt.StateID} =0)) And (RS3ID in (0,{LeadStatementGlobalRpt.RegionID}) Or {LeadStatementGlobalRpt.RegionID} =0 )", sampleData);
34
35
 
@@ -71,18 +71,20 @@ describe("Parser SQL to dx Filter Builder", () => {
71
71
  expected: [
72
72
  [
73
73
  [
74
- ["FromDate", "<=", "2022-01-01"],
75
- 'and',
76
- ["ToDate", ">=", "2022-01-01"]
74
+ [
75
+ ["FromDate", "<=", "2022-01-01"],
76
+ 'and',
77
+ ["ToDate", ">=", "2022-01-01"]
78
+ ],
79
+ 'or',
80
+ ["ToDate", "=", null, { "type": "IS" }, null]
77
81
  ],
78
- 'or',
79
- ["ToDate", "=", null, { "type": "IS" }, null]
80
- ],
81
- "and",
82
- [
83
- ["BranchID", "=", 42],
84
- "or",
85
- ["RefBranchID", "=", null, { "type": "IS" }, null]
82
+ "and",
83
+ [
84
+ ["BranchID", "=", 42],
85
+ "or",
86
+ ["RefBranchID", "=", null, { "type": "IS" }, null]
87
+ ]
86
88
  ],
87
89
  "and",
88
90
  [
@@ -135,15 +137,15 @@ describe("Parser SQL to dx Filter Builder", () => {
135
137
  {
136
138
  input: "ISNULL(SourceID,0) = {ServiceOrderDocument.SourceID} OR ISNULL(SourceID,0) = 0",
137
139
  expected: [
138
- ["SourceID", "=", 2],
139
- "or",
140
- ["SourceID", "=", null, { "defaultValue": 0, "type": "ISNULL" }, null],
141
- "or",
142
- ["SourceID", "=", 0],
140
+ ["SourceID", "=", 2, { "defaultValue": 0, "position": "column", "type": "ISNULL" }, 2],
143
141
  "or",
144
- ["SourceID", "=", null, { "defaultValue": 0, "type": "ISNULL" }, null],
145
- "or",
146
- ["SourceID", "=", false]
142
+ [
143
+ ["SourceID", "=", 0],
144
+ "or",
145
+ ["SourceID", "=", null, { "defaultValue": 0, "position": "column", "type": "ISNULL" }, null],
146
+ "or",
147
+ ["SourceID", "=", false]
148
+ ]
147
149
  ]
148
150
  },
149
151
  {
@@ -155,17 +157,19 @@ describe("Parser SQL to dx Filter Builder", () => {
155
157
  [
156
158
  ["CompanyID", "=", 0],
157
159
  "or",
158
- ["CompanyID", "=", null, { "defaultValue": 0, "type": "ISNULL" }, null],
160
+ ["CompanyID", "=", null, { "defaultValue": 0, "position": "column", "type": "ISNULL" }, null],
159
161
  "or",
160
162
  ["CompanyID", "=", false]
161
163
  ]
162
164
  ],
163
165
  "and",
164
- [
165
- ["IsSubdealer", "=", true],
166
- "or",
167
- ["IsSubdealer", "=", null, { "defaultValue": 0, "type": "ISNULL" }, null]
168
- ]
166
+ // [
167
+ // ["IsSubdealer", "=", 0],
168
+ // "or",
169
+ // ["IsSubdealer", "=", null, { "defaultValue": 0, "position": "column", "type": "ISNULL" }, null],
170
+ // "or",
171
+ ["IsSubdealer", "=", true, { "defaultValue": 0, "position": "column", "type": "ISNULL" }, true],
172
+ // ]
169
173
  ]
170
174
  },
171
175
  {
@@ -189,24 +193,23 @@ describe("Parser SQL to dx Filter Builder", () => {
189
193
  {
190
194
  input: "(ISNULL(TicketID, 0) = ISNULL({SupportResolution.TicketID}, 0))",
191
195
  expected: [
192
- ["TicketID", "=", 123],
196
+ ["TicketID", "=", 123, { "type": "ISNULL", "position": "both", "defaultValue": 0, "defaultValueRight": 0 }, 123],
193
197
  "or",
194
- ["TicketID", "=", null, { "defaultValue": 0, "type": "ISNULL" }, null]
198
+ ["TicketID", "=", null]
195
199
  ]
196
200
  },
197
201
  {
198
202
  input: "CompanyID = ISNULL({LeadDocument.CompanyID},0) OR (ISNULL(CompanyID,0) = 0))",
199
203
  expected: [
200
- ["CompanyID", "=", 7],
201
- "or",
202
- ["CompanyID", "=", null, { "defaultValue": 0, "type": "ISNULL" }, null],
203
- "or",
204
- ["CompanyID", "=", 0],
204
+ ["CompanyID", "=", 7, { "type": "ISNULL", "position": "value", "defaultValue": 0 }, 7],
205
205
  "or",
206
- ["CompanyID", "=", null, { "defaultValue": 0, "type": "ISNULL" }, null],
207
- "or",
208
- ["CompanyID", "=", false]
209
-
206
+ [
207
+ ["CompanyID", "=", 0],
208
+ "or",
209
+ ["CompanyID", "=", null, { "type": "ISNULL", "position": "column", "defaultValue": 0 }, null],
210
+ "or",
211
+ ["CompanyID", "=", false]
212
+ ]
210
213
  ]
211
214
  },
212
215
  {
@@ -306,31 +309,85 @@ describe("Parser SQL to dx Filter Builder", () => {
306
309
  {
307
310
  input: "ISNULL(CompanyID,0) = ISNULL({TransferOutwardDocument.CompanyID},0) OR (ISNULL(CompanyID,0) = 0)",
308
311
  expected: [
309
- [
310
-
311
- ["CompanyID", "=", 7],
312
- "or",
313
- [
314
- ["CompanyID", "=", 0],
315
- "or",
316
- ["CompanyID", "=", null,
317
- {
318
- "type": "ISNULL",
319
- "defaultValue": 0
320
- }, null
321
- ],
322
- "or",
323
- ["CompanyID", "=", false]
324
- ]
325
- ],
312
+ ["CompanyID", "=", 7, { "type": "ISNULL", "position": "both", "defaultValue": 0, "defaultValueRight": 0 }, 7],
326
313
  "or",
327
- [
328
- "CompanyID", "=", null,
329
- {
330
- "type": "ISNULL",
331
- "defaultValue": 0
332
- }, null
333
- ]
314
+ ["CompanyID", "=", null],
315
+ "or",
316
+ ["CompanyID", "=", 0],
317
+ "or",
318
+ ["CompanyID", "=", null, { "type": "ISNULL", "position": "column", "defaultValue": 0 }, null],
319
+ "or",
320
+ ["CompanyID", "=", false]
321
+ ]
322
+ },
323
+ {
324
+ input: "ISNULL(CompanyID,0) = ISNULL({TransferOutwardDocument.CompanyID},0) OR (ISNULL(CompanyID,1) = 0)",
325
+ expected: [
326
+ ["CompanyID", "=", 7, { "type": "ISNULL", "position": "both", "defaultValue": 0, "defaultValueRight": 0 }, 7],
327
+ "or",
328
+ ["CompanyID", "=", null],
329
+ "or",
330
+ ["CompanyID", "=", 0, { "type": "ISNULL", "position": "column", "defaultValue": 1 }, 0],
331
+ "or",
332
+ ["CompanyID", "=", false]
333
+ ]
334
+ },
335
+ {
336
+ input: "ISNULL(CompanyID,0) = {LeadDocument.CompanyID}",
337
+ expected: [
338
+ "CompanyID", "=", 7, { "type": "ISNULL", "position": "column", "defaultValue": 0 }, 7
339
+ ]
340
+ },
341
+ {
342
+ input: "ISNULL(CompanyID,0) = ISNULL({LeadDocument.CompanyID},0)",
343
+ expected: [
344
+ ["CompanyID", "=", 7, { "type": "ISNULL", "position": "both", "defaultValue": 0, "defaultValueRight": 0 }, 7],
345
+ "or",
346
+ ["CompanyID", "=", null]
347
+ ]
348
+ },
349
+ {
350
+ input: "ISNULL(CompanyID,1) = ISNULL({LeadDocument.CompanyID},2)",
351
+ expected: [
352
+ "CompanyID", "=", 7, { "type": "ISNULL", "position": "both", "defaultValue": 1, "defaultValueRight": 2 }, 7
353
+ ]
354
+ },
355
+ {
356
+ input: "CompanyID = ISNULL({LeadDocument.CompanyID},0)",
357
+ expected: [
358
+ "CompanyID", "=", 7, { "type": "ISNULL", "position": "value", "defaultValue": 0 }, 7
359
+ ]
360
+ },
361
+ {
362
+ input: "AllowSubDealer = ISNULL({LeadDocument.AllowSubDealer},0)",
363
+ expected: [
364
+ "AllowSubDealer", "=", true, { "type": "ISNULL", "position": "value", "defaultValue": 0 }, true
365
+ ]
366
+ },
367
+ {
368
+ input: "AllowSubDealer = ISNULL({LeadDocument.AllowSubDealer},1)",
369
+ expected: [
370
+ "AllowSubDealer", "=", true, { "type": "ISNULL", "position": "value", "defaultValue": 1 }, true
371
+ ]
372
+ },
373
+ {
374
+ input: "ISNULL(AllowSubDealer,0) = ISNULL({LeadDocument.AllowSubDealer},1)",
375
+ expected: [
376
+ "AllowSubDealer", "=", true, { "type": "ISNULL", "position": "both", "defaultValue": 0, "defaultValueRight": 1 }, true
377
+ ]
378
+ },
379
+ {
380
+ input: "ISNULL(AllowSubDealer,0) = {LeadDocument.AllowSubDealer}",
381
+ expected: [
382
+ "AllowSubDealer", "=", true, { "type": "ISNULL", "position": "column", "defaultValue": 0 }, true
383
+ ]
384
+ },
385
+ {
386
+ input: "ISNULL(AllowSubDealer,1) = {LeadDocument.AllowSubDealer}",
387
+ expected: [
388
+ ["AllowSubDealer", "=", true],
389
+ 'or',
390
+ ["AllowSubDealer", "=", null, { "type": "ISNULL", "position": "column", "defaultValue": 1 }, null]
334
391
  ]
335
392
  }
336
393
  ];