Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
175 changes: 160 additions & 15 deletions tests/data/operations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import {
ZVecCreateAndOpen,
ZVecInitialize,
ZVecLogLevel,
ZVecLogType
ZVecLogType,
isZVecError
} from '../../src/index';
import { batch, createTestSchema, expectDoc, makeDoc, verifyDocs } from './helpers';

Expand Down Expand Up @@ -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);
}
});
});
});
Loading