@wxn0brp/db-string-query 0.0.12 → 0.0.13
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/CHANGELOG.md +2 -0
- package/bun.lock +9 -9
- package/dist/sql/where.js +107 -6
- package/dist/sql/where.js.map +1 -1
- package/package.json +3 -3
- package/src/sql/where.ts +109 -6
- package/test/sql/select.test.ts +77 -0
- package/typedocs-generated/assets/icons.js +1 -1
- package/typedocs-generated/assets/icons.svg +1 -1
- package/typedocs-generated/classes/js.default.html +2 -2
- package/typedocs-generated/classes/sql_index.default.html +2 -2
- package/typedocs-generated/classes/sql_utils_join.util.JoinToRelationsEngine.html +2 -2
- package/typedocs-generated/functions/sql_handle_collection.handleCreate.html +1 -1
- package/typedocs-generated/functions/sql_handle_collection.handleDrop.html +1 -1
- package/typedocs-generated/functions/sql_handle_collection.handleExists.html +1 -1
- package/typedocs-generated/functions/sql_handle_collection.handleGet.html +1 -1
- package/typedocs-generated/functions/sql_handle_delete.handleDelete.html +1 -1
- package/typedocs-generated/functions/sql_handle_insert.handleInsert.html +1 -1
- package/typedocs-generated/functions/sql_handle_select.handleSelect.html +1 -1
- package/typedocs-generated/functions/sql_handle_select.parseJoinClauses.html +1 -1
- package/typedocs-generated/functions/sql_handle_select.parseSelectClause.html +1 -1
- package/typedocs-generated/functions/sql_handle_update.handleUpdate.html +1 -1
- package/typedocs-generated/functions/sql_utils_index.parseNum.html +1 -1
- package/typedocs-generated/functions/sql_utils_index.parseReturn.html +1 -1
- package/typedocs-generated/functions/sql_utils_index.parseSet.html +1 -1
- package/typedocs-generated/functions/sql_utils_index.removeQuotes.html +1 -1
- package/typedocs-generated/functions/sql_where.parseWhere.html +1 -1
- package/typedocs-generated/index.html +18 -9
- package/typedocs-generated/interfaces/types.Opts.html +2 -2
- package/typedocs-generated/interfaces/types.ValtheraParser.html +2 -2
- package/typedocs-generated/interfaces/types.ValtheraQuery.html +2 -2
- package/typedocs-generated/types/sql_utils_join.util.JoinClause.html +1 -1
- package/typedocs-generated/variables/.ValtheraDbParsers.html +1 -1
- package/typedocs-generated/variables/.ValtheraDbRelations.html +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
+
### [0.0.13](https://github.com/wxn0brP/ValtheraDB-string-query/compare/v0.0.12...v0.0.13) (2026-02-01)
|
|
6
|
+
|
|
5
7
|
### [0.0.12](https://github.com/wxn0brP/ValtheraDB-string-query/compare/v0.0.11...v0.0.12) (2025-12-06)
|
|
6
8
|
|
|
7
9
|
|
package/bun.lock
CHANGED
|
@@ -7,13 +7,13 @@
|
|
|
7
7
|
"devDependencies": {
|
|
8
8
|
"@types/bun": "*",
|
|
9
9
|
"@types/node": "*",
|
|
10
|
-
"@wxn0brp/db-core": "^0.
|
|
10
|
+
"@wxn0brp/db-core": "^0.4.0",
|
|
11
11
|
"json5": "^2.2.3",
|
|
12
12
|
"tsc-alias": "*",
|
|
13
13
|
"typescript": "*",
|
|
14
14
|
},
|
|
15
15
|
"peerDependencies": {
|
|
16
|
-
"@wxn0brp/db-core": ">=0.
|
|
16
|
+
"@wxn0brp/db-core": ">=0.4.0",
|
|
17
17
|
"json5": "^2.2.3",
|
|
18
18
|
},
|
|
19
19
|
"optionalPeers": [
|
|
@@ -29,13 +29,13 @@
|
|
|
29
29
|
|
|
30
30
|
"@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
|
|
31
31
|
|
|
32
|
-
"@types/bun": ["@types/bun@1.3.
|
|
32
|
+
"@types/bun": ["@types/bun@1.3.8", "", { "dependencies": { "bun-types": "1.3.8" } }, "sha512-3LvWJ2q5GerAXYxO2mffLTqOzEu5qnhEAlh48Vnu8WQfnmSwbgagjGZV6BoHKJztENYEDn6QmVd949W4uESRJA=="],
|
|
33
33
|
|
|
34
|
-
"@types/node": ["@types/node@
|
|
34
|
+
"@types/node": ["@types/node@25.1.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-t7frlewr6+cbx+9Ohpl0NOTKXZNV9xHRmNOvql47BFJKcEG1CxtxlPEEe+gR9uhVWM4DwhnvTF110mIL4yP9RA=="],
|
|
35
35
|
|
|
36
|
-
"@wxn0brp/db-core": ["@wxn0brp/db-core@0.
|
|
36
|
+
"@wxn0brp/db-core": ["@wxn0brp/db-core@0.4.0", "", { "dependencies": { "@wxn0brp/event-emitter": "^0.0.4" } }, "sha512-BDteuLKygKT1+jq5zR8RpO+YrvwSBDT9rM8/zNT1QtumA1cHTGR9pmDoHMTePF3JyVpXzxtqviGVVGWM59Pkgw=="],
|
|
37
37
|
|
|
38
|
-
"@wxn0brp/event-emitter": ["@wxn0brp/event-emitter@0.0.
|
|
38
|
+
"@wxn0brp/event-emitter": ["@wxn0brp/event-emitter@0.0.4", "", {}, "sha512-C1Os4hEw13NDysPEFHZ6PDV2WRSGp6Q75rtfIAJHuZTl/gMPy+8ndD4dZDMgGglE+EnVYt2F4BU+/zswfOqcKQ=="],
|
|
39
39
|
|
|
40
40
|
"anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="],
|
|
41
41
|
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
|
|
46
46
|
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
|
|
47
47
|
|
|
48
|
-
"bun-types": ["bun-types@1.3.
|
|
48
|
+
"bun-types": ["bun-types@1.3.8", "", { "dependencies": { "@types/node": "*" } }, "sha512-fL99nxdOWvV4LqjmC+8Q9kW3M4QTtTR1eePs94v5ctGqU8OeceWrSUaRw3JYb7tU3FkMIAjkueehrHPPPGKi5Q=="],
|
|
49
49
|
|
|
50
50
|
"chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
|
|
51
51
|
|
|
@@ -55,13 +55,13 @@
|
|
|
55
55
|
|
|
56
56
|
"fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="],
|
|
57
57
|
|
|
58
|
-
"fastq": ["fastq@1.
|
|
58
|
+
"fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="],
|
|
59
59
|
|
|
60
60
|
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
|
|
61
61
|
|
|
62
62
|
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
|
63
63
|
|
|
64
|
-
"get-tsconfig": ["get-tsconfig@4.13.
|
|
64
|
+
"get-tsconfig": ["get-tsconfig@4.13.1", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-EoY1N2xCn44xU6750Sx7OjOIT59FkmstNc3X6y5xpz7D5cBtZRe/3pSlTkDJgqsOk3WwZPkWfonhhUJfttQo3w=="],
|
|
65
65
|
|
|
66
66
|
"glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
|
67
67
|
|
package/dist/sql/where.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// It’s cheap - it’s cheap
|
|
2
|
+
// It’s good - it’s cheap
|
|
1
3
|
const operators = {
|
|
2
4
|
"=": null,
|
|
3
5
|
"<": "$lt",
|
|
@@ -6,8 +8,19 @@ const operators = {
|
|
|
6
8
|
">=": "$gte",
|
|
7
9
|
"!=": "$not",
|
|
8
10
|
"IN": "$in",
|
|
9
|
-
"NOT IN": "$nin"
|
|
11
|
+
"NOT IN": "$nin",
|
|
12
|
+
"NOT ANY": "$nin",
|
|
13
|
+
"IS": null,
|
|
14
|
+
"IS NOT": "$not"
|
|
10
15
|
};
|
|
16
|
+
function escapeRegex(string) {
|
|
17
|
+
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
18
|
+
}
|
|
19
|
+
function likeToRegex(pattern, caseInsensitive = false) {
|
|
20
|
+
const escaped = escapeRegex(pattern);
|
|
21
|
+
const converted = escaped.replace(/%/g, '.*').replace(/_/g, '.');
|
|
22
|
+
return `${caseInsensitive ? '(?i)' : ''}^${converted}$`;
|
|
23
|
+
}
|
|
11
24
|
function mergeQueries(target, source) {
|
|
12
25
|
for (const key in source) {
|
|
13
26
|
if (key.startsWith('$')) {
|
|
@@ -40,7 +53,7 @@ export function parseWhere(where) {
|
|
|
40
53
|
// Tokenize the input string, handling parentheses and quoted values
|
|
41
54
|
const tokens = where
|
|
42
55
|
.replace(/\s+(?=(?:[^'"]*['"][^'"]*['"])*[^'"]*$)/g, " ")
|
|
43
|
-
.match(/(?:\(|\)|'[^']*'|"[^"]*"
|
|
56
|
+
.match(/(?:\(|\)|'[^']*'|"[^"]*"|>=|<=|!=|<>|=|<|>|[^\s()=<>!]+)/g) || [];
|
|
44
57
|
let frames = [];
|
|
45
58
|
let current = {};
|
|
46
59
|
let operatorStack = [];
|
|
@@ -112,20 +125,54 @@ export function parseWhere(where) {
|
|
|
112
125
|
key = key.split(".").pop();
|
|
113
126
|
}
|
|
114
127
|
let opToken = tokens[++i]?.trim();
|
|
128
|
+
// Handle multi-word operators
|
|
129
|
+
if (opToken) {
|
|
130
|
+
const upperOp = opToken.toUpperCase();
|
|
131
|
+
const nextToken = tokens[i + 1]?.trim().toUpperCase();
|
|
132
|
+
if (upperOp === "NOT" && (nextToken === "IN" || nextToken === "LIKE" || nextToken === "ILIKE" || nextToken === "ANY" || nextToken === "BETWEEN")) {
|
|
133
|
+
opToken = `NOT ${nextToken}`;
|
|
134
|
+
i++;
|
|
135
|
+
}
|
|
136
|
+
else if (upperOp === "IS") {
|
|
137
|
+
if (nextToken === "NOT") {
|
|
138
|
+
opToken = "IS NOT";
|
|
139
|
+
i++;
|
|
140
|
+
// Check for NULL
|
|
141
|
+
if (tokens[i + 1]?.trim().toUpperCase() === "NULL") {
|
|
142
|
+
// Correctly consume NULL as value
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
115
147
|
let value = tokens[++i]?.trim();
|
|
116
148
|
if (!key || !opToken || value === undefined) {
|
|
117
149
|
throw new Error(`Invalid condition near '${key} ${opToken} ${value}'`);
|
|
118
150
|
}
|
|
119
151
|
// Handle quoted values
|
|
120
|
-
if ((value.startsWith("'") && value.endsWith("'")) || (value.startsWith('"') && value.endsWith('"'))) {
|
|
152
|
+
if (typeof value === 'string' && ((value.startsWith("'") && value.endsWith("'")) || (value.startsWith('"') && value.endsWith('"')))) {
|
|
121
153
|
value = value.slice(1, -1);
|
|
122
154
|
}
|
|
155
|
+
// Handle NULL value
|
|
156
|
+
if (typeof value === 'string' && value.toUpperCase() === "NULL") {
|
|
157
|
+
value = null;
|
|
158
|
+
}
|
|
123
159
|
// Parse numeric values
|
|
124
|
-
if (!isNaN(Number(value))) {
|
|
160
|
+
if (value !== null && !isNaN(Number(value))) {
|
|
125
161
|
value = Number(value);
|
|
126
162
|
}
|
|
127
|
-
// Handle IN
|
|
128
|
-
if (opToken.toUpperCase() === "IN" || opToken.toUpperCase() === "NOT IN") {
|
|
163
|
+
// Handle IN, NOT IN, NOT ANY operations
|
|
164
|
+
if (opToken.toUpperCase() === "IN" || opToken.toUpperCase() === "NOT IN" || opToken.toUpperCase() === "NOT ANY") {
|
|
165
|
+
// If the value is just "(", we need to consume tokens until we find the closing ")"
|
|
166
|
+
if (value === "(") {
|
|
167
|
+
let collected = "(";
|
|
168
|
+
while (i < tokens.length - 1) {
|
|
169
|
+
const next = tokens[++i];
|
|
170
|
+
collected += next;
|
|
171
|
+
if (next === ")")
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
value = collected;
|
|
175
|
+
}
|
|
129
176
|
if (!value.startsWith("(") || !value.endsWith(")")) {
|
|
130
177
|
throw new Error(`Invalid syntax for '${opToken}' near '${value}'`);
|
|
131
178
|
}
|
|
@@ -137,6 +184,60 @@ export function parseWhere(where) {
|
|
|
137
184
|
return isNaN(Number(v)) ? v : Number(v);
|
|
138
185
|
});
|
|
139
186
|
}
|
|
187
|
+
// Handle LIKE/NOT LIKE/ILIKE/NOT ILIKE specially to convert to regex
|
|
188
|
+
if (opToken.toUpperCase() === "LIKE" || opToken.toUpperCase() === "NOT LIKE" || opToken.toUpperCase() === "ILIKE" || opToken.toUpperCase() === "NOT ILIKE") {
|
|
189
|
+
if (typeof value !== 'string') {
|
|
190
|
+
throw new Error(`LIKE value must be a string, got ${value}`);
|
|
191
|
+
}
|
|
192
|
+
const isCaseInsensitive = opToken.toUpperCase().includes("ILIKE");
|
|
193
|
+
const regex = likeToRegex(value, isCaseInsensitive);
|
|
194
|
+
const isNot = opToken.toUpperCase().startsWith("NOT");
|
|
195
|
+
if (isNot) {
|
|
196
|
+
// NOT LIKE -> {$not: {$regex: {key: regex}}}
|
|
197
|
+
if (!current["$not"])
|
|
198
|
+
current["$not"] = {};
|
|
199
|
+
if (!current["$not"]["$regex"])
|
|
200
|
+
current["$not"]["$regex"] = {};
|
|
201
|
+
current["$not"]["$regex"][key] = regex;
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
// LIKE -> {$regex: {key: regex}}
|
|
205
|
+
if (!current["$regex"])
|
|
206
|
+
current["$regex"] = {};
|
|
207
|
+
current["$regex"][key] = regex;
|
|
208
|
+
}
|
|
209
|
+
continue; // processing done for this token
|
|
210
|
+
}
|
|
211
|
+
// Handle BETWEEN/NOT BETWEEN
|
|
212
|
+
if (opToken.toUpperCase() === "BETWEEN" || opToken.toUpperCase() === "NOT BETWEEN") {
|
|
213
|
+
const andToken = tokens[++i]?.trim();
|
|
214
|
+
if (!andToken || andToken.toUpperCase() !== "AND") {
|
|
215
|
+
throw new Error("BETWEEN expects AND, got " + andToken);
|
|
216
|
+
}
|
|
217
|
+
let val2 = tokens[++i]?.trim();
|
|
218
|
+
// Handle quoted values for val2
|
|
219
|
+
if (typeof val2 === 'string' && ((val2.startsWith("'") && val2.endsWith("'")) || (val2.startsWith('"') && val2.endsWith('"')))) {
|
|
220
|
+
val2 = val2.slice(1, -1);
|
|
221
|
+
}
|
|
222
|
+
// Handle numerics for val2
|
|
223
|
+
if (val2 !== null && !isNaN(Number(val2))) {
|
|
224
|
+
val2 = Number(val2);
|
|
225
|
+
}
|
|
226
|
+
if (opToken.toUpperCase() === "NOT BETWEEN") {
|
|
227
|
+
if (!current["$not"])
|
|
228
|
+
current["$not"] = {};
|
|
229
|
+
if (!current["$not"]["$between"])
|
|
230
|
+
current["$not"]["$between"] = {};
|
|
231
|
+
current["$not"]["$between"][key] = [value, val2];
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
// BETWEEN -> {$between: {key: [val1, val2]}}
|
|
235
|
+
if (!current["$between"])
|
|
236
|
+
current["$between"] = {};
|
|
237
|
+
current["$between"][key] = [value, val2];
|
|
238
|
+
}
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
140
241
|
// Map the operator to the query operators
|
|
141
242
|
const mappedOp = operators[opToken.toUpperCase()];
|
|
142
243
|
if (mappedOp !== undefined) {
|
package/dist/sql/where.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"where.js","sourceRoot":"","sources":["../../src/sql/where.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"where.js","sourceRoot":"","sources":["../../src/sql/where.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wxn0brp/db-string-query",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.13",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"repository": {
|
|
@@ -14,13 +14,13 @@
|
|
|
14
14
|
"devDependencies": {
|
|
15
15
|
"@types/bun": "*",
|
|
16
16
|
"@types/node": "*",
|
|
17
|
-
"@wxn0brp/db-core": "^0.
|
|
17
|
+
"@wxn0brp/db-core": "^0.4.0",
|
|
18
18
|
"json5": "^2.2.3",
|
|
19
19
|
"tsc-alias": "*",
|
|
20
20
|
"typescript": "*"
|
|
21
21
|
},
|
|
22
22
|
"peerDependencies": {
|
|
23
|
-
"@wxn0brp/db-core": ">=0.
|
|
23
|
+
"@wxn0brp/db-core": ">=0.4.0",
|
|
24
24
|
"json5": "^2.2.3"
|
|
25
25
|
},
|
|
26
26
|
"peerDependenciesMeta": {
|
package/src/sql/where.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
// It’s cheap - it’s cheap
|
|
2
|
+
// It’s good - it’s cheap
|
|
3
|
+
|
|
1
4
|
type QueryObject = Record<string, any>;
|
|
2
5
|
|
|
3
6
|
const operators: Record<string, string> = {
|
|
@@ -8,9 +11,22 @@ const operators: Record<string, string> = {
|
|
|
8
11
|
">=": "$gte",
|
|
9
12
|
"!=": "$not",
|
|
10
13
|
"IN": "$in",
|
|
11
|
-
"NOT IN": "$nin"
|
|
14
|
+
"NOT IN": "$nin",
|
|
15
|
+
"NOT ANY": "$nin",
|
|
16
|
+
"IS": null,
|
|
17
|
+
"IS NOT": "$not"
|
|
12
18
|
};
|
|
13
19
|
|
|
20
|
+
function escapeRegex(string: string) {
|
|
21
|
+
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function likeToRegex(pattern: string, caseInsensitive: boolean = false) {
|
|
25
|
+
const escaped = escapeRegex(pattern);
|
|
26
|
+
const converted = escaped.replace(/%/g, '.*').replace(/_/g, '.');
|
|
27
|
+
return `${caseInsensitive ? '(?i)' : ''}^${converted}$`;
|
|
28
|
+
}
|
|
29
|
+
|
|
14
30
|
function mergeQueries(target: QueryObject, source: QueryObject) {
|
|
15
31
|
for (const key in source) {
|
|
16
32
|
if (key.startsWith('$')) {
|
|
@@ -40,7 +56,7 @@ export function parseWhere(where: string): QueryObject {
|
|
|
40
56
|
// Tokenize the input string, handling parentheses and quoted values
|
|
41
57
|
const tokens = where
|
|
42
58
|
.replace(/\s+(?=(?:[^'"]*['"][^'"]*['"])*[^'"]*$)/g, " ")
|
|
43
|
-
.match(/(?:\(|\)|'[^']*'|"[^"]*"
|
|
59
|
+
.match(/(?:\(|\)|'[^']*'|"[^"]*"|>=|<=|!=|<>|=|<|>|[^\s()=<>!]+)/g) || [];
|
|
44
60
|
|
|
45
61
|
let frames: Array<{ current: QueryObject, operatorStack: string[], stack: any[] }> = [];
|
|
46
62
|
let current: QueryObject = {};
|
|
@@ -113,6 +129,26 @@ export function parseWhere(where: string): QueryObject {
|
|
|
113
129
|
key = key.split(".").pop()!;
|
|
114
130
|
}
|
|
115
131
|
let opToken = tokens[++i]?.trim();
|
|
132
|
+
// Handle multi-word operators
|
|
133
|
+
if (opToken) {
|
|
134
|
+
const upperOp = opToken.toUpperCase();
|
|
135
|
+
const nextToken = tokens[i + 1]?.trim().toUpperCase();
|
|
136
|
+
|
|
137
|
+
if (upperOp === "NOT" && (nextToken === "IN" || nextToken === "LIKE" || nextToken === "ILIKE" || nextToken === "ANY" || nextToken === "BETWEEN")) {
|
|
138
|
+
opToken = `NOT ${nextToken}`;
|
|
139
|
+
i++;
|
|
140
|
+
} else if (upperOp === "IS") {
|
|
141
|
+
if (nextToken === "NOT") {
|
|
142
|
+
opToken = "IS NOT";
|
|
143
|
+
i++;
|
|
144
|
+
// Check for NULL
|
|
145
|
+
if (tokens[i + 1]?.trim().toUpperCase() === "NULL") {
|
|
146
|
+
// Correctly consume NULL as value
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
116
152
|
let value: any = tokens[++i]?.trim();
|
|
117
153
|
|
|
118
154
|
if (!key || !opToken || value === undefined) {
|
|
@@ -120,17 +156,33 @@ export function parseWhere(where: string): QueryObject {
|
|
|
120
156
|
}
|
|
121
157
|
|
|
122
158
|
// Handle quoted values
|
|
123
|
-
if ((value.startsWith("'") && value.endsWith("'")) || (value.startsWith('"') && value.endsWith('"'))) {
|
|
159
|
+
if (typeof value === 'string' && ((value.startsWith("'") && value.endsWith("'")) || (value.startsWith('"') && value.endsWith('"')))) {
|
|
124
160
|
value = value.slice(1, -1);
|
|
125
161
|
}
|
|
126
162
|
|
|
163
|
+
// Handle NULL value
|
|
164
|
+
if (typeof value === 'string' && value.toUpperCase() === "NULL") {
|
|
165
|
+
value = null;
|
|
166
|
+
}
|
|
167
|
+
|
|
127
168
|
// Parse numeric values
|
|
128
|
-
if (!isNaN(Number(value))) {
|
|
169
|
+
if (value !== null && !isNaN(Number(value))) {
|
|
129
170
|
value = Number(value);
|
|
130
171
|
}
|
|
131
172
|
|
|
132
|
-
// Handle IN
|
|
133
|
-
if (opToken.toUpperCase() === "IN" || opToken.toUpperCase() === "NOT IN") {
|
|
173
|
+
// Handle IN, NOT IN, NOT ANY operations
|
|
174
|
+
if (opToken.toUpperCase() === "IN" || opToken.toUpperCase() === "NOT IN" || opToken.toUpperCase() === "NOT ANY") {
|
|
175
|
+
// If the value is just "(", we need to consume tokens until we find the closing ")"
|
|
176
|
+
if (value === "(") {
|
|
177
|
+
let collected = "(";
|
|
178
|
+
while (i < tokens.length - 1) {
|
|
179
|
+
const next = tokens[++i];
|
|
180
|
+
collected += next;
|
|
181
|
+
if (next === ")") break;
|
|
182
|
+
}
|
|
183
|
+
value = collected;
|
|
184
|
+
}
|
|
185
|
+
|
|
134
186
|
if (!value.startsWith("(") || !value.endsWith(")")) {
|
|
135
187
|
throw new Error(`Invalid syntax for '${opToken}' near '${value}'`);
|
|
136
188
|
}
|
|
@@ -143,6 +195,57 @@ export function parseWhere(where: string): QueryObject {
|
|
|
143
195
|
});
|
|
144
196
|
}
|
|
145
197
|
|
|
198
|
+
// Handle LIKE/NOT LIKE/ILIKE/NOT ILIKE specially to convert to regex
|
|
199
|
+
if (opToken.toUpperCase() === "LIKE" || opToken.toUpperCase() === "NOT LIKE" || opToken.toUpperCase() === "ILIKE" || opToken.toUpperCase() === "NOT ILIKE") {
|
|
200
|
+
if (typeof value !== 'string') {
|
|
201
|
+
throw new Error(`LIKE value must be a string, got ${value}`);
|
|
202
|
+
}
|
|
203
|
+
const isCaseInsensitive = opToken.toUpperCase().includes("ILIKE");
|
|
204
|
+
const regex = likeToRegex(value, isCaseInsensitive);
|
|
205
|
+
const isNot = opToken.toUpperCase().startsWith("NOT");
|
|
206
|
+
|
|
207
|
+
if (isNot) {
|
|
208
|
+
// NOT LIKE -> {$not: {$regex: {key: regex}}}
|
|
209
|
+
if (!current["$not"]) current["$not"] = {};
|
|
210
|
+
if (!current["$not"]["$regex"]) current["$not"]["$regex"] = {};
|
|
211
|
+
current["$not"]["$regex"][key] = regex;
|
|
212
|
+
} else {
|
|
213
|
+
// LIKE -> {$regex: {key: regex}}
|
|
214
|
+
if (!current["$regex"]) current["$regex"] = {};
|
|
215
|
+
current["$regex"][key] = regex;
|
|
216
|
+
}
|
|
217
|
+
continue; // processing done for this token
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Handle BETWEEN/NOT BETWEEN
|
|
221
|
+
if (opToken.toUpperCase() === "BETWEEN" || opToken.toUpperCase() === "NOT BETWEEN") {
|
|
222
|
+
const andToken = tokens[++i]?.trim();
|
|
223
|
+
if (!andToken || andToken.toUpperCase() !== "AND") {
|
|
224
|
+
throw new Error("BETWEEN expects AND, got " + andToken);
|
|
225
|
+
}
|
|
226
|
+
let val2: any = tokens[++i]?.trim();
|
|
227
|
+
|
|
228
|
+
// Handle quoted values for val2
|
|
229
|
+
if (typeof val2 === 'string' && ((val2.startsWith("'") && val2.endsWith("'")) || (val2.startsWith('"') && val2.endsWith('"')))) {
|
|
230
|
+
val2 = val2.slice(1, -1);
|
|
231
|
+
}
|
|
232
|
+
// Handle numerics for val2
|
|
233
|
+
if (val2 !== null && !isNaN(Number(val2))) {
|
|
234
|
+
val2 = Number(val2);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (opToken.toUpperCase() === "NOT BETWEEN") {
|
|
238
|
+
if (!current["$not"]) current["$not"] = {};
|
|
239
|
+
if (!current["$not"]["$between"]) current["$not"]["$between"] = {};
|
|
240
|
+
current["$not"]["$between"][key] = [value, val2];
|
|
241
|
+
} else {
|
|
242
|
+
// BETWEEN -> {$between: {key: [val1, val2]}}
|
|
243
|
+
if (!current["$between"]) current["$between"] = {};
|
|
244
|
+
current["$between"][key] = [value, val2];
|
|
245
|
+
}
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
|
|
146
249
|
// Map the operator to the query operators
|
|
147
250
|
const mappedOp = operators[opToken.toUpperCase()];
|
|
148
251
|
|
package/test/sql/select.test.ts
CHANGED
|
@@ -15,6 +15,17 @@ describe("SQL Parser - SELECT", () => {
|
|
|
15
15
|
expect(parsedQuery.args[1]).toEqual({ id: 1 }); // where clause
|
|
16
16
|
});
|
|
17
17
|
|
|
18
|
+
test("1b. should parse a simple SELECT query without spaces", () => {
|
|
19
|
+
const query = "SELECT * FROM users WHERE id=1";
|
|
20
|
+
const parsedQuery = sqlParser.parse(query);
|
|
21
|
+
|
|
22
|
+
expect(parsedQuery).toBeDefined();
|
|
23
|
+
expect(parsedQuery.method).toBe("find");
|
|
24
|
+
expect(parsedQuery.args).toHaveLength(4);
|
|
25
|
+
expect(parsedQuery.args[0]).toBe("users"); // collection name
|
|
26
|
+
expect(parsedQuery.args[1]).toEqual({ id: 1 }); // where clause
|
|
27
|
+
});
|
|
28
|
+
|
|
18
29
|
test("2. should parse a SELECT query with specific columns", () => {
|
|
19
30
|
const query = "SELECT name, email FROM users WHERE active = 1";
|
|
20
31
|
const parsedQuery = sqlParser.parse(query);
|
|
@@ -24,9 +35,21 @@ describe("SQL Parser - SELECT", () => {
|
|
|
24
35
|
expect(parsedQuery.args).toHaveLength(4);
|
|
25
36
|
expect(parsedQuery.args[0]).toBe("users"); // collection name
|
|
26
37
|
expect(parsedQuery.args[1]).toEqual({ active: 1 }); // where clause
|
|
38
|
+
expect(parsedQuery.args[1]).toEqual({ active: 1 }); // where clause
|
|
27
39
|
expect(parsedQuery.args[3]).toEqual({ select: ["name", "email"] }); // select options
|
|
28
40
|
});
|
|
29
41
|
|
|
42
|
+
test("2b. should parse a SELECT query with NOT IN", () => {
|
|
43
|
+
const query = "SELECT * FROM users WHERE status NOT IN (1, 2)";
|
|
44
|
+
const parsedQuery = sqlParser.parse(query);
|
|
45
|
+
|
|
46
|
+
expect(parsedQuery).toBeDefined();
|
|
47
|
+
expect(parsedQuery.method).toBe("find");
|
|
48
|
+
expect(parsedQuery.args).toHaveLength(4);
|
|
49
|
+
expect(parsedQuery.args[0]).toBe("users");
|
|
50
|
+
expect(parsedQuery.args[1]).toEqual({ $nin: { status: [1, 2] } });
|
|
51
|
+
});
|
|
52
|
+
|
|
30
53
|
test("3. should parse a SELECT query without WHERE clause", () => {
|
|
31
54
|
const query = "SELECT * FROM users";
|
|
32
55
|
const parsedQuery = sqlParser.parse(query);
|
|
@@ -89,4 +112,58 @@ describe("SQL Parser - SELECT", () => {
|
|
|
89
112
|
sqlParser.parse(query);
|
|
90
113
|
}).toThrow();
|
|
91
114
|
});
|
|
115
|
+
|
|
116
|
+
test("8. should parse LIKE operator", () => {
|
|
117
|
+
const query = "SELECT * FROM users WHERE name LIKE '%John%'";
|
|
118
|
+
const parsedQuery = sqlParser.parse(query);
|
|
119
|
+
expect(parsedQuery.args[1]).toEqual({ $regex: { name: "^.*John.*$" } });
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test("8b. should parse NOT LIKE operator", () => {
|
|
123
|
+
const query = "SELECT * FROM users WHERE name NOT LIKE '%John%'";
|
|
124
|
+
const parsedQuery = sqlParser.parse(query);
|
|
125
|
+
expect(parsedQuery.args[1]).toEqual({ $not: { $regex: { name: "^.*John.*$" } } });
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test("9. should parse IS NULL operator", () => {
|
|
129
|
+
const query = "SELECT * FROM users WHERE deletedAt IS NULL";
|
|
130
|
+
const parsedQuery = sqlParser.parse(query);
|
|
131
|
+
expect(parsedQuery.args[1]).toEqual({ deletedAt: null });
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("9b. should parse IS NOT NULL operator", () => {
|
|
135
|
+
const query = "SELECT * FROM users WHERE deletedAt IS NOT NULL";
|
|
136
|
+
const parsedQuery = sqlParser.parse(query);
|
|
137
|
+
expect(parsedQuery.args[1]).toEqual({ $not: { deletedAt: null } });
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test("10. should parse NOT ANY operator (alias for NOT IN)", () => {
|
|
141
|
+
const query = "SELECT * FROM users WHERE status NOT ANY (1, 2)";
|
|
142
|
+
const parsedQuery = sqlParser.parse(query);
|
|
143
|
+
expect(parsedQuery.args[1]).toEqual({ $nin: { status: [1, 2] } });
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test("11. should parse ILIKE operator (case insensitive LIKE)", () => {
|
|
147
|
+
const query = "SELECT * FROM users WHERE name ILIKE '%john%'";
|
|
148
|
+
const parsedQuery = sqlParser.parse(query);
|
|
149
|
+
expect(parsedQuery.args[1]).toEqual({ $regex: { name: "(?i)^.*john.*$" } });
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test("11b. should parse NOT ILIKE operator", () => {
|
|
153
|
+
const query = "SELECT * FROM users WHERE name NOT ILIKE '%john%'";
|
|
154
|
+
const parsedQuery = sqlParser.parse(query);
|
|
155
|
+
expect(parsedQuery.args[1]).toEqual({ $not: { $regex: { name: "(?i)^.*john.*$" } } });
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test("12. should parse BETWEEN operator", () => {
|
|
159
|
+
const query = "SELECT * FROM users WHERE age BETWEEN 18 AND 30";
|
|
160
|
+
const parsedQuery = sqlParser.parse(query);
|
|
161
|
+
expect(parsedQuery.args[1]).toEqual({ $between: { age: [18, 30] } });
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test("12b. should parse NOT BETWEEN operator", () => {
|
|
165
|
+
const query = "SELECT * FROM users WHERE age NOT BETWEEN 18 AND 30";
|
|
166
|
+
const parsedQuery = sqlParser.parse(query);
|
|
167
|
+
expect(parsedQuery.args[1]).toEqual({ $not: { $between: { age: [18, 30] } } });
|
|
168
|
+
});
|
|
92
169
|
});
|