@mastra/chroma 0.0.0-tsconfig-compile-20250703214351 → 0.0.0-unified-sidebar-20251010130811

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,1550 +0,0 @@
1
- import type { QueryResult, IndexStats } from '@mastra/core/vector';
2
- import { describe, expect, beforeEach, afterEach, it, beforeAll, afterAll, vi } from 'vitest';
3
-
4
- import { ChromaVector } from './';
5
-
6
- describe('ChromaVector Integration Tests', () => {
7
- let vectorDB = new ChromaVector({
8
- path: 'http://localhost:8000',
9
- });
10
-
11
- const testIndexName = 'test-index';
12
- const testIndexName2 = 'test-index-2';
13
- const testIndexName3 = 'test-index-3';
14
- const dimension = 3;
15
-
16
- beforeEach(async () => {
17
- // Clean up any existing test index
18
- try {
19
- await vectorDB.deleteIndex({ indexName: testIndexName });
20
- } catch {
21
- // Ignore errors if index doesn't exist
22
- }
23
- await vectorDB.createIndex({ indexName: testIndexName, dimension });
24
- }, 5000);
25
-
26
- afterEach(async () => {
27
- // Cleanup after tests
28
- try {
29
- await vectorDB.deleteIndex({ indexName: testIndexName });
30
- } catch {
31
- // Ignore cleanup errors
32
- }
33
- }, 5000);
34
-
35
- describe('Index Management', () => {
36
- it('should create and list indexes', async () => {
37
- const indexes = await vectorDB.listIndexes();
38
- expect(indexes).toContain(testIndexName);
39
- });
40
-
41
- it('should describe index correctly', async () => {
42
- const stats: IndexStats = await vectorDB.describeIndex({ indexName: testIndexName });
43
- expect(stats.dimension).toBe(dimension);
44
- expect(stats.count).toBe(0);
45
- expect(stats.metric).toBe('cosine');
46
- });
47
-
48
- it('should delete index', async () => {
49
- await vectorDB.deleteIndex({ indexName: testIndexName });
50
- const indexes = await vectorDB.listIndexes();
51
- expect(indexes).not.toContain(testIndexName);
52
- });
53
-
54
- it('should create index with different metrics', async () => {
55
- const metricsToTest: Array<'cosine' | 'euclidean' | 'dotproduct'> = ['euclidean', 'dotproduct'];
56
-
57
- for (const metric of metricsToTest) {
58
- const testIndex = `test-index-${metric}`;
59
- await vectorDB.createIndex({ indexName: testIndex, dimension, metric });
60
-
61
- const stats = await vectorDB.describeIndex({ indexName: testIndex });
62
- expect(stats.metric).toBe(metric);
63
-
64
- await vectorDB.deleteIndex({ indexName: testIndex });
65
- }
66
- });
67
- });
68
-
69
- describe('Basic Vector Operations', () => {
70
- const testVectors = [
71
- [1.0, 0.0, 0.0],
72
- [0.0, 1.0, 0.0],
73
- [0.0, 0.0, 1.0],
74
- ];
75
- const testMetadata = [{ label: 'x-axis' }, { label: 'y-axis' }, { label: 'z-axis' }];
76
- const testIds = ['vec1', 'vec2', 'vec3'];
77
-
78
- it('should upsert vectors with generated ids', async () => {
79
- const ids = await vectorDB.upsert({ indexName: testIndexName, vectors: testVectors });
80
- expect(ids).toHaveLength(testVectors.length);
81
- ids.forEach(id => expect(typeof id).toBe('string'));
82
-
83
- const stats = await vectorDB.describeIndex({ indexName: testIndexName });
84
- expect(stats.count).toBe(testVectors.length);
85
- });
86
-
87
- it('should upsert vectors with provided ids and metadata', async () => {
88
- await vectorDB.upsert({ indexName: testIndexName, vectors: testVectors, metadata: testMetadata, ids: testIds });
89
-
90
- const stats = await vectorDB.describeIndex({ indexName: testIndexName });
91
- expect(stats.count).toBe(testVectors.length);
92
-
93
- // Query each vector to verify metadata
94
- for (let i = 0; i < testVectors.length; i++) {
95
- const results = await vectorDB.query({ indexName: testIndexName, queryVector: testVectors?.[i]!, topK: 1 });
96
- expect(results?.[0]?.id).toBe(testIds[i]);
97
- expect(results?.[0]?.metadata).toEqual(testMetadata[i]);
98
- }
99
- });
100
-
101
- it('should update existing vectors', async () => {
102
- // Initial upsert
103
- await vectorDB.upsert({ indexName: testIndexName, vectors: testVectors, metadata: testMetadata, ids: testIds });
104
-
105
- // Update first vector
106
- const updatedVector = [[0.5, 0.5, 0.0]];
107
- const updatedMetadata = [{ label: 'updated-x-axis' }];
108
- await vectorDB.upsert({
109
- indexName: testIndexName,
110
- vectors: updatedVector,
111
- metadata: updatedMetadata,
112
- ids: [testIds?.[0]!],
113
- });
114
-
115
- // Verify update
116
- const results = await vectorDB.query({ indexName: testIndexName, queryVector: updatedVector?.[0]!, topK: 1 });
117
- expect(results?.[0]?.id).toBe(testIds[0]);
118
- expect(results?.[0]?.metadata).toEqual(updatedMetadata[0]);
119
- });
120
-
121
- it('should update the vector by id', async () => {
122
- const ids = await vectorDB.upsert({ indexName: testIndexName, vectors: testVectors });
123
- expect(ids).toHaveLength(3);
124
-
125
- const idToBeUpdated = ids[0];
126
- const newVector = [1, 2, 3];
127
- const newMetaData = {
128
- test: 'updates',
129
- };
130
-
131
- const update = {
132
- vector: newVector,
133
- metadata: newMetaData,
134
- };
135
-
136
- await vectorDB.updateVector({ indexName: testIndexName, id: idToBeUpdated, update });
137
-
138
- const results: QueryResult[] = await vectorDB.query({
139
- indexName: testIndexName,
140
- queryVector: newVector,
141
- topK: 2,
142
- includeVector: true,
143
- });
144
- expect(results[0]?.id).toBe(idToBeUpdated);
145
- expect(results[0]?.vector).toEqual(newVector);
146
- expect(results[0]?.metadata).toEqual(newMetaData);
147
- });
148
-
149
- it('should only update the metadata by id', async () => {
150
- const ids = await vectorDB.upsert({ indexName: testIndexName, vectors: testVectors });
151
- expect(ids).toHaveLength(3);
152
-
153
- const idToBeUpdated = ids[0];
154
- const newMetaData = {
155
- test: 'updates',
156
- };
157
-
158
- const update = {
159
- metadata: newMetaData,
160
- };
161
-
162
- await vectorDB.updateVector({ indexName: testIndexName, id: idToBeUpdated, update });
163
-
164
- const results: QueryResult[] = await vectorDB.query({
165
- indexName: testIndexName,
166
- queryVector: testVectors[0],
167
- topK: 2,
168
- includeVector: true,
169
- });
170
- expect(results[0]?.id).toBe(idToBeUpdated);
171
- expect(results[0]?.vector).toEqual(testVectors[0]);
172
- expect(results[0]?.metadata).toEqual(newMetaData);
173
- });
174
-
175
- it('should only update vector embeddings by id', async () => {
176
- const ids = await vectorDB.upsert({ indexName: testIndexName, vectors: testVectors });
177
- expect(ids).toHaveLength(3);
178
-
179
- const idToBeUpdated = ids[0];
180
- const newVector = [1, 2, 3];
181
-
182
- const update = {
183
- vector: newVector,
184
- };
185
-
186
- await vectorDB.updateVector({ indexName: testIndexName, id: idToBeUpdated, update });
187
-
188
- const results: QueryResult[] = await vectorDB.query({
189
- indexName: testIndexName,
190
- queryVector: newVector,
191
- topK: 2,
192
- includeVector: true,
193
- });
194
- expect(results[0]?.id).toBe(idToBeUpdated);
195
- expect(results[0]?.vector).toEqual(newVector);
196
- });
197
-
198
- it('should throw exception when no updates are given', async () => {
199
- await expect(vectorDB.updateVector({ indexName: testIndexName, id: 'id', update: {} })).rejects.toThrow(
200
- 'No updates provided',
201
- );
202
- });
203
-
204
- it('should delete the vector by id', async () => {
205
- const ids = await vectorDB.upsert({ indexName: testIndexName, vectors: testVectors });
206
- expect(ids).toHaveLength(3);
207
- const idToBeDeleted = ids[0];
208
-
209
- await vectorDB.deleteVector({ indexName: testIndexName, id: idToBeDeleted });
210
-
211
- const results: QueryResult[] = await vectorDB.query({
212
- indexName: testIndexName,
213
- queryVector: [1.0, 0.0, 0.0],
214
- topK: 2,
215
- });
216
-
217
- expect(results).toHaveLength(2);
218
- expect(results.map(res => res.id)).not.toContain(idToBeDeleted);
219
- });
220
- });
221
-
222
- describe('Query Operations', () => {
223
- const testVectors = [
224
- [1.0, 0.0, 0.0],
225
- [0.0, 1.0, 0.0],
226
- [0.0, 0.0, 1.0],
227
- ];
228
- const testMetadata = [{ label: 'x-axis' }, { label: 'y-axis' }, { label: 'z-axis' }];
229
- const testIds = ['vec1', 'vec2', 'vec3'];
230
-
231
- beforeEach(async () => {
232
- await vectorDB.upsert({ indexName: testIndexName, vectors: testVectors, metadata: testMetadata, ids: testIds });
233
- });
234
-
235
- describe('Basic Queries', () => {
236
- it('should perform vector search with topK', async () => {
237
- const queryVector = [1.0, 0.1, 0.1];
238
- const topK = 2;
239
-
240
- const results: QueryResult[] = await vectorDB.query({ indexName: testIndexName, queryVector, topK });
241
-
242
- expect(results).toHaveLength(topK);
243
- expect(results?.[0]?.id).toBe(testIds[0]); // Should match x-axis vector most closely
244
- });
245
- });
246
-
247
- describe('Filter Queries', () => {
248
- it('should filter query results', async () => {
249
- const queryVector = [1.0, 1.0, 1.0];
250
- const filter = { label: 'x-axis' };
251
-
252
- const results = await vectorDB.query({ indexName: testIndexName, queryVector, topK: 3, filter });
253
-
254
- expect(results).toHaveLength(1);
255
- expect(results?.[0]?.metadata?.label).toBe('x-axis');
256
- });
257
- });
258
-
259
- describe('Vector Inclusion', () => {
260
- it('should include vector in query results', async () => {
261
- const queryVector = [1.0, 0.1, 0.1];
262
- const topK = 1;
263
-
264
- const results = await vectorDB.query({
265
- indexName: testIndexName,
266
- queryVector,
267
- topK,
268
- includeVector: true,
269
- });
270
-
271
- expect(results).toHaveLength(topK);
272
- expect(results?.[0]?.vector).toEqual(testVectors[0]);
273
- });
274
- });
275
- });
276
-
277
- describe('Error Handling', () => {
278
- const testIndexName = 'test_index_error';
279
- beforeAll(async () => {
280
- await vectorDB.createIndex({ indexName: testIndexName, dimension: 3 });
281
- });
282
-
283
- afterAll(async () => {
284
- await vectorDB.deleteIndex({ indexName: testIndexName });
285
- });
286
-
287
- it('should handle non-existent index queries', async () => {
288
- await expect(vectorDB.query({ indexName: 'non-existent-index', queryVector: [1, 2, 3] })).rejects.toThrow();
289
- });
290
-
291
- it('should handle invalid dimension vectors', async () => {
292
- const invalidVector = [1, 2, 3, 4]; // 4D vector for 3D index
293
- await expect(vectorDB.upsert({ indexName: testIndexName, vectors: [invalidVector] })).rejects.toThrow();
294
- });
295
-
296
- it('should handle duplicate index creation gracefully', async () => {
297
- const infoSpy = vi.spyOn(vectorDB['logger'], 'info');
298
- const warnSpy = vi.spyOn(vectorDB['logger'], 'warn');
299
-
300
- const duplicateIndexName = `duplicate-test`;
301
- const dimension = 768;
302
-
303
- try {
304
- // Create index first time
305
- await vectorDB.createIndex({
306
- indexName: duplicateIndexName,
307
- dimension,
308
- metric: 'cosine',
309
- });
310
-
311
- // Try to create with same dimensions - should not throw
312
- await expect(
313
- vectorDB.createIndex({
314
- indexName: duplicateIndexName,
315
- dimension,
316
- metric: 'cosine',
317
- }),
318
- ).resolves.not.toThrow();
319
-
320
- expect(infoSpy).toHaveBeenCalledWith(expect.stringContaining('already exists with'));
321
-
322
- // Try to create with same dimensions and different metric - should not throw
323
- await expect(
324
- vectorDB.createIndex({
325
- indexName: duplicateIndexName,
326
- dimension,
327
- metric: 'euclidean',
328
- }),
329
- ).resolves.not.toThrow();
330
-
331
- expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('Attempted to create index with metric'));
332
-
333
- // Try to create with different dimensions - should throw
334
- await expect(
335
- vectorDB.createIndex({
336
- indexName: duplicateIndexName,
337
- dimension: dimension + 1,
338
- metric: 'cosine',
339
- }),
340
- ).rejects.toThrow(
341
- `Index "${duplicateIndexName}" already exists with ${dimension} dimensions, but ${dimension + 1} dimensions were requested`,
342
- );
343
- } finally {
344
- infoSpy.mockRestore();
345
- warnSpy.mockRestore();
346
- // Cleanup
347
- await vectorDB.deleteIndex({ indexName: duplicateIndexName });
348
- }
349
- });
350
- });
351
-
352
- describe('Filter Validation in Queries', () => {
353
- it('rejects queries with null values', async () => {
354
- await expect(
355
- vectorDB.query({
356
- indexName: testIndexName,
357
- queryVector: [1, 0, 0],
358
- filter: {
359
- field: null,
360
- },
361
- }),
362
- ).rejects.toThrow();
363
-
364
- await expect(
365
- vectorDB.query({
366
- indexName: testIndexName,
367
- queryVector: [1, 0, 0],
368
- filter: {
369
- other: { $eq: null },
370
- },
371
- }),
372
- ).rejects.toThrow();
373
- });
374
-
375
- it('validates array operator values', async () => {
376
- await expect(
377
- vectorDB.query({
378
- indexName: testIndexName,
379
- queryVector: [1, 0, 0],
380
- filter: {
381
- tags: { $in: null } as any,
382
- },
383
- }),
384
- ).rejects.toThrow();
385
- });
386
-
387
- it('validates numeric values for comparison operators', async () => {
388
- await expect(
389
- vectorDB.query({
390
- indexName: testIndexName,
391
- queryVector: [1, 0, 0],
392
- filter: {
393
- price: { $gt: 'not-a-number' },
394
- },
395
- }),
396
- ).rejects.toThrow();
397
- });
398
-
399
- it('validates value types', async () => {
400
- await expect(
401
- vectorDB.query({
402
- indexName: testIndexName,
403
- queryVector: [1, 0, 0],
404
- filter: { date: { $gt: 'not-a-date' } },
405
- }),
406
- ).rejects.toThrow();
407
-
408
- await expect(
409
- vectorDB.query({
410
- indexName: testIndexName,
411
- queryVector: [1, 0, 0],
412
- filter: { number: { $lt: 'not-a-number' } },
413
- }),
414
- ).rejects.toThrow();
415
- });
416
-
417
- it('validates array operators', async () => {
418
- const invalidValues = [123, 'string', true, { key: 'value' }, null];
419
- for (const op of ['$in', '$nin']) {
420
- for (const val of invalidValues) {
421
- await expect(
422
- vectorDB.query({
423
- indexName: testIndexName,
424
- queryVector: [1, 0, 0],
425
- filter: { field: { [op]: val } },
426
- }),
427
- ).rejects.toThrow();
428
- }
429
- }
430
- });
431
-
432
- it('rejects invalid array operator values', async () => {
433
- // Test non-undefined values
434
- const invalidValues = [123, 'string', true, { key: 'value' }, null];
435
- for (const op of ['$in', '$nin']) {
436
- for (const val of invalidValues) {
437
- await expect(
438
- vectorDB.query({
439
- indexName: testIndexName,
440
- queryVector: [1, 0, 0],
441
- filter: { field: { [op]: val } },
442
- }),
443
- ).rejects.toThrow();
444
- }
445
- }
446
- });
447
-
448
- it('validates comparison operators', async () => {
449
- // Basic equality can accept any non-undefined value
450
- for (const op of ['$eq', '$ne']) {
451
- await expect(
452
- vectorDB.query({
453
- indexName: testIndexName,
454
- queryVector: [1, 0, 0],
455
- filter: { field: { [op]: undefined } },
456
- }),
457
- ).rejects.toThrow();
458
- }
459
-
460
- // Numeric comparisons require numbers
461
- const numOps = ['$gt', '$gte', '$lt', '$lte'];
462
- const invalidNumericValues = ['not-a-number', true, [], {}, null, undefined];
463
- for (const op of numOps) {
464
- for (const val of invalidNumericValues) {
465
- await expect(
466
- vectorDB.query({
467
- indexName: testIndexName,
468
- queryVector: [1, 0, 0],
469
- filter: { field: { [op]: val } },
470
- }),
471
- ).rejects.toThrow();
472
- }
473
- }
474
- });
475
-
476
- it('validates multiple invalid values', async () => {
477
- await expect(
478
- vectorDB.query({
479
- indexName: testIndexName,
480
- queryVector: [1, 0, 0],
481
- filter: {
482
- field1: { $in: 'not-array' } as any,
483
- field2: { $exists: 'not-boolean' } as any,
484
- field3: { $gt: 'not-number' } as any,
485
- },
486
- }),
487
- ).rejects.toThrow();
488
- });
489
-
490
- it('handles empty object filters', async () => {
491
- // Test empty object at top level
492
- await expect(
493
- vectorDB.query({
494
- indexName: testIndexName,
495
- queryVector: [1, 0, 0],
496
- filter: { field: { $eq: {} } },
497
- }),
498
- ).rejects.toThrow();
499
- });
500
-
501
- it('handles empty/undefined filters by returning all results', async () => {
502
- const noFilterCases = [{ field: {} }, { field: undefined }, { field: { $in: undefined } }];
503
-
504
- for (const filter of noFilterCases) {
505
- await expect(vectorDB.query({ indexName: testIndexName, queryVector: [1, 0, 0], filter })).rejects.toThrow();
506
- }
507
- });
508
- it('handles empty object filters', async () => {
509
- // Test empty object at top level
510
- await expect(
511
- vectorDB.query({
512
- indexName: testIndexName,
513
- queryVector: [1, 0, 0],
514
- filter: {},
515
- }),
516
- ).rejects.toThrow();
517
- });
518
- });
519
-
520
- describe('Metadata Filter Tests', () => {
521
- // Set up test vectors and metadata
522
- beforeAll(async () => {
523
- try {
524
- await vectorDB.deleteIndex({ indexName: testIndexName2 });
525
- } catch {
526
- // Ignore errors if index doesn't exist
527
- }
528
- await vectorDB.createIndex({ indexName: testIndexName2, dimension });
529
-
530
- const vectors = [
531
- [1, 0, 0], // Electronics
532
- [0, 1, 0], // Books
533
- [0, 0, 1], // Electronics
534
- [0, 0, 0.1], // Books
535
- ];
536
-
537
- const metadata = [
538
- {
539
- category: 'electronics',
540
- price: 1000,
541
- rating: 4.8,
542
- inStock: true,
543
- },
544
- {
545
- category: 'books',
546
- price: 25,
547
- rating: 4.2,
548
- inStock: true,
549
- },
550
- {
551
- category: 'electronics',
552
- price: 500,
553
- rating: 4.5,
554
- inStock: false,
555
- },
556
- {
557
- category: 'books',
558
- price: 15,
559
- rating: 4.9,
560
- inStock: true,
561
- },
562
- ];
563
-
564
- await vectorDB.upsert({ indexName: testIndexName2, vectors, metadata });
565
- // Wait for indexing
566
- await new Promise(resolve => setTimeout(resolve, 2000));
567
- });
568
-
569
- afterAll(async () => {
570
- // Cleanup after tests
571
- try {
572
- await vectorDB.deleteIndex({ indexName: testIndexName2 });
573
- } catch {
574
- // Ignore cleanup errors
575
- }
576
- });
577
-
578
- describe('Basic Comparison Operators', () => {
579
- it('filters with $eq operator', async () => {
580
- const results = await vectorDB.query({
581
- indexName: testIndexName2,
582
- queryVector: [1, 0, 0],
583
- filter: { category: { $eq: 'electronics' } },
584
- });
585
- expect(results.length).toBe(2);
586
- results.forEach(result => {
587
- expect(result.metadata?.category).toBe('electronics');
588
- });
589
- });
590
-
591
- it('filters with implicit $eq', async () => {
592
- const results = await vectorDB.query({
593
- indexName: testIndexName2,
594
- queryVector: [1, 0, 0],
595
- filter: { category: 'electronics' }, // implicit $eq
596
- });
597
- expect(results.length).toBe(2);
598
- results.forEach(result => {
599
- expect(result.metadata?.category).toBe('electronics');
600
- });
601
- });
602
- it('filters with $gt operator', async () => {
603
- const results = await vectorDB.query({
604
- indexName: testIndexName2,
605
- queryVector: [1, 0, 0],
606
- filter: { price: { $gt: 500 } },
607
- });
608
- expect(results.length).toBe(1);
609
- results.forEach(result => {
610
- expect(Number(result.metadata?.price)).toBeGreaterThan(500);
611
- });
612
- });
613
-
614
- it('filters with $gte operator', async () => {
615
- const results = await vectorDB.query({
616
- indexName: testIndexName2,
617
- queryVector: [1, 0, 0],
618
- filter: { price: { $gte: 500 } },
619
- });
620
- expect(results.length).toBe(2);
621
- results.forEach(result => {
622
- expect(Number(result.metadata?.price)).toBeGreaterThanOrEqual(500);
623
- });
624
- });
625
-
626
- it('filters with $lt operator', async () => {
627
- const results = await vectorDB.query({
628
- indexName: testIndexName2,
629
- queryVector: [1, 0, 0],
630
- filter: { price: { $lt: 100 } },
631
- });
632
- expect(results.length).toBe(2);
633
- results.forEach(result => {
634
- expect(Number(result.metadata?.price)).toBeLessThan(100);
635
- });
636
- });
637
-
638
- it('filters with $lte operator', async () => {
639
- const results = await vectorDB.query({
640
- indexName: testIndexName2,
641
- queryVector: [1, 0, 0],
642
- filter: { price: { $lte: 500 } },
643
- });
644
- expect(results.length).toBeGreaterThan(0);
645
- results.forEach(result => {
646
- expect(Number(result.metadata?.price)).toBeLessThanOrEqual(500);
647
- });
648
- });
649
-
650
- it('filters with $gte, $lt, $lte operators', async () => {
651
- const results = await vectorDB.query({
652
- indexName: testIndexName2,
653
- queryVector: [1, 0, 0],
654
- filter: { price: { $gte: 25, $lte: 500 } },
655
- });
656
- expect(results.length).toBe(2);
657
- results.forEach(result => {
658
- expect(Number(result.metadata?.price)).toBeLessThanOrEqual(500);
659
- expect(Number(result.metadata?.price)).toBeGreaterThanOrEqual(25);
660
- });
661
- });
662
-
663
- it('filters with $ne operator', async () => {
664
- const results = await vectorDB.query({
665
- indexName: testIndexName2,
666
- queryVector: [1, 0, 0],
667
- filter: { category: { $ne: 'electronics' } },
668
- });
669
- expect(results.length).toBe(2);
670
- results.forEach(result => {
671
- expect(result.metadata?.category).not.toBe('electronics');
672
- });
673
- });
674
-
675
- it('filters with boolean values', async () => {
676
- const results = await vectorDB.query({
677
- indexName: testIndexName2,
678
- queryVector: [1, 0, 0],
679
- filter: { inStock: true }, // test both implicit
680
- });
681
- expect(results.length).toBe(3);
682
- results.forEach(result => {
683
- expect(result.metadata?.inStock).toBe(true);
684
- });
685
- });
686
-
687
- it('filters with multiple fields', async () => {
688
- const results = await vectorDB.query({
689
- indexName: testIndexName2,
690
- queryVector: [1, 0, 0],
691
- filter: { category: 'electronics', price: 1000 },
692
- });
693
- expect(results.length).toBeGreaterThan(0);
694
- results.forEach(result => {
695
- expect(result.metadata?.category === 'electronics' && result.metadata?.price === 1000).toBe(true);
696
- });
697
- });
698
- });
699
-
700
- describe('Array Operators', () => {
701
- it('filters with $in operator', async () => {
702
- const results = await vectorDB.query({
703
- indexName: testIndexName2,
704
- queryVector: [1, 0, 0],
705
- filter: { category: { $in: ['electronics', 'books'] } },
706
- });
707
- expect(results.length).toBeGreaterThan(0);
708
- results.forEach(result => {
709
- expect(['electronics', 'books']).toContain(result.metadata?.category);
710
- });
711
- });
712
-
713
- it('should filter with $in operator for numbers', async () => {
714
- const results = await vectorDB.query({
715
- indexName: testIndexName2,
716
- queryVector: [1, 0, 0],
717
- filter: { price: { $in: [50, 75, 1000] } },
718
- });
719
- expect(results.length).toBeGreaterThan(0);
720
- results.forEach(result => {
721
- expect([50, 75, 1000]).toContain(result.metadata?.price);
722
- });
723
- });
724
-
725
- it('filters with $in operator for booleans', async () => {
726
- const results = await vectorDB.query({
727
- indexName: testIndexName2,
728
- queryVector: [1, 0, 0],
729
- filter: { inStock: { $in: [true] } },
730
- });
731
- expect(results.length).toBeGreaterThan(0);
732
- results.forEach(result => {
733
- expect(result.metadata?.inStock).toBe(true);
734
- });
735
- });
736
- });
737
-
738
- describe('Logical Operators', () => {
739
- it('filters with $and operator', async () => {
740
- const results = await vectorDB.query({
741
- indexName: testIndexName2,
742
- queryVector: [1, 0, 0],
743
- filter: { $and: [{ category: 'electronics' }, { price: { $gt: 500 } }] },
744
- });
745
- expect(results.length).toBe(1);
746
- expect(results[0]?.metadata?.category).toBe('electronics');
747
- expect(Number(results[0]?.metadata?.price)).toBeGreaterThan(500);
748
- });
749
-
750
- it('should filter with $and operator', async () => {
751
- const results = await vectorDB.query({
752
- indexName: testIndexName2,
753
- queryVector: [1, 0, 0],
754
- filter: { $and: [{ category: 'electronics' }, { price: { $gt: 700 } }, { inStock: true }] },
755
- });
756
- expect(results.length).toBeGreaterThan(0);
757
- results.forEach(result => {
758
- expect(result.metadata?.category).toBe('electronics');
759
- expect(Number(result.metadata?.price)).toBeGreaterThan(700);
760
- expect(result.metadata?.inStock).toBe(true);
761
- });
762
- });
763
-
764
- it('filters with $or operator', async () => {
765
- const results = await vectorDB.query({
766
- indexName: testIndexName2,
767
- queryVector: [1, 0, 0],
768
- filter: { $or: [{ price: { $gt: 900 } }, { rating: { $gt: 4.8 } }] },
769
- });
770
- expect(results.length).toBe(2);
771
- results.forEach(result => {
772
- expect(Number(result.metadata?.price) > 900 || Number(result.metadata?.rating) > 4.8).toBe(true);
773
- });
774
- });
775
-
776
- it('should filter with $or operator', async () => {
777
- const results = await vectorDB.query({
778
- indexName: testIndexName2,
779
- queryVector: [1, 0, 0],
780
- filter: { $or: [{ price: { $gt: 900 } }, { category: { $in: ['electronics', 'books'] } }] },
781
- });
782
- expect(results.length).toBeGreaterThan(0);
783
- results.forEach(result => {
784
- const condition1 = Number(result.metadata?.price) > 900;
785
- const condition2 = ['electronics', 'books'].includes(result.metadata?.category);
786
- expect(condition1 || condition2).toBe(true);
787
- });
788
- });
789
-
790
- it('should handle nested logical operators', async () => {
791
- const results = await vectorDB.query({
792
- indexName: testIndexName2,
793
- queryVector: [1, 0, 0],
794
- filter: {
795
- $and: [
796
- {
797
- $or: [{ category: 'electronics' }, { category: 'books' }],
798
- },
799
- { price: { $lt: 100 } },
800
- { inStock: true },
801
- ],
802
- },
803
- });
804
- expect(results.length).toBeGreaterThan(0);
805
- results.forEach(result => {
806
- expect(['electronics', 'books']).toContain(result.metadata?.category);
807
- expect(Number(result.metadata?.price)).toBeLessThan(100);
808
- expect(result.metadata?.inStock).toBe(true);
809
- });
810
- });
811
-
812
- it('uses implicit $eq within $or', async () => {
813
- const results = await vectorDB.query({
814
- indexName: testIndexName2,
815
- queryVector: [1, 0, 0],
816
- filter: { $or: [{ category: 'electronics' }, { price: { $gt: 100 } }] },
817
- });
818
- expect(results.length).toBeGreaterThan(0);
819
- });
820
-
821
- it('accepts single conditions in logical operators', async () => {
822
- const results = await vectorDB.query({
823
- indexName: testIndexName2,
824
- queryVector: [1, 0, 0],
825
- filter: { $and: [{ category: 'electronics' }] },
826
- });
827
- expect(results.length).toBeGreaterThan(0);
828
-
829
- const results2 = await vectorDB.query({
830
- indexName: testIndexName2,
831
- queryVector: [1, 0, 0],
832
- filter: { $or: [{ price: { $gt: 900 } }] },
833
- });
834
- expect(results2.length).toBeGreaterThan(0);
835
- });
836
- });
837
-
838
- describe('Complex Filter Combinations', () => {
839
- it('combines multiple operators and conditions', async () => {
840
- const results = await vectorDB.query({
841
- indexName: testIndexName2,
842
- queryVector: [1, 0, 0],
843
- filter: {
844
- $and: [
845
- { price: { $gt: 20 } },
846
- { inStock: true },
847
- {
848
- $or: [{ category: { $in: ['books'] } }, { rating: { $gt: 4.5 } }],
849
- },
850
- ],
851
- },
852
- });
853
- expect(results.length).toBeGreaterThan(0);
854
- results.forEach(result => {
855
- expect(Number(result.metadata?.price)).toBeGreaterThan(20);
856
- expect(result.metadata?.inStock).toBe(true);
857
- expect(result.metadata?.category === 'books' || Number(result.metadata?.rating) > 4.5).toBe(true);
858
- });
859
- });
860
-
861
- it('handles complex nested conditions', async () => {
862
- const results = await vectorDB.query({
863
- indexName: testIndexName2,
864
- queryVector: [1, 0, 0],
865
- filter: {
866
- $or: [
867
- {
868
- $and: [{ category: 'electronics' }, { price: { $gt: 700 } }],
869
- },
870
- {
871
- $and: [{ category: 'books' }, { price: { $lt: 20 } }],
872
- },
873
- ],
874
- },
875
- });
876
- expect(results.length).toBeGreaterThan(0);
877
- results.forEach(result => {
878
- if (result.metadata?.category === 'electronics') {
879
- expect(Number(result.metadata?.price)).toBeGreaterThan(700);
880
- } else {
881
- expect(Number(result.metadata?.price)).toBeLessThan(20);
882
- }
883
- });
884
- });
885
-
886
- it('should combine comparison and array operators', async () => {
887
- const results = await vectorDB.query({
888
- indexName: testIndexName2,
889
- queryVector: [1, 0, 0],
890
- filter: { $and: [{ price: { $gte: 500 } }, { rating: { $gt: 4.5 } }] },
891
- });
892
- expect(results.length).toBeGreaterThan(0);
893
- results.forEach(result => {
894
- expect(Number(result.metadata?.price)).toBeGreaterThanOrEqual(500);
895
- expect(Number(result.metadata?.rating)).toBeGreaterThan(4.5);
896
- });
897
- });
898
-
899
- it('should handle multiple conditions on same field', async () => {
900
- const results = await vectorDB.query({
901
- indexName: testIndexName2,
902
- queryVector: [1, 0, 0],
903
- filter: { $and: [{ price: { $gte: 30 } }, { price: { $lte: 800 } }] },
904
- });
905
- expect(results.length).toBeGreaterThan(0);
906
- results.forEach(result => {
907
- const price = Number(result.metadata?.price);
908
- expect(price).toBeGreaterThanOrEqual(30);
909
- expect(price).toBeLessThanOrEqual(800);
910
- });
911
- });
912
-
913
- it('should handle deeply nested logical operators', async () => {
914
- const results = await vectorDB.query({
915
- indexName: testIndexName2,
916
- queryVector: [1, 0, 0],
917
- filter: {
918
- $or: [
919
- {
920
- $and: [{ category: 'electronics' }, { price: { $gt: 700 } }, { rating: { $gt: 4.5 } }],
921
- },
922
- {
923
- $and: [{ category: 'books' }, { price: { $lt: 50 } }, { rating: { $gt: 4.0 } }],
924
- },
925
- ],
926
- },
927
- });
928
- expect(results.length).toBeGreaterThan(0);
929
- results.forEach(result => {
930
- const isExpensiveElectronics =
931
- result.metadata?.category === 'electronics' &&
932
- Number(result.metadata?.price) > 700 &&
933
- Number(result.metadata?.rating) > 4.5;
934
-
935
- const isCheapBook =
936
- result.metadata?.category === 'books' &&
937
- Number(result.metadata?.price) < 50 &&
938
- Number(result.metadata?.rating) > 4.0;
939
-
940
- expect(isExpensiveElectronics || isCheapBook).toBe(true);
941
- });
942
- });
943
- });
944
-
945
- describe('Date and Numeric Edge Cases', () => {
946
- beforeAll(async () => {
947
- const vectors = [
948
- [0.1, 0.1, 0.1],
949
- [0.2, 0.2, 0.2],
950
- ];
951
-
952
- const metadata = [
953
- {
954
- zero: 0,
955
- negativeZero: -0,
956
- infinity: Infinity,
957
- negativeInfinity: -Infinity,
958
- decimal: 0.1,
959
- negativeDecimal: -0.1,
960
- currentDate: new Date().toISOString(),
961
- epochDate: new Date(0).toISOString(),
962
- futureDate: new Date('2100-01-01').toISOString(),
963
- },
964
- {
965
- maxInt: Number.MAX_SAFE_INTEGER,
966
- minInt: Number.MIN_SAFE_INTEGER,
967
- maxFloat: Number.MAX_VALUE,
968
- minFloat: Number.MIN_VALUE,
969
- pastDate: new Date('1900-01-01').toISOString(),
970
- currentDate: new Date().toISOString(),
971
- },
972
- ];
973
-
974
- await vectorDB.upsert({
975
- indexName: testIndexName2,
976
- vectors,
977
- metadata,
978
- });
979
- await new Promise(resolve => setTimeout(resolve, 2000));
980
- });
981
-
982
- it('handles special numeric values', async () => {
983
- const results = await vectorDB.query({
984
- indexName: testIndexName2,
985
- queryVector: [1, 0, 0],
986
- filter: { $or: [{ zero: 0 }, { negativeZero: 0 }] },
987
- });
988
- expect(results.length).toBeGreaterThan(0);
989
- results.forEach(result => {
990
- const value = result.metadata?.zero ?? result.metadata?.negativeZero;
991
- expect(value).toBe(0);
992
- });
993
- });
994
-
995
- it('handles extreme numeric values', async () => {
996
- const results = await vectorDB.query({
997
- indexName: testIndexName2,
998
- queryVector: [1, 0, 0],
999
- filter: {
1000
- $or: [{ maxInt: { $gte: Number.MAX_SAFE_INTEGER } }, { minInt: { $lte: Number.MIN_SAFE_INTEGER } }],
1001
- },
1002
- });
1003
- expect(results.length).toBe(1);
1004
- });
1005
-
1006
- it('should handle numeric comparisons with decimals', async () => {
1007
- const results = await vectorDB.query({
1008
- indexName: testIndexName2,
1009
- queryVector: [1, 0, 0],
1010
- filter: {
1011
- rating: { $gt: 4.5 },
1012
- },
1013
- });
1014
- expect(results.length).toBeGreaterThan(0);
1015
- results.forEach(result => {
1016
- expect(Number(result.metadata?.rating)).toBeGreaterThan(4.5);
1017
- });
1018
- });
1019
-
1020
- it('should handle boolean values', async () => {
1021
- const results = await vectorDB.query({
1022
- indexName: testIndexName2,
1023
- queryVector: [1, 0, 0],
1024
- filter: { inStock: { $eq: false } },
1025
- });
1026
- expect(results.length).toBeGreaterThan(0);
1027
- results.forEach(result => {
1028
- expect(result.metadata?.inStock).toBe(false);
1029
- });
1030
- });
1031
- });
1032
-
1033
- describe('Additional Validation Tests', () => {
1034
- it('should throw error as date is not supported', async () => {
1035
- await expect(
1036
- vectorDB.query({
1037
- indexName: testIndexName2,
1038
- queryVector: [1, 0, 0],
1039
- filter: {
1040
- $and: [
1041
- { currentDate: { $lte: new Date().toISOString() } },
1042
- { currentDate: { $gt: new Date(0).toISOString() } },
1043
- ],
1044
- },
1045
- }),
1046
- ).rejects.toThrow();
1047
- });
1048
- it('should throw error as empty array in $in operator is not supported', async () => {
1049
- await expect(
1050
- vectorDB.query({
1051
- indexName: testIndexName2,
1052
- queryVector: [1, 0, 0],
1053
- filter: {
1054
- category: { $in: [] },
1055
- },
1056
- }),
1057
- ).rejects.toThrow();
1058
- });
1059
- it('should reject non-numeric values in numeric comparisons', async () => {
1060
- await expect(
1061
- vectorDB.query({
1062
- indexName: testIndexName2,
1063
- queryVector: [1, 0, 0],
1064
- filter: {
1065
- price: { $gt: '500' }, // string instead of number
1066
- },
1067
- }),
1068
- ).rejects.toThrow();
1069
- });
1070
-
1071
- it('should reject mixed types in $in operator', async () => {
1072
- await expect(
1073
- vectorDB.query({
1074
- indexName: testIndexName2,
1075
- queryVector: [1, 0, 0],
1076
- filter: {
1077
- field: { $in: ['string', 123] }, // mixed string and number
1078
- },
1079
- }),
1080
- ).rejects.toThrow();
1081
- });
1082
- it('should handle undefined filter', async () => {
1083
- const results1 = await vectorDB.query({
1084
- indexName: testIndexName2,
1085
- queryVector: [1, 0, 0],
1086
- filter: undefined,
1087
- });
1088
- const results2 = await vectorDB.query({
1089
- indexName: testIndexName2,
1090
- queryVector: [1, 0, 0],
1091
- });
1092
- expect(results1).toEqual(results2);
1093
- expect(results1.length).toBeGreaterThan(0);
1094
- });
1095
-
1096
- it('should handle null filter', async () => {
1097
- const results = await vectorDB.query({
1098
- indexName: testIndexName2,
1099
- queryVector: [1, 0, 0],
1100
- filter: null,
1101
- });
1102
- const results2 = await vectorDB.query({
1103
- indexName: testIndexName2,
1104
- queryVector: [1, 0, 0],
1105
- });
1106
- expect(results).toEqual(results2);
1107
- expect(results.length).toBeGreaterThan(0);
1108
- });
1109
- });
1110
-
1111
- describe('Additional Edge Cases', () => {
1112
- it('should handle exact boundary conditions', async () => {
1113
- const results = await vectorDB.query({
1114
- indexName: testIndexName2,
1115
- queryVector: [1, 0, 0],
1116
- filter: {
1117
- $and: [{ price: { $gte: 25 } }, { price: { $lte: 1000 } }],
1118
- },
1119
- });
1120
- expect(results.length).toBeGreaterThan(0);
1121
- expect(results.some(r => r.metadata?.price === 25)).toBe(true);
1122
- expect(results.some(r => r.metadata?.price === 1000)).toBe(true);
1123
- });
1124
- });
1125
-
1126
- describe('Additional Complex Logical Combinations', () => {
1127
- it('should handle deeply nested $or conditions', async () => {
1128
- const results = await vectorDB.query({
1129
- indexName: testIndexName2,
1130
- queryVector: [1, 0, 0],
1131
- filter: {
1132
- $or: [
1133
- {
1134
- $and: [{ category: 'electronics' }, { $or: [{ price: { $gt: 900 } }, { rating: { $gt: 4.8 } }] }],
1135
- },
1136
- {
1137
- $and: [{ category: 'books' }, { $or: [{ price: { $lt: 30 } }, { rating: { $gt: 4.5 } }] }],
1138
- },
1139
- ],
1140
- },
1141
- });
1142
- expect(results.length).toBeGreaterThan(0);
1143
- results.forEach(result => {
1144
- if (result.metadata?.category === 'electronics') {
1145
- expect(Number(result.metadata?.price) > 900 || Number(result.metadata?.rating) > 4.8).toBe(true);
1146
- } else if (result.metadata?.category === 'books') {
1147
- expect(Number(result.metadata?.price) < 30 || Number(result.metadata?.rating) > 4.5).toBe(true);
1148
- }
1149
- });
1150
- });
1151
-
1152
- it('should handle multiple field comparisons with same value', async () => {
1153
- const results = await vectorDB.query({
1154
- indexName: testIndexName2,
1155
- queryVector: [1, 0, 0],
1156
- filter: {
1157
- $or: [{ price: { $gt: 500 } }, { rating: { $gt: 4.5 } }],
1158
- },
1159
- });
1160
- expect(results.length).toBeGreaterThan(0);
1161
- results.forEach(result => {
1162
- expect(Number(result.metadata?.price) > 500 || Number(result.metadata?.rating) > 4.5).toBe(true);
1163
- });
1164
- });
1165
- });
1166
-
1167
- describe('Performance Edge Cases', () => {
1168
- it('should handle filters with many conditions', async () => {
1169
- const results = await vectorDB.query({
1170
- indexName: testIndexName2,
1171
- queryVector: [1, 0, 0],
1172
- filter: {
1173
- $and: Array(10)
1174
- .fill(null)
1175
- .map(() => ({
1176
- $or: [{ price: { $gt: 100 } }, { rating: { $gt: 4.0 } }],
1177
- })),
1178
- },
1179
- });
1180
- expect(results.length).toBeGreaterThan(0);
1181
- results.forEach(result => {
1182
- expect(Number(result.metadata?.price) > 100 || Number(result.metadata?.rating) > 4.0).toBe(true);
1183
- });
1184
- });
1185
-
1186
- it('should handle deeply nested conditions efficiently', async () => {
1187
- const results = await vectorDB.query({
1188
- indexName: testIndexName2,
1189
- queryVector: [1, 0, 0],
1190
- filter: {
1191
- $or: Array(5)
1192
- .fill(null)
1193
- .map(() => ({
1194
- $and: [
1195
- { category: { $in: ['electronics', 'books'] } },
1196
- { price: { $gt: 50 } },
1197
- { rating: { $gt: 4.0 } },
1198
- ],
1199
- })),
1200
- },
1201
- });
1202
- expect(results.length).toBeGreaterThan(0);
1203
- results.forEach(result => {
1204
- expect(['electronics', 'books']).toContain(result.metadata?.category);
1205
- expect(Number(result.metadata?.price)).toBeGreaterThan(50);
1206
- expect(Number(result.metadata?.rating)).toBeGreaterThan(4.0);
1207
- });
1208
- });
1209
-
1210
- it('should handle large number of $or conditions', async () => {
1211
- const results = await vectorDB.query({
1212
- indexName: testIndexName2,
1213
- queryVector: [1, 0, 0],
1214
- filter: {
1215
- $or: [
1216
- ...Array(5)
1217
- .fill(null)
1218
- .map((_, i) => ({
1219
- price: { $gt: i * 100 },
1220
- })),
1221
- ...Array(5)
1222
- .fill(null)
1223
- .map((_, i) => ({
1224
- rating: { $gt: 4.0 + i * 0.1 },
1225
- })),
1226
- ],
1227
- },
1228
- });
1229
- expect(results.length).toBeGreaterThan(0);
1230
- });
1231
- });
1232
- });
1233
-
1234
- describe('Document Operations and Filtering', () => {
1235
- const testDocuments = [
1236
- 'The quick brown fox jumps over the lazy dog',
1237
- 'Pack my box with five dozen liquor jugs',
1238
- 'How vexingly quick daft zebras JUMP',
1239
- ];
1240
-
1241
- beforeAll(async () => {
1242
- try {
1243
- await vectorDB.deleteIndex({ indexName: testIndexName3 });
1244
- } catch {
1245
- // Ignore errors if index doesn't exist
1246
- }
1247
- await vectorDB.createIndex({ indexName: testIndexName3, dimension });
1248
-
1249
- const testVectors = [
1250
- [1.0, 0.0, 0.0],
1251
- [0.0, 1.0, 0.0],
1252
- [0.0, 0.0, 1.0],
1253
- ];
1254
-
1255
- const testMetadata = [
1256
- { source: 'pangram1', length: 43 },
1257
- { source: 'pangram2', length: 32 },
1258
- { source: 'pangram3', length: 30 },
1259
- ];
1260
- const testIds = ['doc1', 'doc2', 'doc3'];
1261
-
1262
- await vectorDB.upsert({
1263
- indexName: testIndexName3,
1264
- vectors: testVectors,
1265
- documents: testDocuments,
1266
- metadata: testMetadata,
1267
- ids: testIds,
1268
- });
1269
-
1270
- // Wait for indexing
1271
- await new Promise(resolve => setTimeout(resolve, 2000));
1272
- });
1273
-
1274
- afterAll(async () => {
1275
- // Cleanup after tests
1276
- try {
1277
- await vectorDB.deleteIndex({ indexName: testIndexName3 });
1278
- } catch {
1279
- // Ignore cleanup errors
1280
- }
1281
- });
1282
-
1283
- describe('Basic Document Operations', () => {
1284
- it('should store and retrieve documents', async () => {
1285
- const results = await vectorDB.query({ indexName: testIndexName3, queryVector: [1.0, 0.0, 0.0], topK: 3 });
1286
- expect(results).toHaveLength(3);
1287
- // Verify documents are returned
1288
- expect(results[0].document).toBe(testDocuments[0]);
1289
- });
1290
-
1291
- it('should filter documents using $contains', async () => {
1292
- const results = await vectorDB.query({
1293
- indexName: testIndexName3,
1294
- queryVector: [1.0, 0.0, 0.0],
1295
- topK: 3,
1296
- documentFilter: { $contains: 'quick' },
1297
- });
1298
- expect(results).toHaveLength(2);
1299
- });
1300
-
1301
- it('should filter with $not_contains', async () => {
1302
- const results = await vectorDB.query({
1303
- indexName: testIndexName3,
1304
- queryVector: [1.0, 0.0, 0.0],
1305
- topK: 3,
1306
- documentFilter: { $not_contains: 'fox' },
1307
- });
1308
- expect(results.every(r => !r.document?.includes('fox'))).toBe(true);
1309
- });
1310
-
1311
- it('should combine metadata and document filters', async () => {
1312
- const results = await vectorDB.query({
1313
- indexName: testIndexName3,
1314
- queryVector: [1.0, 0.0, 0.0],
1315
- topK: 3,
1316
- filter: { source: 'pangram1' },
1317
- documentFilter: { $contains: 'fox' },
1318
- });
1319
- expect(results).toHaveLength(1);
1320
- expect(results[0].metadata?.source).toBe('pangram1');
1321
- expect(results[0].document).toContain('fox');
1322
- });
1323
- });
1324
-
1325
- describe('Complex Document Filtering', () => {
1326
- it('should handle $and conditions', async () => {
1327
- const results = await vectorDB.query({
1328
- indexName: testIndexName3,
1329
- queryVector: [1.0, 0.0, 0.0],
1330
- topK: 3,
1331
- documentFilter: { $and: [{ $contains: 'quick' }, { $not_contains: 'fox' }] },
1332
- });
1333
- expect(results).toHaveLength(1);
1334
- expect(results[0].document).toContain('quick');
1335
- expect(results[0].document).not.toContain('fox');
1336
- });
1337
-
1338
- it('should handle $or conditions', async () => {
1339
- const results = await vectorDB.query({
1340
- indexName: testIndexName3,
1341
- queryVector: [1.0, 0.0, 0.0],
1342
- topK: 3,
1343
- documentFilter: { $or: [{ $contains: 'fox' }, { $contains: 'zebras' }] },
1344
- });
1345
- expect(results).toHaveLength(2);
1346
- expect(results[0].document).toContain('fox');
1347
- expect(results[1].document).toContain('zebras');
1348
- });
1349
- });
1350
-
1351
- describe('Edge Cases and Validation', () => {
1352
- it('allows empty string in $contains', async () => {
1353
- const results = await vectorDB.query({
1354
- indexName: testIndexName3,
1355
- queryVector: [1.0, 0.0, 0.0],
1356
- topK: 3,
1357
- documentFilter: { $contains: '' },
1358
- });
1359
- expect(results).toHaveLength(3);
1360
- });
1361
-
1362
- it('should be case sensitive', async () => {
1363
- // First verify lowercase works
1364
- const lowerResults = await vectorDB.query({
1365
- indexName: testIndexName3,
1366
- queryVector: [1.0, 0.0, 0.0],
1367
- topK: 3,
1368
- documentFilter: { $contains: 'quick' },
1369
- });
1370
- expect(lowerResults.length).toBe(2);
1371
-
1372
- // Then verify uppercase doesn't match
1373
- const upperResults = await vectorDB.query({
1374
- indexName: testIndexName3,
1375
- queryVector: [1.0, 0.0, 0.0],
1376
- topK: 3,
1377
- documentFilter: { $contains: 'QUICK' },
1378
- });
1379
- expect(upperResults.length).toBe(0);
1380
-
1381
- const upperResults2 = await vectorDB.query({
1382
- indexName: testIndexName3,
1383
- queryVector: [1.0, 0.0, 0.0],
1384
- topK: 3,
1385
- documentFilter: { $contains: 'JUMP' },
1386
- });
1387
- expect(upperResults2.length).toBe(1);
1388
- });
1389
-
1390
- it('should handle exact string matches', async () => {
1391
- const results = await vectorDB.query({
1392
- indexName: testIndexName3,
1393
- queryVector: [1.0, 0.0, 0.0],
1394
- topK: 3,
1395
- documentFilter: { $contains: 'quick brown' }, // Test multi-word match
1396
- });
1397
- expect(results.length).toBe(1);
1398
- expect(results[0].document).toContain('quick brown');
1399
- });
1400
-
1401
- it('should handle deeply nested logical operators', async () => {
1402
- const results = await vectorDB.query({
1403
- indexName: testIndexName3,
1404
- queryVector: [1.0, 0.0, 0.0],
1405
- topK: 3,
1406
- documentFilter: {
1407
- $or: [
1408
- {
1409
- $and: [{ $contains: 'quick' }, { $not_contains: 'fox' }],
1410
- },
1411
- {
1412
- $and: [{ $contains: 'box' }, { $not_contains: 'quick' }],
1413
- },
1414
- ],
1415
- },
1416
- });
1417
- expect(results.length).toBeGreaterThan(0);
1418
- results.forEach(result => {
1419
- if (result.document?.includes('quick')) {
1420
- expect(result.document).not.toContain('fox');
1421
- } else if (result.document?.includes('box')) {
1422
- expect(result.document).not.toContain('quick');
1423
- }
1424
- });
1425
- });
1426
- it('should handle undefined document filter', async () => {
1427
- const results1 = await vectorDB.query({
1428
- indexName: testIndexName3,
1429
- queryVector: [1, 0, 0],
1430
- documentFilter: undefined,
1431
- });
1432
- const results2 = await vectorDB.query({
1433
- indexName: testIndexName3,
1434
- queryVector: [1, 0, 0],
1435
- });
1436
- expect(results1).toEqual(results2);
1437
- expect(results1.length).toBeGreaterThan(0);
1438
- });
1439
-
1440
- it('should handle empty object document filter', async () => {
1441
- await expect(
1442
- vectorDB.query({
1443
- indexName: testIndexName3,
1444
- queryVector: [1, 0, 0],
1445
- documentFilter: {},
1446
- }),
1447
- ).rejects.toThrow();
1448
- });
1449
-
1450
- it('should handle null filter', async () => {
1451
- const results = await vectorDB.query({
1452
- indexName: testIndexName3,
1453
- queryVector: [1, 0, 0],
1454
- documentFilter: null,
1455
- });
1456
- const results2 = await vectorDB.query({
1457
- indexName: testIndexName3,
1458
- queryVector: [1, 0, 0],
1459
- });
1460
- expect(results).toEqual(results2);
1461
- expect(results.length).toBeGreaterThan(0);
1462
- });
1463
- });
1464
- });
1465
-
1466
- describe('Performance and Concurrency', () => {
1467
- const perfTestIndex = 'perf-test-index';
1468
-
1469
- beforeEach(async () => {
1470
- try {
1471
- await vectorDB.deleteIndex({ indexName: perfTestIndex });
1472
- } catch {
1473
- // Ignore errors if index doesn't exist
1474
- }
1475
- await vectorDB.createIndex({ indexName: perfTestIndex, dimension });
1476
- }, 10000);
1477
-
1478
- afterEach(async () => {
1479
- try {
1480
- await vectorDB.deleteIndex({ indexName: perfTestIndex });
1481
- } catch {
1482
- // Ignore cleanup errors
1483
- }
1484
- }, 10000);
1485
-
1486
- it('handles concurrent operations correctly', async () => {
1487
- const promises = Array(10)
1488
- .fill(0)
1489
- .map((_, i) =>
1490
- vectorDB.upsert({
1491
- indexName: perfTestIndex,
1492
- vectors: [[1, 0, 0]],
1493
- metadata: [{ test: 'concurrent', id: i }],
1494
- ids: [`concurrent-${i}`],
1495
- }),
1496
- );
1497
- await Promise.all(promises);
1498
-
1499
- const results = await vectorDB.query({
1500
- indexName: perfTestIndex,
1501
- queryVector: [1, 0, 0],
1502
- filter: { test: 'concurrent' },
1503
- });
1504
- expect(results).toHaveLength(10);
1505
- }, 15000);
1506
-
1507
- it('handles large batch operations', async () => {
1508
- const batchSize = 100; // Using 100 instead of 1000 to keep tests fast
1509
- const vectors = Array(batchSize)
1510
- .fill(0)
1511
- .map(() => [1, 0, 0]);
1512
- const metadata = vectors.map((_, i) => ({ index: i, test: 'batch' }));
1513
- const ids = vectors.map((_, i) => `batch-${i}`);
1514
-
1515
- await vectorDB.upsert({
1516
- indexName: perfTestIndex,
1517
- vectors,
1518
- metadata,
1519
- ids,
1520
- });
1521
-
1522
- // Verify all vectors were inserted
1523
- const stats = await vectorDB.describeIndex({ indexName: perfTestIndex });
1524
- expect(stats.count).toBe(batchSize);
1525
-
1526
- const results = await vectorDB.query({
1527
- indexName: perfTestIndex,
1528
- queryVector: [1, 0, 0],
1529
- filter: { test: 'batch' },
1530
- topK: batchSize,
1531
- });
1532
- expect(results).toHaveLength(batchSize);
1533
-
1534
- // Test querying with pagination
1535
- const pageSize = 20;
1536
- const pages: QueryResult[][] = [];
1537
- for (let i = 0; i < batchSize; i += pageSize) {
1538
- const page = await vectorDB.query({
1539
- indexName: perfTestIndex,
1540
- queryVector: [1, 0, 0],
1541
- filter: { test: 'batch' },
1542
- topK: pageSize,
1543
- });
1544
- pages.push(page);
1545
- expect(page).toHaveLength(Math.min(pageSize, batchSize - i));
1546
- }
1547
- expect(pages).toHaveLength(Math.ceil(batchSize / pageSize));
1548
- }, 30000);
1549
- });
1550
- });