sqlparser-devexpress 2.1.4 → 2.2.1
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/{LISCENSE.md → LICENSE} +5 -5
- package/README.md +125 -13
- package/package.json +2 -2
- package/src/constants.js +2 -0
- package/src/core/converter.js +9 -17
- package/src/core/tokenizer.js +10 -3
- package/src/debug.js +4 -6
package/{LISCENSE.md → LICENSE}
RENAMED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
MIT License
|
|
2
2
|
|
|
3
3
|
Copyright (c) 2025 Rohit Mahajan
|
|
4
4
|
|
|
@@ -9,13 +9,13 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
|
9
9
|
copies of the Software, and to permit persons to whom the Software is
|
|
10
10
|
furnished to do so, subject to the following conditions:
|
|
11
11
|
|
|
12
|
-
The above copyright notice and this permission notice shall be included in
|
|
13
|
-
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
14
|
|
|
15
15
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
16
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
17
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
18
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
19
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
-
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,29 +1,141 @@
|
|
|
1
1
|
# SQLParser
|
|
2
2
|
|
|
3
|
-
SQLParser is a JavaScript library that converts SQL
|
|
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
|
-
##
|
|
5
|
+
## Features
|
|
6
6
|
|
|
7
|
-
|
|
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
|
-
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
npm install sqlparser-devexpress
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Example Workflow
|
|
21
|
+
|
|
22
|
+
### **Step 1: Input SQL**
|
|
23
|
+
|
|
24
|
+
```sql
|
|
25
|
+
WHERE OrderID = {CustomerOrders.OrderID} AND Status IN (1, 3)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### **Step 2: Generate AST**
|
|
10
29
|
|
|
11
30
|
```javascript
|
|
12
|
-
|
|
13
|
-
|
|
31
|
+
import { convertSQLToAst } from "sqlparser-devexpress";
|
|
32
|
+
|
|
33
|
+
const sqlQuery = "OrderID = {CustomerOrders.OrderID} AND Status IN (1, 3)";
|
|
34
|
+
const { ast, variables } = convertSQLToAst(sqlQuery, true); // Enable logs
|
|
14
35
|
```
|
|
15
36
|
|
|
16
|
-
|
|
37
|
+
#### **AST Output**
|
|
17
38
|
|
|
18
|
-
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"type": "logical",
|
|
42
|
+
"operator": "AND",
|
|
43
|
+
"left": {
|
|
44
|
+
"type": "comparison",
|
|
45
|
+
"field": "OrderID",
|
|
46
|
+
"operator": "=",
|
|
47
|
+
"value": {
|
|
48
|
+
"type": "placeholder",
|
|
49
|
+
"value": "CustomerOrders.OrderID"
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"right": {
|
|
53
|
+
"type": "comparison",
|
|
54
|
+
"field": "Status",
|
|
55
|
+
"operator": "in",
|
|
56
|
+
"value": {
|
|
57
|
+
"type": "value",
|
|
58
|
+
"value": [1, 3]
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
#### Extracted Variables
|
|
65
|
+
|
|
66
|
+
The parser identifies placeholders within the SQL query and extracts them for dynamic value resolution.
|
|
67
|
+
|
|
68
|
+
#### **Example Output:**
|
|
69
|
+
```json
|
|
70
|
+
[
|
|
71
|
+
"CustomerOrders.OrderID"
|
|
72
|
+
]
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
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.
|
|
76
|
+
|
|
77
|
+
### **Step 3: Convert AST to DevExpress Format**
|
|
19
78
|
|
|
20
79
|
```javascript
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const
|
|
80
|
+
import { convertAstToDevextreme } from "sqlparser-devexpress";
|
|
81
|
+
|
|
82
|
+
const sampleState = {
|
|
83
|
+
"CustomerOrders.OrderID": 76548
|
|
84
|
+
};
|
|
24
85
|
|
|
25
|
-
const
|
|
86
|
+
const devexpressFilter = convertAstToDevextreme(ast, sampleState, true); // Short-circuit enabled (default)
|
|
26
87
|
|
|
27
|
-
console.log(
|
|
88
|
+
console.log("DevExpress Filter:", JSON.stringify(devexpressFilter, null, 2));
|
|
28
89
|
```
|
|
29
90
|
|
|
91
|
+
#### **DevExpress Filter Output**
|
|
92
|
+
|
|
93
|
+
```json
|
|
94
|
+
[
|
|
95
|
+
["OrderID", "=", 76548],
|
|
96
|
+
"and",
|
|
97
|
+
[
|
|
98
|
+
["Status", "=", 1],
|
|
99
|
+
"or",
|
|
100
|
+
["Status", "=", 3]
|
|
101
|
+
]
|
|
102
|
+
]
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## API Reference
|
|
106
|
+
|
|
107
|
+
### `convertSQLToAst(filterString, enableConsoleLogs = false)`
|
|
108
|
+
|
|
109
|
+
- **Input:** SQL `WHERE` clause as a string.
|
|
110
|
+
- **Output:** An object `{ ast }` where:
|
|
111
|
+
- `ast`: The parsed Abstract Syntax Tree.
|
|
112
|
+
- **Example:**
|
|
113
|
+
```javascript
|
|
114
|
+
const { ast } = convertSQLToAst("OrderID = {CustomerOrders.OrderID} AND Status IN (1, 3)");
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### `convertAstToDevextreme(ast, state, shortCircuit = true)`
|
|
118
|
+
|
|
119
|
+
- **Input:**
|
|
120
|
+
- `ast`: The AST generated from `convertSQLToAst()`.
|
|
121
|
+
- `state`: An object containing values for placeholders.
|
|
122
|
+
- `shortCircuit`: (Optional, default `true`) Enables short-circuiting of `value = value` expressions for DevExpress compatibility.
|
|
123
|
+
- **Output:** DevExpress filter array.
|
|
124
|
+
- **Example:**
|
|
125
|
+
```javascript
|
|
126
|
+
const devexpressFilter = convertAstToDevextreme(ast, sampleState, false); // Disables short-circuiting
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Roadmap
|
|
130
|
+
|
|
131
|
+
- Support for additional SQL operators and functions.
|
|
132
|
+
- Improved error handling and validation.
|
|
133
|
+
|
|
134
|
+
## Contributing
|
|
135
|
+
|
|
136
|
+
Contributions are welcome! Feel free to open issues or submit pull requests.
|
|
137
|
+
|
|
138
|
+
## License
|
|
139
|
+
|
|
140
|
+
MIT
|
|
141
|
+
|
package/package.json
CHANGED
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'];
|
package/src/core/converter.js
CHANGED
|
@@ -1,31 +1,25 @@
|
|
|
1
|
-
|
|
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,
|
|
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 &&
|
|
115
|
-
if (right.length === 3 &&
|
|
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}
|
|
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,
|
|
387
|
-
return devExpressConverter.init(ast,
|
|
378
|
+
export function convertToDevExpressFormat({ ast, resultObject = null, enableShortCircuit = true }) {
|
|
379
|
+
return devExpressConverter.init(ast, resultObject,enableShortCircuit);
|
|
388
380
|
}
|
package/src/core/tokenizer.js
CHANGED
|
@@ -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.]
|
|
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))
|
|
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
|
-
//
|
|
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);
|