sqlparser-devexpress 2.3.10 → 2.3.12

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.10",
3
+ "version": "2.3.12",
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
 
@@ -205,22 +206,41 @@ function DevExpressConverter() {
205
206
  resolvedValue = resolvedValue.split(',').map(v => v.trim());
206
207
  }
207
208
 
209
+ // handle short circuit evaluation for IN operator
210
+ if (EnableShortCircuit && ast.field?.type === "value" && ast.value?.type === "value") {
211
+ const fieldVal = convertValue(ast.field);
212
+ if (Array.isArray(resolvedValue)) {
213
+ // normalize numeric strings if LHS is number
214
+ const list = resolvedValue.map(x =>
215
+ (typeof x === "string" && !isNaN(x) && typeof fieldVal === "number")
216
+ ? Number(x)
217
+ : x
218
+ );
219
+
220
+ if (operator === "IN")
221
+ return list.includes(fieldVal);
222
+ else if (operator === "NOT IN")
223
+ return !list.includes(fieldVal);
224
+ }
225
+ }
226
+
208
227
  let operatorToken = operator === "IN" ? '=' : operator === "NOT IN" ? '!=' : operator;
209
228
  let joinOperatorToken = operator === "IN" ? 'or' : operator === "NOT IN" ? 'and' : operator;
210
-
229
+ let field = convertValue(ast.field);
211
230
  if (Array.isArray(resolvedValue) && resolvedValue.length) {
212
- return resolvedValue.flatMap(i => [[ast.field, operatorToken, i], joinOperatorToken]).slice(0, -1);
231
+ return resolvedValue.flatMap(i => [[field, operatorToken, i], joinOperatorToken]).slice(0, -1);
213
232
  }
214
233
 
215
- return [ast.field, operatorToken, resolvedValue];
234
+ return [field, operatorToken, resolvedValue];
216
235
  }
217
236
 
218
237
  /**
219
238
  * Converts a single value, resolving placeholders and handling special cases.
220
239
  * @param {*} val - The value to convert.
240
+ * @param {string} parentOperator - The operator of the parent logical node (if any).
221
241
  * @returns {*} Converted value.
222
242
  */
223
- function convertValue(val) {
243
+ function convertValue(val, parentOperator = null) {
224
244
  if (val === null) return null;
225
245
 
226
246
  // Handle array values
@@ -251,6 +271,10 @@ function DevExpressConverter() {
251
271
  }
252
272
  }
253
273
 
274
+ if (parentOperator && parentOperator.toUpperCase() === "IN" && typeof val === "string") {
275
+ return val.split(',').map(v => v.trim());
276
+ }
277
+
254
278
  return val;
255
279
  }
256
280
 
@@ -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": {
@@ -214,6 +214,14 @@ describe("Parser SQL to dx Filter Builder", () => {
214
214
  {
215
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
216
  expected: []
217
+ },
218
+ {
219
+ input: "ID IN ('1,2') AND 0 IN ('0,2')",
220
+ expected: [
221
+ ["ID", "=", "1"],
222
+ "or",
223
+ ["ID", "=", "2"]
224
+ ]
217
225
  }
218
226
  ];
219
227