sqlparser-devexpress 2.3.2 → 2.3.4
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 +1 -1
- package/src/core/converter.js +6 -6
- package/src/core/parser.js +69 -29
- package/src/debug.js +1 -1
- package/tests/parser.test.js +20 -5
package/package.json
CHANGED
package/src/core/converter.js
CHANGED
|
@@ -140,10 +140,10 @@ function DevExpressConverter() {
|
|
|
140
140
|
|
|
141
141
|
let comparison = [left, operatorToken, right];
|
|
142
142
|
|
|
143
|
-
if (isFunctionNullCheck(ast.left, true)) {
|
|
144
|
-
comparison = [[left, operatorToken, right], 'or', [left, operatorToken, null]];
|
|
145
|
-
} else if (isFunctionNullCheck(ast.right, true)) {
|
|
146
|
-
comparison = [[left, operatorToken, right], 'or', [right, operatorToken, null]];
|
|
143
|
+
if ((ast.left && isFunctionNullCheck(ast.left, true)) || (ast.value && isFunctionNullCheck(ast.value, false))) {
|
|
144
|
+
comparison = [[left, operatorToken, right], 'or', [left, operatorToken, null, {type: "ISNULL", defaultValue: (ast.left ?? ast.value).args[1]?.value}]];
|
|
145
|
+
} else if (ast.right && isFunctionNullCheck(ast.right, true)) {
|
|
146
|
+
comparison = [[left, operatorToken, right], 'or', [right, operatorToken, null, {type: "ISNULL", defaultValue: ast.right.args[1]?.value}]];
|
|
147
147
|
}
|
|
148
148
|
|
|
149
149
|
// Apply short-circuit evaluation if enabled
|
|
@@ -322,7 +322,7 @@ function DevExpressConverter() {
|
|
|
322
322
|
* @returns {boolean} True if the condition is always true.
|
|
323
323
|
*/
|
|
324
324
|
function isAlwaysTrue(condition) {
|
|
325
|
-
return Array.isArray(condition) && condition.length
|
|
325
|
+
return Array.isArray(condition) && condition.length >= 3 && evaluateExpression(...condition) == true;
|
|
326
326
|
}
|
|
327
327
|
|
|
328
328
|
/**
|
|
@@ -331,7 +331,7 @@ function DevExpressConverter() {
|
|
|
331
331
|
* @returns {boolean} True if the condition is always false.
|
|
332
332
|
*/
|
|
333
333
|
function isAlwaysFalse(condition) {
|
|
334
|
-
return Array.isArray(condition) && condition.length
|
|
334
|
+
return Array.isArray(condition) && condition.length >= 3 && evaluateExpression(...condition) == false;
|
|
335
335
|
}
|
|
336
336
|
|
|
337
337
|
/**
|
package/src/core/parser.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { LITERALS, OPERATOR_PRECEDENCE, UNSUPPORTED_PATTERN } from "../constants.js";
|
|
1
|
+
import { LITERALS, LOGICAL_OPERATORS, OPERATOR_PRECEDENCE, UNSUPPORTED_PATTERN } from "../constants.js";
|
|
2
2
|
import { Tokenizer } from "./tokenizer.js";
|
|
3
3
|
|
|
4
4
|
|
|
@@ -78,20 +78,20 @@ export function parse(input, variables = []) {
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
function parseFunction() {
|
|
81
|
-
const
|
|
81
|
+
const functionName = currentToken.value.toUpperCase();
|
|
82
82
|
next();
|
|
83
83
|
|
|
84
|
-
expectedToken(currentToken, "(", `Expected ( after ${
|
|
84
|
+
expectedToken(currentToken, "(", `Expected ( after ${functionName}`);
|
|
85
85
|
|
|
86
86
|
next();
|
|
87
87
|
|
|
88
|
-
const
|
|
88
|
+
const functionArgs = [];
|
|
89
89
|
while (currentToken && currentToken.value !== ")") {
|
|
90
|
-
|
|
90
|
+
functionArgs.push(parseExpression());
|
|
91
91
|
if (currentToken && currentToken.value === ",") next();
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
expectedToken(currentToken, ")", `Expected ) after ${
|
|
94
|
+
expectedToken(currentToken, ")", `Expected ) after ${functionName}`);
|
|
95
95
|
|
|
96
96
|
next(); // Consume the closing parenthesis
|
|
97
97
|
|
|
@@ -99,17 +99,22 @@ export function parse(input, variables = []) {
|
|
|
99
99
|
if (currentToken && currentToken.type === "operator") {
|
|
100
100
|
const operator = currentToken.value;
|
|
101
101
|
next(); // Move to the next token after the operator
|
|
102
|
-
const
|
|
102
|
+
const rightOperand = parseValue(); // Parse the value after the operator
|
|
103
|
+
const nodeType = LOGICAL_OPERATORS.includes(operator.toLowerCase()) ? "logical" : "comparison";
|
|
104
|
+
|
|
105
|
+
if(nodeType === "logical") {
|
|
106
|
+
return { type: "logical", operator, left: { type: "function", name: functionName, args: functionArgs }, right: rightOperand };
|
|
107
|
+
}
|
|
103
108
|
|
|
104
109
|
return {
|
|
105
110
|
type: "comparison",
|
|
106
|
-
left: { type: "function", name:
|
|
111
|
+
left: { type: "function", name: functionName, args: functionArgs },
|
|
107
112
|
operator,
|
|
108
|
-
value
|
|
113
|
+
value: rightOperand
|
|
109
114
|
};
|
|
110
115
|
}
|
|
111
116
|
|
|
112
|
-
return { type: "function", name:
|
|
117
|
+
return { type: "function", name: functionName, args: functionArgs };
|
|
113
118
|
}
|
|
114
119
|
|
|
115
120
|
// Parses logical expressions using operator precedence
|
|
@@ -165,6 +170,21 @@ export function parse(input, variables = []) {
|
|
|
165
170
|
|
|
166
171
|
if (operator === "between") return parseBetweenComparison(field, operator);
|
|
167
172
|
|
|
173
|
+
if (currentToken.type === "function") {
|
|
174
|
+
const functionNode = parseFunction();
|
|
175
|
+
|
|
176
|
+
// Wrap the function inside a comparison if it's directly after an operator
|
|
177
|
+
const leftComparison = {
|
|
178
|
+
type: "comparison",
|
|
179
|
+
field,
|
|
180
|
+
operator,
|
|
181
|
+
value: functionNode.left
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
functionNode.left = leftComparison;
|
|
185
|
+
return functionNode;
|
|
186
|
+
}
|
|
187
|
+
|
|
168
188
|
// For other comparison operators, parse a single right-hand value
|
|
169
189
|
const valueType = currentToken.type;
|
|
170
190
|
const value = parseValue(operator);
|
|
@@ -184,39 +204,59 @@ export function parse(input, variables = []) {
|
|
|
184
204
|
function parseValue(operatorToken) {
|
|
185
205
|
if (!currentToken) throw new Error("Unexpected end of input");
|
|
186
206
|
|
|
187
|
-
|
|
207
|
+
// Handle function without consuming the token
|
|
208
|
+
if (currentToken.type === "function") {
|
|
209
|
+
return parseFunction();
|
|
210
|
+
}
|
|
188
211
|
|
|
189
212
|
const token = currentToken;
|
|
190
213
|
next(); // Move to the next token
|
|
191
214
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
if (token.type === "null") return null;
|
|
215
|
+
switch (token.type) {
|
|
216
|
+
case "number":
|
|
217
|
+
return Number(token.value);
|
|
196
218
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
const val = token.value.slice(1, -1);
|
|
200
|
-
if (!variables.includes(val)) variables.push(val);
|
|
201
|
-
return { type: "placeholder", value: val };
|
|
202
|
-
}
|
|
219
|
+
case "string":
|
|
220
|
+
return token.value.slice(1, -1).replace(/''/g, "");
|
|
203
221
|
|
|
204
|
-
|
|
222
|
+
case "identifier":
|
|
223
|
+
return token.value;
|
|
205
224
|
|
|
206
|
-
|
|
207
|
-
|
|
225
|
+
case "null":
|
|
226
|
+
return null;
|
|
208
227
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
228
|
+
case "placeholder": {
|
|
229
|
+
const val = token.value.slice(1, -1);
|
|
230
|
+
if (!variables.includes(val)) variables.push(val);
|
|
231
|
+
return { type: "placeholder", value: val };
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
case "paren": {
|
|
235
|
+
if (currentToken.type === "function") {
|
|
236
|
+
return parseFunction();
|
|
237
|
+
}
|
|
238
|
+
// Handle ({Placeholder}) syntax for placeholders inside parentheses
|
|
239
|
+
const nextToken = tokenizer.peekNextToken();
|
|
240
|
+
if (currentToken && currentToken.type === "placeholder" &&
|
|
241
|
+
nextToken && nextToken.type === "paren") {
|
|
242
|
+
const val = parseValue();
|
|
243
|
+
return { type: "placeholder", value: val };
|
|
244
|
+
}
|
|
245
|
+
break;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Handle IN or NOT IN operator (outside switch as intended)
|
|
250
|
+
operatorToken = operatorToken?.toUpperCase();
|
|
251
|
+
if (operatorToken === "IN" || operatorToken === "NOT IN") {
|
|
252
|
+
return parseInList(token);
|
|
214
253
|
}
|
|
215
254
|
|
|
216
255
|
throw new Error(`Unexpected value: ${token.value}`);
|
|
217
256
|
}
|
|
218
257
|
|
|
219
258
|
|
|
259
|
+
|
|
220
260
|
// Start parsing and return the AST with extracted variables
|
|
221
261
|
return { ast: parseExpression(), variables };
|
|
222
262
|
}
|
package/src/debug.js
CHANGED
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
// return convertToDevExpressFormat({ ast: astTree, resultObject: sampleData });
|
|
29
29
|
// }
|
|
30
30
|
|
|
31
|
-
// const devexpress = parseFilterString("
|
|
31
|
+
// const devexpress = parseFilterString("CompanyID = ISNULL({LeadDocument.CompanyID},0) OR (ISNULL(CompanyID,0) = 0))", sampleData);
|
|
32
32
|
// console.log("DevExpress Filter:", JSON.stringify(devexpress, null, 2));
|
|
33
33
|
// // const devexpress = parseFilterString("(RS2ID in ({LeadStatementGlobalRpt.StateID}) Or ({LeadStatementGlobalRpt.StateID} =0)) And (RS3ID in (0,{LeadStatementGlobalRpt.RegionID}) Or {LeadStatementGlobalRpt.RegionID} =0 )", sampleData);
|
|
34
34
|
|
package/tests/parser.test.js
CHANGED
|
@@ -137,11 +137,11 @@ describe("Parser SQL to dx Filter Builder", () => {
|
|
|
137
137
|
expected: [
|
|
138
138
|
["SourceID", "=", 2],
|
|
139
139
|
"or",
|
|
140
|
-
["SourceID", "=", null],
|
|
140
|
+
["SourceID", "=", null,{ "defaultValue": 0, "type": "ISNULL"}],
|
|
141
141
|
"or",
|
|
142
142
|
["SourceID", "=", 0],
|
|
143
143
|
"or",
|
|
144
|
-
["SourceID", "=", null]
|
|
144
|
+
["SourceID", "=", null,{ "defaultValue": 0, "type": "ISNULL"}]
|
|
145
145
|
]
|
|
146
146
|
},
|
|
147
147
|
{
|
|
@@ -153,14 +153,14 @@ describe("Parser SQL to dx Filter Builder", () => {
|
|
|
153
153
|
[
|
|
154
154
|
["CompanyID", "=", 0],
|
|
155
155
|
"or",
|
|
156
|
-
["CompanyID", "=", null]
|
|
156
|
+
["CompanyID", "=", null,{ "defaultValue": 0, "type": "ISNULL"}]
|
|
157
157
|
]
|
|
158
158
|
],
|
|
159
159
|
"and",
|
|
160
160
|
[
|
|
161
161
|
["IsSubdealer", "=", true],
|
|
162
162
|
"or",
|
|
163
|
-
["IsSubdealer", "=", null]
|
|
163
|
+
["IsSubdealer", "=", null,{ "defaultValue": 0, "type": "ISNULL"}]
|
|
164
164
|
]
|
|
165
165
|
]
|
|
166
166
|
},
|
|
@@ -187,7 +187,22 @@ describe("Parser SQL to dx Filter Builder", () => {
|
|
|
187
187
|
expected: [
|
|
188
188
|
["TicketID", "=", 123],
|
|
189
189
|
"or",
|
|
190
|
-
["TicketID", "=", null]
|
|
190
|
+
["TicketID", "=", null,{ "defaultValue": 0, "type": "ISNULL"}]
|
|
191
|
+
]
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
input: "CompanyID = ISNULL({LeadDocument.CompanyID},0) OR (ISNULL(CompanyID,0) = 0))",
|
|
195
|
+
expected: [
|
|
196
|
+
["CompanyID", "=", 7],
|
|
197
|
+
"or",
|
|
198
|
+
["CompanyID", "=", null,{ "defaultValue": 0, "type": "ISNULL"}],
|
|
199
|
+
"or",
|
|
200
|
+
["CompanyID", "=", 0],
|
|
201
|
+
"or",
|
|
202
|
+
["CompanyID", "=", null,{ "defaultValue": 0, "type": "ISNULL"},
|
|
203
|
+
|
|
204
|
+
]
|
|
205
|
+
|
|
191
206
|
]
|
|
192
207
|
}
|
|
193
208
|
];
|