@mastra/opensearch 0.11.7 → 0.11.8-alpha.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/CHANGELOG.md +9 -0
- package/package.json +17 -4
- package/.turbo/turbo-build.log +0 -4
- package/docker-compose.yaml +0 -23
- package/eslint.config.js +0 -6
- package/src/index.ts +0 -1
- package/src/vector/filter.test.ts +0 -661
- package/src/vector/filter.ts +0 -479
- package/src/vector/index.test.ts +0 -1558
- package/src/vector/index.ts +0 -436
- package/src/vector/prompt.ts +0 -82
- package/tsconfig.build.json +0 -9
- package/tsconfig.json +0 -5
- package/tsup.config.ts +0 -17
- package/vitest.config.ts +0 -11
|
@@ -1,661 +0,0 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, it } from 'vitest';
|
|
2
|
-
|
|
3
|
-
import type { OpenSearchVectorFilter } from './filter';
|
|
4
|
-
import { OpenSearchFilterTranslator } from './filter';
|
|
5
|
-
|
|
6
|
-
describe('OpenSearchFilterTranslator', () => {
|
|
7
|
-
let translator: OpenSearchFilterTranslator;
|
|
8
|
-
|
|
9
|
-
beforeEach(() => {
|
|
10
|
-
translator = new OpenSearchFilterTranslator();
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
// Basic Filter Operations
|
|
14
|
-
describe('basic operations', () => {
|
|
15
|
-
it('handles empty filters', () => {
|
|
16
|
-
expect(translator.translate({})).toEqual(undefined);
|
|
17
|
-
expect(translator.translate(null)).toEqual(undefined);
|
|
18
|
-
expect(translator.translate(undefined)).toEqual(undefined);
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it('translates simple field equality to term query', () => {
|
|
22
|
-
const filter: OpenSearchVectorFilter = { field: 'value' };
|
|
23
|
-
expect(translator.translate(filter)).toEqual({
|
|
24
|
-
term: { 'metadata.field.keyword': 'value' },
|
|
25
|
-
});
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it('translates multiple top-level fields to bool must', () => {
|
|
29
|
-
const filter: OpenSearchVectorFilter = { field1: 'value1', field2: 'value2' };
|
|
30
|
-
expect(translator.translate(filter)).toEqual({
|
|
31
|
-
bool: {
|
|
32
|
-
must: [{ term: { 'metadata.field1.keyword': 'value1' } }, { term: { 'metadata.field2.keyword': 'value2' } }],
|
|
33
|
-
},
|
|
34
|
-
});
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it('handles nested objects', () => {
|
|
38
|
-
const filter = {
|
|
39
|
-
user: {
|
|
40
|
-
profile: {
|
|
41
|
-
age: 25,
|
|
42
|
-
name: 'John',
|
|
43
|
-
},
|
|
44
|
-
},
|
|
45
|
-
};
|
|
46
|
-
expect(translator.translate(filter)).toEqual({
|
|
47
|
-
bool: {
|
|
48
|
-
must: [
|
|
49
|
-
{
|
|
50
|
-
bool: {
|
|
51
|
-
must: [
|
|
52
|
-
{ term: { 'metadata.user.profile.age': 25 } },
|
|
53
|
-
{ term: { 'metadata.user.profile.name.keyword': 'John' } },
|
|
54
|
-
],
|
|
55
|
-
},
|
|
56
|
-
},
|
|
57
|
-
],
|
|
58
|
-
},
|
|
59
|
-
});
|
|
60
|
-
});
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
// Comparison Operators
|
|
64
|
-
describe('comparison operators', () => {
|
|
65
|
-
it('translates $eq operator', () => {
|
|
66
|
-
const filter: OpenSearchVectorFilter = { field: { $eq: 'value' } };
|
|
67
|
-
expect(translator.translate(filter)).toEqual({
|
|
68
|
-
term: { 'metadata.field.keyword': 'value' },
|
|
69
|
-
});
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it('translates $ne operator', () => {
|
|
73
|
-
const filter: OpenSearchVectorFilter = { field: { $ne: 'value' } };
|
|
74
|
-
expect(translator.translate(filter)).toEqual({
|
|
75
|
-
bool: {
|
|
76
|
-
must_not: [{ term: { 'metadata.field.keyword': 'value' } }],
|
|
77
|
-
},
|
|
78
|
-
});
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
it('handles date values', () => {
|
|
82
|
-
const date = new Date('2024-01-01');
|
|
83
|
-
const filter: OpenSearchVectorFilter = { timestamp: { $gt: date } };
|
|
84
|
-
expect(translator.translate(filter)).toEqual({
|
|
85
|
-
range: { 'metadata.timestamp': { gt: date.toISOString() } },
|
|
86
|
-
});
|
|
87
|
-
});
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
// Logical Operators
|
|
91
|
-
describe('logical operators', () => {
|
|
92
|
-
it('translates $and operator', () => {
|
|
93
|
-
const filter: OpenSearchVectorFilter = {
|
|
94
|
-
$and: [{ field1: 'value1' }, { field2: 'value2' }],
|
|
95
|
-
};
|
|
96
|
-
expect(translator.translate(filter)).toEqual({
|
|
97
|
-
bool: {
|
|
98
|
-
must: [{ term: { 'metadata.field1.keyword': 'value1' } }, { term: { 'metadata.field2.keyword': 'value2' } }],
|
|
99
|
-
},
|
|
100
|
-
});
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
it('translates $or operator', () => {
|
|
104
|
-
const filter: OpenSearchVectorFilter = {
|
|
105
|
-
$or: [{ field1: 'value1' }, { field2: 'value2' }],
|
|
106
|
-
};
|
|
107
|
-
expect(translator.translate(filter)).toEqual({
|
|
108
|
-
bool: {
|
|
109
|
-
should: [
|
|
110
|
-
{ term: { 'metadata.field1.keyword': 'value1' } },
|
|
111
|
-
{ term: { 'metadata.field2.keyword': 'value2' } },
|
|
112
|
-
],
|
|
113
|
-
},
|
|
114
|
-
});
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
it('translates $not operator', () => {
|
|
118
|
-
const filter: OpenSearchVectorFilter = {
|
|
119
|
-
$not: { field: 'value' },
|
|
120
|
-
};
|
|
121
|
-
expect(translator.translate(filter)).toEqual({
|
|
122
|
-
bool: {
|
|
123
|
-
must_not: [{ term: { 'metadata.field.keyword': 'value' } }],
|
|
124
|
-
},
|
|
125
|
-
});
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
it('translates $not with $eq operator', () => {
|
|
129
|
-
const filter: OpenSearchVectorFilter = { field: { $not: { $eq: 'value' } } };
|
|
130
|
-
expect(translator.translate(filter)).toEqual({
|
|
131
|
-
bool: {
|
|
132
|
-
must_not: [{ term: { 'metadata.field.keyword': 'value' } }],
|
|
133
|
-
},
|
|
134
|
-
});
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
it('translates $not with $ne operator', () => {
|
|
138
|
-
const filter: OpenSearchVectorFilter = { field: { $not: { $ne: 'value' } } };
|
|
139
|
-
expect(translator.translate(filter)).toEqual({
|
|
140
|
-
bool: {
|
|
141
|
-
must_not: [
|
|
142
|
-
{
|
|
143
|
-
bool: {
|
|
144
|
-
must_not: [{ term: { 'metadata.field.keyword': 'value' } }],
|
|
145
|
-
},
|
|
146
|
-
},
|
|
147
|
-
],
|
|
148
|
-
},
|
|
149
|
-
});
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
it('translates $not with $eq null', () => {
|
|
153
|
-
const filter: OpenSearchVectorFilter = { field: { $not: { $eq: null } } };
|
|
154
|
-
expect(translator.translate(filter)).toEqual({
|
|
155
|
-
exists: { field: 'metadata.field' },
|
|
156
|
-
});
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
it('translates $not with $ne null', () => {
|
|
160
|
-
const filter: OpenSearchVectorFilter = { field: { $not: { $ne: null } } };
|
|
161
|
-
expect(translator.translate(filter)).toEqual({
|
|
162
|
-
bool: {
|
|
163
|
-
must_not: [{ exists: { field: 'metadata.field' } }],
|
|
164
|
-
},
|
|
165
|
-
});
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
it('translates $not with nested fields', () => {
|
|
169
|
-
const filter: OpenSearchVectorFilter = { 'user.profile.age': { $not: { $gt: 25 } } };
|
|
170
|
-
expect(translator.translate(filter)).toEqual({
|
|
171
|
-
bool: {
|
|
172
|
-
must_not: [
|
|
173
|
-
{
|
|
174
|
-
range: { 'metadata.user.profile.age': { gt: 25 } },
|
|
175
|
-
},
|
|
176
|
-
],
|
|
177
|
-
},
|
|
178
|
-
});
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
it('translates $not with multiple operators', () => {
|
|
182
|
-
const filter: OpenSearchVectorFilter = { price: { $not: { $gte: 30, $lte: 70 } } };
|
|
183
|
-
expect(translator.translate(filter)).toEqual({
|
|
184
|
-
bool: {
|
|
185
|
-
must_not: [
|
|
186
|
-
{
|
|
187
|
-
range: { 'metadata.price': { gte: 30, lte: 70 } },
|
|
188
|
-
},
|
|
189
|
-
],
|
|
190
|
-
},
|
|
191
|
-
});
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
it('handles empty $and array', () => {
|
|
195
|
-
const filter: OpenSearchVectorFilter = {
|
|
196
|
-
$and: [],
|
|
197
|
-
};
|
|
198
|
-
// Empty $and should match everything
|
|
199
|
-
expect(translator.translate(filter)).toEqual({ match_all: {} });
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
it('handles empty $or array', () => {
|
|
203
|
-
const filter: OpenSearchVectorFilter = {
|
|
204
|
-
$or: [],
|
|
205
|
-
};
|
|
206
|
-
// Empty $or should match nothing
|
|
207
|
-
expect(translator.translate(filter)).toEqual({
|
|
208
|
-
bool: {
|
|
209
|
-
must_not: [{ match_all: {} }],
|
|
210
|
-
},
|
|
211
|
-
});
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
it('throws error for empty $not condition', () => {
|
|
215
|
-
const filter = {
|
|
216
|
-
$not: {},
|
|
217
|
-
};
|
|
218
|
-
expect(() => translator.translate(filter)).toThrow('not operator cannot be empty');
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
it('handles $not with comparison operators', () => {
|
|
222
|
-
const filter: OpenSearchVectorFilter = {
|
|
223
|
-
price: { $not: { $gt: 100 } },
|
|
224
|
-
};
|
|
225
|
-
expect(translator.translate(filter)).toEqual({
|
|
226
|
-
bool: {
|
|
227
|
-
must_not: [{ range: { 'metadata.price': { gt: 100 } } }],
|
|
228
|
-
},
|
|
229
|
-
});
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
it('handles nested $not with $or', () => {
|
|
233
|
-
const filter: OpenSearchVectorFilter = {
|
|
234
|
-
$not: { $or: [{ category: 'electronics' }, { category: 'books' }] },
|
|
235
|
-
};
|
|
236
|
-
expect(translator.translate(filter)).toEqual({
|
|
237
|
-
bool: {
|
|
238
|
-
must_not: [
|
|
239
|
-
{
|
|
240
|
-
bool: {
|
|
241
|
-
should: [
|
|
242
|
-
{ term: { 'metadata.category.keyword': 'electronics' } },
|
|
243
|
-
{ term: { 'metadata.category.keyword': 'books' } },
|
|
244
|
-
],
|
|
245
|
-
},
|
|
246
|
-
},
|
|
247
|
-
],
|
|
248
|
-
},
|
|
249
|
-
});
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
it('handles $not with $not operator', () => {
|
|
253
|
-
const filter: OpenSearchVectorFilter = {
|
|
254
|
-
$not: { $not: { category: 'electronics' } },
|
|
255
|
-
};
|
|
256
|
-
expect(translator.translate(filter)).toEqual({
|
|
257
|
-
bool: {
|
|
258
|
-
must_not: [
|
|
259
|
-
{
|
|
260
|
-
bool: {
|
|
261
|
-
must_not: [{ term: { 'metadata.category.keyword': 'electronics' } }],
|
|
262
|
-
},
|
|
263
|
-
},
|
|
264
|
-
],
|
|
265
|
-
},
|
|
266
|
-
});
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
it('handles nested logical operators', () => {
|
|
270
|
-
const filter: OpenSearchVectorFilter = {
|
|
271
|
-
$and: [
|
|
272
|
-
{ field1: 'value1' },
|
|
273
|
-
{
|
|
274
|
-
$or: [{ field2: 'value2' }, { field3: 'value3' }],
|
|
275
|
-
},
|
|
276
|
-
],
|
|
277
|
-
};
|
|
278
|
-
expect(translator.translate(filter)).toEqual({
|
|
279
|
-
bool: {
|
|
280
|
-
must: [
|
|
281
|
-
{ term: { 'metadata.field1.keyword': 'value1' } },
|
|
282
|
-
{
|
|
283
|
-
bool: {
|
|
284
|
-
should: [
|
|
285
|
-
{ term: { 'metadata.field2.keyword': 'value2' } },
|
|
286
|
-
{ term: { 'metadata.field3.keyword': 'value3' } },
|
|
287
|
-
],
|
|
288
|
-
},
|
|
289
|
-
},
|
|
290
|
-
],
|
|
291
|
-
},
|
|
292
|
-
});
|
|
293
|
-
});
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
// Array Operators
|
|
297
|
-
describe('array operators', () => {
|
|
298
|
-
it('translates $in operator', () => {
|
|
299
|
-
const filter: OpenSearchVectorFilter = { field: { $in: ['value1', 'value2'] } };
|
|
300
|
-
expect(translator.translate(filter)).toEqual({
|
|
301
|
-
terms: { 'metadata.field.keyword': ['value1', 'value2'] },
|
|
302
|
-
});
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
it('translates $nin operator', () => {
|
|
306
|
-
const filter: OpenSearchVectorFilter = { field: { $nin: ['value1', 'value2'] } };
|
|
307
|
-
expect(translator.translate(filter)).toEqual({
|
|
308
|
-
bool: {
|
|
309
|
-
must_not: [{ terms: { 'metadata.field.keyword': ['value1', 'value2'] } }],
|
|
310
|
-
},
|
|
311
|
-
});
|
|
312
|
-
});
|
|
313
|
-
|
|
314
|
-
it('translates $all operator', () => {
|
|
315
|
-
const filter: OpenSearchVectorFilter = { field: { $all: ['value1', 'value2'] } };
|
|
316
|
-
expect(translator.translate(filter)).toEqual({
|
|
317
|
-
bool: {
|
|
318
|
-
must: [{ term: { 'metadata.field.keyword': 'value1' } }, { term: { 'metadata.field.keyword': 'value2' } }],
|
|
319
|
-
},
|
|
320
|
-
});
|
|
321
|
-
});
|
|
322
|
-
|
|
323
|
-
it('handles empty $in array', () => {
|
|
324
|
-
const filter: OpenSearchVectorFilter = { field: { $in: [] } };
|
|
325
|
-
// Empty $in should match nothing (empty terms)
|
|
326
|
-
expect(translator.translate(filter)).toEqual({
|
|
327
|
-
terms: { 'metadata.field.keyword': [] },
|
|
328
|
-
});
|
|
329
|
-
});
|
|
330
|
-
|
|
331
|
-
it('handles empty $nin array', () => {
|
|
332
|
-
const filter: OpenSearchVectorFilter = { field: { $nin: [] } };
|
|
333
|
-
// Empty $nin should match everything
|
|
334
|
-
expect(translator.translate(filter)).toEqual({
|
|
335
|
-
match_all: {},
|
|
336
|
-
});
|
|
337
|
-
});
|
|
338
|
-
|
|
339
|
-
it('handles empty $all array', () => {
|
|
340
|
-
const filter: OpenSearchVectorFilter = { field: { $all: [] } };
|
|
341
|
-
// Empty $all should match nothing
|
|
342
|
-
expect(translator.translate(filter)).toEqual({
|
|
343
|
-
bool: {
|
|
344
|
-
must_not: [{ match_all: {} }],
|
|
345
|
-
},
|
|
346
|
-
});
|
|
347
|
-
});
|
|
348
|
-
|
|
349
|
-
it('handles $not with array operators', () => {
|
|
350
|
-
const filter: OpenSearchVectorFilter = { tags: { $not: { $in: ['premium', 'new'] } } };
|
|
351
|
-
expect(translator.translate(filter)).toEqual({
|
|
352
|
-
bool: {
|
|
353
|
-
must_not: [
|
|
354
|
-
{
|
|
355
|
-
terms: { 'metadata.tags.keyword': ['premium', 'new'] },
|
|
356
|
-
},
|
|
357
|
-
],
|
|
358
|
-
},
|
|
359
|
-
});
|
|
360
|
-
});
|
|
361
|
-
|
|
362
|
-
it('handles $not with empty array operators', () => {
|
|
363
|
-
const filter: OpenSearchVectorFilter = { tags: { $not: { $in: [] } } };
|
|
364
|
-
expect(translator.translate(filter)).toEqual({
|
|
365
|
-
bool: {
|
|
366
|
-
must_not: [
|
|
367
|
-
{
|
|
368
|
-
terms: { 'metadata.tags.keyword': [] },
|
|
369
|
-
},
|
|
370
|
-
],
|
|
371
|
-
},
|
|
372
|
-
});
|
|
373
|
-
});
|
|
374
|
-
});
|
|
375
|
-
|
|
376
|
-
// Element Operators
|
|
377
|
-
describe('element operators', () => {
|
|
378
|
-
it('translates $exists operator', () => {
|
|
379
|
-
const filter: OpenSearchVectorFilter = { field: { $exists: true } };
|
|
380
|
-
expect(translator.translate(filter)).toEqual({
|
|
381
|
-
exists: { field: 'metadata.field' },
|
|
382
|
-
});
|
|
383
|
-
});
|
|
384
|
-
|
|
385
|
-
it('translates $exists operator with false', () => {
|
|
386
|
-
const filter: OpenSearchVectorFilter = { field: { $exists: false } };
|
|
387
|
-
expect(translator.translate(filter)).toEqual({
|
|
388
|
-
bool: {
|
|
389
|
-
must_not: [{ exists: { field: 'metadata.field' } }],
|
|
390
|
-
},
|
|
391
|
-
});
|
|
392
|
-
});
|
|
393
|
-
});
|
|
394
|
-
|
|
395
|
-
// Regex Operators
|
|
396
|
-
describe('regex operators', () => {
|
|
397
|
-
it('translates $regex operator', () => {
|
|
398
|
-
const filter: OpenSearchVectorFilter = { field: { $regex: 'pattern' } };
|
|
399
|
-
expect(translator.translate(filter)).toEqual({
|
|
400
|
-
regexp: { 'metadata.field': 'pattern' },
|
|
401
|
-
});
|
|
402
|
-
});
|
|
403
|
-
|
|
404
|
-
it('handles $regex with start anchor', () => {
|
|
405
|
-
const filter: OpenSearchVectorFilter = { category: { $regex: '^elect' } };
|
|
406
|
-
// Should use wildcard for better anchor handling
|
|
407
|
-
expect(translator.translate(filter)).toEqual({
|
|
408
|
-
wildcard: { 'metadata.category': 'elect*' },
|
|
409
|
-
});
|
|
410
|
-
});
|
|
411
|
-
|
|
412
|
-
it('handles $regex with end anchor', () => {
|
|
413
|
-
const filter: OpenSearchVectorFilter = { category: { $regex: 'nics$' } };
|
|
414
|
-
// Should use wildcard for better anchor handling
|
|
415
|
-
expect(translator.translate(filter)).toEqual({
|
|
416
|
-
wildcard: { 'metadata.category': '*nics' },
|
|
417
|
-
});
|
|
418
|
-
});
|
|
419
|
-
|
|
420
|
-
it('handles $regex with both anchors', () => {
|
|
421
|
-
const filter: OpenSearchVectorFilter = { category: { $regex: '^electronics$' } };
|
|
422
|
-
// Should use exact match for both anchors
|
|
423
|
-
expect(translator.translate(filter)).toEqual({
|
|
424
|
-
wildcard: { 'metadata.category': 'electronics' },
|
|
425
|
-
});
|
|
426
|
-
});
|
|
427
|
-
|
|
428
|
-
it('handles $not with $regex operator', () => {
|
|
429
|
-
const filter: OpenSearchVectorFilter = { category: { $not: { $regex: '^elect' } } };
|
|
430
|
-
expect(translator.translate(filter)).toEqual({
|
|
431
|
-
bool: {
|
|
432
|
-
must_not: [
|
|
433
|
-
{
|
|
434
|
-
wildcard: { 'metadata.category': 'elect*' },
|
|
435
|
-
},
|
|
436
|
-
],
|
|
437
|
-
},
|
|
438
|
-
});
|
|
439
|
-
});
|
|
440
|
-
});
|
|
441
|
-
|
|
442
|
-
// Complex Queries
|
|
443
|
-
describe('complex queries', () => {
|
|
444
|
-
it('translates numeric operators', () => {
|
|
445
|
-
const filter: OpenSearchVectorFilter = { price: { $gt: 70, $lte: 100 } };
|
|
446
|
-
expect(translator.translate(filter)).toEqual({
|
|
447
|
-
range: { 'metadata.price': { gt: 70, lte: 100 } },
|
|
448
|
-
});
|
|
449
|
-
});
|
|
450
|
-
|
|
451
|
-
it('translates multiple range operators on the same field', () => {
|
|
452
|
-
const filter: OpenSearchVectorFilter = { price: { $gte: 50, $lt: 200 } };
|
|
453
|
-
expect(translator.translate(filter)).toEqual({
|
|
454
|
-
range: { 'metadata.price': { gte: 50, lt: 200 } },
|
|
455
|
-
});
|
|
456
|
-
});
|
|
457
|
-
|
|
458
|
-
it('translates all four range operators combined', () => {
|
|
459
|
-
// This is an edge case that would never occur in practice, but tests the implementation
|
|
460
|
-
const filter: OpenSearchVectorFilter = { value: { $gt: 10, $gte: 20, $lt: 100, $lte: 90 } };
|
|
461
|
-
expect(translator.translate(filter)).toEqual({
|
|
462
|
-
range: { 'metadata.value': { gt: 10, gte: 20, lt: 100, lte: 90 } },
|
|
463
|
-
});
|
|
464
|
-
});
|
|
465
|
-
|
|
466
|
-
it('translates mixed numeric and non-numeric operators', () => {
|
|
467
|
-
const filter: OpenSearchVectorFilter = { price: { $gt: 50, $exists: true } };
|
|
468
|
-
expect(translator.translate(filter)).toEqual({
|
|
469
|
-
bool: {
|
|
470
|
-
must: [{ range: { 'metadata.price': { gt: 50 } } }, { exists: { field: 'metadata.price' } }],
|
|
471
|
-
},
|
|
472
|
-
});
|
|
473
|
-
});
|
|
474
|
-
it('translates mixed operators', () => {
|
|
475
|
-
const filter: OpenSearchVectorFilter = {
|
|
476
|
-
$and: [{ field1: { $gt: 10 } }, { field2: { $in: ['value1', 'value2'] } }, { field3: { $exists: true } }],
|
|
477
|
-
};
|
|
478
|
-
expect(translator.translate(filter)).toEqual({
|
|
479
|
-
bool: {
|
|
480
|
-
must: [
|
|
481
|
-
{ range: { 'metadata.field1': { gt: 10 } } },
|
|
482
|
-
{ terms: { 'metadata.field2.keyword': ['value1', 'value2'] } },
|
|
483
|
-
{ exists: { field: 'metadata.field3' } },
|
|
484
|
-
],
|
|
485
|
-
},
|
|
486
|
-
});
|
|
487
|
-
});
|
|
488
|
-
|
|
489
|
-
it('translates complex nested queries', () => {
|
|
490
|
-
const filter: OpenSearchVectorFilter = {
|
|
491
|
-
$and: [
|
|
492
|
-
{ status: 'active' },
|
|
493
|
-
{
|
|
494
|
-
$or: [{ age: { $gt: 25 } }, { role: { $in: ['admin', 'manager'] } }],
|
|
495
|
-
},
|
|
496
|
-
{
|
|
497
|
-
$not: {
|
|
498
|
-
$and: [{ deleted: true }, { archived: true }],
|
|
499
|
-
},
|
|
500
|
-
},
|
|
501
|
-
],
|
|
502
|
-
};
|
|
503
|
-
expect(translator.translate(filter)).toEqual({
|
|
504
|
-
bool: {
|
|
505
|
-
must: [
|
|
506
|
-
{ term: { 'metadata.status.keyword': 'active' } },
|
|
507
|
-
{
|
|
508
|
-
bool: {
|
|
509
|
-
should: [
|
|
510
|
-
{ range: { 'metadata.age': { gt: 25 } } },
|
|
511
|
-
{ terms: { 'metadata.role.keyword': ['admin', 'manager'] } },
|
|
512
|
-
],
|
|
513
|
-
},
|
|
514
|
-
},
|
|
515
|
-
{
|
|
516
|
-
bool: {
|
|
517
|
-
must_not: [
|
|
518
|
-
{
|
|
519
|
-
bool: {
|
|
520
|
-
must: [{ term: { 'metadata.deleted': true } }, { term: { 'metadata.archived': true } }],
|
|
521
|
-
},
|
|
522
|
-
},
|
|
523
|
-
],
|
|
524
|
-
},
|
|
525
|
-
},
|
|
526
|
-
],
|
|
527
|
-
},
|
|
528
|
-
});
|
|
529
|
-
});
|
|
530
|
-
});
|
|
531
|
-
|
|
532
|
-
// Error Cases
|
|
533
|
-
describe('error cases', () => {
|
|
534
|
-
it('throws error for unsupported operators', () => {
|
|
535
|
-
const filter = { field: { $unsupported: 'value' } };
|
|
536
|
-
expect(() => translator.translate(filter)).toThrow(/Unsupported operator/);
|
|
537
|
-
});
|
|
538
|
-
|
|
539
|
-
it('throws error for invalid logical operator structure', () => {
|
|
540
|
-
const filter = { $and: 'invalid' };
|
|
541
|
-
expect(() => translator.translate(filter)).toThrow();
|
|
542
|
-
});
|
|
543
|
-
|
|
544
|
-
it('throws error for invalid array operator values', () => {
|
|
545
|
-
const filter = { field: { $in: 'not-an-array' } };
|
|
546
|
-
expect(() => translator.translate(filter as any)).toThrow();
|
|
547
|
-
});
|
|
548
|
-
|
|
549
|
-
it('throws error for nested invalid operators', () => {
|
|
550
|
-
const filter = { user: { profile: { age: { $invalid: 25 } } } };
|
|
551
|
-
expect(() => translator.translate(filter)).toThrow();
|
|
552
|
-
});
|
|
553
|
-
});
|
|
554
|
-
|
|
555
|
-
describe('special values', () => {
|
|
556
|
-
it('handles boolean values', () => {
|
|
557
|
-
const filter: OpenSearchVectorFilter = { active: true, disabled: false };
|
|
558
|
-
expect(translator.translate(filter)).toEqual({
|
|
559
|
-
bool: {
|
|
560
|
-
must: [{ term: { 'metadata.active': true } }, { term: { 'metadata.disabled': false } }],
|
|
561
|
-
},
|
|
562
|
-
});
|
|
563
|
-
});
|
|
564
|
-
|
|
565
|
-
it('handles null values', () => {
|
|
566
|
-
const filter: OpenSearchVectorFilter = { field: null };
|
|
567
|
-
expect(translator.translate(filter)).toEqual({
|
|
568
|
-
term: { 'metadata.field': null },
|
|
569
|
-
});
|
|
570
|
-
});
|
|
571
|
-
});
|
|
572
|
-
|
|
573
|
-
describe('array handling', () => {
|
|
574
|
-
it('translates array values to terms query', () => {
|
|
575
|
-
const filter: OpenSearchVectorFilter = { tags: ['premium', 'new'] };
|
|
576
|
-
expect(translator.translate(filter)).toEqual({
|
|
577
|
-
terms: { 'metadata.tags.keyword': ['premium', 'new'] },
|
|
578
|
-
});
|
|
579
|
-
});
|
|
580
|
-
|
|
581
|
-
it('translates numeric array values to terms query', () => {
|
|
582
|
-
const filter: OpenSearchVectorFilter = { scores: [90, 95, 100] };
|
|
583
|
-
expect(translator.translate(filter)).toEqual({
|
|
584
|
-
terms: { 'metadata.scores': [90, 95, 100] },
|
|
585
|
-
});
|
|
586
|
-
});
|
|
587
|
-
|
|
588
|
-
it('translates empty array values to empty terms query', () => {
|
|
589
|
-
const filter: OpenSearchVectorFilter = { tags: [] };
|
|
590
|
-
expect(translator.translate(filter)).toEqual({
|
|
591
|
-
terms: { 'metadata.tags.keyword': [] },
|
|
592
|
-
});
|
|
593
|
-
});
|
|
594
|
-
|
|
595
|
-
it('handles nested arrays in objects', () => {
|
|
596
|
-
const filter = { user: { interests: ['sports', 'music'] } };
|
|
597
|
-
expect(translator.translate(filter)).toEqual({
|
|
598
|
-
bool: {
|
|
599
|
-
must: [
|
|
600
|
-
{
|
|
601
|
-
term: {
|
|
602
|
-
'metadata.user.interests.keyword': ['sports', 'music'],
|
|
603
|
-
},
|
|
604
|
-
},
|
|
605
|
-
],
|
|
606
|
-
},
|
|
607
|
-
});
|
|
608
|
-
});
|
|
609
|
-
});
|
|
610
|
-
|
|
611
|
-
describe('field type handling', () => {
|
|
612
|
-
it('adds .keyword suffix for string fields', () => {
|
|
613
|
-
const filter: OpenSearchVectorFilter = { field: 'value' };
|
|
614
|
-
expect(translator.translate(filter)).toEqual({
|
|
615
|
-
term: { 'metadata.field.keyword': 'value' },
|
|
616
|
-
});
|
|
617
|
-
});
|
|
618
|
-
|
|
619
|
-
it('adds .keyword suffix for string array fields', () => {
|
|
620
|
-
const filter: OpenSearchVectorFilter = { field: { $in: ['value1', 'value2'] } };
|
|
621
|
-
expect(translator.translate(filter)).toEqual({
|
|
622
|
-
terms: { 'metadata.field.keyword': ['value1', 'value2'] },
|
|
623
|
-
});
|
|
624
|
-
});
|
|
625
|
-
|
|
626
|
-
it('does not add .keyword suffix for numeric fields', () => {
|
|
627
|
-
const filter: OpenSearchVectorFilter = { field: 123 };
|
|
628
|
-
expect(translator.translate(filter)).toEqual({
|
|
629
|
-
term: { 'metadata.field': 123 },
|
|
630
|
-
});
|
|
631
|
-
});
|
|
632
|
-
|
|
633
|
-
it('does not add .keyword suffix for numeric array fields', () => {
|
|
634
|
-
const filter: OpenSearchVectorFilter = { field: { $in: [1, 2, 3] } };
|
|
635
|
-
expect(translator.translate(filter)).toEqual({
|
|
636
|
-
terms: { 'metadata.field': [1, 2, 3] },
|
|
637
|
-
});
|
|
638
|
-
});
|
|
639
|
-
|
|
640
|
-
it('handles mixed field types in complex queries', () => {
|
|
641
|
-
const filter: OpenSearchVectorFilter = {
|
|
642
|
-
$and: [
|
|
643
|
-
{ textField: 'value' },
|
|
644
|
-
{ numericField: 123 },
|
|
645
|
-
{ arrayField: { $in: ['a', 'b'] } },
|
|
646
|
-
{ numericArray: { $in: [1, 2] } },
|
|
647
|
-
],
|
|
648
|
-
};
|
|
649
|
-
expect(translator.translate(filter)).toEqual({
|
|
650
|
-
bool: {
|
|
651
|
-
must: [
|
|
652
|
-
{ term: { 'metadata.textField.keyword': 'value' } },
|
|
653
|
-
{ term: { 'metadata.numericField': 123 } },
|
|
654
|
-
{ terms: { 'metadata.arrayField.keyword': ['a', 'b'] } },
|
|
655
|
-
{ terms: { 'metadata.numericArray': [1, 2] } },
|
|
656
|
-
],
|
|
657
|
-
},
|
|
658
|
-
});
|
|
659
|
-
});
|
|
660
|
-
});
|
|
661
|
-
});
|