sqlparser-devexpress 2.1.4 → 2.2.0

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/README.md CHANGED
@@ -1,29 +1,142 @@
1
1
  # SQLParser
2
2
 
3
- SQLParser is a JavaScript library that converts SQL-like filter strings into DevExpress format filters. It provides utilities for parsing, sanitizing, and converting SQL-like expressions into a format that can be used with DevExpress components.
3
+ SQLParser is a JavaScript library that converts SQL `WHERE` clauses into a structured **Abstract Syntax Tree (AST)** and transforms them into DevExpress filter format. It removes inline parameters while preserving them as dynamic variables for flexible query processing.
4
4
 
5
- ## Usage
5
+ ## Features
6
6
 
7
- ### Convert SQL to AST
7
+ - **AST-Based Query Processing**: Parses `WHERE` clauses and generates a structured AST.
8
+ - **Supports Dynamic Parameters**: Identifies and extracts placeholders (`{param}`) for dynamic resolution.
9
+ - **Parameter Cleanup**: Removes inline parameters while maintaining their structure.
10
+ - **DevExpress-Compatible Output**: Converts parsed SQL conditions into the DevExpress filter format.
11
+ - **Short-Circuit Optimization**: By default, eliminates `value = value` expressions for DevExpress compatibility (can be disabled for performance optimization).
12
+ - **Separation of Concerns**: Generate AST once, then use it for multiple state updates.
8
13
 
9
- To convert a SQL-like filter string to an Abstract Syntax Tree (AST):
14
+ ## Example Workflow
15
+
16
+ ### **Step 1: Input SQL**
17
+
18
+ ```sql
19
+ WHERE OrderID = {CustomerOrders.OrderID} AND Status IN (1, 3)
20
+ ```
21
+
22
+ ### **Step 2: Generate AST**
10
23
 
11
24
  ```javascript
12
- const filterString= "(ID <> {Item.ID}) AND (ItemGroupType IN ({Item.AllowedItemGroupType}))";
13
- const parsedResult = convertSQLToAst(filterString);
25
+ import { convertSQLToAst } from "sqlparser-devexpress";
26
+
27
+ const sqlQuery = "OrderID = {CustomerOrders.OrderID} AND Status IN (1, 3)";
28
+ const { ast, variables } = convertSQLToAst(sqlQuery, true); // Enable logs
29
+ ```
30
+
31
+ #### **AST Output**
32
+
33
+ ```json
34
+ {
35
+ "type": "logical",
36
+ "operator": "AND",
37
+ "left": {
38
+ "type": "comparison",
39
+ "field": "OrderID",
40
+ "operator": "=",
41
+ "value": {
42
+ "type": "placeholder",
43
+ "value": "CustomerOrders.OrderID"
44
+ }
45
+ },
46
+ "right": {
47
+ "type": "comparison",
48
+ "field": "Status",
49
+ "operator": "in",
50
+ "value": {
51
+ "type": "value",
52
+ "value": [1, 3]
53
+ }
54
+ }
55
+ }
14
56
  ```
15
57
 
16
- ### Convert AST to DevExpress Format
58
+ #### Extracted Variables
17
59
 
18
- To convert an AST to DevExpress format:
60
+ The parser identifies placeholders within the SQL query and extracts them for dynamic value resolution.
61
+
62
+ #### **Example Output:**
63
+ ```json
64
+ [
65
+ "CustomerOrders.OrderID"
66
+ ]
67
+ ```
68
+
69
+ These extracted variables can be used to fetch the corresponding state values in the application. You can store them in a `Record<string, any>`, where the key is the placeholder name, and the value is the resolved data from the application's state.
70
+
71
+
72
+
73
+ ### **Step 3: Convert AST to DevExpress Format**
19
74
 
20
75
  ```javascript
21
- const ast = { /* your AST here */ };
22
- const variables = [/* your variables here */];
23
- const state = { /* your state here */ };
76
+ import { convertAstToDevextreme } from "sqlparser-devexpress";
77
+
78
+ const sampleState = {
79
+ "CustomerOrders.OrderID": 76548
80
+ };
81
+
82
+ const devexpressFilter = convertAstToDevextreme(ast, sampleState, true); // Short-circuit enabled (default)
24
83
 
25
- const devExpressFilter = convertAstToDevextreme(ast, variables, state);
84
+ console.log("DevExpress Filter:", JSON.stringify(devexpressFilter, null, 2));
85
+ ```
86
+
87
+ #### **DevExpress Filter Output**
88
+
89
+ ```json
90
+ [
91
+ ["OrderID", "=", 76548],
92
+ "and",
93
+ [
94
+ ["Status", "=", 1],
95
+ "or",
96
+ ["Status", "=", 3]
97
+ ]
98
+ ]
99
+ ```
26
100
 
27
- console.log(devExpressFilter);
101
+ ## Installation
102
+
103
+ ```sh
104
+ npm install sqlparser-devexpress
28
105
  ```
29
106
 
107
+ ## API Reference
108
+
109
+ ### `convertSQLToAst(filterString, enableConsoleLogs = false)`
110
+
111
+ - **Input:** SQL `WHERE` clause as a string.
112
+ - **Output:** An object `{ ast }` where:
113
+ - `ast`: The parsed Abstract Syntax Tree.
114
+ - **Example:**
115
+ ```javascript
116
+ const { ast } = convertSQLToAst("OrderID = {CustomerOrders.OrderID} AND Status IN (1, 3)");
117
+ ```
118
+
119
+ ### `convertAstToDevextreme(ast, state, shortCircuit = true)`
120
+
121
+ - **Input:**
122
+ - `ast`: The AST generated from `convertSQLToAst()`.
123
+ - `state`: An object containing values for placeholders.
124
+ - `shortCircuit`: (Optional, default `true`) Enables short-circuiting of `value = value` expressions for DevExpress compatibility.
125
+ - **Output:** DevExpress filter array.
126
+ - **Example:**
127
+ ```javascript
128
+ const devexpressFilter = convertAstToDevextreme(ast, sampleState, false); // Disables short-circuiting
129
+ ```
130
+
131
+ ## Roadmap
132
+
133
+ - Support for additional SQL operators and functions.
134
+ - Improved error handling and validation.
135
+
136
+ ## Contributing
137
+
138
+ Contributions are welcome! Feel free to open issues or submit pull requests.
139
+
140
+ ## License
141
+
142
+ MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sqlparser-devexpress",
3
- "version": "2.1.4",
3
+ "version": "2.2.0",
4
4
  "main": "src/index.js",
5
5
  "type": "module",
6
6
  "scripts": {
package/src/constants.js CHANGED
@@ -9,3 +9,5 @@ export const OPERATOR_PRECEDENCE = {
9
9
 
10
10
  // Regular expression to check for unsupported SQL patterns (like SELECT-FROM or JOIN statements)
11
11
  export const UNSUPPORTED_PATTERN = /\bSELECT\b.*\bFROM\b|\bINNER\s+JOIN\b/i;
12
+
13
+ export const LOGICAL_OPERATORS = ['and', 'or'];
@@ -1,31 +1,25 @@
1
- const logicalOperators = ['and', 'or'];
1
+ import { LOGICAL_OPERATORS } from "../constants.js";
2
2
 
3
3
  /**
4
4
  * Main conversion function that sets up the global context
5
- * @param {Object} ast - The abstract syntax tree
6
- * @param {Array} vars - Array of variable names
7
- * @param {Object} results - Optional object for placeholder resolution
8
5
  * @returns {Array|null} DevExpress format filter
9
6
  */
10
7
  function DevExpressConverter() {
11
8
  // Global variables accessible throughout the converter
12
9
  let resultObject = null;
13
- let primaryEntity = null;
14
- let primaryKey = null;
15
- let variables = [];
16
10
  const EnableShortCircuit = true;
17
11
 
18
12
  /**
19
13
  * Main conversion function that sets up the global context
20
14
  * @param {Object} ast - The abstract syntax tree
21
- * @param {Array} vars - Array of variable names
22
15
  * @param {Object} ResultObject - Optional object for placeholder resolution
16
+ * @param {boolean} enableShortCircuit - Optional enabling and disabling the shortcircuit ie evaluating value = value scenario
23
17
  * @returns {Array|null} DevExpress format filter
24
18
  */
25
- function convert(ast, vars, ResultObject = null) {
19
+ function convert(ast, ResultObject = null, enableShortCircuit = true) {
26
20
  // Set up global context
27
- variables = vars;
28
21
  resultObject = ResultObject;
22
+ EnableShortCircuit = enableShortCircuit;
29
23
 
30
24
  // Process the AST
31
25
  let result = processAstNode(ast);
@@ -111,8 +105,8 @@ function DevExpressConverter() {
111
105
 
112
106
  // Detect and flatten nested logical expressions
113
107
  if (parentOperator === null) {
114
- if (left.length === 3 && logicalOperators.includes(left[1])) parentOperator = left[1];
115
- if (right.length === 3 && logicalOperators.includes(right[1])) parentOperator = right[1];
108
+ if (left.length === 3 && LOGICAL_OPERATORS.includes(left[1])) parentOperator = left[1];
109
+ if (right.length === 3 && LOGICAL_OPERATORS.includes(right[1])) parentOperator = right[1];
116
110
  }
117
111
 
118
112
  // Flatten nested logical expressions if applicable
@@ -377,12 +371,10 @@ const devExpressConverter = DevExpressConverter();
377
371
  /**
378
372
  * Converts an abstract syntax tree to DevExpress format
379
373
  * @param {Object} ast - The abstract syntax tree
380
- * @param {Array} variables - Array of variable names
381
374
  * @param {Object} resultObject - Optional object for placeholder resolution
382
- * @param {string} primaryEntity - Optional primary entity name
383
- * @param {string} primaryKey - Optional primary key value
375
+ * @param {string} enableShortCircuit - Optional enabling and disabling the shortcircuit ie evaluating value = value scenario
384
376
  * @returns {Array|null} DevExpress format filter
385
377
  */
386
- export function convertToDevExpressFormat({ ast, variables, resultObject = null }) {
387
- return devExpressConverter.init(ast, variables, resultObject);
378
+ export function convertToDevExpressFormat({ ast, resultObject = null, enableShortCircuit = true }) {
379
+ return devExpressConverter.init(ast, resultObject,enableShortCircuit);
388
380
  }
@@ -1,4 +1,4 @@
1
- import { LITERALS } from "../constants";
1
+ import { LITERALS } from "../constants.js";
2
2
 
3
3
  // Define regex patterns for different token types
4
4
  const tokenPatterns = {
@@ -9,7 +9,7 @@ const tokenPatterns = {
9
9
  placeholder: "'?\\{[^}]+\\}'?", // Matches placeholders like {variable} or '{variable}'
10
10
  string: "\\('\\w+\\'\\)|'(?:''|[^'])*'", // Matches strings, allowing for escaped single quotes ('')
11
11
  operator: "=>|<=|!=|>=|=|<>|>|<|\\bAND\\b|\\bOR\\b|\\bBETWEEN\\b|\\bIN\\b|\\bNOT IN\\b|\\bLIKE\\b|\\bIS NOT\\b|\\bNOT LIKE\\b|\\bIS\\b", // Matches SQL operators and logical keywords
12
- identifier: "[\\w.]+", // Matches identifiers, including table.column format
12
+ identifier: "[\\w.]+|\"[^\"]+\"|\\[[^\\]]+\\]", // Matches regular identifiers, quoted identifiers ("identifier"), and bracketed identifiers [identifier]
13
13
  paren: "[()]", // Matches standalone parentheses
14
14
  comma: "," // Matches commas
15
15
  };
@@ -59,8 +59,15 @@ class Tokenizer {
59
59
  }
60
60
  }
61
61
 
62
- if (LITERALS.includes(type)) value = value.replace(/^[(]|[)]$/g, "")
62
+ if (LITERALS.includes(type)){
63
+ value = value.replace(/^[(]|[)]$/g, "");
64
+ }
63
65
 
66
+ if (type === "identifier") {
67
+ value = value.replace(/^["\[]|["\]]$/g, "");
68
+ }
69
+
70
+
64
71
  return { type, value };
65
72
  }
66
73
 
package/src/debug.js CHANGED
@@ -6,17 +6,15 @@
6
6
  // import { convertToDevExpressFormat } from "./core/converter.js";
7
7
  // import { parse } from "./core/parser.js";
8
8
  // import { sanitizeQuery } from "./core/sanitizer.js";
9
- // import { convertAstToDevextreme, convertSQLToAst } from "./index.js";
10
9
 
11
10
  // const sampleData = {
12
11
  // 'LeadStatementGlobalRpt.StateID': null,
13
12
  // 'LeadStatementGlobalRpt.RegionID': null,
14
- // 'ServiceOrderDocument.SourceID': 2
13
+ // 'ServiceOrderDocument.SourceID': 2,
14
+ // 'CustomerOrders.OrderID': 76548
15
15
  // }
16
16
 
17
17
  // export function parseFilterString(filterString, sampleData = null) {
18
- // if (filterString.toUpperCase().startsWith("SELECT")) return null; // Skip full SQL queries
19
-
20
18
  // let { sanitizedSQL, extractedVariables } = sanitizeQuery(filterString);
21
19
  // console.log("Sanitized SQL:", sanitizedSQL, "\n");
22
20
 
@@ -30,9 +28,9 @@
30
28
  // return convertToDevExpressFormat({ ast: astTree, variables: extractedVariables, resultObject: sampleData });
31
29
  // }
32
30
 
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);
31
+ // const devexpress = parseFilterString("OrderID = {CustomerOrders.OrderID} AND Status IN (1, 3)", sampleData);
35
32
  // console.log("DevExpress Filter:", JSON.stringify(devexpress, null, 2));
33
+ // // const devexpress = parseFilterString("(RS2ID in ({LeadStatementGlobalRpt.StateID}) Or ({LeadStatementGlobalRpt.StateID} =0)) And (RS3ID in (0,{LeadStatementGlobalRpt.RegionID}) Or {LeadStatementGlobalRpt.RegionID} =0 )", sampleData);
36
34
 
37
35
  // // const devExpressFilter = convertSQLToAst("(RS2ID in ({LeadStatementGlobalRpt.StateID}) Or ({LeadStatementGlobalRpt.StateID} =0)) And (RS3ID in (0,{LeadStatementGlobalRpt.RegionID}) Or {LeadStatementGlobalRpt.RegionID} =0 ) ");
38
36
  // // const devExpressFilterresult = convertAstToDevextreme(devExpressFilter.ast, devExpressFilter.variables, sampleData);