mirror of
https://github.com/asg017/sqlite-vec.git
synced 2026-04-24 16:26:37 +02:00
Implement xRename for vec0 virtual table (fixes #43)
Enables ALTER TABLE RENAME on vec0 tables by renaming all shadow tables (info, rowids, chunks, auxiliary, vector_chunks, metadata, rescore, diskann, ivf) and updating cached names and prepared statements. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
6e2c4c6bab
commit
c5607100ab
2 changed files with 331 additions and 1 deletions
159
sqlite-vec.c
159
sqlite-vec.c
|
|
@ -10362,6 +10362,163 @@ static int vec0Rollback(sqlite3_vtab *pVTab) {
|
|||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* xRename implementation for vec0.
|
||||
* Renames all shadow tables to match the new virtual table name,
|
||||
* then updates cached table names and finalizes stale prepared statements.
|
||||
*/
|
||||
static int vec0Rename(sqlite3_vtab *pVtab, const char *zNew) {
|
||||
vec0_vtab *p = (vec0_vtab *)pVtab;
|
||||
int rc = SQLITE_OK;
|
||||
|
||||
// Build a single SQL string with ALTER TABLE RENAME for every shadow table.
|
||||
sqlite3_str *s = sqlite3_str_new(p->db);
|
||||
|
||||
// Core shadow tables (always present)
|
||||
sqlite3_str_appendf(s,
|
||||
"ALTER TABLE \"%w\".\"%w_info\" RENAME TO \"%w_info\";",
|
||||
p->schemaName, p->tableName, zNew);
|
||||
sqlite3_str_appendf(s,
|
||||
"ALTER TABLE \"%w\".\"%w_rowids\" RENAME TO \"%w_rowids\";",
|
||||
p->schemaName, p->tableName, zNew);
|
||||
sqlite3_str_appendf(s,
|
||||
"ALTER TABLE \"%w\".\"%w_chunks\" RENAME TO \"%w_chunks\";",
|
||||
p->schemaName, p->tableName, zNew);
|
||||
|
||||
// Auxiliary shadow table (only if auxiliary columns exist)
|
||||
if (p->numAuxiliaryColumns > 0) {
|
||||
sqlite3_str_appendf(s,
|
||||
"ALTER TABLE \"%w\".\"%w_auxiliary\" RENAME TO \"%w_auxiliary\";",
|
||||
p->schemaName, p->tableName, zNew);
|
||||
}
|
||||
|
||||
// Per-vector-column shadow tables
|
||||
for (int i = 0; i < p->numVectorColumns; i++) {
|
||||
sqlite3_str_appendf(s,
|
||||
"ALTER TABLE \"%w\".\"%w_vector_chunks%02d\" RENAME TO \"%w_vector_chunks%02d\";",
|
||||
p->schemaName, p->tableName, i, zNew, i);
|
||||
|
||||
#if SQLITE_VEC_ENABLE_RESCORE
|
||||
if (p->shadowRescoreChunksNames[i]) {
|
||||
sqlite3_str_appendf(s,
|
||||
"ALTER TABLE \"%w\".\"%w_rescore_chunks%02d\" RENAME TO \"%w_rescore_chunks%02d\";",
|
||||
p->schemaName, p->tableName, i, zNew, i);
|
||||
sqlite3_str_appendf(s,
|
||||
"ALTER TABLE \"%w\".\"%w_rescore_vectors%02d\" RENAME TO \"%w_rescore_vectors%02d\";",
|
||||
p->schemaName, p->tableName, i, zNew, i);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if SQLITE_VEC_ENABLE_DISKANN
|
||||
if (p->shadowVectorsNames[i]) {
|
||||
sqlite3_str_appendf(s,
|
||||
"ALTER TABLE \"%w\".\"%w_vectors%02d\" RENAME TO \"%w_vectors%02d\";",
|
||||
p->schemaName, p->tableName, i, zNew, i);
|
||||
sqlite3_str_appendf(s,
|
||||
"ALTER TABLE \"%w\".\"%w_diskann_nodes%02d\" RENAME TO \"%w_diskann_nodes%02d\";",
|
||||
p->schemaName, p->tableName, i, zNew, i);
|
||||
sqlite3_str_appendf(s,
|
||||
"ALTER TABLE \"%w\".\"%w_diskann_buffer%02d\" RENAME TO \"%w_diskann_buffer%02d\";",
|
||||
p->schemaName, p->tableName, i, zNew, i);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if SQLITE_VEC_EXPERIMENTAL_IVF_ENABLE
|
||||
for (int i = 0; i < p->numVectorColumns; i++) {
|
||||
if (p->shadowIvfCellsNames[i]) {
|
||||
sqlite3_str_appendf(s,
|
||||
"ALTER TABLE \"%w\".\"%w_ivf_cells%02d\" RENAME TO \"%w_ivf_cells%02d\";",
|
||||
p->schemaName, p->tableName, i, zNew, i);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Per-metadata-column shadow tables
|
||||
for (int i = 0; i < p->numMetadataColumns; i++) {
|
||||
sqlite3_str_appendf(s,
|
||||
"ALTER TABLE \"%w\".\"%w_metadatachunks%02d\" RENAME TO \"%w_metadatachunks%02d\";",
|
||||
p->schemaName, p->tableName, i, zNew, i);
|
||||
if (p->metadata_columns[i].kind == VEC0_METADATA_COLUMN_KIND_TEXT) {
|
||||
sqlite3_str_appendf(s,
|
||||
"ALTER TABLE \"%w\".\"%w_metadatatext%02d\" RENAME TO \"%w_metadatatext%02d\";",
|
||||
p->schemaName, p->tableName, i, zNew, i);
|
||||
}
|
||||
}
|
||||
|
||||
char *zSql = sqlite3_str_finish(s);
|
||||
if (!zSql) {
|
||||
return SQLITE_NOMEM;
|
||||
}
|
||||
|
||||
rc = sqlite3_exec(p->db, zSql, 0, 0, 0);
|
||||
sqlite3_free(zSql);
|
||||
if (rc != SQLITE_OK) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
// Finalize all prepared statements — they reference old table names.
|
||||
vec0_free_resources(p);
|
||||
|
||||
// Update cached table name
|
||||
sqlite3_free(p->tableName);
|
||||
p->tableName = sqlite3_mprintf("%s", zNew);
|
||||
if (!p->tableName) return SQLITE_NOMEM;
|
||||
|
||||
// Update cached shadow table names
|
||||
sqlite3_free(p->shadowRowidsName);
|
||||
p->shadowRowidsName = sqlite3_mprintf("%s_rowids", zNew);
|
||||
|
||||
sqlite3_free(p->shadowChunksName);
|
||||
p->shadowChunksName = sqlite3_mprintf("%s_chunks", zNew);
|
||||
|
||||
for (int i = 0; i < p->numVectorColumns; i++) {
|
||||
sqlite3_free(p->shadowVectorChunksNames[i]);
|
||||
p->shadowVectorChunksNames[i] =
|
||||
sqlite3_mprintf("%s_vector_chunks%02d", zNew, i);
|
||||
|
||||
#if SQLITE_VEC_ENABLE_RESCORE
|
||||
if (p->shadowRescoreChunksNames[i]) {
|
||||
sqlite3_free(p->shadowRescoreChunksNames[i]);
|
||||
p->shadowRescoreChunksNames[i] =
|
||||
sqlite3_mprintf("%s_rescore_chunks%02d", zNew, i);
|
||||
sqlite3_free(p->shadowRescoreVectorsNames[i]);
|
||||
p->shadowRescoreVectorsNames[i] =
|
||||
sqlite3_mprintf("%s_rescore_vectors%02d", zNew, i);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if SQLITE_VEC_ENABLE_DISKANN
|
||||
if (p->shadowVectorsNames[i]) {
|
||||
sqlite3_free(p->shadowVectorsNames[i]);
|
||||
p->shadowVectorsNames[i] =
|
||||
sqlite3_mprintf("%s_vectors%02d", zNew, i);
|
||||
sqlite3_free(p->shadowDiskannNodesNames[i]);
|
||||
p->shadowDiskannNodesNames[i] =
|
||||
sqlite3_mprintf("%s_diskann_nodes%02d", zNew, i);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if SQLITE_VEC_EXPERIMENTAL_IVF_ENABLE
|
||||
for (int i = 0; i < p->numVectorColumns; i++) {
|
||||
if (p->shadowIvfCellsNames[i]) {
|
||||
sqlite3_free(p->shadowIvfCellsNames[i]);
|
||||
p->shadowIvfCellsNames[i] =
|
||||
sqlite3_mprintf("%s_ivf_cells%02d", zNew, i);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
for (int i = 0; i < p->numMetadataColumns; i++) {
|
||||
sqlite3_free(p->shadowMetadataChunksNames[i]);
|
||||
p->shadowMetadataChunksNames[i] =
|
||||
sqlite3_mprintf("%s_metadatachunks%02d", zNew, i);
|
||||
}
|
||||
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
static sqlite3_module vec0Module = {
|
||||
/* iVersion */ 3,
|
||||
/* xCreate */ vec0Create,
|
||||
|
|
@ -10382,7 +10539,7 @@ static sqlite3_module vec0Module = {
|
|||
/* xCommit */ vec0Commit,
|
||||
/* xRollback */ vec0Rollback,
|
||||
/* xFindFunction */ 0,
|
||||
/* xRename */ 0, // https://github.com/asg017/sqlite-vec/issues/43
|
||||
/* xRename */ vec0Rename,
|
||||
/* xSavepoint */ 0,
|
||||
/* xRelease */ 0,
|
||||
/* xRollbackTo */ 0,
|
||||
|
|
|
|||
173
tests/test-rename.py
Normal file
173
tests/test-rename.py
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
import sqlite3
|
||||
import pytest
|
||||
from helpers import _f32
|
||||
|
||||
|
||||
def _shadow_tables(db, prefix):
|
||||
"""Return sorted list of shadow table names for a given prefix."""
|
||||
return sorted([
|
||||
row[0] for row in db.execute(
|
||||
r"select name from sqlite_master where name like ? escape '\' and type='table' order by 1",
|
||||
[f"{prefix}\\__%"],
|
||||
).fetchall()
|
||||
])
|
||||
|
||||
|
||||
def test_rename_basic(db):
|
||||
"""ALTER TABLE RENAME should rename vec0 table and all shadow tables."""
|
||||
db.execute("create virtual table v using vec0(a float[2], chunk_size=8)")
|
||||
db.execute("insert into v(rowid, a) values (1, ?)", [_f32([0.1, 0.2])])
|
||||
db.execute("insert into v(rowid, a) values (2, ?)", [_f32([0.3, 0.4])])
|
||||
|
||||
assert _shadow_tables(db, "v") == [
|
||||
"v_chunks",
|
||||
"v_info",
|
||||
"v_rowids",
|
||||
"v_vector_chunks00",
|
||||
]
|
||||
|
||||
db.execute("ALTER TABLE v RENAME TO v2")
|
||||
|
||||
# Old name should no longer work
|
||||
with pytest.raises(sqlite3.OperationalError):
|
||||
db.execute("select * from v")
|
||||
|
||||
# New name should work and return the same data
|
||||
rows = db.execute(
|
||||
"select rowid, distance from v2 where a match ? and k=10",
|
||||
[_f32([0.1, 0.2])],
|
||||
).fetchall()
|
||||
assert len(rows) == 2
|
||||
assert rows[0][0] == 1 # closest match
|
||||
|
||||
# Shadow tables should all be renamed
|
||||
assert _shadow_tables(db, "v2") == [
|
||||
"v2_chunks",
|
||||
"v2_info",
|
||||
"v2_rowids",
|
||||
"v2_vector_chunks00",
|
||||
]
|
||||
|
||||
# No old shadow tables should remain
|
||||
assert _shadow_tables(db, "v") == []
|
||||
|
||||
|
||||
def test_rename_insert_after(db):
|
||||
"""Inserts and queries should work after rename."""
|
||||
db.execute("create virtual table v using vec0(a float[2], chunk_size=8)")
|
||||
db.execute("insert into v(rowid, a) values (1, ?)", [_f32([0.1, 0.2])])
|
||||
db.execute("ALTER TABLE v RENAME TO v2")
|
||||
|
||||
# Insert into renamed table
|
||||
db.execute("insert into v2(rowid, a) values (2, ?)", [_f32([0.3, 0.4])])
|
||||
|
||||
rows = db.execute(
|
||||
"select rowid from v2 where a match ? and k=10",
|
||||
[_f32([0.3, 0.4])],
|
||||
).fetchall()
|
||||
assert len(rows) == 2
|
||||
assert rows[0][0] == 2
|
||||
|
||||
|
||||
def test_rename_delete_after(db):
|
||||
"""Deletes should work after rename."""
|
||||
db.execute("create virtual table v using vec0(a float[2], chunk_size=8)")
|
||||
db.execute("insert into v(rowid, a) values (1, ?)", [_f32([0.1, 0.2])])
|
||||
db.execute("insert into v(rowid, a) values (2, ?)", [_f32([0.3, 0.4])])
|
||||
db.execute("ALTER TABLE v RENAME TO v2")
|
||||
|
||||
db.execute("delete from v2 where rowid = 1")
|
||||
rows = db.execute(
|
||||
"select rowid from v2 where a match ? and k=10",
|
||||
[_f32([0.3, 0.4])],
|
||||
).fetchall()
|
||||
assert len(rows) == 1
|
||||
assert rows[0][0] == 2
|
||||
|
||||
|
||||
def test_rename_with_auxiliary(db):
|
||||
"""Rename should also rename the _auxiliary shadow table."""
|
||||
db.execute(
|
||||
"create virtual table v using vec0(a float[2], +name text, chunk_size=8)"
|
||||
)
|
||||
db.execute(
|
||||
"insert into v(rowid, a, name) values (1, ?, 'hello')",
|
||||
[_f32([0.1, 0.2])],
|
||||
)
|
||||
|
||||
assert _shadow_tables(db, "v") == [
|
||||
"v_auxiliary",
|
||||
"v_chunks",
|
||||
"v_info",
|
||||
"v_rowids",
|
||||
"v_vector_chunks00",
|
||||
]
|
||||
|
||||
db.execute("ALTER TABLE v RENAME TO v2")
|
||||
|
||||
# Auxiliary data should be accessible
|
||||
rows = db.execute(
|
||||
"select rowid, name from v2 where a match ? and k=10",
|
||||
[_f32([0.1, 0.2])],
|
||||
).fetchall()
|
||||
assert rows[0][0] == 1
|
||||
assert rows[0][1] == "hello"
|
||||
|
||||
assert _shadow_tables(db, "v2") == [
|
||||
"v2_auxiliary",
|
||||
"v2_chunks",
|
||||
"v2_info",
|
||||
"v2_rowids",
|
||||
"v2_vector_chunks00",
|
||||
]
|
||||
assert _shadow_tables(db, "v") == []
|
||||
|
||||
|
||||
def test_rename_with_metadata(db):
|
||||
"""Rename should also rename metadata shadow tables."""
|
||||
db.execute(
|
||||
"create virtual table v using vec0(a float[2], tag text, chunk_size=8)"
|
||||
)
|
||||
db.execute(
|
||||
"insert into v(rowid, a, tag) values (1, ?, 'a')",
|
||||
[_f32([0.1, 0.2])],
|
||||
)
|
||||
|
||||
assert _shadow_tables(db, "v") == [
|
||||
"v_chunks",
|
||||
"v_info",
|
||||
"v_metadatachunks00",
|
||||
"v_metadatatext00",
|
||||
"v_rowids",
|
||||
"v_vector_chunks00",
|
||||
]
|
||||
|
||||
db.execute("ALTER TABLE v RENAME TO v2")
|
||||
|
||||
rows = db.execute(
|
||||
"select rowid, tag from v2 where a match ? and k=10",
|
||||
[_f32([0.1, 0.2])],
|
||||
).fetchall()
|
||||
assert rows[0][0] == 1
|
||||
assert rows[0][1] == "a"
|
||||
|
||||
assert _shadow_tables(db, "v2") == [
|
||||
"v2_chunks",
|
||||
"v2_info",
|
||||
"v2_metadatachunks00",
|
||||
"v2_metadatatext00",
|
||||
"v2_rowids",
|
||||
"v2_vector_chunks00",
|
||||
]
|
||||
assert _shadow_tables(db, "v") == []
|
||||
|
||||
|
||||
def test_rename_drop_after(db):
|
||||
"""DROP TABLE should work on a renamed table."""
|
||||
db.execute("create virtual table v using vec0(a float[2], chunk_size=8)")
|
||||
db.execute("insert into v(rowid, a) values (1, ?)", [_f32([0.1, 0.2])])
|
||||
db.execute("ALTER TABLE v RENAME TO v2")
|
||||
db.execute("DROP TABLE v2")
|
||||
|
||||
# Nothing should remain
|
||||
assert _shadow_tables(db, "v2") == []
|
||||
Loading…
Add table
Add a link
Reference in a new issue