From 3a6bc722773dd0342c4c596dbeb3860397db821f Mon Sep 17 00:00:00 2001 From: Aaron Paterson Date: Fri, 23 Oct 2026 09:02:06 -0500 Subject: [PATCH] Find IVF cells through an index that a rename renames ivf_create_shadow_tables() indexed _ivf_cellsNN by centroid with CREATE INDEX "_ivf_cellsNN_centroid", which ALTER TABLE RENAME leaves under the old table's name, and xRename cannot drop it (DROP INDEX there fails with "database table is locked"). After a rename, creating an IVF table under the old name failed with "Could not create IVF shadow tables for column 0". The cells table now declares cell_id as its rowid and UNIQUE (centroid_id, cell_id), whose index SQLite renames with the table and which serves the same lookups by centroid. Every statement on the cells names its columns, so tables created before this open as they did. --- sqlite-vec-ivf.c | 20 ++++++++------------ tests/test-ivf-rename.py | 26 ++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/sqlite-vec-ivf.c b/sqlite-vec-ivf.c index 5bc8edbc..5be80aa8 100644 --- a/sqlite-vec-ivf.c +++ b/sqlite-vec-ivf.c @@ -466,29 +466,25 @@ static int ivf_create_shadow_tables(vec0_vtab *p, int col_idx) { if (rc != SQLITE_OK || sqlite3_step(stmt) != SQLITE_DONE) { sqlite3_finalize(stmt); return SQLITE_ERROR; } sqlite3_finalize(stmt); - // cell_id is rowid (auto-increment), centroid_id is indexed + // cell_id is the rowid. A centroid's cells are found through the index of + // the UNIQUE constraint, which ALTER TABLE RENAME renames with its table; an + // index from CREATE INDEX would keep the old table's name, and xRename + // cannot drop it ("database table is locked"). zSql = sqlite3_mprintf( "CREATE TABLE " VEC0_SHADOW_IVF_CELLS_NAME - " (centroid_id INTEGER NOT NULL," + " (cell_id INTEGER PRIMARY KEY," + " centroid_id INTEGER NOT NULL," " n_vectors INTEGER NOT NULL DEFAULT 0," " validity BLOB NOT NULL," " rowids BLOB NOT NULL," - " vectors BLOB NOT NULL)", + " vectors BLOB NOT NULL," + " UNIQUE (centroid_id, cell_id))", p->schemaName, p->tableName, col_idx); if (!zSql) return SQLITE_NOMEM; rc = sqlite3_prepare_v2(p->db, zSql, -1, &stmt, NULL); sqlite3_free(zSql); if (rc != SQLITE_OK || sqlite3_step(stmt) != SQLITE_DONE) { sqlite3_finalize(stmt); return SQLITE_ERROR; } sqlite3_finalize(stmt); - // Index on centroid_id for cell lookup - zSql = sqlite3_mprintf( - "CREATE INDEX \"%w_ivf_cells%02d_centroid\" ON \"%w_ivf_cells%02d\" (centroid_id)", - p->tableName, col_idx, p->tableName, col_idx); - if (!zSql) return SQLITE_NOMEM; - rc = sqlite3_prepare_v2(p->db, zSql, -1, &stmt, NULL); sqlite3_free(zSql); - if (rc != SQLITE_OK || sqlite3_step(stmt) != SQLITE_DONE) { sqlite3_finalize(stmt); return SQLITE_ERROR; } - sqlite3_finalize(stmt); - zSql = sqlite3_mprintf( "CREATE TABLE " VEC0_SHADOW_IVF_ROWID_MAP_NAME " (rowid INTEGER PRIMARY KEY, cell_id INTEGER NOT NULL, slot INTEGER NOT NULL)", diff --git a/tests/test-ivf-rename.py b/tests/test-ivf-rename.py index 980c9588..e050dc7a 100644 --- a/tests/test-ivf-rename.py +++ b/tests/test-ivf-rename.py @@ -96,3 +96,29 @@ def test_rename_ivf_drop_after(db): assert _shadow_tables(db, "v") == [] assert _shadow_tables(db, "v2") == [] + + +def test_rename_ivf_then_create_under_old_name(db): + """A rename leaves no index named for the old table, so a new IVF table + can take the old name.""" + create = """ + CREATE VIRTUAL TABLE v USING vec0( + a float[4] indexed by ivf(nlist=2) + ) + """ + db.execute(create) + db.execute("insert into v(rowid, a) values (1, ?)", [_f32([0.1] * 4)]) + db.execute("ALTER TABLE v RENAME TO v2") + + indexes = [ + row[0] for row in db.execute( + r"select name from sqlite_master where type='index' and tbl_name like 'v2\_%' escape '\'" + ).fetchall() + ] + assert indexes + assert not any(name.startswith("v_") for name in indexes) + + db.execute(create) + db.execute("insert into v(rowid, a) values (1, ?)", [_f32([0.9] * 4)]) + assert db.execute("select a from v where rowid = 1").fetchone()[0] == _f32([0.9] * 4) + assert db.execute("select a from v2 where rowid = 1").fetchone()[0] == _f32([0.1] * 4)