@mastra/libsql 0.13.7 → 0.13.8-alpha.1

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