sqlparser-devexpress 2.3.2 → 2.3.3
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 +2 -2
- package/src/core/parser.js +69 -29
- package/tests/parser.test.js +13 -0
package/package.json
CHANGED
package/src/core/converter.js
CHANGED
|
@@ -140,9 +140,9 @@ function DevExpressConverter() {
|
|
|
140
140
|
|
|
141
141
|
let comparison = [left, operatorToken, right];
|
|
142
142
|
|
|
143
|
-
if (isFunctionNullCheck(ast.left, true)) {
|
|
143
|
+
if ((ast.left && isFunctionNullCheck(ast.left, true)) || (ast.value && isFunctionNullCheck(ast.value, false))) {
|
|
144
144
|
comparison = [[left, operatorToken, right], 'or', [left, operatorToken, null]];
|
|
145
|
-
} else if (isFunctionNullCheck(ast.right, true)) {
|
|
145
|
+
} else if (ast.right && isFunctionNullCheck(ast.right, true)) {
|
|
146
146
|
comparison = [[left, operatorToken, right], 'or', [right, operatorToken, null]];
|
|
147
147
|
}
|
|
148
148
|
|
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/tests/parser.test.js
CHANGED
|
@@ -189,6 +189,19 @@ describe("Parser SQL to dx Filter Builder", () => {
|
|
|
189
189
|
"or",
|
|
190
190
|
["TicketID", "=", null]
|
|
191
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],
|
|
199
|
+
"or",
|
|
200
|
+
["CompanyID", "=", 0],
|
|
201
|
+
"or",
|
|
202
|
+
["CompanyID", "=", null]
|
|
203
|
+
|
|
204
|
+
]
|
|
192
205
|
}
|
|
193
206
|
];
|
|
194
207
|
|