sqlparser-devexpress 2.3.0 → 2.3.2

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.0",
3
+ "version": "2.3.2",
4
4
  "main": "src/index.js",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -248,7 +248,7 @@ function DevExpressConverter() {
248
248
  if (!resultObject) return `{${placeholder}}`;
249
249
 
250
250
 
251
- return resultObject.hasOwnProperty(placeholder) ? resultObject[placeholder] : `{${placeholder}}`;
251
+ return resultObject.hasOwnProperty(placeholder) ? resultObject[placeholder] : `{${placeholder.value ?? placeholder}}`;
252
252
  }
253
253
 
254
254
  /**
@@ -184,6 +184,8 @@ export function parse(input, variables = []) {
184
184
  function parseValue(operatorToken) {
185
185
  if (!currentToken) throw new Error("Unexpected end of input");
186
186
 
187
+ if(currentToken.type === "function") return parseFunction();
188
+
187
189
  const token = currentToken;
188
190
  next(); // Move to the next token
189
191
 
@@ -199,11 +201,18 @@ export function parse(input, variables = []) {
199
201
  return { type: "placeholder", value: val };
200
202
  }
201
203
 
202
- operatorToken = operatorToken.toUpperCase();
204
+ operatorToken = operatorToken?.toUpperCase();
203
205
 
204
206
  // Handle IN operator which requires a list of values
205
207
  if (operatorToken && (operatorToken === "IN" || operatorToken === "NOT IN")) return parseInList(token);
206
208
 
209
+ // Handle ({Placeholder}) syntax for placeholders inside parentheses
210
+ const nextToken = tokenizer.peekNextToken();
211
+ if(token.type === "paren" && currentToken && currentToken.type === "placeholder" && nextToken && nextToken.type === "paren") {
212
+ const val = parseValue();
213
+ return { type: "placeholder", value: val };
214
+ }
215
+
207
216
  throw new Error(`Unexpected value: ${token.value}`);
208
217
  }
209
218
 
@@ -6,7 +6,7 @@ const tokenPatterns = {
6
6
  function: "\\b(ISNULL)\\b", // Matches function names like ISNULL (case-insensitive)
7
7
  null: "\\bNULL\\b|\\(\\s*NULL\\s*\\)", // Matches NULL as a keyword
8
8
  number: "\\(\\d+\\)|\\d+", // Matches numbers while stripping unnecessary parentheses
9
- placeholder: "'?\\{[^}]+\\}'?", // Matches placeholders like {variable} or '{variable}'
9
+ placeholder: "\\('?\\{[^}]+\\}'?\\)|'?\\{[^}]+\\}'?", // Matches placeholders like {variable} or '{variable}' or ({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
12
  identifier: "[\\w.]+|\"[^\"]+\"|\\[[^\\]]+\\]", // Matches regular identifiers, quoted identifiers ("identifier"), and bracketed identifiers [identifier]
@@ -47,7 +47,7 @@ class Tokenizer {
47
47
  let value = match.groups[type];
48
48
 
49
49
  // Remove surrounding single quotes from placeholders
50
- if (type === "placeholder") value = value.replace(/^['"]|['"]$/g, "").replace(" ", "");
50
+ if (type === "placeholder") value = value.replace(/^[\s'"\(\)]+|[\s'"\(\)]+|[\s]+/g, "");
51
51
 
52
52
  if (type === "operator") {
53
53
  const lowerValue = value.toLowerCase();
@@ -59,15 +59,15 @@ class Tokenizer {
59
59
  }
60
60
  }
61
61
 
62
- if (LITERALS.includes(type)){
62
+ if (LITERALS.includes(type)) {
63
63
  value = value.replace(/^[(]|[)]$/g, "");
64
64
  }
65
65
 
66
66
  if (type === "identifier") {
67
67
  value = value.replace(/^["\[]|["\]]$/g, "");
68
68
  }
69
-
70
-
69
+
70
+
71
71
  return { type, value };
72
72
  }
73
73
 
package/src/debug.js CHANGED
@@ -25,10 +25,10 @@
25
25
  // const astTree = parsedResult.ast;
26
26
  // console.log("AST Tree:", JSON.stringify(astTree, null, 2), "\n");
27
27
 
28
- // return convertToDevExpressFormat({ ast: astTree, variables: extractedVariables, resultObject: sampleData });
28
+ // return convertToDevExpressFormat({ ast: astTree, resultObject: sampleData });
29
29
  // }
30
30
 
31
- // const devexpress = parseFilterString("OrderID = {CustomerOrders.OrderID} AND Status IN (1, 3)", sampleData);
31
+ // const devexpress = parseFilterString("(ISNULL(TicketID, 0) = ISNULL({CustomerOrders.OrderID}, 0))", sampleData);
32
32
  // console.log("DevExpress Filter:", JSON.stringify(devexpress, null, 2));
33
33
  // // const devexpress = parseFilterString("(RS2ID in ({LeadStatementGlobalRpt.StateID}) Or ({LeadStatementGlobalRpt.StateID} =0)) And (RS3ID in (0,{LeadStatementGlobalRpt.RegionID}) Or {LeadStatementGlobalRpt.RegionID} =0 )", sampleData);
34
34
 
@@ -0,0 +1,66 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { convertToDevExpressFormat } from "../src/core/converter";
3
+ import { parse } from "../src/core/parser";
4
+ import { sanitizeQuery } from "../src/core/sanitizer";
5
+ import { convertAstToDevextreme, convertSQLToAst } from "../src";
6
+
7
+ describe("Parser SQL to dx Filter Builder", () => {
8
+ const testCases = [
9
+ {
10
+ input: "NULL",
11
+ expected: []
12
+ },
13
+ {
14
+ input: "SELECT DISTINCT O.OrderID AS ID, O.CustomerName, O.OrderType, O.CustomerName AS [Online Order], O.OrderDate AS OrderDate, D.DeliveryStatus, O.OrderDate AS [Online Order Date], O.CompanyID, CAST(CAST(O.OrderDate AS DATE) AS VARCHAR(10)) AS DocumentDate FROM Orders O INNER JOIN Payment P ON P.OrderID = O.OrderID INNER JOIN Shipment S ON S.PaymentID = P.PaymentID INNER JOIN Delivery D ON D.ShipmentID = S.ShipmentID ",
15
+ expect: null
16
+ },
17
+ {
18
+ input: "CompanyID = CompanyID2 = {AccountingRule.CompanyID}",
19
+ expected: "Error: Invalid comparison: CompanyID = CompanyID2",
20
+ },
21
+ {
22
+ input: "( CompanyID = {AccountingRule.CompanyID}",
23
+ expected: "Error: Missing closing parenthesis"
24
+ }
25
+
26
+ ];
27
+
28
+ testCases.forEach(({ input, expected }, index) => {
29
+ it(`Test Case ${index + 1}: ${input}`, () => {
30
+
31
+ if (expected == undefined) {
32
+ expected = null
33
+ }
34
+
35
+ let astwithVariables;
36
+ try {
37
+ astwithVariables = convertSQLToAst(input);
38
+ } catch (error) {
39
+ expect(error.message).toEqual(expected.replace("Error: ", ""));
40
+ return;
41
+ }
42
+
43
+ if (astwithVariables == null) {
44
+ expect(null).toEqual(expected);
45
+ return;
46
+ }
47
+
48
+ const variables = astwithVariables.variables;
49
+ const ast = astwithVariables.ast;
50
+
51
+ const result = convertAstToDevextreme(ast, sampleData);
52
+
53
+ if (result == null || result == true || result == false) {
54
+ expect([]).toEqual(expected);
55
+ return;
56
+ }
57
+
58
+ expect(result).toEqual(expected);
59
+ });
60
+ });
61
+ });
62
+
63
+
64
+ const sampleData = {
65
+ "AccountingRule.CompanyID": 42,
66
+ };
@@ -102,10 +102,6 @@ describe("Parser SQL to dx Filter Builder", () => {
102
102
  ["ItemGroupType", "=", ""]
103
103
  ]
104
104
  },
105
- {
106
- input: "NULL",
107
- expected: []
108
- },
109
105
  {
110
106
  input: "((ISNULL({0}, 0) = 0 AND CompanyID = {1}) OR CompanyID IS NULL) OR BranchID = {0} | [LeadDocument.BranchID] | [LeadDocument.CompanyID]",
111
107
  expected: [
@@ -122,10 +118,6 @@ describe("Parser SQL to dx Filter Builder", () => {
122
118
  ["BranchID", "=", 42]
123
119
  ]
124
120
  },
125
- {
126
- input: "SELECT DISTINCT OP.DocID ID,OP.DocName,OP.DocType,OP.DocName [Work Purchase Order],OP.DocDate DocDate,SP.WoStatus,OP.DocDate [Work Purchase Order Date], OP.CompanyID, cast(cast(OP.DocDate as date) as varchar(10)) DocumentDate FROM OpenDocuments OP inner join PurchaseHeader PH on PH.Id=op.DocID inner JOIN PurchasePosting PP ON PP.DocID = PH.ID inner JOIN SalePosting SP ON SP.PurchasePostingLineID = PP.ID",
127
- expect: null
128
- },
129
121
  {
130
122
  input: "FromDate Between '10-10-2021' AND '10-10-2022'",
131
123
  expected: [
@@ -152,10 +144,6 @@ describe("Parser SQL to dx Filter Builder", () => {
152
144
  ["SourceID", "=", null]
153
145
  ]
154
146
  },
155
- {
156
- input: "CompanyID = CompanyID2 = {AccountingRule.CompanyID}",
157
- expected: "Error: Invalid comparison: CompanyID = CompanyID2",
158
- },
159
147
  {
160
148
  input: "(CompanyID = {LeadDocument.CompanyID} OR ISNULL(CompanyID,0) = 0) AND (ISNULL(IsSubdealer,0) = {LeadDocument.AllowSubDealer})",
161
149
  expected: [
@@ -185,11 +173,21 @@ describe("Parser SQL to dx Filter Builder", () => {
185
173
  ]
186
174
  },
187
175
  {
188
- input: "AddressType IN ('2', ('4'))",
176
+ input: "AddressType IN ('2', ('4')) OR AddressType =({ServiceOrderDocument .SourceID})",
189
177
  expected: [
190
178
  ["AddressType", "=", '2'],
191
179
  "or",
192
- ["AddressType", "=", '4']
180
+ ["AddressType", "=", '4'],
181
+ "or",
182
+ ["AddressType", "=", 2]
183
+ ]
184
+ },
185
+ {
186
+ input: "(ISNULL(TicketID, 0) = ISNULL({SupportResolution.TicketID}, 0))",
187
+ expected: [
188
+ ["TicketID", "=", 123],
189
+ "or",
190
+ ["TicketID", "=", null]
193
191
  ]
194
192
  }
195
193
  ];
@@ -202,12 +200,8 @@ describe("Parser SQL to dx Filter Builder", () => {
202
200
  }
203
201
 
204
202
  let astwithVariables;
205
- try {
206
- astwithVariables = convertSQLToAst(input);
207
- } catch (error) {
208
- expect(error.message).toEqual(expected.replace("Error: ", ""));
209
- return;
210
- }
203
+ astwithVariables = convertSQLToAst(input);
204
+
211
205
 
212
206
  if (astwithVariables == null) {
213
207
  expect(null).toEqual(expected);
@@ -217,7 +211,7 @@ describe("Parser SQL to dx Filter Builder", () => {
217
211
  const variables = astwithVariables.variables;
218
212
  const ast = astwithVariables.ast;
219
213
 
220
- const result = convertAstToDevextreme(ast, variables, sampleData);
214
+ const result = convertAstToDevextreme(ast, sampleData);
221
215
 
222
216
  if (result == null || result == true || result == false) {
223
217
  expect([]).toEqual(expected);
@@ -254,5 +248,6 @@ const sampleData = {
254
248
  "LeadDocument.BranchID": 42,
255
249
  "LeadDocument.CompanyID": 7,
256
250
  "ServiceOrderDocument.SourceID": 2,
257
- "LeadDocument.AllowSubDealer": true
251
+ "LeadDocument.AllowSubDealer": true,
252
+ "SupportResolution.TicketID": 123
258
253
  };