diff --git a/src/zvec b/src/zvec index bdf58fc..d9b0920 160000 --- a/src/zvec +++ b/src/zvec @@ -1 +1 @@ -Subproject commit bdf58fc2d2d2be3ee69eeb5fc62c4601d4d6cc6a +Subproject commit d9b0920ac7426cad85a79e2daeb652e6698ae2bf diff --git a/tests/data/operations.test.ts b/tests/data/operations.test.ts index 288a3a8..c2ab116 100644 --- a/tests/data/operations.test.ts +++ b/tests/data/operations.test.ts @@ -4,7 +4,8 @@ import { ZVecCreateAndOpen, ZVecInitialize, ZVecLogLevel, - ZVecLogType + ZVecLogType, + isZVecError } from '../../src/index'; import { batch, createTestSchema, expectDoc, makeDoc, verifyDocs } from './helpers'; @@ -116,19 +117,163 @@ describe('Data Operations Pipeline', () => { expect(collection.stats.indexCompleteness['sparse']).toBeCloseTo(1); }); - // it('should resolve concurrent async queries with correct results', async () => { - // const targets = [550, 600, 700, 800, 900, 950]; - // const queries = targets.map(k => { - // const doc = makeDoc(k, 1, 1); - // return collection.query({ - // fieldName: 'dense', vector: doc.vectors!.dense, topk: 10, includeVector: true, - // }).then(results => ({ k, results })); - // }); - - // const allResults = await Promise.all(queries); - // for (const { k, results } of allResults) { - // expectDoc(results[0], k, 1, 1); - // } - // }); + it('should resolve concurrent async queries with correct results', async () => { + const targets = [550, 600, 700, 800, 900, 950]; + const denseQueries = targets.map(k => { + const doc = makeDoc(k, 1, 1); + return collection.query({ + fieldName: 'dense', vector: doc.vectors!.dense, topk: 10, includeVector: true, + }).then(results => ({ k, results })); + }); + const sparseQueries = targets.map(k => { + const doc = makeDoc(k, 1, 1); + return collection.query({ + fieldName: 'sparse', vector: doc.vectors!.sparse, topk: 10, includeVector: true, + filter: `title = "Product_${k}_v1"` + }).then(results => ({ k, results })); + }); + + const denseResults = await Promise.all(denseQueries); + for (const { k, results } of denseResults) { + expect(results.length).toBe(10); + expectDoc(results[0], k, 1, 1); + } + const sparseResults = await Promise.all(sparseQueries); + for (const { k, results } of sparseResults) { + expect(results.length).toBe(1); + expectDoc(results[0], k, 1, 1); + } + }); + }); + + + describe('update', () => { + it('should update scalar fields without changing vectors', () => { + batch(collection, 'update', 501, 1000, 3, 1); + verifyDocs(collection, 501, 1000, 3, 1); + }); + + it('should not affect docs outside the update range', () => { + verifyDocs(collection, 1, 500, 2, 2); + verifyDocs(collection, 1001, 1500, 2, 2); + }); + + it('should return correct results from vector query after field update', () => { + const doc = makeDoc(750, 3, 1); + const denseResults = collection.querySync({ + fieldName: 'dense', vector: doc.vectors!.dense, topk: 1, includeVector: true + }); + expectDoc(denseResults[0], 750, 3, 1); + const sparseResults = collection.querySync({ + fieldName: 'sparse', vector: doc.vectors!.sparse, topk: 1, includeVector: true + }); + expectDoc(sparseResults[0], 750, 3, 1); + }); + }); + + + describe('delete', () => { + it('should delete a single doc by id', () => { + const doc = makeDoc(1001, 2, 2); + const result = collection.deleteSync(doc.id); + expect(result.ok).toBe(true); + const fetched = collection.fetchSync(doc.id); + expect(fetched[doc.id]).toBeUndefined(); + }); + + it('should delete a batch of docs by ids', () => { + const ids = Array.from({ length: 100 }, (_, i) => makeDoc(1002 + i, 2, 2).id); + const results = collection.deleteSync(ids); + for (const status of results) { + expect(status.ok).toBe(true); + } + const fetched = collection.fetchSync(ids); + for (const id of ids) { + expect(fetched[id]).toBeUndefined(); + } + }); + + it('should reflect correct docCount after deletions', () => { + expect(collection.stats.docCount).toBe(1500 - 101); + }); + + it('should not affect remaining docs', () => { + verifyDocs(collection, 1, 500, 2, 2); + verifyDocs(collection, 501, 1000, 3, 1); + verifyDocs(collection, 1102, 1500, 2, 2); + }); + + it('should delete docs matching a filter expression', async () => { + const result = await collection.deleteByFilter('price > 2800 AND title LIKE "%_v2"'); + expect(result.ok).toBe(true); + const fetched = collection.fetchSync(['doc_1400', 'doc_1450', 'doc_1500']); + expect(fetched['doc_1400']).toBeUndefined(); + expect(fetched['doc_1450']).toBeUndefined(); + expect(fetched['doc_1500']).toBeUndefined(); + }); + + it('should not affect docs outside the filter criteria', () => { + verifyDocs(collection, 1, 500, 2, 2); + verifyDocs(collection, 501, 1000, 3, 1); + verifyDocs(collection, 1102, 1399, 2, 2); + }); + }); + + + describe('error handling', () => { + it('should throw on insert with missing required vector field', () => { + try { + collection.insertSync({ id: 'bad_1', fields: { title: 'no vectors' } } as any); + fail('expected to throw'); + } catch (e) { + expect(isZVecError(e)).toBe(true); + } + }); + + it('should throw on insert with wrong vector dimension', () => { + try { + collection.insertSync({ + id: 'bad_2', + vectors: { dense: [1, 2, 3], sparse: { 0: 1.0 } }, + fields: { title: 'wrong dim' } + }); + fail('expected to throw'); + } catch (e) { + expect(isZVecError(e)).toBe(true); + } + }); + + it('should fail to insert a duplicate doc id', () => { + const doc = makeDoc(1, 1, 1); + const result = collection.insertSync(doc); + expect(result.ok).toBe(false); + }); + + it('should fail to update a non-existent doc', () => { + const result = collection.updateSync({ id: 'nonexistent', fields: { title: 'ghost' } }); + expect(result.ok).toBe(false); + }); + + it('should throw on query with invalid filter syntax', () => { + const doc = makeDoc(1, 1, 1); + try { + collection.querySync({ + fieldName: 'dense', vector: doc.vectors!.dense, topk: 1, + filter: 'invalid @@@ syntax' + }); + fail('expected to throw'); + } catch (e) { + expect(isZVecError(e)).toBe(true); + } + }); + + it('should throw on query with non-existent vector field', () => { + try { + collection.querySync({ fieldName: 'nonexistent', vector: [1, 2, 3], topk: 1 }); + fail('expected to throw'); + } catch (e) { + expect(isZVecError(e)).toBe(true); + } + }); }); });