sqlparser-devexpress 2.3.9 → 2.3.11

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.3.9",
3
+ "version": "2.3.11",
4
4
  "main": "src/index.js",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -0,0 +1,22 @@
1
+ import { ASTNode } from "./parser.js";
2
+
3
+ export interface ResultObject {
4
+ [key: string]: any;
5
+ }
6
+
7
+ export type DevExpressFilter = any[] | null;
8
+
9
+ export interface ConvertOptions {
10
+ ast: ASTNode;
11
+ resultObject?: ResultObject;
12
+ enableShortCircuit?: boolean;
13
+ }
14
+
15
+ /**
16
+ * Converts an abstract syntax tree (AST) to DevExpress filter format.
17
+ * This function uses short-circuit evaluation for optimization.
18
+ *
19
+ * @param options - The conversion options containing AST, result object, and short-circuit flag.
20
+ * @returns DevExpressFilter - The DevExpress compatible filter array or null.
21
+ */
22
+ export function convertToDevExpressFormat(options: ConvertOptions): DevExpressFilter;
@@ -0,0 +1,27 @@
1
+ export interface ASTNode {
2
+ type: string;
3
+ operator?: string;
4
+ field?: string;
5
+ value?: any;
6
+ left?: ASTNode;
7
+ right?: ASTNode;
8
+ args?: ASTNode[];
9
+ name?: string;
10
+ }
11
+
12
+ /**
13
+ * Represents the result of the parse function.
14
+ */
15
+ export interface ParseResult {
16
+ ast: ASTNode;
17
+ variables: string[];
18
+ }
19
+
20
+ /**
21
+ * The main parse function that converts SQL-like queries into AST.
22
+ *
23
+ * @param input - The SQL-like string to be parsed.
24
+ * @param variables - The list of extracted variables during parsing.
25
+ * @returns ParseResult - The resulting AST and extracted variables.
26
+ */
27
+ export function parse(input: string, variables?: string[]): ParseResult;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Represents the result of sanitizing the SQL query.
3
+ */
4
+ export interface SanitizeResult {
5
+ sanitizedSQL: string;
6
+ variables: string[];
7
+ }
8
+
9
+ /**
10
+ * Sanitizes the SQL query by replacing placeholders or pipe-separated variables
11
+ * with standardized `{placeholder}` format and extracts all variable names.
12
+ *
13
+ * Example Input:
14
+ * ```
15
+ * SELECT * FROM Orders WHERE CustomerID = {0} | [CustomerID]
16
+ * ```
17
+ *
18
+ * Output:
19
+ * ```
20
+ * {
21
+ * sanitizedSQL: "SELECT * FROM Orders WHERE CustomerID = {CustomerID}",
22
+ * variables: ["CustomerID"]
23
+ * }
24
+ *
25
+ * @param sql - The raw SQL query containing placeholders or pipes.
26
+ * @returns SanitizeResult - The cleaned SQL query and extracted variables.
27
+ */
28
+ export function sanitizeQuery(sql: string): SanitizeResult;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Represents a single token from the tokenizer.
3
+ */
4
+ export interface Token {
5
+ type: string;
6
+ value: string;
7
+ dataType?: string;
8
+ }
@@ -1,35 +1,41 @@
1
- export type StateDataObject = Record<string, any>;
2
-
3
- export interface SanitizedQuery {
4
- sanitizedSQL: string;
5
- extractedVariables: string[];
6
- }
7
-
8
- export interface ParsedResult {
9
- ast: any; // Define a more specific type if possible
10
- variables: string[];
11
- }
12
-
13
- export interface ConvertToDevExpressFormatParams {
14
- ast: any; // Define a more specific type if possible
15
- resultObject?: StateDataObject | null;
16
- enableShortCircuit?: boolean;
17
- }
18
-
19
- export function sanitizeQuery(filterString: string): SanitizedQuery;
20
-
21
- export function parse(query: string, variables: string[]): ParsedResult;
22
-
23
- export function convertToDevExpressFormat(params: ConvertToDevExpressFormatParams): any;
24
-
25
- export function convertSQLToAst(
26
- filterString: string,
27
- SampleData?: StateDataObject | null,
28
- enableConsoleLogs?: boolean
29
- ): ParsedResult;
30
-
1
+ import { DevExpressFilter, ResultObject } from "./core/converter";
2
+ import { ASTNode, ParseResult } from "./core/parser";
3
+
4
+ /**
5
+ * Converts an SQL-like filter string into an Abstract Syntax Tree (AST).
6
+ * It also extracts variables from placeholders like `{CustomerID}` or pipe-separated sections.
7
+ * Optionally logs the conversion process if `enableConsoleLogs` is `true`.
8
+ *
9
+ * Example:
10
+ * ```
11
+ * const { ast, variables } = convertSQLToAst("ID = {CustomerID} AND Status = {OrderStatus}");
12
+ * console.log(ast);
13
+ * console.log(variables);
14
+ * ```
15
+ *
16
+ * @param filterString - The raw SQL-like filter string.
17
+ * @param enableConsoleLogs - Whether to log the parsing and sanitization process.
18
+ * @returns ParseResult - The AST and extracted variables.
19
+ */
20
+ export function convertSQLToAst(filterString: string, enableConsoleLogs?: boolean): ParseResult;
21
+
22
+ /**
23
+ * Converts an Abstract Syntax Tree (AST) into a DevExpress-compatible filter format.
24
+ * Optionally supports a result object for dynamic value resolution and short-circuit evaluation.
25
+ *
26
+ * Example:
27
+ * ```
28
+ * const filter = convertAstToDevextreme(ast, state, true);
29
+ * console.log(filter);
30
+ * ```
31
+ *
32
+ * @param ast - The parsed AST from `convertSQLToAst`.
33
+ * @param state - An optional result object to resolve placeholders to actual values.
34
+ * @param enableShortCircuit - Whether to apply short-circuit evaluation.
35
+ * @returns DevExpressFilter - The DevExpress-compatible filter array or null.
36
+ */
31
37
  export function convertAstToDevextreme(
32
- ast: any, // Define a more specific type if possible
33
- state?: StateDataObject | null,
38
+ ast: ASTNode,
39
+ state?: ResultObject | null,
34
40
  enableShortCircuit?: boolean,
35
- ): any;
41
+ ): DevExpressFilter;
@@ -48,7 +48,7 @@ function DevExpressConverter() {
48
48
  return handleFunction(ast);
49
49
  case "field":
50
50
  case "value":
51
- return convertValue(ast.value);
51
+ return convertValue(ast.value, parentOperator);
52
52
  default:
53
53
  return null;
54
54
  }
@@ -113,6 +113,7 @@ function DevExpressConverter() {
113
113
  if (shouldFlattenLogicalTree(parentOperator, operator, ast)) {
114
114
  return flattenLogicalTree(left, operator, right);
115
115
  }
116
+
116
117
  return [left, operator, right];
117
118
  }
118
119
 
@@ -135,7 +136,9 @@ function DevExpressConverter() {
135
136
  }
136
137
 
137
138
  const left = ast.left !== undefined ? processAstNode(ast.left) : convertValue(ast.field);
139
+ const leftDefault = ast.left?.args[1]?.value;
138
140
  const right = ast.right !== undefined ? processAstNode(ast.right) : convertValue(ast.value);
141
+ const rightDefault = ast.right?.args[1]?.value;
139
142
  let operatorToken = ast.operator.toLowerCase();
140
143
 
141
144
  if (operatorToken === "like") {
@@ -155,8 +158,8 @@ function DevExpressConverter() {
155
158
 
156
159
  // Apply short-circuit evaluation if enabled
157
160
  if (EnableShortCircuit) {
158
- if (isAlwaysTrue(comparison)) return true;
159
- if (isAlwaysFalse(comparison)) return false;
161
+ if (isAlwaysTrue(comparison, leftDefault, rightDefault)) return true;
162
+ if (isAlwaysFalse(comparison, leftDefault, rightDefault)) return false;
160
163
  }
161
164
 
162
165
  return comparison;
@@ -205,20 +208,21 @@ function DevExpressConverter() {
205
208
 
206
209
  let operatorToken = operator === "IN" ? '=' : operator === "NOT IN" ? '!=' : operator;
207
210
  let joinOperatorToken = operator === "IN" ? 'or' : operator === "NOT IN" ? 'and' : operator;
208
-
211
+ let field = convertValue(ast.field);
209
212
  if (Array.isArray(resolvedValue) && resolvedValue.length) {
210
- return resolvedValue.flatMap(i => [[ast.field, operatorToken, i], joinOperatorToken]).slice(0, -1);
213
+ return resolvedValue.flatMap(i => [[field, operatorToken, i], joinOperatorToken]).slice(0, -1);
211
214
  }
212
215
 
213
- return [ast.field, operatorToken, resolvedValue];
216
+ return [field, operatorToken, resolvedValue];
214
217
  }
215
218
 
216
219
  /**
217
220
  * Converts a single value, resolving placeholders and handling special cases.
218
221
  * @param {*} val - The value to convert.
222
+ * @param {string} parentOperator - The operator of the parent logical node (if any).
219
223
  * @returns {*} Converted value.
220
224
  */
221
- function convertValue(val) {
225
+ function convertValue(val, parentOperator = null) {
222
226
  if (val === null) return null;
223
227
 
224
228
  // Handle array values
@@ -249,6 +253,10 @@ function DevExpressConverter() {
249
253
  }
250
254
  }
251
255
 
256
+ if (parentOperator && parentOperator.toUpperCase() === "IN" && typeof val === "string") {
257
+ return val.split(',').map(v => v.trim());
258
+ }
259
+
252
260
  return val;
253
261
  }
254
262
 
@@ -332,19 +340,23 @@ function DevExpressConverter() {
332
340
  /**
333
341
  * Checks if a condition is always true.
334
342
  * @param {Array} condition - The condition to check.
343
+ * @param {*} leftDefault - The default value for the left operand.
344
+ * @param {*} rightDefault - The default value for the right operand.
335
345
  * @returns {boolean} True if the condition is always true.
336
346
  */
337
- function isAlwaysTrue(condition) {
338
- return Array.isArray(condition) && condition.length >= 3 && evaluateExpression(...condition) == true;
347
+ function isAlwaysTrue(condition, leftDefault, rightDefault) {
348
+ return Array.isArray(condition) && condition.length >= 3 && evaluateExpression(...condition, leftDefault, rightDefault) == true;
339
349
  }
340
350
 
341
351
  /**
342
352
  * Checks if a condition is always false.
343
353
  * @param {Array} condition - The condition to check.
354
+ * @param {*} leftDefault - The default value for the left operand.
355
+ * @param {*} rightDefault - The default value for the right operand.
344
356
  * @returns {boolean} True if the condition is always false.
345
357
  */
346
- function isAlwaysFalse(condition) {
347
- return Array.isArray(condition) && condition.length >= 3 && evaluateExpression(...condition) == false;
358
+ function isAlwaysFalse(condition, leftDefault, rightDefault) {
359
+ return Array.isArray(condition) && condition.length >= 3 && evaluateExpression(...condition, leftDefault, rightDefault) == false;
348
360
  }
349
361
 
350
362
  /**
@@ -352,9 +364,14 @@ function DevExpressConverter() {
352
364
  * @param {*} left - The left operand.
353
365
  * @param {string} operator - The operator.
354
366
  * @param {*} right - The right operand.
367
+ * @param {*} leftDefault - The default value for the left operand.
368
+ * @param {*} rightDefault - The default value for the right
355
369
  * @returns {boolean|null} The result of the evaluation or null if not evaluable.
356
370
  */
357
- function evaluateExpression(left, operator, right) {
371
+ function evaluateExpression(left, operator, right, leftDefault, rightDefault) {
372
+ if (left == null && leftDefault != undefined) left = leftDefault;
373
+ if (right == null && rightDefault != undefined) right = rightDefault;
374
+
358
375
  if ((left !== null && isNaN(left)) || (right !== null && isNaN(right))) return null;
359
376
 
360
377
  if (left === null || right === null) {
@@ -102,15 +102,15 @@ export function parse(input, variables = []) {
102
102
  const rightOperand = parseValue(); // Parse the value after the operator
103
103
  const nodeType = LOGICAL_OPERATORS.includes(operator.toLowerCase()) ? "logical" : "comparison";
104
104
 
105
- if(nodeType === "logical") {
106
- return { type: "logical", operator, left: { type: "function", name: functionName, args: functionArgs }, right: rightOperand };
105
+ if (nodeType === "logical") {
106
+ return { type: "logical", operator, left: { type: "function", name: functionName, args: functionArgs }, right: rightOperand };
107
107
  }
108
108
 
109
109
  return {
110
110
  type: "comparison",
111
111
  left: { type: "function", name: functionName, args: functionArgs },
112
112
  operator,
113
- value: rightOperand
113
+ value: rightOperand
114
114
  };
115
115
  }
116
116
 
@@ -126,9 +126,21 @@ export function parse(input, variables = []) {
126
126
  const operator = currentToken.value.toUpperCase();
127
127
  next(); // Move to the next token
128
128
 
129
- // Recursively parse the right-hand expression with adjusted precedence
130
- const right = parseExpression(OPERATOR_PRECEDENCE[operator]);
131
- left = { type: "logical", operator, left, right };
129
+ if (operator === "IN" || operator === "NOT IN") {
130
+ const rightList = parseValue(operator);
131
+ left = {
132
+ type: "comparison",
133
+ field: left,
134
+ operator: operator,
135
+ value: rightList
136
+ };
137
+ }
138
+
139
+ if (LOGICAL_OPERATORS.includes(operator.toLowerCase())) {
140
+ // Recursively parse the right-hand expression with adjusted precedence
141
+ const right = parseExpression(OPERATOR_PRECEDENCE[operator]);
142
+ left = { type: "logical", operator, left, right };
143
+ }
132
144
  }
133
145
 
134
146
  return left;
@@ -173,7 +185,7 @@ export function parse(input, variables = []) {
173
185
  if (currentToken.type === "function") {
174
186
  const functionNode = parseFunction();
175
187
 
176
- if(fieldType === "identifier" && functionNode.type === "function") {
188
+ if (fieldType === "identifier" && functionNode.type === "function") {
177
189
  return {
178
190
  type: "comparison",
179
191
  field,
@@ -189,7 +201,7 @@ export function parse(input, variables = []) {
189
201
  operator,
190
202
  value: functionNode.left
191
203
  };
192
-
204
+
193
205
  functionNode.left = leftComparison;
194
206
  return functionNode;
195
207
  }
@@ -237,7 +249,7 @@ export function parse(input, variables = []) {
237
249
  case "placeholder": {
238
250
  const val = token.value.slice(1, -1);
239
251
  if (!variables.includes(val)) variables.push(val);
240
- return { ...token ,type: "placeholder", value: val };
252
+ return { ...token, type: "placeholder", value: val };
241
253
  }
242
254
 
243
255
  case "paren": {
@@ -210,6 +210,19 @@ describe("Parser SQL to dx Filter Builder", () => {
210
210
  "and",
211
211
  ["BranchName", "notcontains", "42"]
212
212
  ]
213
+ },
214
+ {
215
+ input: "(RS2ID in ({SaleOrderStatusStmtGlobalRpt.StateID}) Or (ISNULL({SaleOrderStatusStmtGlobalRpt.StateID},0) =0)) And (RS3ID in (0,{SaleOrderStatusStmtGlobalRpt.RegionID}) Or ISNULL({SaleOrderStatusStmtGlobalRpt.RegionID},0) =0 )",
216
+ expected: []
217
+ },
218
+ {
219
+ input: "0 IN ('1,2')",
220
+ expected: [
221
+ [0, "=", "1"],
222
+ "or",
223
+ [0, "=", "2"]
224
+
225
+ ]
213
226
  }
214
227
  ];
215
228
 
@@ -270,5 +283,7 @@ const sampleData = {
270
283
  "LeadDocument.CompanyID": 7,
271
284
  "ServiceOrderDocument.SourceID": 2,
272
285
  "LeadDocument.AllowSubDealer": true,
273
- "SupportResolution.TicketID": 123
286
+ "SupportResolution.TicketID": 123,
287
+ "SaleOrderStatusStmtGlobalRpt.StateID": null,
288
+ "SaleOrderStatusStmtGlobalRpt.RegionID": null,
274
289
  };