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 +5 -1
- package/src/core/converter.js +24 -10
- package/src/core/parser.js +210 -176
- package/src/debug.js +40 -0
- package/src/index.js +1 -25
- package/tests/parser.test.js +17 -14
- package/src/utils.js +0 -14
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sqlparser-devexpress",
|
|
3
|
-
"version": "2.
|
|
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",
|
package/src/core/converter.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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 (
|
|
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
|
|
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
|
|
350
|
-
return devExpressConverter.init(ast, variables, resultObject
|
|
363
|
+
export function convertToDevExpressFormat({ ast, variables, resultObject = null }) {
|
|
364
|
+
return devExpressConverter.init(ast, variables, resultObject);
|
|
351
365
|
}
|
package/src/core/parser.js
CHANGED
|
@@ -1,186 +1,220 @@
|
|
|
1
1
|
import { Tokenizer } from "./tokenizer.js";
|
|
2
2
|
|
|
3
3
|
// Define operator precedence for parsing expressions
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
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
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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,
|
|
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
|
|
package/tests/parser.test.js
CHANGED
|
@@ -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", "
|
|
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", "
|
|
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", "
|
|
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:
|
|
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
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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 =
|
|
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 };
|