diff --git a/Makefile b/Makefile index b604171..b50751b 100644 --- a/Makefile +++ b/Makefile @@ -43,10 +43,12 @@ ifndef OMIT_SIMD CFLAGS += -mcpu=apple-m1 -DSQLITE_VEC_ENABLE_NEON endif ifeq ($(shell uname -s),Linux) + ifeq ($(findstring android,$(CC)),) ifneq ($(filter avx,$(shell grep -o 'avx[^ ]*' /proc/cpuinfo 2>/dev/null | head -1)),) CFLAGS += -mavx -DSQLITE_VEC_ENABLE_AVX endif endif + endif endif ifdef USE_BREW_SQLITE diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..4c3cc19 --- /dev/null +++ b/TODO.md @@ -0,0 +1,73 @@ +# TODO: `ann` base branch + consolidated benchmarks + +## 1. Create `ann` branch with shared code + +### 1.1 Branch setup +- [x] `git checkout -B ann origin/main` +- [x] Cherry-pick `624f998` (vec0_distance_full shared distance dispatch) +- [x] Cherry-pick stdint.h fix for test header +- [ ] Pull NEON cosine optimization from ivf-yolo3 into shared code + - Currently only in ivf branch but is general-purpose (benefits all distance calcs) + - Lives in `distance_cosine_float()` — ~57 lines of ARM NEON vectorized cosine + +### 1.2 Benchmark infrastructure (`benchmarks-ann/`) +- [x] Seed data pipeline (`seed/Makefile`, `seed/build_base_db.py`) +- [x] Ground truth generator (`ground_truth.py`) +- [x] Results schema (`schema.sql`) +- [x] Benchmark runner with `INDEX_REGISTRY` extension point (`bench.py`) + - Baseline configs (float, int8-rescore, bit-rescore) implemented + - Index branches register their types via `INDEX_REGISTRY` dict +- [x] Makefile with baseline targets +- [x] README + +### 1.3 Rebase feature branches onto `ann` +- [x] Rebase `diskann-yolo2` onto `ann` (1 commit: DiskANN implementation) +- [x] Rebase `ivf-yolo3` onto `ann` (1 commit: IVF implementation) +- [x] Rebase `annoy-yolo2` onto `ann` (2 commits: Annoy implementation + schema fix) +- [x] Verify each branch has only its index-specific commits remaining +- [ ] Force-push all 4 branches to origin + +--- + +## 2. Per-branch: register index type in benchmarks + +Each index branch should add to `benchmarks-ann/` when rebased onto `ann`: + +### 2.1 Register in `bench.py` + +Add an `INDEX_REGISTRY` entry. Each entry provides: +- `defaults` — default param values +- `create_table_sql(params)` — CREATE VIRTUAL TABLE with INDEXED BY clause +- `insert_sql(params)` — custom insert SQL, or None for default +- `post_insert_hook(conn, params)` — training/building step, returns time +- `run_query(conn, params, query, k)` — custom query, or None for default MATCH +- `describe(params)` — one-line description for report output + +### 2.2 Add configs to `Makefile` + +Append index-specific config variables and targets. Example pattern: + +```makefile +DISKANN_CONFIGS = \ + "diskann-R48-binary:type=diskann,R=48,L=128,quantizer=binary" \ + ... + +ALL_CONFIGS += $(DISKANN_CONFIGS) + +bench-diskann: seed + $(BENCH) --subset-size 10000 -k 10 -o runs/diskann $(BASELINES) $(DISKANN_CONFIGS) + ... +``` + +### 2.3 Migrate existing benchmark results/docs + +- Move useful results docs (RESULTS.md, etc.) into `benchmarks-ann/results/` +- Delete redundant per-branch benchmark directories once consolidated infra is proven + +--- + +## 3. Future improvements + +- [ ] Reporting script (`report.py`) — query results.db, produce markdown comparison tables +- [ ] Profiling targets in Makefile (lift from ivf-yolo3's Instruments/perf wrappers) +- [ ] Pre-computed ground truth integration (use GT DB files instead of on-the-fly brute-force) diff --git a/VERSION b/VERSION index 699c6c6..1a03094 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.1.8 +0.1.9 diff --git a/benchmarks-ann/Makefile b/benchmarks-ann/Makefile index 762abea..0789d38 100644 --- a/benchmarks-ann/Makefile +++ b/benchmarks-ann/Makefile @@ -49,6 +49,7 @@ bench-rescore: seed $(BENCH) --subset-size 10000 -k 10 -o runs/rescore \ $(RESCORE_CONFIGS) + # --- Standard sizes --- bench-10k: seed $(BENCH) --subset-size 10000 -k 10 -o runs/10k $(ALL_CONFIGS) diff --git a/sqlite-vec.c b/sqlite-vec.c index ff9e0da..7079f7e 100644 --- a/sqlite-vec.c +++ b/sqlite-vec.c @@ -2553,6 +2553,7 @@ struct Vec0RescoreConfig { }; #endif + struct VectorColumnDefinition { char *name; int name_length; @@ -8899,11 +8900,17 @@ int vec0Update_Delete_ClearMetadata(vec0_vtab *p, int metadata_idx, i64 rowid, i } sqlite3_bind_int64(stmt, 1, rowid); rc = sqlite3_step(stmt); + sqlite3_finalize(stmt); if(rc != SQLITE_DONE) { rc = SQLITE_ERROR; goto done; } - sqlite3_finalize(stmt); + // Fix for https://github.com/asg017/sqlite-vec/issues/274 + // sqlite3_step returns SQLITE_DONE (101) on DML success, but the + // `done:` epilogue treats anything other than SQLITE_OK as an error. + // Without this, SQLITE_DONE propagates up to vec0Update_Delete, + // which aborts the DELETE scan and silently drops remaining rows. + rc = SQLITE_OK; } break; } diff --git a/tests/__snapshots__/test-metadata.ambr b/tests/__snapshots__/test-metadata.ambr index ff7b112..e5ffaf2 100644 --- a/tests/__snapshots__/test-metadata.ambr +++ b/tests/__snapshots__/test-metadata.ambr @@ -27,8 +27,8 @@ OrderedDict({ 'chunk_id': 1, 'size': 8, - 'validity': b'\x06', - 'rowids': b'\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 'validity': b'\x02', + 'rowids': b'\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', }), ]), }), @@ -37,7 +37,7 @@ 'rows': list([ OrderedDict({ 'rowid': 1, - 'data': b'\x06', + 'data': b'\x02', }), ]), }), @@ -46,7 +46,7 @@ 'rows': list([ OrderedDict({ 'rowid': 1, - 'data': b'\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 'data': b'\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', }), ]), }), @@ -55,7 +55,7 @@ 'rows': list([ OrderedDict({ 'rowid': 1, - 'data': b'\x00\x00\x00\x00\x00\x00\x00\x00\x9a\x99\x99\x99\x99\x99\x01@ffffff\n@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 'data': b'\x00\x00\x00\x00\x00\x00\x00\x00\x9a\x99\x99\x99\x99\x99\x01@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', }), ]), }), @@ -64,17 +64,13 @@ 'rows': list([ OrderedDict({ 'rowid': 1, - 'data': b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00test2\x00\x00\x00\x00\x00\x00\x00\r\x00\x00\x00123456789012\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 'data': b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00test2\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', }), ]), }), 'v_metadatatext03': OrderedDict({ 'sql': 'select * from v_metadatatext03', 'rows': list([ - OrderedDict({ - 'rowid': 3, - 'data': '1234567890123', - }), ]), }), 'v_rowids': OrderedDict({ @@ -86,12 +82,6 @@ 'chunk_id': 1, 'chunk_offset': 1, }), - OrderedDict({ - 'rowid': 3, - 'id': None, - 'chunk_id': 1, - 'chunk_offset': 2, - }), ]), }), 'v_vector_chunks00': OrderedDict({ @@ -99,7 +89,7 @@ 'rows': list([ OrderedDict({ 'rowid': 1, - 'vectors': b'\x00\x00\x00\x00""""3333\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 'vectors': b'\x00\x00\x00\x00""""\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', }), ]), }), @@ -370,14 +360,6 @@ 'f': 2.2, 't': 'test2', }), - OrderedDict({ - 'rowid': 3, - 'vector': b'3333', - 'b': 1, - 'n': 3, - 'f': 3.3, - 't': '1234567890123', - }), ]), }) # --- diff --git a/tests/sqlite-vec-internal.h b/tests/sqlite-vec-internal.h index cbc2c08..ca04b74 100644 --- a/tests/sqlite-vec-internal.h +++ b/tests/sqlite-vec-internal.h @@ -82,6 +82,7 @@ struct Vec0RescoreConfig { }; #endif + struct VectorColumnDefinition { char *name; int name_length; diff --git a/tests/test-metadata.py b/tests/test-metadata.py index 9e5202e..f805362 100644 --- a/tests/test-metadata.py +++ b/tests/test-metadata.py @@ -265,6 +265,35 @@ def test_deletes(db, snapshot): assert vec0_shadow_table_contents(db, "v") == snapshot() +def test_delete_by_metadata_with_long_text(db): + """Regression for https://github.com/asg017/sqlite-vec/issues/274. + + ClearMetadata left rc=SQLITE_DONE after the long-text DELETE, which + propagated as an error and silently aborted the DELETE scan. + """ + db.execute( + "create virtual table v using vec0(" + " tag text, embedding float[4], chunk_size=8" + ")" + ) + for i in range(6): + db.execute( + "insert into v(tag, embedding) values (?, zeroblob(16))", + [f"long_text_value_{i}"], + ) + for i in range(4): + db.execute( + "insert into v(tag, embedding) values (?, zeroblob(16))", + [f"long_text_value_0"], + ) + assert db.execute("select count(*) from v").fetchone()[0] == 10 + + # DELETE by metadata WHERE — the pattern from the issue + db.execute("delete from v where tag = 'long_text_value_0'") + assert db.execute("select count(*) from v where tag = 'long_text_value_0'").fetchone()[0] == 0 + assert db.execute("select count(*) from v").fetchone()[0] == 5 + + def test_knn(db, snapshot): db.execute( "create virtual table v using vec0(vector float[1], name text, chunk_size=8)"