sqlparser-devexpress 2.0.7 → 2.1.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/package.json +2 -1
- package/src/@types/default.d.ts +35 -0
- package/src/core/converter.js +18 -7
- package/src/core/parser.js +210 -176
- package/src/index.js +2 -2
- package/tests/parser.test.js +17 -14
- package/src/utils.js +0 -14
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sqlparser-devexpress",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"main": "src/index.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"import": "./src/index.js",
|
|
11
11
|
"require": "./src/index.js"
|
|
12
12
|
},
|
|
13
|
+
"types": "src/@types/default.d.ts",
|
|
13
14
|
"keywords": [
|
|
14
15
|
"sql",
|
|
15
16
|
"parser",
|
|
@@ -0,0 +1,35 @@
|
|
|
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
|
+
variables: string[];
|
|
16
|
+
resultObject: StateDataObject;
|
|
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
|
+
|
|
31
|
+
export function convertAstToDevextreme(
|
|
32
|
+
ast: any, // Define a more specific type if possible
|
|
33
|
+
variables: string[],
|
|
34
|
+
state: StateDataObject
|
|
35
|
+
): any;
|
package/src/core/converter.js
CHANGED
|
@@ -20,18 +20,19 @@ function DevExpressConverter() {
|
|
|
20
20
|
* @param {Object} ast - The abstract syntax tree
|
|
21
21
|
* @param {Array} vars - Array of variable names
|
|
22
22
|
* @param {Object} ResultObject - Optional object for placeholder resolution
|
|
23
|
-
* @param {string} primaryEntity - Optional primary entity name
|
|
24
|
-
* @param {string} primaryKey - Optional primary key value
|
|
25
23
|
* @returns {Array|null} DevExpress format filter
|
|
26
24
|
*/
|
|
27
|
-
function convert(ast, vars, ResultObject = null
|
|
25
|
+
function convert(ast, vars, ResultObject = null) {
|
|
28
26
|
// Set up global context
|
|
29
27
|
variables = vars;
|
|
30
|
-
primaryKey = PrimaryKey;
|
|
31
|
-
primaryEntity = PrimaryEntity;
|
|
32
28
|
resultObject = ResultObject;
|
|
33
29
|
|
|
34
30
|
// Process the AST
|
|
31
|
+
let result = processAstNode(ast);
|
|
32
|
+
|
|
33
|
+
// Handle special cases for short circuit
|
|
34
|
+
if(result === true || result === false || result === null) return [];
|
|
35
|
+
|
|
35
36
|
return processAstNode(ast);
|
|
36
37
|
}
|
|
37
38
|
|
|
@@ -191,6 +192,16 @@ function DevExpressConverter() {
|
|
|
191
192
|
}
|
|
192
193
|
}
|
|
193
194
|
|
|
195
|
+
if(Array.isArray(resolvedValue) && resolvedValue.length){
|
|
196
|
+
|
|
197
|
+
return Array.prototype.concat
|
|
198
|
+
.apply(
|
|
199
|
+
[],
|
|
200
|
+
resolvedValue.map((i) => [[ast.field, '=', i], 'or']),
|
|
201
|
+
)
|
|
202
|
+
.slice(0, -1)
|
|
203
|
+
}
|
|
204
|
+
|
|
194
205
|
return [ast.field, "in", resolvedValue];
|
|
195
206
|
}
|
|
196
207
|
|
|
@@ -346,6 +357,6 @@ const devExpressConverter = DevExpressConverter();
|
|
|
346
357
|
* @param {string} primaryKey - Optional primary key value
|
|
347
358
|
* @returns {Array|null} DevExpress format filter
|
|
348
359
|
*/
|
|
349
|
-
export function convertToDevExpressFormat({ ast, variables, resultObject = null
|
|
350
|
-
return devExpressConverter.init(ast, variables, resultObject
|
|
360
|
+
export function convertToDevExpressFormat({ ast, variables, resultObject = null }) {
|
|
361
|
+
return devExpressConverter.init(ast, variables, resultObject);
|
|
351
362
|
}
|
package/src/core/parser.js
CHANGED
|
@@ -1,186 +1,220 @@
|
|
|
1
1
|
import { Tokenizer } from "./tokenizer.js";
|
|
2
2
|
|
|
3
3
|
// Define operator precedence for parsing expressions
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
const OPERATOR_PRECEDENCE = {
|
|
5
|
+
"OR": 1, "AND": 2, "=": 3, "!=": 3, ">": 3, "<": 3, ">=": 3, "<=": 3,
|
|
6
|
+
"IN": 3, "<>": 3, "LIKE": 3, "IS": 3, "BETWEEN": 3
|
|
7
7
|
};
|
|
8
8
|
|
|
9
|
+
const LITERALS = ["number", "string", "null"];
|
|
10
|
+
|
|
9
11
|
// Regular expression to check for unsupported SQL patterns (like SELECT-FROM or JOIN statements)
|
|
10
|
-
const
|
|
12
|
+
const UNSUPPORTED_PATTERN = /\bSELECT\b.*\bFROM\b|\bINNER\s+JOIN\b/i;
|
|
11
13
|
|
|
12
14
|
export function parse(input, variables = []) {
|
|
13
15
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
16
|
+
// Return null if the input contains unsupported SQL statements
|
|
17
|
+
if (UNSUPPORTED_PATTERN.test(input)) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const tokenizer = new Tokenizer(input);
|
|
22
|
+
let currentToken = tokenizer.nextToken();
|
|
23
|
+
|
|
24
|
+
// // Debugging: log the tokens
|
|
25
|
+
// const tokens = [];
|
|
26
|
+
// let tempToken = currentToken;
|
|
27
|
+
// while (tempToken) {
|
|
28
|
+
// tokens.push(tempToken);
|
|
29
|
+
// tempToken = tokenizer.peekNextToken();
|
|
30
|
+
// tokenizer.nextToken();
|
|
31
|
+
// }
|
|
32
|
+
|
|
33
|
+
// console.log("Tokens:", tokens);
|
|
34
|
+
|
|
35
|
+
// // Reset the tokenizer
|
|
36
|
+
// tokenizer.reset();
|
|
37
|
+
// currentToken = tokenizer.nextToken();
|
|
38
|
+
|
|
39
|
+
// Moves to the next token in the input
|
|
40
|
+
function next() {
|
|
41
|
+
currentToken = tokenizer.nextToken();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Validate the current token
|
|
45
|
+
const expectedToken = (token, expected, errorMessage) => {
|
|
46
|
+
if (!token || token.value.toUpperCase() !== expected) {
|
|
47
|
+
throw new Error(errorMessage);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// Parse IN list of values
|
|
52
|
+
function parseInList(token) {
|
|
53
|
+
expectedToken(token, "(", "Expected ( after IN");
|
|
54
|
+
|
|
55
|
+
const values = [];
|
|
56
|
+
while (currentToken && currentToken.value !== ")") {
|
|
57
|
+
if (currentToken.type === "comma") {
|
|
58
|
+
next();
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
values.push(parseValue());
|
|
62
|
+
}
|
|
63
|
+
expectedToken(currentToken, ")", "Expected ) after IN list");
|
|
64
|
+
|
|
65
|
+
next(); // Consume closing parenthesis
|
|
66
|
+
|
|
67
|
+
return { type: "value", value: values };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
// Parse BETWEEN operator which requires two values separated by AND
|
|
72
|
+
function parseBetweenComparison(field, operator) {
|
|
73
|
+
const firstValue = parseValue();
|
|
74
|
+
|
|
75
|
+
expectedToken(currentToken, "AND", "Expected AND after BETWEEN");
|
|
76
|
+
|
|
77
|
+
next(); // Consume AND
|
|
78
|
+
|
|
79
|
+
const secondValue = parseValue();
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
type: "comparison",
|
|
83
|
+
field,
|
|
84
|
+
operator,
|
|
85
|
+
value: [firstValue, secondValue] // Store both values in an array
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function parseFunction() {
|
|
90
|
+
const funcName = currentToken.value.toUpperCase();
|
|
91
|
+
next();
|
|
92
|
+
|
|
93
|
+
expectedToken(currentToken, "(", `Expected ( after ${funcName}`);
|
|
94
|
+
|
|
95
|
+
next();
|
|
96
|
+
|
|
97
|
+
const args = [];
|
|
98
|
+
while (currentToken && currentToken.value !== ")") {
|
|
99
|
+
args.push(parseExpression());
|
|
100
|
+
if (currentToken && currentToken.value === ",") next();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
expectedToken(currentToken, ")", `Expected ) after ${funcName}`);
|
|
104
|
+
|
|
105
|
+
next(); // Consume the closing parenthesis
|
|
106
|
+
|
|
107
|
+
// Check if the next token is an operator and process it
|
|
108
|
+
if (currentToken && currentToken.type === "operator") {
|
|
109
|
+
const operator = currentToken.value;
|
|
110
|
+
next(); // Move to the next token after the operator
|
|
111
|
+
const value = parseValue(); // Parse the value after the operator
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
type: "comparison",
|
|
115
|
+
left: { type: "function", name: funcName, args },
|
|
116
|
+
operator,
|
|
117
|
+
value
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return { type: "function", name: funcName, args };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Parses logical expressions using operator precedence
|
|
125
|
+
function parseExpression(minPrecedence = 0) {
|
|
126
|
+
let left = parseTerm();
|
|
127
|
+
|
|
128
|
+
// Continue parsing while the current token is an operator with sufficient precedence
|
|
129
|
+
while (currentToken && currentToken.type === "operator" && OPERATOR_PRECEDENCE[currentToken.value.toUpperCase()] >= minPrecedence) {
|
|
130
|
+
const operator = currentToken.value.toUpperCase();
|
|
131
|
+
next(); // Move to the next token
|
|
132
|
+
|
|
133
|
+
// Recursively parse the right-hand expression with adjusted precedence
|
|
134
|
+
const right = parseExpression(OPERATOR_PRECEDENCE[operator]);
|
|
135
|
+
left = { type: "logical", operator, left, right };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return left;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Parses individual terms, including literals, functions, and comparisons
|
|
142
|
+
function parseTerm() {
|
|
143
|
+
if (!currentToken) throw new Error("Unexpected end of input");
|
|
144
|
+
|
|
145
|
+
// Handle parenthesized expressions
|
|
146
|
+
if (currentToken.type === "paren" && currentToken.value === "(") {
|
|
147
|
+
next();
|
|
148
|
+
const expr = parseExpression();
|
|
149
|
+
|
|
150
|
+
expectedToken(currentToken, ")", "Missing closing parenthesis");
|
|
151
|
+
|
|
152
|
+
next();
|
|
153
|
+
|
|
154
|
+
return expr;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Handle function calls like ISNULL(field)
|
|
158
|
+
if (currentToken.type === "function") return parseFunction();
|
|
159
|
+
|
|
160
|
+
// Handle literal values (numbers, strings, null)
|
|
161
|
+
if (LITERALS.includes(currentToken.type)) {
|
|
162
|
+
const value = parseValue();
|
|
163
|
+
return { type: "value", value };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Otherwise, assume it's a field name
|
|
167
|
+
const fieldType = currentToken.type;
|
|
168
|
+
const field = parseValue();
|
|
169
|
+
|
|
170
|
+
// Check if it's part of a comparison expression
|
|
171
|
+
if (currentToken && currentToken.type === "operator") {
|
|
172
|
+
const operator = currentToken.value.toLowerCase();
|
|
173
|
+
next();
|
|
174
|
+
|
|
175
|
+
if (operator === "between") return parseBetweenComparison(field, operator);
|
|
176
|
+
|
|
177
|
+
// For other comparison operators, parse a single right-hand value
|
|
178
|
+
const valueType = currentToken.type;
|
|
179
|
+
const value = parseValue(operator);
|
|
180
|
+
|
|
181
|
+
// Check for invalid comparisons between two identifiers
|
|
182
|
+
if (fieldType === "identifier" && valueType === "identifier") {
|
|
183
|
+
throw new Error(`Invalid comparison: ${field} ${operator} ${value}`);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return { type: "comparison", field, operator, value };
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return { type: "field", value: field };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Parses values including numbers, strings, placeholders, and IN lists
|
|
193
|
+
function parseValue(operatorToken) {
|
|
194
|
+
if (!currentToken) throw new Error("Unexpected end of input");
|
|
195
|
+
|
|
196
|
+
const token = currentToken;
|
|
197
|
+
next(); // Move to the next token
|
|
198
|
+
|
|
199
|
+
if (token.type === "number") return Number(token.value);
|
|
200
|
+
if (token.type === "string") return token.value.slice(1, -1).replace(/''/g, "");
|
|
201
|
+
if (token.type === "identifier") return token.value;
|
|
202
|
+
if (token.type === "null") return null;
|
|
203
|
+
|
|
204
|
+
// Handle placeholders like `{VariableName}`
|
|
205
|
+
if (token.type === "placeholder") {
|
|
206
|
+
const val = token.value.slice(1, -1);
|
|
207
|
+
if (!variables.includes(val)) variables.push(val);
|
|
208
|
+
return { type: "placeholder", value: val };
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Handle IN operator which requires a list of values
|
|
212
|
+
if (operatorToken && operatorToken.toUpperCase() === "IN") return parseInList(token);
|
|
213
|
+
|
|
214
|
+
throw new Error(`Unexpected value: ${token.value}`);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
// Start parsing and return the AST with extracted variables
|
|
219
|
+
return { ast: parseExpression(), variables };
|
|
186
220
|
}
|
package/src/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import { parse } from "./core/parser.js";
|
|
|
3
3
|
import { sanitizeQuery } from "./core/sanitizer.js";
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
export function convertSQLToAst(filterString,
|
|
6
|
+
export function convertSQLToAst(filterString, enableConsoleLogs = false) {
|
|
7
7
|
let { sanitizedSQL, extractedVariables } = sanitizeQuery(filterString);
|
|
8
8
|
enableConsoleLogs && console.log("Sanitized SQL:", sanitizedSQL, "\n");
|
|
9
9
|
|
|
@@ -40,7 +40,7 @@ export function convertAstToDevextreme(ast, variables, state) {
|
|
|
40
40
|
// const devExpressFilter = parseFilterString("FromDate <= '{TransferOutwardDocument.DocDate}' ", sampleResultObject, "TransferOutwardDocument", "789");
|
|
41
41
|
// const devExpressFilter = parseFilterString("(RS2ID in ({SaleOrderStatusStmtGlobalRpt.StateID}) Or ({SaleOrderStatusStmtGlobalRpt.StateID} =0)) And (RS3ID in (0,{SaleOrderStatusStmtGlobalRpt.RegionID}) Or {SaleOrderStatusStmtGlobalRpt.RegionID} =0 )", sampleResultObject,);
|
|
42
42
|
|
|
43
|
-
// const devExpressFilter = convertSQLToAst("
|
|
43
|
+
// const devExpressFilter = convertSQLToAst("CompanyID = CompanyID2 = {AccountingRule.CompanyID}");
|
|
44
44
|
// const devExpressFilterresult = convertAstToDevextreme(devExpressFilter.ast, devExpressFilter.variables,{'ServiceOrderDocument.SourceID': 2});
|
|
45
45
|
// console.log("DevExpress Filter:", JSON.stringify(devExpressFilter, null, 2));
|
|
46
46
|
// console.log("DevExpress Result:", JSON.stringify(devExpressFilterresult, null, 2));
|
package/tests/parser.test.js
CHANGED
|
@@ -2,6 +2,7 @@ import { describe, expect, it } from "vitest";
|
|
|
2
2
|
import { convertToDevExpressFormat } from "../src/core/converter";
|
|
3
3
|
import { parse } from "../src/core/parser";
|
|
4
4
|
import { sanitizeQuery } from "../src/core/sanitizer";
|
|
5
|
+
import { convertAstToDevextreme, convertSQLToAst } from "../src";
|
|
5
6
|
|
|
6
7
|
describe("Parser SQL to dx Filter Builder", () => {
|
|
7
8
|
const testCases = [
|
|
@@ -30,13 +31,13 @@ describe("Parser SQL to dx Filter Builder", () => {
|
|
|
30
31
|
expected: [
|
|
31
32
|
["ContactID", "=", 42],
|
|
32
33
|
"and",
|
|
33
|
-
["AddressType", "
|
|
34
|
+
[["AddressType", "=", 2], 'or', ["AddressType", "=", 4]],
|
|
34
35
|
],
|
|
35
36
|
},
|
|
36
37
|
{
|
|
37
38
|
input: "ID IN ({WorkOrderLine.ApplicableUoms}) AND (CompanyID = {WorkOrderDocument.CompanyID} OR {WorkOrderDocument.CompanyID} = 0)",
|
|
38
39
|
expected: [
|
|
39
|
-
["ID", "
|
|
40
|
+
[["ID", "=", "UOM1"] , 'or', ["ID", "=", "UOM2"], 'or', ["ID", "=", "UOM3"]],
|
|
40
41
|
"and",
|
|
41
42
|
// [
|
|
42
43
|
["CompanyID", "=", 42],
|
|
@@ -62,7 +63,7 @@ describe("Parser SQL to dx Filter Builder", () => {
|
|
|
62
63
|
expected: [
|
|
63
64
|
["ID", "<>", 42],
|
|
64
65
|
'and',
|
|
65
|
-
["ItemGroupType", "
|
|
66
|
+
[["ItemGroupType", "=", "1"], 'or', ["ItemGroupType", "=", "2"]]
|
|
66
67
|
]
|
|
67
68
|
},
|
|
68
69
|
{
|
|
@@ -103,7 +104,7 @@ describe("Parser SQL to dx Filter Builder", () => {
|
|
|
103
104
|
},
|
|
104
105
|
{
|
|
105
106
|
input: "NULL",
|
|
106
|
-
expected:
|
|
107
|
+
expected: []
|
|
107
108
|
},
|
|
108
109
|
{
|
|
109
110
|
input: "((ISNULL({0}, 0) = 0 AND CompanyID = {1}) OR CompanyID IS NULL) OR BranchID = {0} | [LeadDocument.BranchID] | [LeadDocument.CompanyID]",
|
|
@@ -146,6 +147,10 @@ describe("Parser SQL to dx Filter Builder", () => {
|
|
|
146
147
|
"or",
|
|
147
148
|
["SourceID", "=", 0]
|
|
148
149
|
]
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
input: "CompanyID = CompanyID2 = {AccountingRule.CompanyID}",
|
|
153
|
+
expected: "Error: Invalid comparison: CompanyID = CompanyID2",
|
|
149
154
|
}
|
|
150
155
|
];
|
|
151
156
|
|
|
@@ -156,25 +161,23 @@ describe("Parser SQL to dx Filter Builder", () => {
|
|
|
156
161
|
expected = null
|
|
157
162
|
}
|
|
158
163
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
164
|
+
let astwithVariables;
|
|
165
|
+
try {
|
|
166
|
+
astwithVariables = convertSQLToAst(input);
|
|
167
|
+
} catch (error) {
|
|
168
|
+
expect(error.message).toEqual(expected.replace("Error: ",""));
|
|
162
169
|
return;
|
|
163
170
|
}
|
|
164
171
|
|
|
165
|
-
let { sanitizedSQL, variables } = sanitizeQuery(input);
|
|
166
|
-
|
|
167
|
-
const astwithVariables = parse(sanitizedSQL, variables);
|
|
168
|
-
|
|
169
172
|
if (astwithVariables == null) {
|
|
170
173
|
expect(null).toEqual(expected);
|
|
171
174
|
return;
|
|
172
175
|
}
|
|
173
176
|
|
|
174
|
-
variables = astwithVariables.variables;
|
|
177
|
+
const variables = astwithVariables.variables;
|
|
175
178
|
const ast = astwithVariables.ast;
|
|
176
179
|
|
|
177
|
-
const result =
|
|
180
|
+
const result = convertAstToDevextreme(ast, variables, sampleData);
|
|
178
181
|
|
|
179
182
|
if (result == null || result == true || result == false) {
|
|
180
183
|
expect([]).toEqual(expected);
|
|
@@ -183,7 +186,7 @@ describe("Parser SQL to dx Filter Builder", () => {
|
|
|
183
186
|
|
|
184
187
|
expect(result).toEqual(expected);
|
|
185
188
|
});
|
|
186
|
-
|
|
189
|
+
});
|
|
187
190
|
});
|
|
188
191
|
|
|
189
192
|
|
package/src/utils.js
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
export function replacePlaceholders(sql, staticValues = {}, dynamicValues = []) {
|
|
2
|
-
// Replace static placeholders (e.g., {0})
|
|
3
|
-
sql = sql.replace(/\{(\w+)\}/g, (_, key) => staticValues[key] ?? `{${key}}`);
|
|
4
|
-
|
|
5
|
-
// Replace dynamic placeholders (e.g., | [AccountTaxPaymentDocument.CompanyID])
|
|
6
|
-
sql = sql.replace(/\|\s*\[([^\]]+)\]/g, (_, keys) => {
|
|
7
|
-
const replacements = keys.split(",").map(key => dynamicValues.shift() ?? `{${key.trim()}}`);
|
|
8
|
-
return replacements.join(", ");
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
return sql;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
module.exports = { replacePlaceholders };
|