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)