sqlparser-devexpress 2.0.8 → 2.1.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.0.8",
3
+ "version": "2.1.1",
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",
@@ -20,18 +20,19 @@ function DevExpressConverter() {
20
20
  * @param {Object} ast - The abstract syntax tree
21
21
  * @param {Array} vars - Array of variable names
22
22
  * @param {Object} ResultObject - Optional object for placeholder resolution
23
- * @param {string} primaryEntity - Optional primary entity name
24
- * @param {string} primaryKey - Optional primary key value
25
23
  * @returns {Array|null} DevExpress format filter
26
24
  */
27
- function convert(ast, vars, ResultObject = null, PrimaryEntity = null, PrimaryKey = null) {
25
+ function convert(ast, vars, ResultObject = null) {
28
26
  // Set up global context
29
27
  variables = vars;
30
- primaryKey = PrimaryKey;
31
- primaryEntity = PrimaryEntity;
32
28
  resultObject = ResultObject;
33
29
 
34
30
  // Process the AST
31
+ let result = processAstNode(ast);
32
+
33
+ // Handle special cases for short circuit
34
+ if (result === true || result === false || result === null) return [];
35
+
35
36
  return processAstNode(ast);
36
37
  }
37
38
 
@@ -191,7 +192,13 @@ function DevExpressConverter() {
191
192
  }
192
193
  }
193
194
 
194
- return [ast.field, "in", resolvedValue];
195
+ if (Array.isArray(resolvedValue) && resolvedValue.length) {
196
+
197
+ return resolvedValue.flatMap(i => [[ast.field, '=', i], 'or']).slice(0, -1);
198
+
199
+ }
200
+
201
+ return [ast.field, "=", resolvedValue];
195
202
  }
196
203
 
197
204
  /**
@@ -318,7 +325,14 @@ function DevExpressConverter() {
318
325
  * @returns {boolean|null} The result of the evaluation or null if not evaluable.
319
326
  */
320
327
  function evaluateExpression(left, operator, right) {
321
- if (isNaN(left) || isNaN(right) || left === null || right === null) return null;
328
+ if ((left !== null && isNaN(left)) || (right !== null && isNaN(right))) return null;
329
+
330
+ if (left === null || right === null) {
331
+ if (operator === '=' || operator === '==') return left === right;
332
+ if (operator === '<>' || operator === '!=') return left !== right;
333
+ return null; // Any comparison with null should return null
334
+ }
335
+
322
336
  switch (operator) {
323
337
  case '=': case '==': return left === right;
324
338
  case '<>': case '!=': return left !== right;
@@ -326,7 +340,7 @@ function DevExpressConverter() {
326
340
  case '>=': return left >= right;
327
341
  case '<': return left < right;
328
342
  case '<=': return left <= right;
329
- default: return false;
343
+ default: return null; // Invalid operator
330
344
  }
331
345
  }
332
346
 
@@ -346,6 +360,6 @@ const devExpressConverter = DevExpressConverter();
346
360
  * @param {string} primaryKey - Optional primary key value
347
361
  * @returns {Array|null} DevExpress format filter
348
362
  */
349
- export function convertToDevExpressFormat({ ast, variables, resultObject = null, primaryEntity = null, primaryKey = null }) {
350
- return devExpressConverter.init(ast, variables, resultObject, primaryEntity, primaryKey);
363
+ export function convertToDevExpressFormat({ ast, variables, resultObject = null }) {
364
+ return devExpressConverter.init(ast, variables, resultObject);
351
365
  }
@@ -1,186 +1,220 @@
1
1
  import { Tokenizer } from "./tokenizer.js";
2
2
 
3
3
  // Define operator precedence for parsing expressions
4
- const precedence = {
5
- "OR": 1, "AND": 2, "=": 3, "!=": 3, ">": 3, "<": 3, ">=": 3, "<=": 3,
6
- "IN": 3, "<>": 3, "LIKE": 3, "IS": 3, "BETWEEN": 3
4
+ const OPERATOR_PRECEDENCE = {
5
+ "OR": 1, "AND": 2, "=": 3, "!=": 3, ">": 3, "<": 3, ">=": 3, "<=": 3,
6
+ "IN": 3, "<>": 3, "LIKE": 3, "IS": 3, "BETWEEN": 3
7
7
  };
8
8
 
9
+ const LITERALS = ["number", "string", "null"];
10
+
9
11
  // Regular expression to check for unsupported SQL patterns (like SELECT-FROM or JOIN statements)
10
- const unsupportedPattern = /\bSELECT\b.*\bFROM\b|\bINNER\s+JOIN\b/i;
12
+ const UNSUPPORTED_PATTERN = /\bSELECT\b.*\bFROM\b|\bINNER\s+JOIN\b/i;
11
13
 
12
14
  export function parse(input, variables = []) {
13
15
 
14
- // Return null if the input contains unsupported SQL statements
15
- if (unsupportedPattern.test(input)) {
16
- return null;
17
- }
18
-
19
- const tokenizer = new Tokenizer(input);
20
- let currentToken = tokenizer.nextToken();
21
-
22
- // // Debugging: log the tokens
23
- // const tokens = [];
24
- // let tempToken = currentToken;
25
- // while (tempToken) {
26
- // tokens.push(tempToken);
27
- // tempToken = tokenizer.peekNextToken();
28
- // tokenizer.nextToken();
29
- // }
30
-
31
- // console.log("Tokens:", tokens);
32
-
33
- // // Reset the tokenizer
34
- // tokenizer.reset();
35
- // currentToken = tokenizer.nextToken();
36
-
37
- // Moves to the next token in the input
38
- function next() {
39
- currentToken = tokenizer.nextToken();
40
- }
41
-
42
- // Parses logical expressions using operator precedence
43
- function parseExpression(minPrecedence = 0) {
44
- let left = parseTerm();
45
-
46
- // Continue parsing while the current token is an operator with sufficient precedence
47
- while (currentToken && currentToken.type === "operator" && precedence[currentToken.value.toUpperCase()] >= minPrecedence) {
48
- const operator = currentToken.value.toUpperCase();
49
- next(); // Move to the next token
50
-
51
- // Recursively parse the right-hand expression with adjusted precedence
52
- const right = parseExpression(precedence[operator]);
53
- left = { type: "logical", operator, left, right };
54
- }
55
-
56
- return left;
57
- }
58
-
59
- // Parses individual terms, including literals, functions, and comparisons
60
- function parseTerm() {
61
- if (!currentToken) throw new Error("Unexpected end of input");
62
-
63
- // Handle parenthesized expressions
64
- if (currentToken.type === "paren" && currentToken.value === "(") {
65
- next();
66
- const expr = parseExpression();
67
- if (!currentToken || currentToken.value !== ")") throw new Error("Missing closing parenthesis");
68
- next();
69
- return expr;
70
- }
71
-
72
- // Handle function calls like ISNULL(field)
73
- if (currentToken.type === "function") {
74
- const funcName = currentToken.value.toUpperCase();
75
- next();
76
- if (!currentToken || currentToken.value !== "(") throw new Error(`Expected ( after ${funcName}`);
77
- next();
78
-
79
- const args = [];
80
- while (currentToken && currentToken.value !== ")") {
81
- args.push(parseExpression());
82
- if (currentToken && currentToken.value === ",") next();
83
- }
84
-
85
- next(); // Consume the closing parenthesis
86
-
87
- // Check if the next token is an operator and process it
88
- if (currentToken && currentToken.type === "operator") {
89
- const operator = currentToken.value;
90
- next(); // Move to the next token after the operator
91
- const value = parseValue(); // Parse the value after the operator
92
-
93
- return {
94
- type: "comparison",
95
- left: { type: "function", name: funcName, args },
96
- operator,
97
- value
98
- };
99
- }
100
-
101
- return { type: "function", name: funcName, args };
102
- }
103
-
104
- // Handle literal values (numbers, strings, null)
105
- if (["number", "string", "null"].includes(currentToken.type)) {
106
- const value = parseValue();
107
- return { type: "value", value };
108
- }
109
-
110
- // Otherwise, assume it's a field name
111
- const field = parseValue();
112
-
113
- // Check if it's part of a comparison expression
114
- if (currentToken && currentToken.type === "operator") {
115
- const operator = currentToken.value.toLowerCase();
116
- next();
117
-
118
- if (operator === "between") {
119
- // Parse BETWEEN operator which requires two values separated by AND
120
- const firstValue = parseValue();
121
-
122
- if (!currentToken || currentToken.value.toUpperCase() !== "AND") {
123
- throw new Error("Expected AND after BETWEEN");
124
- }
125
- next(); // Consume AND
126
-
127
- const secondValue = parseValue();
128
-
129
- return {
130
- type: "comparison",
131
- field,
132
- operator,
133
- value: [firstValue, secondValue] // Store both values in an array
134
- };
135
- }
136
-
137
- // For other comparison operators, parse a single right-hand value
138
- const value = parseValue(operator);
139
- return { type: "comparison", field, operator, value };
140
- }
141
-
142
- return { type: "field", value: field };
143
- }
144
-
145
- // Parses values including numbers, strings, placeholders, and IN lists
146
- function parseValue(operatorToken) {
147
- if (!currentToken) throw new Error("Unexpected end of input");
148
-
149
- const token = currentToken;
150
- next(); // Move to the next token
151
-
152
- if (token.type === "number") return Number(token.value);
153
- if (token.type === "string") return token.value.slice(1, -1).replace(/''/g, "");
154
- if (token.type === "identifier") return token.value;
155
- if (token.type === "null") return null;
156
-
157
- // Handle placeholders like `{VariableName}`
158
- if (token.type === "placeholder") {
159
- const val = token.value.slice(1, -1);
160
- if (!variables.includes(val)) variables.push(val);
161
- return { type: "placeholder", value: val };
162
- }
163
-
164
- // Handle IN operator which requires a list of values
165
- if (operatorToken && operatorToken.toUpperCase() === "IN") {
166
- if (!token || token.value !== "(") throw new Error("Expected ( after IN");
167
-
168
- const values = [];
169
- while (currentToken && currentToken.value !== ")") {
170
- if (currentToken.type === "comma") {
171
- next();
172
- continue;
173
- }
174
- values.push(parseValue());
175
- }
176
-
177
- if (currentToken && currentToken.value === ")") next(); // Consume closing parenthesis
178
- return { type: "value", value: values };
179
- }
180
-
181
- throw new Error(`Unexpected value: ${token.value}`);
182
- }
183
-
184
- // Start parsing and return the AST with extracted variables
185
- return { ast: parseExpression(), variables };
16
+ // Return null if the input contains unsupported SQL statements
17
+ if (UNSUPPORTED_PATTERN.test(input)) {
18
+ return null;
19
+ }
20
+
21
+ const tokenizer = new Tokenizer(input);
22
+ let currentToken = tokenizer.nextToken();
23
+
24
+ // // Debugging: log the tokens
25
+ // const tokens = [];
26
+ // let tempToken = currentToken;
27
+ // while (tempToken) {
28
+ // tokens.push(tempToken);
29
+ // tempToken = tokenizer.peekNextToken();
30
+ // tokenizer.nextToken();
31
+ // }
32
+
33
+ // console.log("Tokens:", tokens);
34
+
35
+ // // Reset the tokenizer
36
+ // tokenizer.reset();
37
+ // currentToken = tokenizer.nextToken();
38
+
39
+ // Moves to the next token in the input
40
+ function next() {
41
+ currentToken = tokenizer.nextToken();
42
+ }
43
+
44
+ // Validate the current token
45
+ const expectedToken = (token, expected, errorMessage) => {
46
+ if (!token || token.value.toUpperCase() !== expected) {
47
+ throw new Error(errorMessage);
48
+ }
49
+ };
50
+
51
+ // Parse IN list of values
52
+ function parseInList(token) {
53
+ expectedToken(token, "(", "Expected ( after IN");
54
+
55
+ const values = [];
56
+ while (currentToken && currentToken.value !== ")") {
57
+ if (currentToken.type === "comma") {
58
+ next();
59
+ continue;
60
+ }
61
+ values.push(parseValue());
62
+ }
63
+ expectedToken(currentToken, ")", "Expected ) after IN list");
64
+
65
+ next(); // Consume closing parenthesis
66
+
67
+ return { type: "value", value: values };
68
+ }
69
+
70
+
71
+ // Parse BETWEEN operator which requires two values separated by AND
72
+ function parseBetweenComparison(field, operator) {
73
+ const firstValue = parseValue();
74
+
75
+ expectedToken(currentToken, "AND", "Expected AND after BETWEEN");
76
+
77
+ next(); // Consume AND
78
+
79
+ const secondValue = parseValue();
80
+
81
+ return {
82
+ type: "comparison",
83
+ field,
84
+ operator,
85
+ value: [firstValue, secondValue] // Store both values in an array
86
+ };
87
+ }
88
+
89
+ function parseFunction() {
90
+ const funcName = currentToken.value.toUpperCase();
91
+ next();
92
+
93
+ expectedToken(currentToken, "(", `Expected ( after ${funcName}`);
94
+
95
+ next();
96
+
97
+ const args = [];
98
+ while (currentToken && currentToken.value !== ")") {
99
+ args.push(parseExpression());
100
+ if (currentToken && currentToken.value === ",") next();
101
+ }
102
+
103
+ expectedToken(currentToken, ")", `Expected ) after ${funcName}`);
104
+
105
+ next(); // Consume the closing parenthesis
106
+
107
+ // Check if the next token is an operator and process it
108
+ if (currentToken && currentToken.type === "operator") {
109
+ const operator = currentToken.value;
110
+ next(); // Move to the next token after the operator
111
+ const value = parseValue(); // Parse the value after the operator
112
+
113
+ return {
114
+ type: "comparison",
115
+ left: { type: "function", name: funcName, args },
116
+ operator,
117
+ value
118
+ };
119
+ }
120
+
121
+ return { type: "function", name: funcName, args };
122
+ }
123
+
124
+ // Parses logical expressions using operator precedence
125
+ function parseExpression(minPrecedence = 0) {
126
+ let left = parseTerm();
127
+
128
+ // Continue parsing while the current token is an operator with sufficient precedence
129
+ while (currentToken && currentToken.type === "operator" && OPERATOR_PRECEDENCE[currentToken.value.toUpperCase()] >= minPrecedence) {
130
+ const operator = currentToken.value.toUpperCase();
131
+ next(); // Move to the next token
132
+
133
+ // Recursively parse the right-hand expression with adjusted precedence
134
+ const right = parseExpression(OPERATOR_PRECEDENCE[operator]);
135
+ left = { type: "logical", operator, left, right };
136
+ }
137
+
138
+ return left;
139
+ }
140
+
141
+ // Parses individual terms, including literals, functions, and comparisons
142
+ function parseTerm() {
143
+ if (!currentToken) throw new Error("Unexpected end of input");
144
+
145
+ // Handle parenthesized expressions
146
+ if (currentToken.type === "paren" && currentToken.value === "(") {
147
+ next();
148
+ const expr = parseExpression();
149
+
150
+ expectedToken(currentToken, ")", "Missing closing parenthesis");
151
+
152
+ next();
153
+
154
+ return expr;
155
+ }
156
+
157
+ // Handle function calls like ISNULL(field)
158
+ if (currentToken.type === "function") return parseFunction();
159
+
160
+ // Handle literal values (numbers, strings, null)
161
+ if (LITERALS.includes(currentToken.type)) {
162
+ const value = parseValue();
163
+ return { type: "value", value };
164
+ }
165
+
166
+ // Otherwise, assume it's a field name
167
+ const fieldType = currentToken.type;
168
+ const field = parseValue();
169
+
170
+ // Check if it's part of a comparison expression
171
+ if (currentToken && currentToken.type === "operator") {
172
+ const operator = currentToken.value.toLowerCase();
173
+ next();
174
+
175
+ if (operator === "between") return parseBetweenComparison(field, operator);
176
+
177
+ // For other comparison operators, parse a single right-hand value
178
+ const valueType = currentToken.type;
179
+ const value = parseValue(operator);
180
+
181
+ // Check for invalid comparisons between two identifiers
182
+ if (fieldType === "identifier" && valueType === "identifier") {
183
+ throw new Error(`Invalid comparison: ${field} ${operator} ${value}`);
184
+ }
185
+
186
+ return { type: "comparison", field, operator, value };
187
+ }
188
+
189
+ return { type: "field", value: field };
190
+ }
191
+
192
+ // Parses values including numbers, strings, placeholders, and IN lists
193
+ function parseValue(operatorToken) {
194
+ if (!currentToken) throw new Error("Unexpected end of input");
195
+
196
+ const token = currentToken;
197
+ next(); // Move to the next token
198
+
199
+ if (token.type === "number") return Number(token.value);
200
+ if (token.type === "string") return token.value.slice(1, -1).replace(/''/g, "");
201
+ if (token.type === "identifier") return token.value;
202
+ if (token.type === "null") return null;
203
+
204
+ // Handle placeholders like `{VariableName}`
205
+ if (token.type === "placeholder") {
206
+ const val = token.value.slice(1, -1);
207
+ if (!variables.includes(val)) variables.push(val);
208
+ return { type: "placeholder", value: val };
209
+ }
210
+
211
+ // Handle IN operator which requires a list of values
212
+ if (operatorToken && operatorToken.toUpperCase() === "IN") return parseInList(token);
213
+
214
+ throw new Error(`Unexpected value: ${token.value}`);
215
+ }
216
+
217
+
218
+ // Start parsing and return the AST with extracted variables
219
+ return { ast: parseExpression(), variables };
186
220
  }
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
@@ -3,7 +3,7 @@ import { parse } from "./core/parser.js";
3
3
  import { sanitizeQuery } from "./core/sanitizer.js";
4
4
 
5
5
 
6
- export function convertSQLToAst(filterString, SampleData = null, enableConsoleLogs = false) {
6
+ export function convertSQLToAst(filterString, enableConsoleLogs = false) {
7
7
  let { sanitizedSQL, extractedVariables } = sanitizeQuery(filterString);
8
8
  enableConsoleLogs && console.log("Sanitized SQL:", sanitizedSQL, "\n");
9
9
 
@@ -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("ISNULL(SourceID,0) = {ServiceOrderDocument.SourceID} OR ISNULL(SourceID,0) = 0");
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
 
@@ -2,6 +2,7 @@ import { describe, expect, it } from "vitest";
2
2
  import { convertToDevExpressFormat } from "../src/core/converter";
3
3
  import { parse } from "../src/core/parser";
4
4
  import { sanitizeQuery } from "../src/core/sanitizer";
5
+ import { convertAstToDevextreme, convertSQLToAst } from "../src";
5
6
 
6
7
  describe("Parser SQL to dx Filter Builder", () => {
7
8
  const testCases = [
@@ -30,13 +31,13 @@ describe("Parser SQL to dx Filter Builder", () => {
30
31
  expected: [
31
32
  ["ContactID", "=", 42],
32
33
  "and",
33
- ["AddressType", "in", [2, 4]],
34
+ [["AddressType", "=", 2], 'or', ["AddressType", "=", 4]],
34
35
  ],
35
36
  },
36
37
  {
37
38
  input: "ID IN ({WorkOrderLine.ApplicableUoms}) AND (CompanyID = {WorkOrderDocument.CompanyID} OR {WorkOrderDocument.CompanyID} = 0)",
38
39
  expected: [
39
- ["ID", "in", ["UOM1", "UOM2", "UOM3"]],
40
+ [["ID", "=", "UOM1"] , 'or', ["ID", "=", "UOM2"], 'or', ["ID", "=", "UOM3"]],
40
41
  "and",
41
42
  // [
42
43
  ["CompanyID", "=", 42],
@@ -62,7 +63,7 @@ describe("Parser SQL to dx Filter Builder", () => {
62
63
  expected: [
63
64
  ["ID", "<>", 42],
64
65
  'and',
65
- ["ItemGroupType", "in", ["1", "2"]]
66
+ [["ItemGroupType", "=", "1"], 'or', ["ItemGroupType", "=", "2"]]
66
67
  ]
67
68
  },
68
69
  {
@@ -103,7 +104,7 @@ describe("Parser SQL to dx Filter Builder", () => {
103
104
  },
104
105
  {
105
106
  input: "NULL",
106
- expected: null
107
+ expected: []
107
108
  },
108
109
  {
109
110
  input: "((ISNULL({0}, 0) = 0 AND CompanyID = {1}) OR CompanyID IS NULL) OR BranchID = {0} | [LeadDocument.BranchID] | [LeadDocument.CompanyID]",
@@ -146,6 +147,10 @@ describe("Parser SQL to dx Filter Builder", () => {
146
147
  "or",
147
148
  ["SourceID", "=", 0]
148
149
  ]
150
+ },
151
+ {
152
+ input: "CompanyID = CompanyID2 = {AccountingRule.CompanyID}",
153
+ expected: "Error: Invalid comparison: CompanyID = CompanyID2",
149
154
  }
150
155
  ];
151
156
 
@@ -156,25 +161,23 @@ describe("Parser SQL to dx Filter Builder", () => {
156
161
  expected = null
157
162
  }
158
163
 
159
- // Need to handle NULL as a special case
160
- if (input.toLowerCase() === "null") {
161
- expect(null).toEqual(null);
164
+ let astwithVariables;
165
+ try {
166
+ astwithVariables = convertSQLToAst(input);
167
+ } catch (error) {
168
+ expect(error.message).toEqual(expected.replace("Error: ",""));
162
169
  return;
163
170
  }
164
171
 
165
- let { sanitizedSQL, variables } = sanitizeQuery(input);
166
-
167
- const astwithVariables = parse(sanitizedSQL, variables);
168
-
169
172
  if (astwithVariables == null) {
170
173
  expect(null).toEqual(expected);
171
174
  return;
172
175
  }
173
176
 
174
- variables = astwithVariables.variables;
177
+ const variables = astwithVariables.variables;
175
178
  const ast = astwithVariables.ast;
176
179
 
177
- const result = convertToDevExpressFormat({ ast, variables, resultObject: sampleData });
180
+ const result = convertAstToDevextreme(ast, variables, sampleData);
178
181
 
179
182
  if (result == null || result == true || result == false) {
180
183
  expect([]).toEqual(expected);
@@ -183,7 +186,7 @@ describe("Parser SQL to dx Filter Builder", () => {
183
186
 
184
187
  expect(result).toEqual(expected);
185
188
  });
186
- });
189
+ });
187
190
  });
188
191
 
189
192
 
package/src/utils.js DELETED
@@ -1,14 +0,0 @@
1
- export function replacePlaceholders(sql, staticValues = {}, dynamicValues = []) {
2
- // Replace static placeholders (e.g., {0})
3
- sql = sql.replace(/\{(\w+)\}/g, (_, key) => staticValues[key] ?? `{${key}}`);
4
-
5
- // Replace dynamic placeholders (e.g., | [AccountTaxPaymentDocument.CompanyID])
6
- sql = sql.replace(/\|\s*\[([^\]]+)\]/g, (_, keys) => {
7
- const replacements = keys.split(",").map(key => dynamicValues.shift() ?? `{${key.trim()}}`);
8
- return replacements.join(", ");
9
- });
10
-
11
- return sql;
12
- }
13
-
14
- module.exports = { replacePlaceholders };