diff --git a/CHANGELOG.md b/CHANGELOG.md index f267fbfa..3cd5a5db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ ### Fixed - A KNN query that takes its query vector, `k`, or `mmr_lambda` from another table of a join now uses it. `select v.rowid from p join v on v.embedding match ? and v.k = p.k` failed with `A LIMIT or 'k = ?' constraint is required`; the same join with `v.k = 3 and v.mmr_lambda = p.lambda` returned no rows; and `select v.rowid from v join p on p.id = v.tag where v.embedding match p.vector and v.k = 2`, with `p.id` an integer primary key, could return no rows. These joins now run one KNN per row of the other table, so a filter on the `vec0` table whose value comes from that table applies before the k nearest rows are chosen. Ported in part from the unmerged [vlasky#14](https://github.com/vlasky/sqlite-vec/pull/14) ([asg017#331](https://github.com/asg017/sqlite-vec/pull/331)), which also applies filters whose value comes from a joined table before the k nearest rows are chosen; that part is not ported, and ARCHITECTURE.md's idxStr section says why. +- Fixed a memory leak: closing a connection freed the `vec_static_blobs` registry but not the name of each blob registered in it. +- Fixed a write one byte past the end of a heap allocation in KNN queries on a `vec_static_blob_entries` table. SQLite's allocator rounds the allocation up, which hides the write unless the blob's vector count rounds up to a multiple of 64, as 64 vectors do. ## [2.1.1] - 2026-10-09 diff --git a/sqlite-vec.c b/sqlite-vec.c index ab9f42b1..4f4fb04f 100644 --- a/sqlite-vec.c +++ b/sqlite-vec.c @@ -11756,6 +11756,15 @@ struct vec_static_blob_data { static_blob static_blobs[MAX_STATIC_BLOBS]; }; +/** Frees the registry and the names vec_static_blobsUpdate() copied in. */ +static void vec_static_blob_data_free(void *p) { + vec_static_blob_data *data = p; + for (int i = 0; i < MAX_STATIC_BLOBS; i++) { + sqlite3_free(data->static_blobs[i].name); + } + sqlite3_free(data); +} + typedef struct vec_static_blobs_vtab vec_static_blobs_vtab; struct vec_static_blobs_vtab { sqlite3_vtab base; @@ -12242,7 +12251,7 @@ static int vec_static_blob_entriesFilter(sqlite3_vtab_cursor *pVtabCursor, } bitmap_fill(candidates, bsize); - for (size_t i = bsize; i >= p->blob->nvectors; i--) { + for (size_t i = p->blob->nvectors; i < bsize; i++) { bitmap_set(candidates, i, 0); } i32 k_used = 0; @@ -12542,7 +12551,7 @@ sqlite3_vec_static_blobs_init(sqlite3 *db, char **pzErrMsg, return rc; rc = sqlite3_create_module_v2(db, "vec_static_blobs", &vec_static_blobsModule, - static_blob_data, sqlite3_free); + static_blob_data, vec_static_blob_data_free); if (rc != SQLITE_OK) return rc; rc = sqlite3_create_module_v2(db, "vec_static_blob_entries", diff --git a/tests/memory-test.c b/tests/memory-test.c index 67f9fcfe..ead017c3 100644 --- a/tests/memory-test.c +++ b/tests/memory-test.c @@ -22,6 +22,8 @@ extern int sqlite3_vec_init(sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi); extern int sqlite3_vec_numpy_init(sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi); +extern int sqlite3_vec_static_blobs_init(sqlite3 *db, char **pzErrMsg, + const sqlite3_api_routines *pApi); #define CHECK_OK(rc, msg) \ do { \ @@ -986,6 +988,87 @@ static int test_unaligned_blob_vectors(void) { return result; } +static int test_static_blobs(void) { + printf("Testing static blobs...\n"); + sqlite3 *db = NULL; + sqlite3_stmt *stmt = NULL; + int rc; + int result = 1; + + // 64 vectors fill a KNN's candidate bitmap to its last byte, so a write past + // its end lands outside the allocation. + static float vectors[64 * 4]; + for (int i = 0; i < 64; i++) { + for (int j = 0; j < 4; j++) { + vectors[i * 4 + j] = i / 64.0f; + } + } + float query[4] = {0.5f, 0.5f, 0.5f, 0.5f}; + + rc = sqlite3_open(":memory:", &db); + CHECK_OK(rc, "open database"); + rc = sqlite3_vec_static_blobs_init(db, NULL, NULL); + CHECK_OK(rc, "register static blobs"); + + rc = sqlite3_prepare_v2(db, + "INSERT INTO temp.vec_static_blobs(name, data) " + "SELECT 'w', vec_static_blob_from_raw(?1, '