sqlparser-devexpress 2.1.0 → 2.1.2

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.1.0",
3
+ "version": "2.1.2",
4
4
  "main": "src/index.js",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -11,6 +11,10 @@
11
11
  "require": "./src/index.js"
12
12
  },
13
13
  "types": "src/@types/default.d.ts",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "https://github.com/RohitM-IN/SQLParser.git"
17
+ },
14
18
  "keywords": [
15
19
  "sql",
16
20
  "parser",
@@ -31,7 +31,7 @@ function DevExpressConverter() {
31
31
  let result = processAstNode(ast);
32
32
 
33
33
  // Handle special cases for short circuit
34
- if(result === true || result === false || result === null) return [];
34
+ if (result === true || result === false || result === null) return [];
35
35
 
36
36
  return processAstNode(ast);
37
37
  }
@@ -136,13 +136,21 @@ function DevExpressConverter() {
136
136
  }
137
137
 
138
138
  // Handle "IN" condition, including comma-separated values
139
- if (operator === "IN") {
140
- return handleInOperator(ast);
139
+ if (operator === "IN" || operator === "NOT IN") {
140
+ return handleInOperator(ast, operator);
141
141
  }
142
142
 
143
143
  const left = ast.left !== undefined ? processAstNode(ast.left) : convertValue(ast.field);
144
144
  const right = ast.right !== undefined ? processAstNode(ast.right) : convertValue(ast.value);
145
- const comparison = [left, ast.operator.toLowerCase(), right];
145
+ const operatorToken = ast.operator.toLowerCase();
146
+
147
+ let comparison = [left, operatorToken, right];
148
+
149
+ if (isFunctionNullCheck(ast.left, true)) {
150
+ comparison = [[left, operatorToken, right], 'or', [left, operatorToken, null]];
151
+ } else if (isFunctionNullCheck(ast.right, true)) {
152
+ comparison = [[left, operatorToken, right], 'or', [right, operatorToken, null]];
153
+ }
146
154
 
147
155
  // Apply short-circuit evaluation if enabled
148
156
  if (EnableShortCircuit) {
@@ -179,7 +187,7 @@ function DevExpressConverter() {
179
187
  * @param {Object} ast - The comparison operator AST node.
180
188
  * @returns {Array} DevExpress format filter.
181
189
  */
182
- function handleInOperator(ast) {
190
+ function handleInOperator(ast, operator) {
183
191
  let resolvedValue = convertValue(ast.value);
184
192
 
185
193
  // Handle comma-separated values in a string
@@ -190,19 +198,18 @@ function DevExpressConverter() {
190
198
  } else {
191
199
  resolvedValue = firstValue;
192
200
  }
201
+ } else if (typeof resolvedValue === 'string' && resolvedValue.includes(',')) {
202
+ resolvedValue = resolvedValue.split(',').map(v => v.trim());
193
203
  }
194
204
 
195
- if(Array.isArray(resolvedValue) && resolvedValue.length){
205
+ let operatorToken = operator === "IN" ? '=' : operator === "NOT IN" ? '!=' : operator;
206
+ let joinOperatorToken = operator === "IN" ? 'or' : operator === "NOT IN" ? 'and' : operator;
196
207
 
197
- return Array.prototype.concat
198
- .apply(
199
- [],
200
- resolvedValue.map((i) => [[ast.field, '=', i], 'or']),
201
- )
202
- .slice(0, -1)
208
+ if (Array.isArray(resolvedValue) && resolvedValue.length) {
209
+ return resolvedValue.flatMap(i => [[ast.field, operatorToken, i], joinOperatorToken]).slice(0, -1);
203
210
  }
204
211
 
205
- return [ast.field, "in", resolvedValue];
212
+ return [ast.field, operatorToken, resolvedValue];
206
213
  }
207
214
 
208
215
  /**
@@ -225,7 +232,7 @@ function DevExpressConverter() {
225
232
  }
226
233
 
227
234
  // Special handling for ISNULL function
228
- if (val.type === "function" && val.name === "ISNULL" && val.args?.length >= 2) {
235
+ if (isFunctionNullCheck(val)) {
229
236
  return convertValue(val.args[0]);
230
237
  }
231
238
 
@@ -260,6 +267,18 @@ function DevExpressConverter() {
260
267
  return node?.type === "function" && node.name === "ISNULL" && valueNode?.type === "value";
261
268
  }
262
269
 
270
+ /**
271
+ * Checks if a node is a ISNULL function without value
272
+ * @param {Object} node
273
+ * @returns {boolean} True if this is an ISNULL check.
274
+ */
275
+ function isFunctionNullCheck(node, isPlaceholderCheck = false) {
276
+ const isValidFunction = node?.type === "function" && node?.name === "ISNULL" && node?.args?.length >= 2;
277
+
278
+ return isPlaceholderCheck ? isValidFunction && node?.args[0]?.value?.type !== "placeholder" : isValidFunction;
279
+ }
280
+
281
+
263
282
  /**
264
283
  * Determines whether the logical tree should be flattened.
265
284
  * This is based on the parent operator and the current operator.
@@ -329,7 +348,14 @@ function DevExpressConverter() {
329
348
  * @returns {boolean|null} The result of the evaluation or null if not evaluable.
330
349
  */
331
350
  function evaluateExpression(left, operator, right) {
332
- if (isNaN(left) || isNaN(right) || left === null || right === null) return null;
351
+ if ((left !== null && isNaN(left)) || (right !== null && isNaN(right))) return null;
352
+
353
+ if (left === null || right === null) {
354
+ if (operator === '=' || operator === '==') return left === right;
355
+ if (operator === '<>' || operator === '!=') return left !== right;
356
+ return null; // Any comparison with null should return null
357
+ }
358
+
333
359
  switch (operator) {
334
360
  case '=': case '==': return left === right;
335
361
  case '<>': case '!=': return left !== right;
@@ -337,7 +363,7 @@ function DevExpressConverter() {
337
363
  case '>=': return left >= right;
338
364
  case '<': return left < right;
339
365
  case '<=': return left <= right;
340
- default: return false;
366
+ default: return null; // Invalid operator
341
367
  }
342
368
  }
343
369
 
@@ -25,9 +25,9 @@ export function parse(input, variables = []) {
25
25
  // const tokens = [];
26
26
  // let tempToken = currentToken;
27
27
  // while (tempToken) {
28
- // tokens.push(tempToken);
29
- // tempToken = tokenizer.peekNextToken();
30
- // tokenizer.nextToken();
28
+ // tokens.push(tempToken);
29
+ // tempToken = tokenizer.peekNextToken();
30
+ // tokenizer.nextToken();
31
31
  // }
32
32
 
33
33
  // console.log("Tokens:", tokens);
@@ -208,8 +208,10 @@ export function parse(input, variables = []) {
208
208
  return { type: "placeholder", value: val };
209
209
  }
210
210
 
211
+ operatorToken = operatorToken.toUpperCase();
212
+
211
213
  // Handle IN operator which requires a list of values
212
- if (operatorToken && operatorToken.toUpperCase() === "IN") return parseInList(token);
214
+ if (operatorToken && (operatorToken === "IN" || operatorToken === "NOT IN")) return parseInList(token);
213
215
 
214
216
  throw new Error(`Unexpected value: ${token.value}`);
215
217
  }
@@ -6,7 +6,7 @@ const tokenPatterns = {
6
6
  number: "\\d+", // Matches numerical values
7
7
  placeholder: "'?\\{[^}]+\\}'?", // Matches placeholders like {variable} or '{variable}'
8
8
  string: "'(?:''|[^'])*'", // Matches strings, allowing for escaped single quotes ('')
9
- operator: "=>|<=|!=|>=|=|<>|>|<|\\bAND\\b|\\bOR\\b|\\bBETWEEN\\b|\\bIN\\b|\\bLIKE\\b|\\bIS NOT\\b|\\bNOT LIKE\\b|\\bIS\\b", // Matches SQL operators and logical keywords
9
+ operator: "=>|<=|!=|>=|=|<>|>|<|\\bAND\\b|\\bOR\\b|\\bBETWEEN\\b|\\bIN\\b|\\bNOT IN\\b|\\bLIKE\\b|\\bIS NOT\\b|\\bNOT LIKE\\b|\\bIS\\b", // Matches SQL operators and logical keywords
10
10
  identifier: "[\\w.]+", // Matches identifiers, including table.column format
11
11
  paren: "[()]", // Matches parentheses
12
12
  comma: "," // Matches commas
@@ -49,14 +49,14 @@ class Tokenizer {
49
49
 
50
50
  if (type === "operator") {
51
51
  const lowerValue = value.toLowerCase();
52
-
52
+
53
53
  if (lowerValue === "is") {
54
54
  value = "=";
55
55
  } else if (lowerValue === "is not") {
56
56
  value = "!=";
57
57
  }
58
58
  }
59
-
59
+
60
60
  return { type, value };
61
61
  }
62
62
 
@@ -66,7 +66,7 @@ class Tokenizer {
66
66
 
67
67
  peekNextToken() {
68
68
  if (this.index >= this.input.length) return null;
69
-
69
+
70
70
  const savedIndex = this.index; // Save current index
71
71
  try {
72
72
  return this.nextToken(); // Get next token
@@ -78,7 +78,7 @@ class Tokenizer {
78
78
  reset() {
79
79
  this.index = 0; // Reset index to the beginning of the input
80
80
  }
81
-
81
+
82
82
  }
83
83
 
84
84
  export { Tokenizer };
package/src/debug.js ADDED
@@ -0,0 +1,40 @@
1
+ // // Example usage
2
+ // // const devExpressFilter = parseFilterString("((ISNULL({0}, 0) = 0 AND CompanyID = {1}) OR CompanyID IS NULL) OR BranchID = {0} | [LeadDocument.BranchID] | [LeadDocument.CompanyID]", sampleResultObject);
3
+ // // const devExpressFilter = parseFilterString("FromDate <= '{TransferOutwardDocument.DocDate}' ", sampleResultObject, "TransferOutwardDocument", "789");
4
+ // // const devExpressFilter = parseFilterString("(RS2ID in ({SaleOrderStatusStmtGlobalRpt.StateID}) Or ({SaleOrderStatusStmtGlobalRpt.StateID} =0)) And (RS3ID in (0,{SaleOrderStatusStmtGlobalRpt.RegionID}) Or {SaleOrderStatusStmtGlobalRpt.RegionID} =0 )", sampleResultObject,);
5
+
6
+ // import { convertToDevExpressFormat } from "./core/converter.js";
7
+ // import { parse } from "./core/parser.js";
8
+ // import { sanitizeQuery } from "./core/sanitizer.js";
9
+ // import { convertAstToDevextreme, convertSQLToAst } from "./index.js";
10
+
11
+ // const sampleData = {
12
+ // 'LeadStatementGlobalRpt.StateID': null,
13
+ // 'LeadStatementGlobalRpt.RegionID': null,
14
+ // 'ServiceOrderDocument.SourceID': 2
15
+ // }
16
+
17
+ // export function parseFilterString(filterString, sampleData = null) {
18
+ // if (filterString.toUpperCase().startsWith("SELECT")) return null; // Skip full SQL queries
19
+
20
+ // let { sanitizedSQL, extractedVariables } = sanitizeQuery(filterString);
21
+ // console.log("Sanitized SQL:", sanitizedSQL, "\n");
22
+
23
+ // const parsedResult = parse(sanitizedSQL, extractedVariables);
24
+ // extractedVariables = parsedResult.variables;
25
+ // console.log("Extracted Variables:", JSON.stringify(extractedVariables, null, 2), "\n");
26
+
27
+ // const astTree = parsedResult.ast;
28
+ // console.log("AST Tree:", JSON.stringify(astTree, null, 2), "\n");
29
+
30
+ // return convertToDevExpressFormat({ ast: astTree, variables: extractedVariables, resultObject: sampleData });
31
+ // }
32
+
33
+ // // const devexpress = parseFilterString("(RS2ID in ({LeadStatementGlobalRpt.StateID}) Or ({LeadStatementGlobalRpt.StateID} =0)) And (RS3ID in (0,{LeadStatementGlobalRpt.RegionID}) Or {LeadStatementGlobalRpt.RegionID} =0 )", sampleData);
34
+ // const devexpress = parseFilterString("{LeadStatementGlobalRpt.RegionID} =0", sampleData);
35
+ // console.log("DevExpress Filter:", JSON.stringify(devexpress, null, 2));
36
+
37
+ // // const devExpressFilter = convertSQLToAst("(RS2ID in ({LeadStatementGlobalRpt.StateID}) Or ({LeadStatementGlobalRpt.StateID} =0)) And (RS3ID in (0,{LeadStatementGlobalRpt.RegionID}) Or {LeadStatementGlobalRpt.RegionID} =0 ) ");
38
+ // // const devExpressFilterresult = convertAstToDevextreme(devExpressFilter.ast, devExpressFilter.variables, sampleData);
39
+ // // console.log("DevExpress Filter:", JSON.stringify(devExpressFilter, null, 2));
40
+ // // console.log("DevExpress Result:", JSON.stringify(devExpressFilterresult, null, 2));
package/src/index.js CHANGED
@@ -5,12 +5,12 @@ import { sanitizeQuery } from "./core/sanitizer.js";
5
5
 
6
6
  export function convertSQLToAst(filterString, enableConsoleLogs = false) {
7
7
  let { sanitizedSQL, extractedVariables } = sanitizeQuery(filterString);
8
- enableConsoleLogs && console.log("Sanitized SQL:", sanitizedSQL, "\n");
8
+ !!enableConsoleLogs && console.log("Sanitized SQL:", sanitizedSQL, "\n");
9
9
 
10
10
  const parsedResult = parse(sanitizedSQL, extractedVariables);
11
11
 
12
- enableConsoleLogs && console.log("Extracted Variables:", JSON.stringify(parsedResult.variables, null, 2), "\n");
13
- enableConsoleLogs && console.log("AST Tree:", JSON.stringify(parsedResult.ast, null, 2), "\n");
12
+ !!enableConsoleLogs && console.log("Extracted Variables:", JSON.stringify(parsedResult.variables, null, 2), "\n");
13
+ !!enableConsoleLogs && console.log("AST Tree:", JSON.stringify(parsedResult.ast, null, 2), "\n");
14
14
 
15
15
  return parsedResult;
16
16
  }
@@ -19,29 +19,5 @@ export function convertAstToDevextreme(ast, variables, state) {
19
19
  return convertToDevExpressFormat({ ast, variables, resultObject: state })
20
20
  }
21
21
 
22
- // export function parseFilterString(filterString, sampleData = null) {
23
- // if (filterString.toUpperCase().startsWith("SELECT")) return null; // Skip full SQL queries
24
22
 
25
- // let { sanitizedSQL, extractedVariables } = sanitizeQuery(filterString);
26
- // console.log("Sanitized SQL:", sanitizedSQL, "\n");
27
-
28
- // const parsedResult = parse(sanitizedSQL, extractedVariables);
29
- // extractedVariables = parsedResult.variables;
30
- // console.log("Extracted Variables:", JSON.stringify(extractedVariables, null, 2), "\n");
31
-
32
- // const astTree = parsedResult.ast;
33
- // console.log("AST Tree:", JSON.stringify(astTree, null, 2), "\n");
34
-
35
- // return convertToDevExpressFormat({ ast: astTree, variables: extractedVariables, resultObject: sampleData });
36
- // }
37
-
38
- // Example usage
39
- // const devExpressFilter = parseFilterString("((ISNULL({0}, 0) = 0 AND CompanyID = {1}) OR CompanyID IS NULL) OR BranchID = {0} | [LeadDocument.BranchID] | [LeadDocument.CompanyID]", sampleResultObject);
40
- // const devExpressFilter = parseFilterString("FromDate <= '{TransferOutwardDocument.DocDate}' ", sampleResultObject, "TransferOutwardDocument", "789");
41
- // const devExpressFilter = parseFilterString("(RS2ID in ({SaleOrderStatusStmtGlobalRpt.StateID}) Or ({SaleOrderStatusStmtGlobalRpt.StateID} =0)) And (RS3ID in (0,{SaleOrderStatusStmtGlobalRpt.RegionID}) Or {SaleOrderStatusStmtGlobalRpt.RegionID} =0 )", sampleResultObject,);
42
-
43
- // const devExpressFilter = convertSQLToAst("CompanyID = CompanyID2 = {AccountingRule.CompanyID}");
44
- // const devExpressFilterresult = convertAstToDevextreme(devExpressFilter.ast, devExpressFilter.variables,{'ServiceOrderDocument.SourceID': 2});
45
- // console.log("DevExpress Filter:", JSON.stringify(devExpressFilter, null, 2));
46
- // console.log("DevExpress Result:", JSON.stringify(devExpressFilterresult, null, 2));
47
23
 
@@ -37,7 +37,7 @@ describe("Parser SQL to dx Filter Builder", () => {
37
37
  {
38
38
  input: "ID IN ({WorkOrderLine.ApplicableUoms}) AND (CompanyID = {WorkOrderDocument.CompanyID} OR {WorkOrderDocument.CompanyID} = 0)",
39
39
  expected: [
40
- [["ID", "=", "UOM1"] , 'or', ["ID", "=", "UOM2"], 'or', ["ID", "=", "UOM3"]],
40
+ [["ID", "=", "UOM1"], 'or', ["ID", "=", "UOM2"], 'or', ["ID", "=", "UOM3"]],
41
41
  "and",
42
42
  // [
43
43
  ["CompanyID", "=", 42],
@@ -145,12 +145,44 @@ describe("Parser SQL to dx Filter Builder", () => {
145
145
  expected: [
146
146
  ["SourceID", "=", 2],
147
147
  "or",
148
- ["SourceID", "=", 0]
148
+ ["SourceID", "=", null],
149
+ "or",
150
+ ["SourceID", "=", 0],
151
+ "or",
152
+ ["SourceID", "=", null]
149
153
  ]
150
154
  },
151
155
  {
152
156
  input: "CompanyID = CompanyID2 = {AccountingRule.CompanyID}",
153
157
  expected: "Error: Invalid comparison: CompanyID = CompanyID2",
158
+ },
159
+ {
160
+ input: "(CompanyID = {LeadDocument.CompanyID} OR ISNULL(CompanyID,0) = 0) AND (ISNULL(IsSubdealer,0) = {LeadDocument.AllowSubDealer})",
161
+ expected: [
162
+ [
163
+ ["CompanyID", "=", 7],
164
+ "or",
165
+ [
166
+ ["CompanyID", "=", 0],
167
+ "or",
168
+ ["CompanyID", "=", null]
169
+ ]
170
+ ],
171
+ "and",
172
+ [
173
+ ["IsSubdealer", "=", true],
174
+ "or",
175
+ ["IsSubdealer", "=", null]
176
+ ]
177
+ ]
178
+ },
179
+ {
180
+ input: 'AddressType NOT IN (2, 4)',
181
+ expected: [
182
+ ["AddressType", "!=", 2],
183
+ "and",
184
+ ["AddressType", "!=", 4]
185
+ ]
154
186
  }
155
187
  ];
156
188
 
@@ -165,7 +197,7 @@ describe("Parser SQL to dx Filter Builder", () => {
165
197
  try {
166
198
  astwithVariables = convertSQLToAst(input);
167
199
  } catch (error) {
168
- expect(error.message).toEqual(expected.replace("Error: ",""));
200
+ expect(error.message).toEqual(expected.replace("Error: ", ""));
169
201
  return;
170
202
  }
171
203
 
@@ -186,7 +218,7 @@ describe("Parser SQL to dx Filter Builder", () => {
186
218
 
187
219
  expect(result).toEqual(expected);
188
220
  });
189
- });
221
+ });
190
222
  });
191
223
 
192
224
 
@@ -213,5 +245,6 @@ const sampleData = {
213
245
  "TransferOutwardDocument.CompanyID": 7,
214
246
  "LeadDocument.BranchID": 42,
215
247
  "LeadDocument.CompanyID": 7,
216
- "ServiceOrderDocument.SourceID": 2
248
+ "ServiceOrderDocument.SourceID": 2,
249
+ "LeadDocument.AllowSubDealer": true
217
250
  };