@mastra/pg 0.1.6-alpha.1 → 0.1.6-alpha.4
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/.turbo/turbo-build.log +11 -6
- package/CHANGELOG.md +32 -0
- package/README.md +22 -17
- package/dist/_tsup-dts-rollup.d.cts +329 -0
- package/dist/_tsup-dts-rollup.d.ts +45 -20
- package/dist/index.cjs +1050 -0
- package/dist/index.d.cts +4 -0
- package/dist/index.js +24 -17
- package/package.json +7 -3
- package/src/storage/index.ts +2 -2
- package/src/vector/filter.ts +5 -5
- package/src/vector/index.test.ts +696 -314
- package/src/vector/index.ts +62 -42
- package/src/vector/performance.helpers.ts +1 -1
- package/src/vector/sql-builder.ts +10 -6
- package/src/vector/vector.performance.test.ts +14 -17
package/src/vector/index.test.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { describe, it, expect, beforeAll, afterAll, beforeEach, afterEach } from 'vitest';
|
|
1
|
+
import { describe, it, expect, beforeAll, afterAll, beforeEach, afterEach, vi } from 'vitest';
|
|
2
2
|
|
|
3
3
|
import { PgVector } from '.';
|
|
4
4
|
|
|
@@ -27,32 +27,39 @@ describe('PgVector', () => {
|
|
|
27
27
|
});
|
|
28
28
|
|
|
29
29
|
it('should create a new vector table with specified dimensions', async () => {
|
|
30
|
-
await vectorDB.createIndex(testIndexName, 3);
|
|
30
|
+
await vectorDB.createIndex({ indexName: testIndexName, dimension: 3 });
|
|
31
31
|
const stats = await vectorDB.describeIndex(testIndexName);
|
|
32
32
|
expect(stats?.dimension).toBe(3);
|
|
33
33
|
expect(stats?.count).toBe(0);
|
|
34
34
|
});
|
|
35
35
|
|
|
36
36
|
it('should create index with specified metric', async () => {
|
|
37
|
-
await vectorDB.createIndex(testIndexName2, 3, 'euclidean');
|
|
37
|
+
await vectorDB.createIndex({ indexName: testIndexName2, dimension: 3, metric: 'euclidean' });
|
|
38
38
|
const stats = await vectorDB.describeIndex(testIndexName2);
|
|
39
39
|
expect(stats.metric).toBe('euclidean');
|
|
40
40
|
});
|
|
41
41
|
|
|
42
42
|
it('should throw error if dimension is invalid', async () => {
|
|
43
|
-
await expect(vectorDB.createIndex(
|
|
43
|
+
await expect(vectorDB.createIndex({ indexName: 'testIndexNameFail', dimension: 0 })).rejects.toThrow();
|
|
44
44
|
});
|
|
45
45
|
|
|
46
46
|
it('should create index with flat type', async () => {
|
|
47
|
-
await vectorDB.createIndex(
|
|
47
|
+
await vectorDB.createIndex({
|
|
48
|
+
indexName: testIndexName2,
|
|
49
|
+
dimension: 3,
|
|
50
|
+
metric: 'cosine',
|
|
51
|
+
indexConfig: { type: 'flat' },
|
|
52
|
+
});
|
|
48
53
|
const stats = await vectorDB.describeIndex(testIndexName2);
|
|
49
54
|
expect(stats.type).toBe('flat');
|
|
50
55
|
});
|
|
51
56
|
|
|
52
57
|
it('should create index with hnsw type', async () => {
|
|
53
|
-
await vectorDB.createIndex(
|
|
54
|
-
|
|
55
|
-
|
|
58
|
+
await vectorDB.createIndex({
|
|
59
|
+
indexName: testIndexName2,
|
|
60
|
+
dimension: 3,
|
|
61
|
+
metric: 'cosine',
|
|
62
|
+
indexConfig: { type: 'hnsw', hnsw: { m: 16, efConstruction: 64 } }, // Any reasonable values work
|
|
56
63
|
});
|
|
57
64
|
const stats = await vectorDB.describeIndex(testIndexName2);
|
|
58
65
|
expect(stats.type).toBe('hnsw');
|
|
@@ -60,9 +67,11 @@ describe('PgVector', () => {
|
|
|
60
67
|
});
|
|
61
68
|
|
|
62
69
|
it('should create index with ivfflat type and lists', async () => {
|
|
63
|
-
await vectorDB.createIndex(
|
|
64
|
-
|
|
65
|
-
|
|
70
|
+
await vectorDB.createIndex({
|
|
71
|
+
indexName: testIndexName2,
|
|
72
|
+
dimension: 3,
|
|
73
|
+
metric: 'cosine',
|
|
74
|
+
indexConfig: { type: 'ivfflat', ivf: { lists: 100 } },
|
|
66
75
|
});
|
|
67
76
|
const stats = await vectorDB.describeIndex(testIndexName2);
|
|
68
77
|
expect(stats.type).toBe('ivfflat');
|
|
@@ -73,7 +82,7 @@ describe('PgVector', () => {
|
|
|
73
82
|
describe('listIndexes', () => {
|
|
74
83
|
const indexName = 'test_query_3';
|
|
75
84
|
beforeAll(async () => {
|
|
76
|
-
await vectorDB.createIndex(indexName, 3);
|
|
85
|
+
await vectorDB.createIndex({ indexName, dimension: 3 });
|
|
77
86
|
});
|
|
78
87
|
|
|
79
88
|
afterAll(async () => {
|
|
@@ -95,7 +104,7 @@ describe('PgVector', () => {
|
|
|
95
104
|
describe('describeIndex', () => {
|
|
96
105
|
const indexName = 'test_query_4';
|
|
97
106
|
beforeAll(async () => {
|
|
98
|
-
await vectorDB.createIndex(indexName, 3);
|
|
107
|
+
await vectorDB.createIndex({ indexName, dimension: 3 });
|
|
99
108
|
});
|
|
100
109
|
|
|
101
110
|
afterAll(async () => {
|
|
@@ -103,12 +112,12 @@ describe('PgVector', () => {
|
|
|
103
112
|
});
|
|
104
113
|
|
|
105
114
|
it('should return correct index stats', async () => {
|
|
106
|
-
await vectorDB.createIndex(indexName, 3, 'cosine');
|
|
115
|
+
await vectorDB.createIndex({ indexName, dimension: 3, metric: 'cosine' });
|
|
107
116
|
const vectors = [
|
|
108
117
|
[1, 2, 3],
|
|
109
118
|
[4, 5, 6],
|
|
110
119
|
];
|
|
111
|
-
await vectorDB.upsert(indexName, vectors);
|
|
120
|
+
await vectorDB.upsert({ indexName, vectors });
|
|
112
121
|
|
|
113
122
|
const stats = await vectorDB.describeIndex(indexName);
|
|
114
123
|
expect(stats).toEqual({
|
|
@@ -126,13 +135,50 @@ describe('PgVector', () => {
|
|
|
126
135
|
await expect(vectorDB.describeIndex('non_existent')).rejects.toThrow();
|
|
127
136
|
});
|
|
128
137
|
});
|
|
138
|
+
|
|
139
|
+
describe('buildIndex', () => {
|
|
140
|
+
const indexName = 'test_build_index';
|
|
141
|
+
beforeAll(async () => {
|
|
142
|
+
await vectorDB.createIndex({ indexName, dimension: 3 });
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
afterAll(async () => {
|
|
146
|
+
await vectorDB.deleteIndex(indexName);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('should build index with specified metric and config', async () => {
|
|
150
|
+
await vectorDB.buildIndex({
|
|
151
|
+
indexName,
|
|
152
|
+
metric: 'cosine',
|
|
153
|
+
indexConfig: { type: 'hnsw', hnsw: { m: 16, efConstruction: 64 } },
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
const stats = await vectorDB.describeIndex(indexName);
|
|
157
|
+
expect(stats.type).toBe('hnsw');
|
|
158
|
+
expect(stats.metric).toBe('cosine');
|
|
159
|
+
expect(stats.config.m).toBe(16);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('should build ivfflat index with specified lists', async () => {
|
|
163
|
+
await vectorDB.buildIndex({
|
|
164
|
+
indexName,
|
|
165
|
+
metric: 'euclidean',
|
|
166
|
+
indexConfig: { type: 'ivfflat', ivf: { lists: 100 } },
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
const stats = await vectorDB.describeIndex(indexName);
|
|
170
|
+
expect(stats.type).toBe('ivfflat');
|
|
171
|
+
expect(stats.metric).toBe('euclidean');
|
|
172
|
+
expect(stats.config.lists).toBe(100);
|
|
173
|
+
});
|
|
174
|
+
});
|
|
129
175
|
});
|
|
130
176
|
|
|
131
177
|
// Vector Operations Tests
|
|
132
178
|
describe('Vector Operations', () => {
|
|
133
179
|
describe('upsert', () => {
|
|
134
180
|
beforeEach(async () => {
|
|
135
|
-
await vectorDB.createIndex(testIndexName, 3);
|
|
181
|
+
await vectorDB.createIndex({ indexName: testIndexName, dimension: 3 });
|
|
136
182
|
});
|
|
137
183
|
|
|
138
184
|
afterEach(async () => {
|
|
@@ -144,7 +190,7 @@ describe('PgVector', () => {
|
|
|
144
190
|
[1, 2, 3],
|
|
145
191
|
[4, 5, 6],
|
|
146
192
|
];
|
|
147
|
-
const ids = await vectorDB.upsert(testIndexName, vectors);
|
|
193
|
+
const ids = await vectorDB.upsert({ indexName: testIndexName, vectors });
|
|
148
194
|
|
|
149
195
|
expect(ids).toHaveLength(2);
|
|
150
196
|
const stats = await vectorDB.describeIndex(testIndexName);
|
|
@@ -154,13 +200,18 @@ describe('PgVector', () => {
|
|
|
154
200
|
it('should update existing vectors', async () => {
|
|
155
201
|
const vectors = [[1, 2, 3]];
|
|
156
202
|
const metadata = [{ test: 'initial' }];
|
|
157
|
-
const [id] = await vectorDB.upsert(testIndexName, vectors, metadata);
|
|
203
|
+
const [id] = await vectorDB.upsert({ indexName: testIndexName, vectors, metadata });
|
|
158
204
|
|
|
159
205
|
const updatedVectors = [[4, 5, 6]];
|
|
160
206
|
const updatedMetadata = [{ test: 'updated' }];
|
|
161
|
-
await vectorDB.upsert(
|
|
207
|
+
await vectorDB.upsert({
|
|
208
|
+
indexName: testIndexName,
|
|
209
|
+
vectors: updatedVectors,
|
|
210
|
+
metadata: updatedMetadata,
|
|
211
|
+
ids: [id!],
|
|
212
|
+
});
|
|
162
213
|
|
|
163
|
-
const results = await vectorDB.query(testIndexName, [4, 5, 6], 1);
|
|
214
|
+
const results = await vectorDB.query({ indexName: testIndexName, queryVector: [4, 5, 6], topK: 1 });
|
|
164
215
|
expect(results[0]?.id).toBe(id);
|
|
165
216
|
expect(results[0]?.metadata).toEqual({ test: 'updated' });
|
|
166
217
|
});
|
|
@@ -169,15 +220,15 @@ describe('PgVector', () => {
|
|
|
169
220
|
const vectors = [[1, 2, 3]];
|
|
170
221
|
const metadata = [{ test: 'value', num: 123 }];
|
|
171
222
|
|
|
172
|
-
await vectorDB.upsert(testIndexName, vectors, metadata);
|
|
173
|
-
const results = await vectorDB.query(testIndexName, [1, 2, 3], 1);
|
|
223
|
+
await vectorDB.upsert({ indexName: testIndexName, vectors, metadata });
|
|
224
|
+
const results = await vectorDB.query({ indexName: testIndexName, queryVector: [1, 2, 3], topK: 1 });
|
|
174
225
|
|
|
175
226
|
expect(results[0]?.metadata).toEqual(metadata[0]);
|
|
176
227
|
});
|
|
177
228
|
|
|
178
229
|
it('should throw error if vector dimensions dont match', async () => {
|
|
179
230
|
const vectors = [[1, 2, 3, 4]]; // 4D vector for 3D index
|
|
180
|
-
await expect(vectorDB.upsert(testIndexName, vectors)).rejects.toThrow();
|
|
231
|
+
await expect(vectorDB.upsert({ indexName: testIndexName, vectors })).rejects.toThrow();
|
|
181
232
|
});
|
|
182
233
|
});
|
|
183
234
|
|
|
@@ -190,7 +241,7 @@ describe('PgVector', () => {
|
|
|
190
241
|
} catch {
|
|
191
242
|
// Ignore if doesn't exist
|
|
192
243
|
}
|
|
193
|
-
await vectorDB.createIndex(indexName, 3);
|
|
244
|
+
await vectorDB.createIndex({ indexName, dimension: 3 });
|
|
194
245
|
});
|
|
195
246
|
|
|
196
247
|
beforeEach(async () => {
|
|
@@ -205,7 +256,7 @@ describe('PgVector', () => {
|
|
|
205
256
|
{ type: 'b', value: 2 },
|
|
206
257
|
{ type: 'a', value: 3 },
|
|
207
258
|
];
|
|
208
|
-
await vectorDB.upsert(indexName, vectors, metadata);
|
|
259
|
+
await vectorDB.upsert({ indexName, vectors, metadata });
|
|
209
260
|
});
|
|
210
261
|
|
|
211
262
|
afterAll(async () => {
|
|
@@ -213,25 +264,30 @@ describe('PgVector', () => {
|
|
|
213
264
|
});
|
|
214
265
|
|
|
215
266
|
it('should return closest vectors', async () => {
|
|
216
|
-
const results = await vectorDB.query(indexName, [1, 0, 0], 1);
|
|
267
|
+
const results = await vectorDB.query({ indexName, queryVector: [1, 0, 0], topK: 1 });
|
|
217
268
|
expect(results).toHaveLength(1);
|
|
218
269
|
expect(results[0]?.vector).toBe(undefined);
|
|
219
270
|
expect(results[0]?.score).toBeCloseTo(1, 5);
|
|
220
271
|
});
|
|
221
272
|
|
|
222
273
|
it('should return vector with result', async () => {
|
|
223
|
-
const results = await vectorDB.query(indexName, [1, 0, 0], 1,
|
|
274
|
+
const results = await vectorDB.query({ indexName, queryVector: [1, 0, 0], topK: 1, includeVector: true });
|
|
224
275
|
expect(results).toHaveLength(1);
|
|
225
276
|
expect(results[0]?.vector).toStrictEqual([1, 0, 0]);
|
|
226
277
|
});
|
|
227
278
|
|
|
228
279
|
it('should respect topK parameter', async () => {
|
|
229
|
-
const results = await vectorDB.query(indexName, [1, 0, 0], 2);
|
|
280
|
+
const results = await vectorDB.query({ indexName, queryVector: [1, 0, 0], topK: 2 });
|
|
230
281
|
expect(results).toHaveLength(2);
|
|
231
282
|
});
|
|
232
283
|
|
|
233
284
|
it('should handle filters correctly', async () => {
|
|
234
|
-
const results = await vectorDB.query(
|
|
285
|
+
const results = await vectorDB.query({
|
|
286
|
+
indexName,
|
|
287
|
+
queryVector: [1, 0, 0],
|
|
288
|
+
topK: 10,
|
|
289
|
+
filter: { type: 'a' },
|
|
290
|
+
});
|
|
235
291
|
|
|
236
292
|
expect(results).toHaveLength(1);
|
|
237
293
|
results.forEach(result => {
|
|
@@ -251,7 +307,7 @@ describe('PgVector', () => {
|
|
|
251
307
|
} catch {
|
|
252
308
|
// Ignore if doesn't exist
|
|
253
309
|
}
|
|
254
|
-
await vectorDB.createIndex(indexName, 3);
|
|
310
|
+
await vectorDB.createIndex({ indexName, dimension: 3 });
|
|
255
311
|
});
|
|
256
312
|
|
|
257
313
|
beforeEach(async () => {
|
|
@@ -301,7 +357,7 @@ describe('PgVector', () => {
|
|
|
301
357
|
{ category: 'clothing', price: 60, tags: ['new'], active: true },
|
|
302
358
|
];
|
|
303
359
|
|
|
304
|
-
await vectorDB.upsert(indexName, vectors, metadata);
|
|
360
|
+
await vectorDB.upsert({ indexName, vectors, metadata });
|
|
305
361
|
});
|
|
306
362
|
|
|
307
363
|
afterAll(async () => {
|
|
@@ -312,26 +368,32 @@ describe('PgVector', () => {
|
|
|
312
368
|
describe('Comparison Operators', () => {
|
|
313
369
|
it('should handle numeric string comparisons', async () => {
|
|
314
370
|
// Insert a record with numeric string
|
|
315
|
-
await vectorDB.upsert(indexName, [[1, 0.1, 0]], [{ numericString: '123' }]);
|
|
371
|
+
await vectorDB.upsert({ indexName, vectors: [[1, 0.1, 0]], metadata: [{ numericString: '123' }] });
|
|
316
372
|
|
|
317
|
-
const results = await vectorDB.query(
|
|
318
|
-
|
|
373
|
+
const results = await vectorDB.query({
|
|
374
|
+
indexName,
|
|
375
|
+
queryVector: [1, 0, 0],
|
|
376
|
+
filter: { numericString: { $gt: '100' } },
|
|
319
377
|
});
|
|
320
378
|
expect(results.length).toBeGreaterThan(0);
|
|
321
379
|
expect(results[0]?.metadata?.numericString).toBe('123');
|
|
322
380
|
});
|
|
323
381
|
|
|
324
382
|
it('should filter with $gt operator', async () => {
|
|
325
|
-
const results = await vectorDB.query(
|
|
326
|
-
|
|
383
|
+
const results = await vectorDB.query({
|
|
384
|
+
indexName,
|
|
385
|
+
queryVector: [1, 0, 0],
|
|
386
|
+
filter: { price: { $gt: 75 } },
|
|
327
387
|
});
|
|
328
388
|
expect(results).toHaveLength(1);
|
|
329
389
|
expect(results[0]?.metadata?.price).toBe(100);
|
|
330
390
|
});
|
|
331
391
|
|
|
332
392
|
it('should filter with $lte operator', async () => {
|
|
333
|
-
const results = await vectorDB.query(
|
|
334
|
-
|
|
393
|
+
const results = await vectorDB.query({
|
|
394
|
+
indexName,
|
|
395
|
+
queryVector: [1, 0, 0],
|
|
396
|
+
filter: { price: { $lte: 50 } },
|
|
335
397
|
});
|
|
336
398
|
expect(results).toHaveLength(2);
|
|
337
399
|
results.forEach(result => {
|
|
@@ -340,8 +402,10 @@ describe('PgVector', () => {
|
|
|
340
402
|
});
|
|
341
403
|
|
|
342
404
|
it('should filter with lt operator', async () => {
|
|
343
|
-
const results = await vectorDB.query(
|
|
344
|
-
|
|
405
|
+
const results = await vectorDB.query({
|
|
406
|
+
indexName,
|
|
407
|
+
queryVector: [1, 0, 0],
|
|
408
|
+
filter: { price: { $lt: 60 } },
|
|
345
409
|
});
|
|
346
410
|
expect(results).toHaveLength(2);
|
|
347
411
|
results.forEach(result => {
|
|
@@ -350,8 +414,10 @@ describe('PgVector', () => {
|
|
|
350
414
|
});
|
|
351
415
|
|
|
352
416
|
it('should filter with gte operator', async () => {
|
|
353
|
-
const results = await vectorDB.query(
|
|
354
|
-
|
|
417
|
+
const results = await vectorDB.query({
|
|
418
|
+
indexName,
|
|
419
|
+
queryVector: [1, 0, 0],
|
|
420
|
+
filter: { price: { $gte: 75 } },
|
|
355
421
|
});
|
|
356
422
|
expect(results).toHaveLength(2);
|
|
357
423
|
results.forEach(result => {
|
|
@@ -360,8 +426,10 @@ describe('PgVector', () => {
|
|
|
360
426
|
});
|
|
361
427
|
|
|
362
428
|
it('should filter with ne operator', async () => {
|
|
363
|
-
const results = await vectorDB.query(
|
|
364
|
-
|
|
429
|
+
const results = await vectorDB.query({
|
|
430
|
+
indexName,
|
|
431
|
+
queryVector: [1, 0, 0],
|
|
432
|
+
filter: { category: { $ne: 'electronics' } },
|
|
365
433
|
});
|
|
366
434
|
expect(results.length).toBeGreaterThan(0);
|
|
367
435
|
results.forEach(result => {
|
|
@@ -370,8 +438,10 @@ describe('PgVector', () => {
|
|
|
370
438
|
});
|
|
371
439
|
|
|
372
440
|
it('should filter with $gt and $lte operator', async () => {
|
|
373
|
-
const results = await vectorDB.query(
|
|
374
|
-
|
|
441
|
+
const results = await vectorDB.query({
|
|
442
|
+
indexName,
|
|
443
|
+
queryVector: [1, 0, 0],
|
|
444
|
+
filter: { price: { $gt: 70, $lte: 100 } },
|
|
375
445
|
});
|
|
376
446
|
expect(results).toHaveLength(2);
|
|
377
447
|
results.forEach(result => {
|
|
@@ -384,8 +454,10 @@ describe('PgVector', () => {
|
|
|
384
454
|
// Array Operator Tests
|
|
385
455
|
describe('Array Operators', () => {
|
|
386
456
|
it('should filter with $in operator', async () => {
|
|
387
|
-
const results = await vectorDB.query(
|
|
388
|
-
|
|
457
|
+
const results = await vectorDB.query({
|
|
458
|
+
indexName,
|
|
459
|
+
queryVector: [1, 0, 0],
|
|
460
|
+
filter: { category: { $in: ['electronics', 'clothing'] } },
|
|
389
461
|
});
|
|
390
462
|
expect(results).toHaveLength(3);
|
|
391
463
|
results.forEach(result => {
|
|
@@ -394,8 +466,10 @@ describe('PgVector', () => {
|
|
|
394
466
|
});
|
|
395
467
|
|
|
396
468
|
it('should filter with $nin operator', async () => {
|
|
397
|
-
const results = await vectorDB.query(
|
|
398
|
-
|
|
469
|
+
const results = await vectorDB.query({
|
|
470
|
+
indexName,
|
|
471
|
+
queryVector: [1, 0, 0],
|
|
472
|
+
filter: { category: { $nin: ['electronics', 'books'] } },
|
|
399
473
|
});
|
|
400
474
|
expect(results.length).toBeGreaterThan(0);
|
|
401
475
|
results.forEach(result => {
|
|
@@ -405,21 +479,27 @@ describe('PgVector', () => {
|
|
|
405
479
|
|
|
406
480
|
it('should handle empty arrays in in/nin operators', async () => {
|
|
407
481
|
// Should return no results for empty IN
|
|
408
|
-
const resultsIn = await vectorDB.query(
|
|
409
|
-
|
|
482
|
+
const resultsIn = await vectorDB.query({
|
|
483
|
+
indexName,
|
|
484
|
+
queryVector: [1, 0, 0],
|
|
485
|
+
filter: { category: { $in: [] } },
|
|
410
486
|
});
|
|
411
487
|
expect(resultsIn).toHaveLength(0);
|
|
412
488
|
|
|
413
489
|
// Should return all results for empty NIN
|
|
414
|
-
const resultsNin = await vectorDB.query(
|
|
415
|
-
|
|
490
|
+
const resultsNin = await vectorDB.query({
|
|
491
|
+
indexName,
|
|
492
|
+
queryVector: [1, 0, 0],
|
|
493
|
+
filter: { category: { $nin: [] } },
|
|
416
494
|
});
|
|
417
495
|
expect(resultsNin.length).toBeGreaterThan(0);
|
|
418
496
|
});
|
|
419
497
|
|
|
420
498
|
it('should filter with array $contains operator', async () => {
|
|
421
|
-
const results = await vectorDB.query(
|
|
422
|
-
|
|
499
|
+
const results = await vectorDB.query({
|
|
500
|
+
indexName,
|
|
501
|
+
queryVector: [1, 0.1, 0],
|
|
502
|
+
filter: { tags: { $contains: ['new'] } },
|
|
423
503
|
});
|
|
424
504
|
expect(results.length).toBeGreaterThan(0);
|
|
425
505
|
results.forEach(result => {
|
|
@@ -428,12 +508,10 @@ describe('PgVector', () => {
|
|
|
428
508
|
});
|
|
429
509
|
|
|
430
510
|
it('should filter with $elemMatch operator', async () => {
|
|
431
|
-
const results = await vectorDB.query(
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
},
|
|
436
|
-
},
|
|
511
|
+
const results = await vectorDB.query({
|
|
512
|
+
indexName,
|
|
513
|
+
queryVector: [1, 0, 0],
|
|
514
|
+
filter: { tags: { $elemMatch: { $in: ['new', 'premium'] } } },
|
|
437
515
|
});
|
|
438
516
|
expect(results.length).toBeGreaterThan(0);
|
|
439
517
|
results.forEach(result => {
|
|
@@ -442,25 +520,20 @@ describe('PgVector', () => {
|
|
|
442
520
|
});
|
|
443
521
|
|
|
444
522
|
it('should filter with $elemMatch using equality', async () => {
|
|
445
|
-
const results = await vectorDB.query(
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
},
|
|
450
|
-
},
|
|
523
|
+
const results = await vectorDB.query({
|
|
524
|
+
indexName,
|
|
525
|
+
queryVector: [1, 0, 0],
|
|
526
|
+
filter: { tags: { $elemMatch: { $eq: 'sale' } } },
|
|
451
527
|
});
|
|
452
528
|
expect(results).toHaveLength(1);
|
|
453
529
|
expect(results[0]?.metadata?.tags).toContain('sale');
|
|
454
530
|
});
|
|
455
531
|
|
|
456
532
|
it('should filter with $elemMatch using multiple conditions', async () => {
|
|
457
|
-
const results = await vectorDB.query(
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
$lt: 4.5,
|
|
462
|
-
},
|
|
463
|
-
},
|
|
533
|
+
const results = await vectorDB.query({
|
|
534
|
+
indexName,
|
|
535
|
+
queryVector: [1, 0, 0],
|
|
536
|
+
filter: { ratings: { $elemMatch: { $gt: 4, $lt: 4.5 } } },
|
|
464
537
|
});
|
|
465
538
|
expect(results.length).toBeGreaterThan(0);
|
|
466
539
|
results.forEach(result => {
|
|
@@ -470,13 +543,10 @@ describe('PgVector', () => {
|
|
|
470
543
|
});
|
|
471
544
|
|
|
472
545
|
it('should handle complex $elemMatch conditions', async () => {
|
|
473
|
-
const results = await vectorDB.query(
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
count: { $gt: 20 },
|
|
478
|
-
},
|
|
479
|
-
},
|
|
546
|
+
const results = await vectorDB.query({
|
|
547
|
+
indexName,
|
|
548
|
+
queryVector: [1, 0, 0],
|
|
549
|
+
filter: { stock: { $elemMatch: { location: 'A', count: { $gt: 20 } } } },
|
|
480
550
|
});
|
|
481
551
|
expect(results.length).toBeGreaterThan(0);
|
|
482
552
|
results.forEach(result => {
|
|
@@ -486,12 +556,10 @@ describe('PgVector', () => {
|
|
|
486
556
|
});
|
|
487
557
|
|
|
488
558
|
it('should filter with $elemMatch on nested numeric fields', async () => {
|
|
489
|
-
const results = await vectorDB.query(
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
},
|
|
494
|
-
},
|
|
559
|
+
const results = await vectorDB.query({
|
|
560
|
+
indexName,
|
|
561
|
+
queryVector: [1, 0, 0],
|
|
562
|
+
filter: { reviews: { $elemMatch: { score: { $gt: 4 } } } },
|
|
495
563
|
});
|
|
496
564
|
expect(results.length).toBeGreaterThan(0);
|
|
497
565
|
results.forEach(result => {
|
|
@@ -500,13 +568,10 @@ describe('PgVector', () => {
|
|
|
500
568
|
});
|
|
501
569
|
|
|
502
570
|
it('should filter with $elemMatch on multiple nested fields', async () => {
|
|
503
|
-
const results = await vectorDB.query(
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
verified: true,
|
|
508
|
-
},
|
|
509
|
-
},
|
|
571
|
+
const results = await vectorDB.query({
|
|
572
|
+
indexName,
|
|
573
|
+
queryVector: [1, 0, 0],
|
|
574
|
+
filter: { reviews: { $elemMatch: { score: { $gte: 4 }, verified: true } } },
|
|
510
575
|
});
|
|
511
576
|
expect(results.length).toBeGreaterThan(0);
|
|
512
577
|
results.forEach(result => {
|
|
@@ -515,31 +580,29 @@ describe('PgVector', () => {
|
|
|
515
580
|
});
|
|
516
581
|
|
|
517
582
|
it('should filter with $elemMatch on exact string match', async () => {
|
|
518
|
-
const results = await vectorDB.query(
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
},
|
|
523
|
-
},
|
|
583
|
+
const results = await vectorDB.query({
|
|
584
|
+
indexName,
|
|
585
|
+
queryVector: [1, 0, 0],
|
|
586
|
+
filter: { reviews: { $elemMatch: { user: 'alice' } } },
|
|
524
587
|
});
|
|
525
588
|
expect(results).toHaveLength(1);
|
|
526
589
|
expect(results[0].metadata?.reviews.some(r => r.user === 'alice')).toBe(true);
|
|
527
590
|
});
|
|
528
591
|
|
|
529
592
|
it('should handle $elemMatch with no matches', async () => {
|
|
530
|
-
const results = await vectorDB.query(
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
},
|
|
535
|
-
},
|
|
593
|
+
const results = await vectorDB.query({
|
|
594
|
+
indexName,
|
|
595
|
+
queryVector: [1, 0, 0],
|
|
596
|
+
filter: { reviews: { $elemMatch: { score: 10 } } },
|
|
536
597
|
});
|
|
537
598
|
expect(results).toHaveLength(0);
|
|
538
599
|
});
|
|
539
600
|
|
|
540
601
|
it('should filter with $all operator', async () => {
|
|
541
|
-
const results = await vectorDB.query(
|
|
542
|
-
|
|
602
|
+
const results = await vectorDB.query({
|
|
603
|
+
indexName,
|
|
604
|
+
queryVector: [1, 0, 0],
|
|
605
|
+
filter: { tags: { $all: ['used', 'sale'] } },
|
|
543
606
|
});
|
|
544
607
|
expect(results).toHaveLength(1);
|
|
545
608
|
results.forEach(result => {
|
|
@@ -549,8 +612,10 @@ describe('PgVector', () => {
|
|
|
549
612
|
});
|
|
550
613
|
|
|
551
614
|
it('should filter with $all using single value', async () => {
|
|
552
|
-
const results = await vectorDB.query(
|
|
553
|
-
|
|
615
|
+
const results = await vectorDB.query({
|
|
616
|
+
indexName,
|
|
617
|
+
queryVector: [1, 0, 0],
|
|
618
|
+
filter: { tags: { $all: ['new'] } },
|
|
554
619
|
});
|
|
555
620
|
expect(results.length).toBeGreaterThan(0);
|
|
556
621
|
results.forEach(result => {
|
|
@@ -559,26 +624,32 @@ describe('PgVector', () => {
|
|
|
559
624
|
});
|
|
560
625
|
|
|
561
626
|
it('should handle empty array for $all', async () => {
|
|
562
|
-
const results = await vectorDB.query(
|
|
563
|
-
|
|
627
|
+
const results = await vectorDB.query({
|
|
628
|
+
indexName,
|
|
629
|
+
queryVector: [1, 0, 0],
|
|
630
|
+
filter: { tags: { $all: [] } },
|
|
564
631
|
});
|
|
565
632
|
expect(results).toHaveLength(0);
|
|
566
633
|
});
|
|
567
634
|
|
|
568
635
|
it('should handle non-array field $all', async () => {
|
|
569
636
|
// First insert a record with non-array field
|
|
570
|
-
await vectorDB.upsert(indexName, [[1, 0.1, 0]], [{ tags: 'not-an-array' }]);
|
|
637
|
+
await vectorDB.upsert({ indexName, vectors: [[1, 0.1, 0]], metadata: [{ tags: 'not-an-array' }] });
|
|
571
638
|
|
|
572
|
-
const results = await vectorDB.query(
|
|
573
|
-
|
|
639
|
+
const results = await vectorDB.query({
|
|
640
|
+
indexName,
|
|
641
|
+
queryVector: [1, 0, 0],
|
|
642
|
+
filter: { tags: { $all: ['value'] } },
|
|
574
643
|
});
|
|
575
644
|
expect(results).toHaveLength(0);
|
|
576
645
|
});
|
|
577
646
|
|
|
578
647
|
// Contains Operator Tests
|
|
579
648
|
it('should filter with contains operator for exact field match', async () => {
|
|
580
|
-
const results = await vectorDB.query(
|
|
581
|
-
|
|
649
|
+
const results = await vectorDB.query({
|
|
650
|
+
indexName,
|
|
651
|
+
queryVector: [1, 0.1, 0],
|
|
652
|
+
filter: { category: { $contains: 'electronics' } },
|
|
582
653
|
});
|
|
583
654
|
expect(results.length).toBeGreaterThan(0);
|
|
584
655
|
results.forEach(result => {
|
|
@@ -588,19 +659,21 @@ describe('PgVector', () => {
|
|
|
588
659
|
|
|
589
660
|
it('should filter with $contains operator for nested objects', async () => {
|
|
590
661
|
// First insert a record with nested object
|
|
591
|
-
await vectorDB.upsert(
|
|
662
|
+
await vectorDB.upsert({
|
|
592
663
|
indexName,
|
|
593
|
-
[[1, 0.1, 0]],
|
|
594
|
-
[
|
|
664
|
+
vectors: [[1, 0.1, 0]],
|
|
665
|
+
metadata: [
|
|
595
666
|
{
|
|
596
667
|
details: { color: 'red', size: 'large' },
|
|
597
668
|
category: 'clothing',
|
|
598
669
|
},
|
|
599
670
|
],
|
|
600
|
-
);
|
|
671
|
+
});
|
|
601
672
|
|
|
602
|
-
const results = await vectorDB.query(
|
|
603
|
-
|
|
673
|
+
const results = await vectorDB.query({
|
|
674
|
+
indexName,
|
|
675
|
+
queryVector: [1, 0.1, 0],
|
|
676
|
+
filter: { details: { $contains: { color: 'red' } } },
|
|
604
677
|
});
|
|
605
678
|
expect(results.length).toBeGreaterThan(0);
|
|
606
679
|
results.forEach(result => {
|
|
@@ -610,37 +683,47 @@ describe('PgVector', () => {
|
|
|
610
683
|
|
|
611
684
|
// String Pattern Tests
|
|
612
685
|
it('should handle exact string matches', async () => {
|
|
613
|
-
const results = await vectorDB.query(
|
|
614
|
-
|
|
686
|
+
const results = await vectorDB.query({
|
|
687
|
+
indexName,
|
|
688
|
+
queryVector: [1, 0, 0],
|
|
689
|
+
filter: { category: 'electronics' },
|
|
615
690
|
});
|
|
616
691
|
expect(results).toHaveLength(2);
|
|
617
692
|
});
|
|
618
693
|
|
|
619
694
|
it('should handle case-sensitive string matches', async () => {
|
|
620
|
-
const results = await vectorDB.query(
|
|
621
|
-
|
|
695
|
+
const results = await vectorDB.query({
|
|
696
|
+
indexName,
|
|
697
|
+
queryVector: [1, 0, 0],
|
|
698
|
+
filter: { category: 'ELECTRONICS' },
|
|
622
699
|
});
|
|
623
700
|
expect(results).toHaveLength(0);
|
|
624
701
|
});
|
|
625
702
|
it('should filter arrays by size', async () => {
|
|
626
|
-
const results = await vectorDB.query(
|
|
627
|
-
|
|
703
|
+
const results = await vectorDB.query({
|
|
704
|
+
indexName,
|
|
705
|
+
queryVector: [1, 0, 0],
|
|
706
|
+
filter: { ratings: { $size: 3 } },
|
|
628
707
|
});
|
|
629
708
|
expect(results.length).toBeGreaterThan(0);
|
|
630
709
|
results.forEach(result => {
|
|
631
710
|
expect(result.metadata?.ratings).toHaveLength(3);
|
|
632
711
|
});
|
|
633
712
|
|
|
634
|
-
const noResults = await vectorDB.query(
|
|
635
|
-
|
|
713
|
+
const noResults = await vectorDB.query({
|
|
714
|
+
indexName,
|
|
715
|
+
queryVector: [1, 0, 0],
|
|
716
|
+
filter: { ratings: { $size: 10 } },
|
|
636
717
|
});
|
|
637
718
|
expect(noResults).toHaveLength(0);
|
|
638
719
|
});
|
|
639
720
|
|
|
640
721
|
it('should handle $size with nested arrays', async () => {
|
|
641
|
-
await vectorDB.upsert(indexName, [[1, 0.1, 0]], [{ nested: { array: [1, 2, 3, 4] } }]);
|
|
642
|
-
const results = await vectorDB.query(
|
|
643
|
-
|
|
722
|
+
await vectorDB.upsert({ indexName, vectors: [[1, 0.1, 0]], metadata: [{ nested: { array: [1, 2, 3, 4] } }] });
|
|
723
|
+
const results = await vectorDB.query({
|
|
724
|
+
indexName,
|
|
725
|
+
queryVector: [1, 0, 0],
|
|
726
|
+
filter: { 'nested.array': { $size: 4 } },
|
|
644
727
|
});
|
|
645
728
|
expect(results.length).toBeGreaterThan(0);
|
|
646
729
|
results.forEach(result => {
|
|
@@ -652,8 +735,10 @@ describe('PgVector', () => {
|
|
|
652
735
|
// Logical Operator Tests
|
|
653
736
|
describe('Logical Operators', () => {
|
|
654
737
|
it('should handle AND filter conditions', async () => {
|
|
655
|
-
const results = await vectorDB.query(
|
|
656
|
-
|
|
738
|
+
const results = await vectorDB.query({
|
|
739
|
+
indexName,
|
|
740
|
+
queryVector: [1, 0, 0],
|
|
741
|
+
filter: { $and: [{ category: { $eq: 'electronics' } }, { price: { $gt: 75 } }] },
|
|
657
742
|
});
|
|
658
743
|
expect(results).toHaveLength(1);
|
|
659
744
|
expect(results[0]?.metadata?.category).toBe('electronics');
|
|
@@ -661,8 +746,10 @@ describe('PgVector', () => {
|
|
|
661
746
|
});
|
|
662
747
|
|
|
663
748
|
it('should handle OR filter conditions', async () => {
|
|
664
|
-
const results = await vectorDB.query(
|
|
665
|
-
|
|
749
|
+
const results = await vectorDB.query({
|
|
750
|
+
indexName,
|
|
751
|
+
queryVector: [1, 0, 0],
|
|
752
|
+
filter: { $or: [{ category: { $eq: 'electronics' } }, { category: { $eq: 'books' } }] },
|
|
666
753
|
});
|
|
667
754
|
expect(results.length).toBeGreaterThan(1);
|
|
668
755
|
results.forEach(result => {
|
|
@@ -671,8 +758,10 @@ describe('PgVector', () => {
|
|
|
671
758
|
});
|
|
672
759
|
|
|
673
760
|
it('should handle $not operator', async () => {
|
|
674
|
-
const results = await vectorDB.query(
|
|
675
|
-
|
|
761
|
+
const results = await vectorDB.query({
|
|
762
|
+
indexName,
|
|
763
|
+
queryVector: [1, 0, 0],
|
|
764
|
+
filter: { $not: { category: 'electronics' } },
|
|
676
765
|
});
|
|
677
766
|
expect(results.length).toBeGreaterThan(0);
|
|
678
767
|
results.forEach(result => {
|
|
@@ -681,8 +770,10 @@ describe('PgVector', () => {
|
|
|
681
770
|
});
|
|
682
771
|
|
|
683
772
|
it('should handle $nor operator', async () => {
|
|
684
|
-
const results = await vectorDB.query(
|
|
685
|
-
|
|
773
|
+
const results = await vectorDB.query({
|
|
774
|
+
indexName,
|
|
775
|
+
queryVector: [1, 0, 0],
|
|
776
|
+
filter: { $nor: [{ category: 'electronics' }, { category: 'books' }] },
|
|
686
777
|
});
|
|
687
778
|
expect(results.length).toBeGreaterThan(0);
|
|
688
779
|
results.forEach(result => {
|
|
@@ -691,8 +782,10 @@ describe('PgVector', () => {
|
|
|
691
782
|
});
|
|
692
783
|
|
|
693
784
|
it('should handle nested $not with $or', async () => {
|
|
694
|
-
const results = await vectorDB.query(
|
|
695
|
-
|
|
785
|
+
const results = await vectorDB.query({
|
|
786
|
+
indexName,
|
|
787
|
+
queryVector: [1, 0, 0],
|
|
788
|
+
filter: { $not: { $or: [{ category: 'electronics' }, { category: 'books' }] } },
|
|
696
789
|
});
|
|
697
790
|
expect(results.length).toBeGreaterThan(0);
|
|
698
791
|
results.forEach(result => {
|
|
@@ -701,8 +794,10 @@ describe('PgVector', () => {
|
|
|
701
794
|
});
|
|
702
795
|
|
|
703
796
|
it('should handle $not with comparison operators', async () => {
|
|
704
|
-
const results = await vectorDB.query(
|
|
705
|
-
|
|
797
|
+
const results = await vectorDB.query({
|
|
798
|
+
indexName,
|
|
799
|
+
queryVector: [1, 0, 0],
|
|
800
|
+
filter: { price: { $not: { $gt: 100 } } },
|
|
706
801
|
});
|
|
707
802
|
expect(results.length).toBeGreaterThan(0);
|
|
708
803
|
results.forEach(result => {
|
|
@@ -711,8 +806,10 @@ describe('PgVector', () => {
|
|
|
711
806
|
});
|
|
712
807
|
|
|
713
808
|
it('should handle $not with $in operator', async () => {
|
|
714
|
-
const results = await vectorDB.query(
|
|
715
|
-
|
|
809
|
+
const results = await vectorDB.query({
|
|
810
|
+
indexName,
|
|
811
|
+
queryVector: [1, 0, 0],
|
|
812
|
+
filter: { category: { $not: { $in: ['electronics', 'books'] } } },
|
|
716
813
|
});
|
|
717
814
|
expect(results.length).toBeGreaterThan(0);
|
|
718
815
|
results.forEach(result => {
|
|
@@ -721,8 +818,10 @@ describe('PgVector', () => {
|
|
|
721
818
|
});
|
|
722
819
|
|
|
723
820
|
it('should handle $not with multiple nested conditions', async () => {
|
|
724
|
-
const results = await vectorDB.query(
|
|
725
|
-
|
|
821
|
+
const results = await vectorDB.query({
|
|
822
|
+
indexName,
|
|
823
|
+
queryVector: [1, 0, 0],
|
|
824
|
+
filter: { $not: { $and: [{ category: 'electronics' }, { price: { $gt: 50 } }] } },
|
|
726
825
|
});
|
|
727
826
|
expect(results.length).toBeGreaterThan(0);
|
|
728
827
|
results.forEach(result => {
|
|
@@ -731,15 +830,19 @@ describe('PgVector', () => {
|
|
|
731
830
|
});
|
|
732
831
|
|
|
733
832
|
it('should handle $not with $exists operator', async () => {
|
|
734
|
-
const results = await vectorDB.query(
|
|
735
|
-
|
|
833
|
+
const results = await vectorDB.query({
|
|
834
|
+
indexName,
|
|
835
|
+
queryVector: [1, 0, 0],
|
|
836
|
+
filter: { tags: { $not: { $exists: true } } },
|
|
736
837
|
});
|
|
737
838
|
expect(results.length).toBe(0); // All test data has tags
|
|
738
839
|
});
|
|
739
840
|
|
|
740
841
|
it('should handle $not with array operators', async () => {
|
|
741
|
-
const results = await vectorDB.query(
|
|
742
|
-
|
|
842
|
+
const results = await vectorDB.query({
|
|
843
|
+
indexName,
|
|
844
|
+
queryVector: [1, 0, 0],
|
|
845
|
+
filter: { tags: { $not: { $all: ['new', 'premium'] } } },
|
|
743
846
|
});
|
|
744
847
|
expect(results.length).toBeGreaterThan(0);
|
|
745
848
|
results.forEach(result => {
|
|
@@ -748,16 +851,20 @@ describe('PgVector', () => {
|
|
|
748
851
|
});
|
|
749
852
|
|
|
750
853
|
it('should handle $not with complex nested conditions', async () => {
|
|
751
|
-
const results = await vectorDB.query(
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
854
|
+
const results = await vectorDB.query({
|
|
855
|
+
indexName,
|
|
856
|
+
queryVector: [1, 0, 0],
|
|
857
|
+
filter: {
|
|
858
|
+
$not: {
|
|
859
|
+
$or: [
|
|
860
|
+
{
|
|
861
|
+
$and: [{ category: 'electronics' }, { price: { $gt: 90 } }],
|
|
862
|
+
},
|
|
863
|
+
{
|
|
864
|
+
$and: [{ category: 'books' }, { price: { $lt: 30 } }],
|
|
865
|
+
},
|
|
866
|
+
],
|
|
867
|
+
},
|
|
761
868
|
},
|
|
762
869
|
});
|
|
763
870
|
expect(results.length).toBeGreaterThan(0);
|
|
@@ -769,18 +876,26 @@ describe('PgVector', () => {
|
|
|
769
876
|
});
|
|
770
877
|
|
|
771
878
|
it('should handle $not with empty arrays', async () => {
|
|
772
|
-
const results = await vectorDB.query(
|
|
773
|
-
|
|
879
|
+
const results = await vectorDB.query({
|
|
880
|
+
indexName,
|
|
881
|
+
queryVector: [1, 0, 0],
|
|
882
|
+
filter: { tags: { $not: { $in: [] } } },
|
|
774
883
|
});
|
|
775
884
|
expect(results.length).toBeGreaterThan(0); // Should match all records
|
|
776
885
|
});
|
|
777
886
|
|
|
778
887
|
it('should handle $not with null values', async () => {
|
|
779
888
|
// First insert a record with null value
|
|
780
|
-
await vectorDB.upsert(
|
|
889
|
+
await vectorDB.upsert({
|
|
890
|
+
indexName,
|
|
891
|
+
vectors: [[1, 0.1, 0]],
|
|
892
|
+
metadata: [{ category: null, price: 0 }],
|
|
893
|
+
});
|
|
781
894
|
|
|
782
|
-
const results = await vectorDB.query(
|
|
783
|
-
|
|
895
|
+
const results = await vectorDB.query({
|
|
896
|
+
indexName,
|
|
897
|
+
queryVector: [1, 0, 0],
|
|
898
|
+
filter: { category: { $not: { $eq: null } } },
|
|
784
899
|
});
|
|
785
900
|
expect(results.length).toBeGreaterThan(0);
|
|
786
901
|
results.forEach(result => {
|
|
@@ -789,8 +904,10 @@ describe('PgVector', () => {
|
|
|
789
904
|
});
|
|
790
905
|
|
|
791
906
|
it('should handle $not with boolean values', async () => {
|
|
792
|
-
const results = await vectorDB.query(
|
|
793
|
-
|
|
907
|
+
const results = await vectorDB.query({
|
|
908
|
+
indexName,
|
|
909
|
+
queryVector: [1, 0, 0],
|
|
910
|
+
filter: { active: { $not: { $eq: true } } },
|
|
794
911
|
});
|
|
795
912
|
expect(results.length).toBeGreaterThan(0);
|
|
796
913
|
results.forEach(result => {
|
|
@@ -799,30 +916,42 @@ describe('PgVector', () => {
|
|
|
799
916
|
});
|
|
800
917
|
|
|
801
918
|
it('should handle $not with multiple conditions', async () => {
|
|
802
|
-
const results = await vectorDB.query(
|
|
803
|
-
|
|
919
|
+
const results = await vectorDB.query({
|
|
920
|
+
indexName,
|
|
921
|
+
queryVector: [1, 0, 0],
|
|
922
|
+
filter: { $not: { category: 'electronics', price: { $gt: 50 } } },
|
|
804
923
|
});
|
|
805
924
|
expect(results.length).toBeGreaterThan(0);
|
|
806
925
|
});
|
|
807
926
|
|
|
808
927
|
it('should handle $not with $not operator', async () => {
|
|
809
|
-
const results = await vectorDB.query(
|
|
810
|
-
|
|
928
|
+
const results = await vectorDB.query({
|
|
929
|
+
indexName,
|
|
930
|
+
queryVector: [1, 0, 0],
|
|
931
|
+
filter: { $not: { $not: { category: 'electronics' } } },
|
|
811
932
|
});
|
|
812
933
|
expect(results.length).toBeGreaterThan(0);
|
|
813
934
|
});
|
|
814
935
|
|
|
815
936
|
it('should handle $not in nested fields', async () => {
|
|
816
|
-
await vectorDB.upsert(
|
|
817
|
-
|
|
818
|
-
|
|
937
|
+
await vectorDB.upsert({
|
|
938
|
+
indexName,
|
|
939
|
+
vectors: [[1, 0.1, 0]],
|
|
940
|
+
metadata: [{ user: { profile: { price: 10 } } }],
|
|
941
|
+
});
|
|
942
|
+
const results = await vectorDB.query({
|
|
943
|
+
indexName,
|
|
944
|
+
queryVector: [1, 0, 0],
|
|
945
|
+
filter: { 'user.profile.price': { $not: { $gt: 25 } } },
|
|
819
946
|
});
|
|
820
947
|
expect(results.length).toBe(1);
|
|
821
948
|
});
|
|
822
949
|
|
|
823
950
|
it('should handle $not with multiple operators', async () => {
|
|
824
|
-
const results = await vectorDB.query(
|
|
825
|
-
|
|
951
|
+
const results = await vectorDB.query({
|
|
952
|
+
indexName,
|
|
953
|
+
queryVector: [1, 0, 0],
|
|
954
|
+
filter: { price: { $not: { $gte: 30, $lte: 70 } } },
|
|
826
955
|
});
|
|
827
956
|
expect(results.length).toBeGreaterThan(0);
|
|
828
957
|
results.forEach(result => {
|
|
@@ -832,8 +961,10 @@ describe('PgVector', () => {
|
|
|
832
961
|
});
|
|
833
962
|
|
|
834
963
|
it('should handle $not with comparison operators', async () => {
|
|
835
|
-
const results = await vectorDB.query(
|
|
836
|
-
|
|
964
|
+
const results = await vectorDB.query({
|
|
965
|
+
indexName,
|
|
966
|
+
queryVector: [1, 0, 0],
|
|
967
|
+
filter: { price: { $not: { $gt: 100 } } },
|
|
837
968
|
});
|
|
838
969
|
expect(results.length).toBeGreaterThan(0);
|
|
839
970
|
results.forEach(result => {
|
|
@@ -842,10 +973,10 @@ describe('PgVector', () => {
|
|
|
842
973
|
});
|
|
843
974
|
|
|
844
975
|
it('should handle $not with $and', async () => {
|
|
845
|
-
const results = await vectorDB.query(
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
},
|
|
976
|
+
const results = await vectorDB.query({
|
|
977
|
+
indexName,
|
|
978
|
+
queryVector: [1, 0, 0],
|
|
979
|
+
filter: { $not: { $and: [{ category: 'electronics' }, { price: { $gt: 50 } }] } },
|
|
849
980
|
});
|
|
850
981
|
expect(results.length).toBeGreaterThan(0);
|
|
851
982
|
results.forEach(result => {
|
|
@@ -854,8 +985,10 @@ describe('PgVector', () => {
|
|
|
854
985
|
});
|
|
855
986
|
|
|
856
987
|
it('should handle $nor with $or', async () => {
|
|
857
|
-
const results = await vectorDB.query(
|
|
858
|
-
|
|
988
|
+
const results = await vectorDB.query({
|
|
989
|
+
indexName,
|
|
990
|
+
queryVector: [1, 0, 0],
|
|
991
|
+
filter: { $nor: [{ $or: [{ category: 'electronics' }, { category: 'books' }] }, { price: { $gt: 75 } }] },
|
|
859
992
|
});
|
|
860
993
|
expect(results.length).toBeGreaterThan(0);
|
|
861
994
|
results.forEach(result => {
|
|
@@ -865,11 +998,15 @@ describe('PgVector', () => {
|
|
|
865
998
|
});
|
|
866
999
|
|
|
867
1000
|
it('should handle $nor with nested $and conditions', async () => {
|
|
868
|
-
const results = await vectorDB.query(
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
1001
|
+
const results = await vectorDB.query({
|
|
1002
|
+
indexName,
|
|
1003
|
+
queryVector: [1, 0, 0],
|
|
1004
|
+
filter: {
|
|
1005
|
+
$nor: [
|
|
1006
|
+
{ $and: [{ category: 'electronics' }, { active: true }] },
|
|
1007
|
+
{ $and: [{ category: 'books' }, { price: { $lt: 30 } }] },
|
|
1008
|
+
],
|
|
1009
|
+
},
|
|
873
1010
|
});
|
|
874
1011
|
expect(results.length).toBeGreaterThan(0);
|
|
875
1012
|
results.forEach(result => {
|
|
@@ -882,8 +1019,12 @@ describe('PgVector', () => {
|
|
|
882
1019
|
});
|
|
883
1020
|
|
|
884
1021
|
it('should handle nested $and with $or and $not', async () => {
|
|
885
|
-
const results = await vectorDB.query(
|
|
886
|
-
|
|
1022
|
+
const results = await vectorDB.query({
|
|
1023
|
+
indexName,
|
|
1024
|
+
queryVector: [1, 0, 0],
|
|
1025
|
+
filter: {
|
|
1026
|
+
$and: [{ $or: [{ category: 'electronics' }, { category: 'books' }] }, { $not: { price: { $lt: 50 } } }],
|
|
1027
|
+
},
|
|
887
1028
|
});
|
|
888
1029
|
expect(results.length).toBeGreaterThan(0);
|
|
889
1030
|
results.forEach(result => {
|
|
@@ -893,8 +1034,10 @@ describe('PgVector', () => {
|
|
|
893
1034
|
});
|
|
894
1035
|
|
|
895
1036
|
it('should handle $or with multiple $not conditions', async () => {
|
|
896
|
-
const results = await vectorDB.query(
|
|
897
|
-
|
|
1037
|
+
const results = await vectorDB.query({
|
|
1038
|
+
indexName,
|
|
1039
|
+
queryVector: [1, 0, 0],
|
|
1040
|
+
filter: { $or: [{ $not: { category: 'electronics' } }, { $not: { price: { $gt: 50 } } }] },
|
|
898
1041
|
});
|
|
899
1042
|
expect(results.length).toBeGreaterThan(0);
|
|
900
1043
|
results.forEach(result => {
|
|
@@ -906,29 +1049,43 @@ describe('PgVector', () => {
|
|
|
906
1049
|
// Edge Cases and Special Values
|
|
907
1050
|
describe('Edge Cases and Special Values', () => {
|
|
908
1051
|
it('should handle empty result sets with valid filters', async () => {
|
|
909
|
-
const results = await vectorDB.query(
|
|
910
|
-
|
|
1052
|
+
const results = await vectorDB.query({
|
|
1053
|
+
indexName,
|
|
1054
|
+
queryVector: [1, 0, 0],
|
|
1055
|
+
filter: { price: { $gt: 1000 } },
|
|
911
1056
|
});
|
|
912
1057
|
expect(results).toHaveLength(0);
|
|
913
1058
|
});
|
|
914
1059
|
|
|
915
1060
|
it('should throw error for invalid operator', async () => {
|
|
916
1061
|
await expect(
|
|
917
|
-
vectorDB.query(
|
|
918
|
-
|
|
1062
|
+
vectorDB.query({
|
|
1063
|
+
indexName,
|
|
1064
|
+
queryVector: [1, 0, 0],
|
|
1065
|
+
filter: { price: { $invalid: 100 } },
|
|
919
1066
|
}),
|
|
920
1067
|
).rejects.toThrow('Unsupported operator: $invalid');
|
|
921
1068
|
});
|
|
922
1069
|
|
|
923
1070
|
it('should handle empty filter object', async () => {
|
|
924
|
-
const results = await vectorDB.query(
|
|
1071
|
+
const results = await vectorDB.query({
|
|
1072
|
+
indexName,
|
|
1073
|
+
queryVector: [1, 0, 0],
|
|
1074
|
+
filter: {},
|
|
1075
|
+
});
|
|
925
1076
|
expect(results.length).toBeGreaterThan(0);
|
|
926
1077
|
});
|
|
927
1078
|
|
|
928
1079
|
it('should handle numeric string comparisons', async () => {
|
|
929
|
-
await vectorDB.upsert(
|
|
930
|
-
|
|
931
|
-
|
|
1080
|
+
await vectorDB.upsert({
|
|
1081
|
+
indexName,
|
|
1082
|
+
vectors: [[1, 0.1, 0]],
|
|
1083
|
+
metadata: [{ numericString: '123' }],
|
|
1084
|
+
});
|
|
1085
|
+
const results = await vectorDB.query({
|
|
1086
|
+
indexName,
|
|
1087
|
+
queryVector: [1, 0, 0],
|
|
1088
|
+
filter: { numericString: { $gt: '100' } },
|
|
932
1089
|
});
|
|
933
1090
|
expect(results.length).toBeGreaterThan(0);
|
|
934
1091
|
expect(results[0]?.metadata?.numericString).toBe('123');
|
|
@@ -938,7 +1095,12 @@ describe('PgVector', () => {
|
|
|
938
1095
|
// Score Threshold Tests
|
|
939
1096
|
describe('Score Threshold', () => {
|
|
940
1097
|
it('should respect minimum score threshold', async () => {
|
|
941
|
-
const results = await vectorDB.query(
|
|
1098
|
+
const results = await vectorDB.query({
|
|
1099
|
+
indexName,
|
|
1100
|
+
queryVector: [1, 0, 0],
|
|
1101
|
+
filter: { category: 'electronics' },
|
|
1102
|
+
minScore: 0.9,
|
|
1103
|
+
});
|
|
942
1104
|
expect(results.length).toBeGreaterThan(0);
|
|
943
1105
|
results.forEach(result => {
|
|
944
1106
|
expect(result.score).toBeGreaterThan(0.9);
|
|
@@ -949,43 +1111,46 @@ describe('PgVector', () => {
|
|
|
949
1111
|
describe('Edge Cases and Special Values', () => {
|
|
950
1112
|
// Additional Edge Cases
|
|
951
1113
|
it('should handle empty result sets with valid filters', async () => {
|
|
952
|
-
const results = await vectorDB.query(
|
|
953
|
-
|
|
1114
|
+
const results = await vectorDB.query({
|
|
1115
|
+
indexName,
|
|
1116
|
+
queryVector: [1, 0, 0],
|
|
1117
|
+
filter: { price: { $gt: 1000 } },
|
|
954
1118
|
});
|
|
955
1119
|
expect(results).toHaveLength(0);
|
|
956
1120
|
});
|
|
957
1121
|
|
|
958
1122
|
it('should handle empty filter object', async () => {
|
|
959
|
-
const results = await vectorDB.query(
|
|
1123
|
+
const results = await vectorDB.query({
|
|
1124
|
+
indexName,
|
|
1125
|
+
queryVector: [1, 0, 0],
|
|
1126
|
+
filter: {},
|
|
1127
|
+
});
|
|
960
1128
|
expect(results.length).toBeGreaterThan(0);
|
|
961
1129
|
});
|
|
962
1130
|
|
|
963
1131
|
it('should handle non-existent field', async () => {
|
|
964
|
-
const results = await vectorDB.query(
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
},
|
|
969
|
-
},
|
|
1132
|
+
const results = await vectorDB.query({
|
|
1133
|
+
indexName,
|
|
1134
|
+
queryVector: [1, 0, 0],
|
|
1135
|
+
filter: { nonexistent: { $elemMatch: { $eq: 'value' } } },
|
|
970
1136
|
});
|
|
971
1137
|
expect(results).toHaveLength(0);
|
|
972
1138
|
});
|
|
973
1139
|
|
|
974
1140
|
it('should handle non-existent values', async () => {
|
|
975
|
-
const results = await vectorDB.query(
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
},
|
|
980
|
-
},
|
|
1141
|
+
const results = await vectorDB.query({
|
|
1142
|
+
indexName,
|
|
1143
|
+
queryVector: [1, 0, 0],
|
|
1144
|
+
filter: { tags: { $elemMatch: { $eq: 'nonexistent-tag' } } },
|
|
981
1145
|
});
|
|
982
1146
|
expect(results).toHaveLength(0);
|
|
983
1147
|
});
|
|
984
1148
|
// Empty Conditions Tests
|
|
985
1149
|
it('should handle empty conditions in logical operators', async () => {
|
|
986
|
-
const results = await vectorDB.query(
|
|
987
|
-
|
|
988
|
-
|
|
1150
|
+
const results = await vectorDB.query({
|
|
1151
|
+
indexName,
|
|
1152
|
+
queryVector: [1, 0, 0],
|
|
1153
|
+
filter: { $and: [], category: 'electronics' },
|
|
989
1154
|
});
|
|
990
1155
|
expect(results.length).toBeGreaterThan(0);
|
|
991
1156
|
results.forEach(result => {
|
|
@@ -994,9 +1159,10 @@ describe('PgVector', () => {
|
|
|
994
1159
|
});
|
|
995
1160
|
|
|
996
1161
|
it('should handle empty $and conditions', async () => {
|
|
997
|
-
const results = await vectorDB.query(
|
|
998
|
-
|
|
999
|
-
|
|
1162
|
+
const results = await vectorDB.query({
|
|
1163
|
+
indexName,
|
|
1164
|
+
queryVector: [1, 0, 0],
|
|
1165
|
+
filter: { $and: [], category: 'electronics' },
|
|
1000
1166
|
});
|
|
1001
1167
|
expect(results.length).toBeGreaterThan(0);
|
|
1002
1168
|
results.forEach(result => {
|
|
@@ -1005,17 +1171,19 @@ describe('PgVector', () => {
|
|
|
1005
1171
|
});
|
|
1006
1172
|
|
|
1007
1173
|
it('should handle empty $or conditions', async () => {
|
|
1008
|
-
const results = await vectorDB.query(
|
|
1009
|
-
|
|
1010
|
-
|
|
1174
|
+
const results = await vectorDB.query({
|
|
1175
|
+
indexName,
|
|
1176
|
+
queryVector: [1, 0, 0],
|
|
1177
|
+
filter: { $or: [], category: 'electronics' },
|
|
1011
1178
|
});
|
|
1012
1179
|
expect(results).toHaveLength(0);
|
|
1013
1180
|
});
|
|
1014
1181
|
|
|
1015
1182
|
it('should handle empty $nor conditions', async () => {
|
|
1016
|
-
const results = await vectorDB.query(
|
|
1017
|
-
|
|
1018
|
-
|
|
1183
|
+
const results = await vectorDB.query({
|
|
1184
|
+
indexName,
|
|
1185
|
+
queryVector: [1, 0, 0],
|
|
1186
|
+
filter: { $nor: [], category: 'electronics' },
|
|
1019
1187
|
});
|
|
1020
1188
|
expect(results.length).toBeGreaterThan(0);
|
|
1021
1189
|
results.forEach(result => {
|
|
@@ -1025,29 +1193,29 @@ describe('PgVector', () => {
|
|
|
1025
1193
|
|
|
1026
1194
|
it('should handle empty $not conditions', async () => {
|
|
1027
1195
|
await expect(
|
|
1028
|
-
vectorDB.query(
|
|
1029
|
-
|
|
1030
|
-
|
|
1196
|
+
vectorDB.query({
|
|
1197
|
+
indexName,
|
|
1198
|
+
queryVector: [1, 0, 0],
|
|
1199
|
+
filter: { $not: {}, category: 'electronics' },
|
|
1031
1200
|
}),
|
|
1032
1201
|
).rejects.toThrow('$not operator cannot be empty');
|
|
1033
1202
|
});
|
|
1034
1203
|
|
|
1035
1204
|
it('should handle multiple empty logical operators', async () => {
|
|
1036
|
-
const results = await vectorDB.query(
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
$nor: [],
|
|
1040
|
-
category: 'electronics',
|
|
1205
|
+
const results = await vectorDB.query({
|
|
1206
|
+
indexName,
|
|
1207
|
+
queryVector: [1, 0, 0],
|
|
1208
|
+
filter: { $and: [], $or: [], $nor: [], category: 'electronics' },
|
|
1041
1209
|
});
|
|
1042
1210
|
expect(results).toHaveLength(0);
|
|
1043
1211
|
});
|
|
1044
1212
|
|
|
1045
1213
|
// Nested Field Tests
|
|
1046
1214
|
it('should handle deeply nested metadata paths', async () => {
|
|
1047
|
-
await vectorDB.upsert(
|
|
1215
|
+
await vectorDB.upsert({
|
|
1048
1216
|
indexName,
|
|
1049
|
-
[[1, 0.1, 0]],
|
|
1050
|
-
[
|
|
1217
|
+
vectors: [[1, 0.1, 0]],
|
|
1218
|
+
metadata: [
|
|
1051
1219
|
{
|
|
1052
1220
|
level1: {
|
|
1053
1221
|
level2: {
|
|
@@ -1056,32 +1224,34 @@ describe('PgVector', () => {
|
|
|
1056
1224
|
},
|
|
1057
1225
|
},
|
|
1058
1226
|
],
|
|
1059
|
-
);
|
|
1227
|
+
});
|
|
1060
1228
|
|
|
1061
|
-
const results = await vectorDB.query(
|
|
1062
|
-
|
|
1229
|
+
const results = await vectorDB.query({
|
|
1230
|
+
indexName,
|
|
1231
|
+
queryVector: [1, 0, 0],
|
|
1232
|
+
filter: { 'level1.level2.level3': 'deep value' },
|
|
1063
1233
|
});
|
|
1064
1234
|
expect(results).toHaveLength(1);
|
|
1065
1235
|
expect(results[0]?.metadata?.level1?.level2?.level3).toBe('deep value');
|
|
1066
1236
|
});
|
|
1067
1237
|
|
|
1068
1238
|
it('should handle non-existent nested paths', async () => {
|
|
1069
|
-
const results = await vectorDB.query(
|
|
1070
|
-
|
|
1239
|
+
const results = await vectorDB.query({
|
|
1240
|
+
indexName,
|
|
1241
|
+
queryVector: [1, 0, 0],
|
|
1242
|
+
filter: { 'nonexistent.path': 'value' },
|
|
1071
1243
|
});
|
|
1072
1244
|
expect(results).toHaveLength(0);
|
|
1073
1245
|
});
|
|
1074
1246
|
|
|
1075
1247
|
// Score Threshold Tests
|
|
1076
1248
|
it('should respect minimum score threshold', async () => {
|
|
1077
|
-
const results = await vectorDB.query(
|
|
1078
|
-
indexName,
|
|
1079
|
-
[1, 0, 0],
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
0.9, // minScore
|
|
1084
|
-
);
|
|
1249
|
+
const results = await vectorDB.query({
|
|
1250
|
+
indexName,
|
|
1251
|
+
queryVector: [1, 0, 0],
|
|
1252
|
+
filter: { category: 'electronics' },
|
|
1253
|
+
minScore: 0.9, // minScore
|
|
1254
|
+
});
|
|
1085
1255
|
expect(results.length).toBeGreaterThan(0);
|
|
1086
1256
|
results.forEach(result => {
|
|
1087
1257
|
expect(result.score).toBeGreaterThan(0.9);
|
|
@@ -1090,17 +1260,21 @@ describe('PgVector', () => {
|
|
|
1090
1260
|
|
|
1091
1261
|
// Complex Nested Operators Test
|
|
1092
1262
|
it('should handle deeply nested logical operators', async () => {
|
|
1093
|
-
const results = await vectorDB.query(
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
$or: [{ active: false }, { price: { $gt: 100 } }],
|
|
1263
|
+
const results = await vectorDB.query({
|
|
1264
|
+
indexName,
|
|
1265
|
+
queryVector: [1, 0, 0],
|
|
1266
|
+
filter: {
|
|
1267
|
+
$and: [
|
|
1268
|
+
{
|
|
1269
|
+
$or: [{ category: 'electronics' }, { $and: [{ category: 'books' }, { price: { $lt: 30 } }] }],
|
|
1101
1270
|
},
|
|
1102
|
-
|
|
1103
|
-
|
|
1271
|
+
{
|
|
1272
|
+
$not: {
|
|
1273
|
+
$or: [{ active: false }, { price: { $gt: 100 } }],
|
|
1274
|
+
},
|
|
1275
|
+
},
|
|
1276
|
+
],
|
|
1277
|
+
},
|
|
1104
1278
|
});
|
|
1105
1279
|
expect(results.length).toBeGreaterThan(0);
|
|
1106
1280
|
results.forEach(result => {
|
|
@@ -1118,16 +1292,19 @@ describe('PgVector', () => {
|
|
|
1118
1292
|
|
|
1119
1293
|
it('should throw error for invalid operator', async () => {
|
|
1120
1294
|
await expect(
|
|
1121
|
-
vectorDB.query(
|
|
1122
|
-
|
|
1295
|
+
vectorDB.query({
|
|
1296
|
+
indexName,
|
|
1297
|
+
queryVector: [1, 0, 0],
|
|
1298
|
+
filter: { price: { $invalid: 100 } },
|
|
1123
1299
|
}),
|
|
1124
1300
|
).rejects.toThrow('Unsupported operator: $invalid');
|
|
1125
1301
|
});
|
|
1126
1302
|
|
|
1127
1303
|
it('should handle multiple logical operators at root level', async () => {
|
|
1128
|
-
const results = await vectorDB.query(
|
|
1129
|
-
|
|
1130
|
-
|
|
1304
|
+
const results = await vectorDB.query({
|
|
1305
|
+
indexName,
|
|
1306
|
+
queryVector: [1, 0, 0],
|
|
1307
|
+
filter: { $and: [{ category: 'electronics' }], $or: [{ price: { $lt: 100 } }, { price: { $gt: 20 } }] },
|
|
1131
1308
|
});
|
|
1132
1309
|
expect(results.length).toBeGreaterThan(0);
|
|
1133
1310
|
results.forEach(result => {
|
|
@@ -1138,34 +1315,63 @@ describe('PgVector', () => {
|
|
|
1138
1315
|
|
|
1139
1316
|
it('should handle non-array field with $elemMatch', async () => {
|
|
1140
1317
|
// First insert a record with non-array field
|
|
1141
|
-
await vectorDB.upsert(
|
|
1318
|
+
await vectorDB.upsert({
|
|
1319
|
+
indexName,
|
|
1320
|
+
vectors: [[1, 0.1, 0]],
|
|
1321
|
+
metadata: [{ tags: 'not-an-array' }],
|
|
1322
|
+
});
|
|
1142
1323
|
|
|
1143
|
-
const results = await vectorDB.query(
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1324
|
+
const results = await vectorDB.query({
|
|
1325
|
+
indexName,
|
|
1326
|
+
queryVector: [1, 0, 0],
|
|
1327
|
+
filter: {
|
|
1328
|
+
tags: {
|
|
1329
|
+
$elemMatch: {
|
|
1330
|
+
$eq: 'value',
|
|
1331
|
+
},
|
|
1147
1332
|
},
|
|
1148
1333
|
},
|
|
1149
1334
|
});
|
|
1150
1335
|
expect(results).toHaveLength(0); // Should return no results for non-array field
|
|
1151
1336
|
});
|
|
1152
1337
|
it('should handle undefined filter', async () => {
|
|
1153
|
-
const results1 = await vectorDB.query(
|
|
1154
|
-
|
|
1338
|
+
const results1 = await vectorDB.query({
|
|
1339
|
+
indexName,
|
|
1340
|
+
queryVector: [1, 0, 0],
|
|
1341
|
+
filter: undefined,
|
|
1342
|
+
});
|
|
1343
|
+
const results2 = await vectorDB.query({
|
|
1344
|
+
indexName,
|
|
1345
|
+
queryVector: [1, 0, 0],
|
|
1346
|
+
});
|
|
1155
1347
|
expect(results1).toEqual(results2);
|
|
1156
1348
|
expect(results1.length).toBeGreaterThan(0);
|
|
1157
1349
|
});
|
|
1158
1350
|
|
|
1159
1351
|
it('should handle empty object filter', async () => {
|
|
1160
|
-
const results = await vectorDB.query(
|
|
1161
|
-
|
|
1352
|
+
const results = await vectorDB.query({
|
|
1353
|
+
indexName,
|
|
1354
|
+
queryVector: [1, 0, 0],
|
|
1355
|
+
filter: {},
|
|
1356
|
+
});
|
|
1357
|
+
const results2 = await vectorDB.query({
|
|
1358
|
+
indexName,
|
|
1359
|
+
queryVector: [1, 0, 0],
|
|
1360
|
+
});
|
|
1162
1361
|
expect(results).toEqual(results2);
|
|
1163
1362
|
expect(results.length).toBeGreaterThan(0);
|
|
1164
1363
|
});
|
|
1165
1364
|
|
|
1166
1365
|
it('should handle null filter', async () => {
|
|
1167
|
-
const results = await vectorDB.query(
|
|
1168
|
-
|
|
1366
|
+
const results = await vectorDB.query({
|
|
1367
|
+
indexName,
|
|
1368
|
+
queryVector: [1, 0, 0],
|
|
1369
|
+
filter: null,
|
|
1370
|
+
});
|
|
1371
|
+
const results2 = await vectorDB.query({
|
|
1372
|
+
indexName,
|
|
1373
|
+
queryVector: [1, 0, 0],
|
|
1374
|
+
});
|
|
1169
1375
|
expect(results).toEqual(results2);
|
|
1170
1376
|
expect(results.length).toBeGreaterThan(0);
|
|
1171
1377
|
});
|
|
@@ -1174,58 +1380,82 @@ describe('PgVector', () => {
|
|
|
1174
1380
|
// Regex Operator Tests
|
|
1175
1381
|
describe('Regex Operators', () => {
|
|
1176
1382
|
it('should handle $regex with case sensitivity', async () => {
|
|
1177
|
-
const results = await vectorDB.query(
|
|
1178
|
-
|
|
1383
|
+
const results = await vectorDB.query({
|
|
1384
|
+
indexName,
|
|
1385
|
+
queryVector: [1, 0, 0],
|
|
1386
|
+
filter: { category: { $regex: 'ELECTRONICS' } },
|
|
1179
1387
|
});
|
|
1180
1388
|
expect(results).toHaveLength(0);
|
|
1181
1389
|
});
|
|
1182
1390
|
|
|
1183
1391
|
it('should handle $regex with case insensitivity', async () => {
|
|
1184
|
-
const results = await vectorDB.query(
|
|
1185
|
-
|
|
1392
|
+
const results = await vectorDB.query({
|
|
1393
|
+
indexName,
|
|
1394
|
+
queryVector: [1, 0, 0],
|
|
1395
|
+
filter: { category: { $regex: 'ELECTRONICS', $options: 'i' } },
|
|
1186
1396
|
});
|
|
1187
1397
|
expect(results).toHaveLength(2);
|
|
1188
1398
|
});
|
|
1189
1399
|
|
|
1190
1400
|
it('should handle $regex with start anchor', async () => {
|
|
1191
|
-
const results = await vectorDB.query(
|
|
1192
|
-
|
|
1401
|
+
const results = await vectorDB.query({
|
|
1402
|
+
indexName,
|
|
1403
|
+
queryVector: [1, 0, 0],
|
|
1404
|
+
filter: { category: { $regex: '^elect' } },
|
|
1193
1405
|
});
|
|
1194
1406
|
expect(results).toHaveLength(2);
|
|
1195
1407
|
});
|
|
1196
1408
|
|
|
1197
1409
|
it('should handle $regex with end anchor', async () => {
|
|
1198
|
-
const results = await vectorDB.query(
|
|
1199
|
-
|
|
1410
|
+
const results = await vectorDB.query({
|
|
1411
|
+
indexName,
|
|
1412
|
+
queryVector: [1, 0, 0],
|
|
1413
|
+
filter: { category: { $regex: 'nics$' } },
|
|
1200
1414
|
});
|
|
1201
1415
|
expect(results).toHaveLength(2);
|
|
1202
1416
|
});
|
|
1203
1417
|
|
|
1204
1418
|
it('should handle multiline flag', async () => {
|
|
1205
|
-
await vectorDB.upsert(
|
|
1419
|
+
await vectorDB.upsert({
|
|
1420
|
+
indexName,
|
|
1421
|
+
vectors: [[1, 0.1, 0]],
|
|
1422
|
+
metadata: [{ description: 'First line\nSecond line\nThird line' }],
|
|
1423
|
+
});
|
|
1206
1424
|
|
|
1207
|
-
const results = await vectorDB.query(
|
|
1208
|
-
|
|
1425
|
+
const results = await vectorDB.query({
|
|
1426
|
+
indexName,
|
|
1427
|
+
queryVector: [1, 0, 0],
|
|
1428
|
+
filter: { description: { $regex: '^Second', $options: 'm' } },
|
|
1209
1429
|
});
|
|
1210
1430
|
expect(results).toHaveLength(1);
|
|
1211
1431
|
});
|
|
1212
1432
|
|
|
1213
1433
|
it('should handle dotall flag', async () => {
|
|
1214
|
-
await vectorDB.upsert(
|
|
1434
|
+
await vectorDB.upsert({
|
|
1435
|
+
indexName,
|
|
1436
|
+
vectors: [[1, 0.1, 0]],
|
|
1437
|
+
metadata: [{ description: 'First\nSecond\nThird' }],
|
|
1438
|
+
});
|
|
1215
1439
|
|
|
1216
|
-
const withoutS = await vectorDB.query(
|
|
1217
|
-
|
|
1440
|
+
const withoutS = await vectorDB.query({
|
|
1441
|
+
indexName,
|
|
1442
|
+
queryVector: [1, 0, 0],
|
|
1443
|
+
filter: { description: { $regex: 'First[^\\n]*Third' } },
|
|
1218
1444
|
});
|
|
1219
1445
|
expect(withoutS).toHaveLength(0);
|
|
1220
1446
|
|
|
1221
|
-
const withS = await vectorDB.query(
|
|
1222
|
-
|
|
1447
|
+
const withS = await vectorDB.query({
|
|
1448
|
+
indexName,
|
|
1449
|
+
queryVector: [1, 0, 0],
|
|
1450
|
+
filter: { description: { $regex: 'First.*Third', $options: 's' } },
|
|
1223
1451
|
});
|
|
1224
1452
|
expect(withS).toHaveLength(1);
|
|
1225
1453
|
});
|
|
1226
1454
|
it('should handle $not with $regex operator', async () => {
|
|
1227
|
-
const results = await vectorDB.query(
|
|
1228
|
-
|
|
1455
|
+
const results = await vectorDB.query({
|
|
1456
|
+
indexName,
|
|
1457
|
+
queryVector: [1, 0, 0],
|
|
1458
|
+
filter: { category: { $not: { $regex: '^elect' } } },
|
|
1229
1459
|
});
|
|
1230
1460
|
expect(results.length).toBeGreaterThan(0);
|
|
1231
1461
|
results.forEach(result => {
|
|
@@ -1245,11 +1475,19 @@ describe('PgVector', () => {
|
|
|
1245
1475
|
|
|
1246
1476
|
describe('HNSW Parameters', () => {
|
|
1247
1477
|
beforeAll(async () => {
|
|
1248
|
-
await vectorDB.createIndex(
|
|
1249
|
-
|
|
1250
|
-
|
|
1478
|
+
await vectorDB.createIndex({
|
|
1479
|
+
indexName,
|
|
1480
|
+
dimension: 3,
|
|
1481
|
+
metric: 'cosine',
|
|
1482
|
+
indexConfig: {
|
|
1483
|
+
type: 'hnsw',
|
|
1484
|
+
hnsw: { m: 16, efConstruction: 64 },
|
|
1485
|
+
},
|
|
1486
|
+
});
|
|
1487
|
+
await vectorDB.upsert({
|
|
1488
|
+
indexName,
|
|
1489
|
+
vectors,
|
|
1251
1490
|
});
|
|
1252
|
-
await vectorDB.upsert(indexName, vectors);
|
|
1253
1491
|
});
|
|
1254
1492
|
|
|
1255
1493
|
afterAll(async () => {
|
|
@@ -1257,14 +1495,23 @@ describe('PgVector', () => {
|
|
|
1257
1495
|
});
|
|
1258
1496
|
|
|
1259
1497
|
it('should use default ef value', async () => {
|
|
1260
|
-
const results = await vectorDB.query(
|
|
1498
|
+
const results = await vectorDB.query({
|
|
1499
|
+
indexName,
|
|
1500
|
+
queryVector: [1, 0, 0],
|
|
1501
|
+
topK: 2,
|
|
1502
|
+
});
|
|
1261
1503
|
expect(results).toHaveLength(2);
|
|
1262
1504
|
expect(results[0]?.score).toBeCloseTo(1, 5);
|
|
1263
1505
|
expect(results[1]?.score).toBeGreaterThan(0.9); // Second vector should be close
|
|
1264
1506
|
});
|
|
1265
1507
|
|
|
1266
1508
|
it('should respect custom ef value', async () => {
|
|
1267
|
-
const results = await vectorDB.query(
|
|
1509
|
+
const results = await vectorDB.query({
|
|
1510
|
+
indexName,
|
|
1511
|
+
queryVector: [1, 0, 0],
|
|
1512
|
+
topK: 2,
|
|
1513
|
+
ef: 100,
|
|
1514
|
+
});
|
|
1268
1515
|
expect(results).toHaveLength(2);
|
|
1269
1516
|
expect(results[0]?.score).toBeCloseTo(1, 5);
|
|
1270
1517
|
expect(results[1]?.score).toBeGreaterThan(0.9);
|
|
@@ -1273,11 +1520,19 @@ describe('PgVector', () => {
|
|
|
1273
1520
|
|
|
1274
1521
|
describe('IVF Parameters', () => {
|
|
1275
1522
|
beforeAll(async () => {
|
|
1276
|
-
await vectorDB.createIndex(
|
|
1277
|
-
|
|
1278
|
-
|
|
1523
|
+
await vectorDB.createIndex({
|
|
1524
|
+
indexName,
|
|
1525
|
+
dimension: 3,
|
|
1526
|
+
metric: 'cosine',
|
|
1527
|
+
indexConfig: {
|
|
1528
|
+
type: 'ivfflat',
|
|
1529
|
+
ivf: { lists: 2 }, // Small number for test data
|
|
1530
|
+
},
|
|
1531
|
+
});
|
|
1532
|
+
await vectorDB.upsert({
|
|
1533
|
+
indexName,
|
|
1534
|
+
vectors,
|
|
1279
1535
|
});
|
|
1280
|
-
await vectorDB.upsert(indexName, vectors);
|
|
1281
1536
|
});
|
|
1282
1537
|
|
|
1283
1538
|
afterAll(async () => {
|
|
@@ -1285,18 +1540,145 @@ describe('PgVector', () => {
|
|
|
1285
1540
|
});
|
|
1286
1541
|
|
|
1287
1542
|
it('should use default probe value', async () => {
|
|
1288
|
-
const results = await vectorDB.query(
|
|
1543
|
+
const results = await vectorDB.query({
|
|
1544
|
+
indexName,
|
|
1545
|
+
queryVector: [1, 0, 0],
|
|
1546
|
+
topK: 2,
|
|
1547
|
+
});
|
|
1289
1548
|
expect(results).toHaveLength(2);
|
|
1290
1549
|
expect(results[0]?.score).toBeCloseTo(1, 5);
|
|
1291
1550
|
expect(results[1]?.score).toBeGreaterThan(0.9);
|
|
1292
1551
|
});
|
|
1293
1552
|
|
|
1294
1553
|
it('should respect custom probe value', async () => {
|
|
1295
|
-
const results = await vectorDB.query(
|
|
1554
|
+
const results = await vectorDB.query({
|
|
1555
|
+
indexName,
|
|
1556
|
+
queryVector: [1, 0, 0],
|
|
1557
|
+
topK: 2,
|
|
1558
|
+
probes: 2,
|
|
1559
|
+
});
|
|
1296
1560
|
expect(results).toHaveLength(2);
|
|
1297
1561
|
expect(results[0]?.score).toBeCloseTo(1, 5);
|
|
1298
1562
|
expect(results[1]?.score).toBeGreaterThan(0.9);
|
|
1299
1563
|
});
|
|
1300
1564
|
});
|
|
1301
1565
|
});
|
|
1566
|
+
describe('Deprecation Warnings', () => {
|
|
1567
|
+
const indexName = 'testdeprecationwarnings';
|
|
1568
|
+
|
|
1569
|
+
const indexName2 = 'testdeprecationwarnings2';
|
|
1570
|
+
|
|
1571
|
+
let warnSpy;
|
|
1572
|
+
|
|
1573
|
+
beforeAll(async () => {
|
|
1574
|
+
await vectorDB.createIndex({ indexName: indexName, dimension: 3 });
|
|
1575
|
+
});
|
|
1576
|
+
|
|
1577
|
+
afterAll(async () => {
|
|
1578
|
+
await vectorDB.deleteIndex(indexName);
|
|
1579
|
+
await vectorDB.deleteIndex(indexName2);
|
|
1580
|
+
});
|
|
1581
|
+
|
|
1582
|
+
beforeEach(async () => {
|
|
1583
|
+
warnSpy = vi.spyOn(vectorDB['logger'], 'warn');
|
|
1584
|
+
});
|
|
1585
|
+
|
|
1586
|
+
afterEach(async () => {
|
|
1587
|
+
warnSpy.mockRestore();
|
|
1588
|
+
await vectorDB.deleteIndex(indexName2);
|
|
1589
|
+
});
|
|
1590
|
+
|
|
1591
|
+
it('should show deprecation warning when using individual args for createIndex', async () => {
|
|
1592
|
+
await vectorDB.createIndex(indexName2, 3, 'cosine');
|
|
1593
|
+
|
|
1594
|
+
expect(warnSpy).toHaveBeenCalledWith(
|
|
1595
|
+
expect.stringContaining('Deprecation Warning: Passing individual arguments to createIndex() is deprecated'),
|
|
1596
|
+
);
|
|
1597
|
+
});
|
|
1598
|
+
|
|
1599
|
+
it('should show deprecation warning when using individual args for upsert', async () => {
|
|
1600
|
+
await vectorDB.upsert(indexName, [[1, 2, 3]], [{ test: 'data' }]);
|
|
1601
|
+
|
|
1602
|
+
expect(warnSpy).toHaveBeenCalledWith(
|
|
1603
|
+
expect.stringContaining('Deprecation Warning: Passing individual arguments to upsert() is deprecated'),
|
|
1604
|
+
);
|
|
1605
|
+
});
|
|
1606
|
+
|
|
1607
|
+
it('should show deprecation warning when using individual args for query', async () => {
|
|
1608
|
+
await vectorDB.query(indexName, [1, 2, 3], 5);
|
|
1609
|
+
|
|
1610
|
+
expect(warnSpy).toHaveBeenCalledWith(
|
|
1611
|
+
expect.stringContaining('Deprecation Warning: Passing individual arguments to query() is deprecated'),
|
|
1612
|
+
);
|
|
1613
|
+
});
|
|
1614
|
+
|
|
1615
|
+
it('should show deprecation warning when using individual args for buildIndex', async () => {
|
|
1616
|
+
await vectorDB.buildIndex(indexName, 'cosine', { type: 'flat' });
|
|
1617
|
+
|
|
1618
|
+
expect(warnSpy).toHaveBeenCalledWith(
|
|
1619
|
+
expect.stringContaining('Deprecation Warning: Passing individual arguments to buildIndex() is deprecated'),
|
|
1620
|
+
);
|
|
1621
|
+
});
|
|
1622
|
+
|
|
1623
|
+
it('should not show deprecation warning when using object param for buildIndex', async () => {
|
|
1624
|
+
await vectorDB.buildIndex({
|
|
1625
|
+
indexName: indexName,
|
|
1626
|
+
metric: 'cosine',
|
|
1627
|
+
indexConfig: { type: 'flat' },
|
|
1628
|
+
});
|
|
1629
|
+
|
|
1630
|
+
expect(warnSpy).not.toHaveBeenCalled();
|
|
1631
|
+
});
|
|
1632
|
+
|
|
1633
|
+
it('should not show deprecation warning when using object param for query', async () => {
|
|
1634
|
+
await vectorDB.query({
|
|
1635
|
+
indexName,
|
|
1636
|
+
queryVector: [1, 2, 3],
|
|
1637
|
+
topK: 5,
|
|
1638
|
+
});
|
|
1639
|
+
|
|
1640
|
+
expect(warnSpy).not.toHaveBeenCalled();
|
|
1641
|
+
});
|
|
1642
|
+
|
|
1643
|
+
it('should not show deprecation warning when using object param for createIndex', async () => {
|
|
1644
|
+
await vectorDB.createIndex({
|
|
1645
|
+
indexName: indexName2,
|
|
1646
|
+
dimension: 3,
|
|
1647
|
+
metric: 'cosine',
|
|
1648
|
+
});
|
|
1649
|
+
|
|
1650
|
+
expect(warnSpy).not.toHaveBeenCalled();
|
|
1651
|
+
});
|
|
1652
|
+
|
|
1653
|
+
it('should not show deprecation warning when using object param for upsert', async () => {
|
|
1654
|
+
await vectorDB.upsert({
|
|
1655
|
+
indexName,
|
|
1656
|
+
vectors: [[1, 2, 3]],
|
|
1657
|
+
metadata: [{ test: 'data' }],
|
|
1658
|
+
});
|
|
1659
|
+
|
|
1660
|
+
expect(warnSpy).not.toHaveBeenCalled();
|
|
1661
|
+
});
|
|
1662
|
+
|
|
1663
|
+
it('should maintain backward compatibility with individual args', async () => {
|
|
1664
|
+
// Query
|
|
1665
|
+
const queryResults = await vectorDB.query(indexName, [1, 2, 3], 5);
|
|
1666
|
+
expect(Array.isArray(queryResults)).toBe(true);
|
|
1667
|
+
|
|
1668
|
+
// CreateIndex
|
|
1669
|
+
await expect(vectorDB.createIndex(indexName2, 3, 'cosine')).resolves.not.toThrow();
|
|
1670
|
+
|
|
1671
|
+
// Upsert
|
|
1672
|
+
const upsertResults = await vectorDB.upsert({
|
|
1673
|
+
indexName,
|
|
1674
|
+
vectors: [[1, 2, 3]],
|
|
1675
|
+
metadata: [{ test: 'data' }],
|
|
1676
|
+
});
|
|
1677
|
+
expect(Array.isArray(upsertResults)).toBe(true);
|
|
1678
|
+
expect(upsertResults).toHaveLength(1);
|
|
1679
|
+
|
|
1680
|
+
// BuildIndex
|
|
1681
|
+
await expect(vectorDB.buildIndex(indexName, 'cosine', { type: 'flat' })).resolves.not.toThrow();
|
|
1682
|
+
});
|
|
1683
|
+
});
|
|
1302
1684
|
});
|