search-input-query-parser 0.1.1 → 0.1.3
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 +137 -0
- package/dist/cjs/search-query-to-ilike-sql.js +208 -0
- package/dist/cjs/search-query-to-paradedb-sql.js +227 -0
- package/dist/cjs/search-query-to-sql.js +10 -1
- package/dist/cjs/search-query-to-tsvector-sql.js +200 -0
- package/dist/esm/search-query-to-ilike-sql.js +203 -0
- package/dist/esm/search-query-to-paradedb-sql.js +222 -0
- package/dist/esm/search-query-to-sql.js +3 -0
- package/dist/esm/search-query-to-tsvector-sql.js +195 -0
- package/dist/types/search-query-to-ilike-sql.d.ts +16 -0
- package/dist/types/search-query-to-paradedb-sql.d.ts +16 -0
- package/dist/types/search-query-to-sql.d.ts +3 -0
- package/dist/types/search-query-to-tsvector-sql.d.ts +16 -0
- package/package.json +1 -1
- package/src/search-query-to-ilike-sql.ts +332 -0
- package/src/search-query-to-paradedb-sql.ts +346 -0
- package/src/search-query-to-sql.test.ts +53 -55
- package/src/search-query-to-sql.ts +10 -0
- package/src/search-query-to-tsvector-sql.ts +320 -0
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { describe, expect, test } from "@jest/globals";
|
|
2
|
-
import {
|
|
2
|
+
import { searchQueryToIlikeSql, searchStringToIlikeSql } from "./search-query-to-ilike-sql";
|
|
3
|
+
import { searchQueryToTsVectorSql } from "./search-query-to-tsvector-sql";
|
|
4
|
+
import { searchQueryToParadeDbSql } from "./search-query-to-paradedb-sql";
|
|
3
5
|
import { parseSearchInputQuery } from "./parser";
|
|
4
6
|
import type { SearchQuery, FieldSchema } from "./parser";
|
|
5
7
|
|
|
@@ -19,14 +21,14 @@ describe("Search Query to SQL Converter", () => {
|
|
|
19
21
|
{ name: "user_id", type: "number" },
|
|
20
22
|
];
|
|
21
23
|
|
|
22
|
-
const
|
|
24
|
+
const testIlikeConversion = (
|
|
23
25
|
query: string,
|
|
24
26
|
expectedSql: string,
|
|
25
27
|
expectedValues: any[]
|
|
26
28
|
) => {
|
|
27
29
|
const parsedQuery = parseSearchInputQuery(query, schemas);
|
|
28
30
|
expect(parsedQuery.type).toBe("SEARCH_QUERY");
|
|
29
|
-
const result =
|
|
31
|
+
const result = searchQueryToIlikeSql(
|
|
30
32
|
parsedQuery as SearchQuery,
|
|
31
33
|
searchableColumns,
|
|
32
34
|
schemas
|
|
@@ -37,7 +39,7 @@ describe("Search Query to SQL Converter", () => {
|
|
|
37
39
|
|
|
38
40
|
describe("Basic Term Conversion", () => {
|
|
39
41
|
test("converts single search term", () => {
|
|
40
|
-
|
|
42
|
+
testIlikeConversion(
|
|
41
43
|
"boots",
|
|
42
44
|
"(lower(title) LIKE lower($1) OR lower(description) LIKE lower($1) OR lower(content) LIKE lower($1))",
|
|
43
45
|
["%boots%"]
|
|
@@ -45,7 +47,7 @@ describe("Search Query to SQL Converter", () => {
|
|
|
45
47
|
});
|
|
46
48
|
|
|
47
49
|
test("converts quoted search term", () => {
|
|
48
|
-
|
|
50
|
+
testIlikeConversion(
|
|
49
51
|
'"red shoes"',
|
|
50
52
|
"(lower(title) LIKE lower($1) OR lower(description) LIKE lower($1) OR lower(content) LIKE lower($1))",
|
|
51
53
|
["%red shoes%"]
|
|
@@ -53,13 +55,13 @@ describe("Search Query to SQL Converter", () => {
|
|
|
53
55
|
});
|
|
54
56
|
|
|
55
57
|
test("escapes special characters in search terms", () => {
|
|
56
|
-
//
|
|
58
|
+
// testIlikeConversion(
|
|
57
59
|
// "100%",
|
|
58
60
|
// "(lower(title) LIKE lower($1) OR lower(description) LIKE lower($1) OR lower(content) LIKE lower($1))",
|
|
59
61
|
// ["%100\\%%"]
|
|
60
62
|
// );
|
|
61
63
|
|
|
62
|
-
|
|
64
|
+
testIlikeConversion(
|
|
63
65
|
"under_score",
|
|
64
66
|
"(lower(title) LIKE lower($1) OR lower(description) LIKE lower($1) OR lower(content) LIKE lower($1))",
|
|
65
67
|
["%under\\_score%"]
|
|
@@ -69,27 +71,27 @@ describe("Search Query to SQL Converter", () => {
|
|
|
69
71
|
|
|
70
72
|
describe("Field Value Conversion", () => {
|
|
71
73
|
test("converts simple field:value pairs", () => {
|
|
72
|
-
//
|
|
74
|
+
// testIlikeConversion("color:red", "lower(color) LIKE lower($1)", ["%red%"]);
|
|
73
75
|
});
|
|
74
76
|
|
|
75
77
|
test("converts field values with spaces", () => {
|
|
76
|
-
|
|
78
|
+
testIlikeConversion('status:"in progress"', "lower(status) LIKE lower($1)", [
|
|
77
79
|
"%in progress%",
|
|
78
80
|
]);
|
|
79
81
|
});
|
|
80
82
|
|
|
81
83
|
test("handles special date fields", () => {
|
|
82
|
-
|
|
84
|
+
testIlikeConversion("date:2024-01-01", "date = $1", [
|
|
83
85
|
"2024-01-01",
|
|
84
86
|
]);
|
|
85
87
|
});
|
|
86
88
|
|
|
87
89
|
test("handles ID fields", () => {
|
|
88
|
-
|
|
90
|
+
testIlikeConversion("user_id:123", "user_id = $1", [123]);
|
|
89
91
|
});
|
|
90
92
|
|
|
91
93
|
test("escapes special characters in field values", () => {
|
|
92
|
-
|
|
94
|
+
testIlikeConversion("category:100%", "lower(category) LIKE lower($1)", [
|
|
93
95
|
"%100\\%%",
|
|
94
96
|
]);
|
|
95
97
|
});
|
|
@@ -97,7 +99,7 @@ describe("Search Query to SQL Converter", () => {
|
|
|
97
99
|
|
|
98
100
|
describe("Logical Operators", () => {
|
|
99
101
|
test("converts AND expressions", () => {
|
|
100
|
-
|
|
102
|
+
testIlikeConversion(
|
|
101
103
|
"comfortable AND leather",
|
|
102
104
|
"((lower(title) LIKE lower($1) OR lower(description) LIKE lower($1) OR lower(content) LIKE lower($1)) AND (lower(title) LIKE lower($2) OR lower(description) LIKE lower($2) OR lower(content) LIKE lower($2)))",
|
|
103
105
|
["%comfortable%", "%leather%"]
|
|
@@ -105,7 +107,7 @@ describe("Search Query to SQL Converter", () => {
|
|
|
105
107
|
});
|
|
106
108
|
|
|
107
109
|
test("converts OR expressions", () => {
|
|
108
|
-
|
|
110
|
+
testIlikeConversion(
|
|
109
111
|
"leather OR suede",
|
|
110
112
|
"((lower(title) LIKE lower($1) OR lower(description) LIKE lower($1) OR lower(content) LIKE lower($1)) OR (lower(title) LIKE lower($2) OR lower(description) LIKE lower($2) OR lower(content) LIKE lower($2)))",
|
|
111
113
|
["%leather%", "%suede%"]
|
|
@@ -113,7 +115,7 @@ describe("Search Query to SQL Converter", () => {
|
|
|
113
115
|
});
|
|
114
116
|
|
|
115
117
|
test("converts mixed operators", () => {
|
|
116
|
-
|
|
118
|
+
testIlikeConversion(
|
|
117
119
|
"comfortable AND (leather OR suede)",
|
|
118
120
|
"((lower(title) LIKE lower($1) OR lower(description) LIKE lower($1) OR lower(content) LIKE lower($1)) AND ((lower(title) LIKE lower($2) OR lower(description) LIKE lower($2) OR lower(content) LIKE lower($2)) OR (lower(title) LIKE lower($3) OR lower(description) LIKE lower($3) OR lower(content) LIKE lower($3))))",
|
|
119
121
|
["%comfortable%", "%leather%", "%suede%"]
|
|
@@ -123,7 +125,7 @@ describe("Search Query to SQL Converter", () => {
|
|
|
123
125
|
|
|
124
126
|
describe("NOT SQL Conversion", () => {
|
|
125
127
|
test("converts simple NOT expressions", () => {
|
|
126
|
-
|
|
128
|
+
testIlikeConversion(
|
|
127
129
|
"NOT test",
|
|
128
130
|
"NOT (lower(title) LIKE lower($1) OR lower(description) LIKE lower($1) OR lower(content) LIKE lower($1))",
|
|
129
131
|
["%test%"]
|
|
@@ -131,19 +133,19 @@ describe("Search Query to SQL Converter", () => {
|
|
|
131
133
|
});
|
|
132
134
|
|
|
133
135
|
test("converts NOT with field:value", () => {
|
|
134
|
-
|
|
136
|
+
testIlikeConversion("NOT status:active", "NOT lower(status) LIKE lower($1)", [
|
|
135
137
|
"%active%",
|
|
136
138
|
]);
|
|
137
139
|
});
|
|
138
140
|
|
|
139
141
|
test("converts complex NOT expressions", () => {
|
|
140
|
-
|
|
142
|
+
testIlikeConversion(
|
|
141
143
|
"boots AND NOT leather",
|
|
142
144
|
"((lower(title) LIKE lower($1) OR lower(description) LIKE lower($1) OR lower(content) LIKE lower($1)) AND NOT (lower(title) LIKE lower($2) OR lower(description) LIKE lower($2) OR lower(content) LIKE lower($2)))",
|
|
143
145
|
["%boots%", "%leather%"]
|
|
144
146
|
);
|
|
145
147
|
|
|
146
|
-
|
|
148
|
+
testIlikeConversion(
|
|
147
149
|
"NOT (color:red OR color:blue)",
|
|
148
150
|
"NOT (lower(color) LIKE lower($1) OR lower(color) LIKE lower($2))",
|
|
149
151
|
["%red%", "%blue%"]
|
|
@@ -153,7 +155,7 @@ describe("Search Query to SQL Converter", () => {
|
|
|
153
155
|
|
|
154
156
|
describe("Complex Queries", () => {
|
|
155
157
|
test("converts complex field and term combinations", () => {
|
|
156
|
-
|
|
158
|
+
testIlikeConversion(
|
|
157
159
|
'category:"winter boots" AND (color:black OR color:brown)',
|
|
158
160
|
"(lower(category) LIKE lower($1) AND (lower(color) LIKE lower($2) OR lower(color) LIKE lower($3)))",
|
|
159
161
|
["%winter boots%", "%black%", "%brown%"]
|
|
@@ -161,7 +163,7 @@ describe("Search Query to SQL Converter", () => {
|
|
|
161
163
|
});
|
|
162
164
|
|
|
163
165
|
test("converts nested expressions with multiple operators", () => {
|
|
164
|
-
|
|
166
|
+
testIlikeConversion(
|
|
165
167
|
'(color:red OR color:blue) AND category:"winter boots" AND available:true',
|
|
166
168
|
"(((lower(color) LIKE lower($1) OR lower(color) LIKE lower($2)) AND lower(category) LIKE lower($3)) AND lower(available) LIKE lower($4))",
|
|
167
169
|
["%red%", "%blue%", "%winter boots%", "%true%"]
|
|
@@ -169,7 +171,7 @@ describe("Search Query to SQL Converter", () => {
|
|
|
169
171
|
});
|
|
170
172
|
|
|
171
173
|
test("handles mixed fields and search terms", () => {
|
|
172
|
-
|
|
174
|
+
testIlikeConversion(
|
|
173
175
|
'boots AND color:black AND "winter gear"',
|
|
174
176
|
"(((lower(title) LIKE lower($1) OR lower(description) LIKE lower($1) OR lower(content) LIKE lower($1)) AND lower(color) LIKE lower($2)) AND (lower(title) LIKE lower($3) OR lower(description) LIKE lower($3) OR lower(content) LIKE lower($3)))",
|
|
175
177
|
["%boots%", "%black%", "%winter gear%"]
|
|
@@ -179,7 +181,7 @@ describe("Search Query to SQL Converter", () => {
|
|
|
179
181
|
|
|
180
182
|
describe("Edge Cases", () => {
|
|
181
183
|
test("handles empty query", () => {
|
|
182
|
-
const result =
|
|
184
|
+
const result = searchQueryToIlikeSql(
|
|
183
185
|
{ type: "SEARCH_QUERY", expression: null },
|
|
184
186
|
searchableColumns
|
|
185
187
|
);
|
|
@@ -189,23 +191,23 @@ describe("Search Query to SQL Converter", () => {
|
|
|
189
191
|
|
|
190
192
|
test("throws error for invalid query syntax", () => {
|
|
191
193
|
expect(() =>
|
|
192
|
-
|
|
194
|
+
searchStringToIlikeSql("AND", searchableColumns, schemas)
|
|
193
195
|
).toThrow("Parse error");
|
|
194
196
|
expect(() =>
|
|
195
|
-
|
|
197
|
+
searchStringToIlikeSql("field:", searchableColumns, schemas)
|
|
196
198
|
).toThrow("Parse error");
|
|
197
199
|
});
|
|
198
200
|
|
|
199
201
|
test("throws error for invalid fields", () => {
|
|
200
202
|
expect(() =>
|
|
201
|
-
|
|
203
|
+
searchStringToIlikeSql("invalid_field:value", searchableColumns, schemas)
|
|
202
204
|
).toThrow('Parse error: Invalid field: "invalid_field"');
|
|
203
205
|
});
|
|
204
206
|
});
|
|
205
207
|
|
|
206
208
|
describe("Parameter Counting", () => {
|
|
207
209
|
test("maintains correct parameter count in complex queries", () => {
|
|
208
|
-
|
|
210
|
+
testIlikeConversion(
|
|
209
211
|
'term1 AND field1:value1 OR (term2 AND field2:"value 2")',
|
|
210
212
|
"(((lower(title) LIKE lower($1) OR lower(description) LIKE lower($1) OR lower(content) LIKE lower($1)) AND lower(field1) LIKE lower($2)) OR ((lower(title) LIKE lower($3) OR lower(description) LIKE lower($3) OR lower(content) LIKE lower($3)) AND lower(field2) LIKE lower($4)))",
|
|
211
213
|
["%term1%", "%value1%", "%term2%", "%value 2%"]
|
|
@@ -215,7 +217,7 @@ describe("Search Query to SQL Converter", () => {
|
|
|
215
217
|
|
|
216
218
|
describe("Special Character Handling", () => {
|
|
217
219
|
test("escapes SQL wildcards", () => {
|
|
218
|
-
|
|
220
|
+
testIlikeConversion(
|
|
219
221
|
"prefix% AND suffix_",
|
|
220
222
|
"((lower(title) LIKE lower($1) OR lower(description) LIKE lower($1) OR lower(content) LIKE lower($1)) AND (lower(title) LIKE lower($2) OR lower(description) LIKE lower($2) OR lower(content) LIKE lower($2)))",
|
|
221
223
|
["%prefix\\%%", "%suffix\\_%"]
|
|
@@ -223,7 +225,7 @@ describe("Search Query to SQL Converter", () => {
|
|
|
223
225
|
});
|
|
224
226
|
|
|
225
227
|
test("handles quoted strings with escaped characters", () => {
|
|
226
|
-
|
|
228
|
+
testIlikeConversion(
|
|
227
229
|
'"value\\"with\\"quotes"',
|
|
228
230
|
"(lower(title) LIKE lower($1) OR lower(description) LIKE lower($1) OR lower(content) LIKE lower($1))",
|
|
229
231
|
['%value"with"quotes%']
|
|
@@ -233,15 +235,15 @@ describe("Search Query to SQL Converter", () => {
|
|
|
233
235
|
|
|
234
236
|
describe("Range Query Conversion", () => {
|
|
235
237
|
test("converts comparison operators for numbers", () => {
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
238
|
+
testIlikeConversion("price:>100", "price > $1", [100]);
|
|
239
|
+
testIlikeConversion("price:>=100", "price >= $1", [100]);
|
|
240
|
+
testIlikeConversion("price:<50", "price < $1", [50]);
|
|
241
|
+
testIlikeConversion("price:<=50", "price <= $1", [50]);
|
|
240
242
|
});
|
|
241
243
|
|
|
242
244
|
test("converts between ranges for numbers", () => {
|
|
243
|
-
|
|
244
|
-
|
|
245
|
+
testIlikeConversion("price:10..20", "price BETWEEN $1 AND $2", [10, 20]);
|
|
246
|
+
testIlikeConversion(
|
|
245
247
|
"amount:50.99..100.50",
|
|
246
248
|
"amount BETWEEN $1 AND $2",
|
|
247
249
|
[50.99, 100.5]
|
|
@@ -249,15 +251,15 @@ describe("Search Query to SQL Converter", () => {
|
|
|
249
251
|
});
|
|
250
252
|
|
|
251
253
|
test("converts open-ended ranges for numbers", () => {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
+
testIlikeConversion("price:10..", "price >= $1", [10]);
|
|
255
|
+
testIlikeConversion("price:..20", "price <= $1", [20]);
|
|
254
256
|
});
|
|
255
257
|
|
|
256
258
|
test("converts date ranges", () => {
|
|
257
|
-
|
|
259
|
+
testIlikeConversion("date:>2024-01-01", "date > $1", [
|
|
258
260
|
"2024-01-01",
|
|
259
261
|
]);
|
|
260
|
-
|
|
262
|
+
testIlikeConversion(
|
|
261
263
|
"date:2024-01-01..2024-12-31",
|
|
262
264
|
"date BETWEEN $1 AND $2",
|
|
263
265
|
["2024-01-01", "2024-12-31"]
|
|
@@ -265,17 +267,17 @@ describe("Search Query to SQL Converter", () => {
|
|
|
265
267
|
});
|
|
266
268
|
|
|
267
269
|
test("converts complex expressions with ranges", () => {
|
|
268
|
-
|
|
270
|
+
testIlikeConversion(
|
|
269
271
|
"price:>100 AND amount:<50",
|
|
270
272
|
"(price > $1 AND amount < $2)",
|
|
271
273
|
[100, 50]
|
|
272
274
|
);
|
|
273
|
-
|
|
275
|
+
testIlikeConversion(
|
|
274
276
|
"price:10..20 OR amount:>=100",
|
|
275
277
|
"(price BETWEEN $1 AND $2 OR amount >= $3)",
|
|
276
278
|
[10, 20, 100]
|
|
277
279
|
);
|
|
278
|
-
|
|
280
|
+
testIlikeConversion(
|
|
279
281
|
"(price:>100 AND amount:<50) OR date:>=2024-01-01",
|
|
280
282
|
"((price > $1 AND amount < $2) OR date >= $3)",
|
|
281
283
|
[100, 50, "2024-01-01"]
|
|
@@ -283,7 +285,7 @@ describe("Search Query to SQL Converter", () => {
|
|
|
283
285
|
});
|
|
284
286
|
|
|
285
287
|
test("mixes ranges with regular field searches", () => {
|
|
286
|
-
|
|
288
|
+
testIlikeConversion(
|
|
287
289
|
'title:"winter boots" AND price:10..20',
|
|
288
290
|
"(lower(title) LIKE lower($1) AND price BETWEEN $2 AND $3)",
|
|
289
291
|
["%winter boots%", 10, 20]
|
|
@@ -291,7 +293,7 @@ describe("Search Query to SQL Converter", () => {
|
|
|
291
293
|
});
|
|
292
294
|
|
|
293
295
|
test("handles multiple date ranges in one query", () => {
|
|
294
|
-
|
|
296
|
+
testIlikeConversion(
|
|
295
297
|
"date:>=2024-01-01 AND date:<=2024-12-31",
|
|
296
298
|
"(date >= $1 AND date <= $2)",
|
|
297
299
|
["2024-01-01", "2024-12-31"]
|
|
@@ -299,7 +301,7 @@ describe("Search Query to SQL Converter", () => {
|
|
|
299
301
|
});
|
|
300
302
|
|
|
301
303
|
test("handles decimal numbers in ranges", () => {
|
|
302
|
-
|
|
304
|
+
testIlikeConversion(
|
|
303
305
|
"price:10.5..20.99",
|
|
304
306
|
"price BETWEEN $1 AND $2",
|
|
305
307
|
[10.5, 20.99]
|
|
@@ -307,16 +309,16 @@ describe("Search Query to SQL Converter", () => {
|
|
|
307
309
|
});
|
|
308
310
|
|
|
309
311
|
test("preserves numeric precision", () => {
|
|
310
|
-
|
|
312
|
+
testIlikeConversion("price:>=99.99", "price >= $1", [99.99]);
|
|
311
313
|
});
|
|
312
314
|
|
|
313
315
|
test("handles negative numbers in ranges", () => {
|
|
314
|
-
|
|
316
|
+
testIlikeConversion(
|
|
315
317
|
"amount:-10..10",
|
|
316
318
|
"amount BETWEEN $1 AND $2",
|
|
317
319
|
[-10, 10]
|
|
318
320
|
);
|
|
319
|
-
|
|
321
|
+
testIlikeConversion("amount:<-10", "amount < $1", [-10]);
|
|
320
322
|
});
|
|
321
323
|
});
|
|
322
324
|
|
|
@@ -328,12 +330,11 @@ describe("Search Query to SQL Converter", () => {
|
|
|
328
330
|
) => {
|
|
329
331
|
const parsedQuery = parseSearchInputQuery(query, schemas);
|
|
330
332
|
expect(parsedQuery.type).toBe("SEARCH_QUERY");
|
|
331
|
-
const result =
|
|
333
|
+
const result = searchQueryToTsVectorSql(
|
|
332
334
|
parsedQuery as SearchQuery,
|
|
333
335
|
searchableColumns,
|
|
334
336
|
schemas,
|
|
335
337
|
{
|
|
336
|
-
searchType: "tsvector",
|
|
337
338
|
language: "english",
|
|
338
339
|
}
|
|
339
340
|
);
|
|
@@ -382,13 +383,10 @@ describe("Search Query to SQL Converter", () => {
|
|
|
382
383
|
) => {
|
|
383
384
|
const parsedQuery = parseSearchInputQuery(query, schemas);
|
|
384
385
|
expect(parsedQuery.type).toBe("SEARCH_QUERY");
|
|
385
|
-
const result =
|
|
386
|
+
const result = searchQueryToParadeDbSql(
|
|
386
387
|
parsedQuery as SearchQuery,
|
|
387
388
|
searchableColumns,
|
|
388
|
-
schemas
|
|
389
|
-
{
|
|
390
|
-
searchType: "paradedb",
|
|
391
|
-
}
|
|
389
|
+
schemas
|
|
392
390
|
);
|
|
393
391
|
expect(result.text).toBe(expectedSql);
|
|
394
392
|
expect(result.values).toEqual(expectedValues);
|
|
@@ -6,6 +6,16 @@ import {
|
|
|
6
6
|
WildcardPattern,
|
|
7
7
|
} from "./parser";
|
|
8
8
|
|
|
9
|
+
export {
|
|
10
|
+
searchStringToIlikeSql,
|
|
11
|
+
searchQueryToIlikeSql,
|
|
12
|
+
} from "./search-query-to-ilike-sql";
|
|
13
|
+
export { searchQueryToTsVectorSql, searchStringToTsVectorSql } from "./search-query-to-tsvector-sql";
|
|
14
|
+
export {
|
|
15
|
+
searchQueryToParadeDbSql,
|
|
16
|
+
searchStringToParadeDbSql,
|
|
17
|
+
} from "./search-query-to-paradedb-sql";
|
|
18
|
+
|
|
9
19
|
export interface SqlQueryResult {
|
|
10
20
|
text: string;
|
|
11
21
|
values: any[];
|