Skip to content

Commit c560710

Browse files
asg017claude
andcommitted
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>
1 parent 6e2c4c6 commit c560710

2 files changed

Lines changed: 331 additions & 1 deletion

File tree

sqlite-vec.c

Lines changed: 158 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10362,6 +10362,163 @@ static int vec0Rollback(sqlite3_vtab *pVTab) {
1036210362
return SQLITE_OK;
1036310363
}
1036410364

10365+
/**
10366+
* xRename implementation for vec0.
10367+
* Renames all shadow tables to match the new virtual table name,
10368+
* then updates cached table names and finalizes stale prepared statements.
10369+
*/
10370+
static int vec0Rename(sqlite3_vtab *pVtab, const char *zNew) {
10371+
vec0_vtab *p = (vec0_vtab *)pVtab;
10372+
int rc = SQLITE_OK;
10373+
10374+
// Build a single SQL string with ALTER TABLE RENAME for every shadow table.
10375+
sqlite3_str *s = sqlite3_str_new(p->db);
10376+
10377+
// Core shadow tables (always present)
10378+
sqlite3_str_appendf(s,
10379+
"ALTER TABLE \"%w\".\"%w_info\" RENAME TO \"%w_info\";",
10380+
p->schemaName, p->tableName, zNew);
10381+
sqlite3_str_appendf(s,
10382+
"ALTER TABLE \"%w\".\"%w_rowids\" RENAME TO \"%w_rowids\";",
10383+
p->schemaName, p->tableName, zNew);
10384+
sqlite3_str_appendf(s,
10385+
"ALTER TABLE \"%w\".\"%w_chunks\" RENAME TO \"%w_chunks\";",
10386+
p->schemaName, p->tableName, zNew);
10387+
10388+
// Auxiliary shadow table (only if auxiliary columns exist)
10389+
if (p->numAuxiliaryColumns > 0) {
10390+
sqlite3_str_appendf(s,
10391+
"ALTER TABLE \"%w\".\"%w_auxiliary\" RENAME TO \"%w_auxiliary\";",
10392+
p->schemaName, p->tableName, zNew);
10393+
}
10394+
10395+
// Per-vector-column shadow tables
10396+
for (int i = 0; i < p->numVectorColumns; i++) {
10397+
sqlite3_str_appendf(s,
10398+
"ALTER TABLE \"%w\".\"%w_vector_chunks%02d\" RENAME TO \"%w_vector_chunks%02d\";",
10399+
p->schemaName, p->tableName, i, zNew, i);
10400+
10401+
#if SQLITE_VEC_ENABLE_RESCORE
10402+
if (p->shadowRescoreChunksNames[i]) {
10403+
sqlite3_str_appendf(s,
10404+
"ALTER TABLE \"%w\".\"%w_rescore_chunks%02d\" RENAME TO \"%w_rescore_chunks%02d\";",
10405+
p->schemaName, p->tableName, i, zNew, i);
10406+
sqlite3_str_appendf(s,
10407+
"ALTER TABLE \"%w\".\"%w_rescore_vectors%02d\" RENAME TO \"%w_rescore_vectors%02d\";",
10408+
p->schemaName, p->tableName, i, zNew, i);
10409+
}
10410+
#endif
10411+
10412+
#if SQLITE_VEC_ENABLE_DISKANN
10413+
if (p->shadowVectorsNames[i]) {
10414+
sqlite3_str_appendf(s,
10415+
"ALTER TABLE \"%w\".\"%w_vectors%02d\" RENAME TO \"%w_vectors%02d\";",
10416+
p->schemaName, p->tableName, i, zNew, i);
10417+
sqlite3_str_appendf(s,
10418+
"ALTER TABLE \"%w\".\"%w_diskann_nodes%02d\" RENAME TO \"%w_diskann_nodes%02d\";",
10419+
p->schemaName, p->tableName, i, zNew, i);
10420+
sqlite3_str_appendf(s,
10421+
"ALTER TABLE \"%w\".\"%w_diskann_buffer%02d\" RENAME TO \"%w_diskann_buffer%02d\";",
10422+
p->schemaName, p->tableName, i, zNew, i);
10423+
}
10424+
#endif
10425+
}
10426+
10427+
#if SQLITE_VEC_EXPERIMENTAL_IVF_ENABLE
10428+
for (int i = 0; i < p->numVectorColumns; i++) {
10429+
if (p->shadowIvfCellsNames[i]) {
10430+
sqlite3_str_appendf(s,
10431+
"ALTER TABLE \"%w\".\"%w_ivf_cells%02d\" RENAME TO \"%w_ivf_cells%02d\";",
10432+
p->schemaName, p->tableName, i, zNew, i);
10433+
}
10434+
}
10435+
#endif
10436+
10437+
// Per-metadata-column shadow tables
10438+
for (int i = 0; i < p->numMetadataColumns; i++) {
10439+
sqlite3_str_appendf(s,
10440+
"ALTER TABLE \"%w\".\"%w_metadatachunks%02d\" RENAME TO \"%w_metadatachunks%02d\";",
10441+
p->schemaName, p->tableName, i, zNew, i);
10442+
if (p->metadata_columns[i].kind == VEC0_METADATA_COLUMN_KIND_TEXT) {
10443+
sqlite3_str_appendf(s,
10444+
"ALTER TABLE \"%w\".\"%w_metadatatext%02d\" RENAME TO \"%w_metadatatext%02d\";",
10445+
p->schemaName, p->tableName, i, zNew, i);
10446+
}
10447+
}
10448+
10449+
char *zSql = sqlite3_str_finish(s);
10450+
if (!zSql) {
10451+
return SQLITE_NOMEM;
10452+
}
10453+
10454+
rc = sqlite3_exec(p->db, zSql, 0, 0, 0);
10455+
sqlite3_free(zSql);
10456+
if (rc != SQLITE_OK) {
10457+
return rc;
10458+
}
10459+
10460+
// Finalize all prepared statements — they reference old table names.
10461+
vec0_free_resources(p);
10462+
10463+
// Update cached table name
10464+
sqlite3_free(p->tableName);
10465+
p->tableName = sqlite3_mprintf("%s", zNew);
10466+
if (!p->tableName) return SQLITE_NOMEM;
10467+
10468+
// Update cached shadow table names
10469+
sqlite3_free(p->shadowRowidsName);
10470+
p->shadowRowidsName = sqlite3_mprintf("%s_rowids", zNew);
10471+
10472+
sqlite3_free(p->shadowChunksName);
10473+
p->shadowChunksName = sqlite3_mprintf("%s_chunks", zNew);
10474+
10475+
for (int i = 0; i < p->numVectorColumns; i++) {
10476+
sqlite3_free(p->shadowVectorChunksNames[i]);
10477+
p->shadowVectorChunksNames[i] =
10478+
sqlite3_mprintf("%s_vector_chunks%02d", zNew, i);
10479+
10480+
#if SQLITE_VEC_ENABLE_RESCORE
10481+
if (p->shadowRescoreChunksNames[i]) {
10482+
sqlite3_free(p->shadowRescoreChunksNames[i]);
10483+
p->shadowRescoreChunksNames[i] =
10484+
sqlite3_mprintf("%s_rescore_chunks%02d", zNew, i);
10485+
sqlite3_free(p->shadowRescoreVectorsNames[i]);
10486+
p->shadowRescoreVectorsNames[i] =
10487+
sqlite3_mprintf("%s_rescore_vectors%02d", zNew, i);
10488+
}
10489+
#endif
10490+
10491+
#if SQLITE_VEC_ENABLE_DISKANN
10492+
if (p->shadowVectorsNames[i]) {
10493+
sqlite3_free(p->shadowVectorsNames[i]);
10494+
p->shadowVectorsNames[i] =
10495+
sqlite3_mprintf("%s_vectors%02d", zNew, i);
10496+
sqlite3_free(p->shadowDiskannNodesNames[i]);
10497+
p->shadowDiskannNodesNames[i] =
10498+
sqlite3_mprintf("%s_diskann_nodes%02d", zNew, i);
10499+
}
10500+
#endif
10501+
}
10502+
10503+
#if SQLITE_VEC_EXPERIMENTAL_IVF_ENABLE
10504+
for (int i = 0; i < p->numVectorColumns; i++) {
10505+
if (p->shadowIvfCellsNames[i]) {
10506+
sqlite3_free(p->shadowIvfCellsNames[i]);
10507+
p->shadowIvfCellsNames[i] =
10508+
sqlite3_mprintf("%s_ivf_cells%02d", zNew, i);
10509+
}
10510+
}
10511+
#endif
10512+
10513+
for (int i = 0; i < p->numMetadataColumns; i++) {
10514+
sqlite3_free(p->shadowMetadataChunksNames[i]);
10515+
p->shadowMetadataChunksNames[i] =
10516+
sqlite3_mprintf("%s_metadatachunks%02d", zNew, i);
10517+
}
10518+
10519+
return SQLITE_OK;
10520+
}
10521+
1036510522
static sqlite3_module vec0Module = {
1036610523
/* iVersion */ 3,
1036710524
/* xCreate */ vec0Create,
@@ -10382,7 +10539,7 @@ static sqlite3_module vec0Module = {
1038210539
/* xCommit */ vec0Commit,
1038310540
/* xRollback */ vec0Rollback,
1038410541
/* xFindFunction */ 0,
10385-
/* xRename */ 0, // https://github.com/asg017/sqlite-vec/issues/43
10542+
/* xRename */ vec0Rename,
1038610543
/* xSavepoint */ 0,
1038710544
/* xRelease */ 0,
1038810545
/* xRollbackTo */ 0,

tests/test-rename.py

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
import sqlite3
2+
import pytest
3+
from helpers import _f32
4+
5+
6+
def _shadow_tables(db, prefix):
7+
"""Return sorted list of shadow table names for a given prefix."""
8+
return sorted([
9+
row[0] for row in db.execute(
10+
r"select name from sqlite_master where name like ? escape '\' and type='table' order by 1",
11+
[f"{prefix}\\__%"],
12+
).fetchall()
13+
])
14+
15+
16+
def test_rename_basic(db):
17+
"""ALTER TABLE RENAME should rename vec0 table and all shadow tables."""
18+
db.execute("create virtual table v using vec0(a float[2], chunk_size=8)")
19+
db.execute("insert into v(rowid, a) values (1, ?)", [_f32([0.1, 0.2])])
20+
db.execute("insert into v(rowid, a) values (2, ?)", [_f32([0.3, 0.4])])
21+
22+
assert _shadow_tables(db, "v") == [
23+
"v_chunks",
24+
"v_info",
25+
"v_rowids",
26+
"v_vector_chunks00",
27+
]
28+
29+
db.execute("ALTER TABLE v RENAME TO v2")
30+
31+
# Old name should no longer work
32+
with pytest.raises(sqlite3.OperationalError):
33+
db.execute("select * from v")
34+
35+
# New name should work and return the same data
36+
rows = db.execute(
37+
"select rowid, distance from v2 where a match ? and k=10",
38+
[_f32([0.1, 0.2])],
39+
).fetchall()
40+
assert len(rows) == 2
41+
assert rows[0][0] == 1 # closest match
42+
43+
# Shadow tables should all be renamed
44+
assert _shadow_tables(db, "v2") == [
45+
"v2_chunks",
46+
"v2_info",
47+
"v2_rowids",
48+
"v2_vector_chunks00",
49+
]
50+
51+
# No old shadow tables should remain
52+
assert _shadow_tables(db, "v") == []
53+
54+
55+
def test_rename_insert_after(db):
56+
"""Inserts and queries should work after rename."""
57+
db.execute("create virtual table v using vec0(a float[2], chunk_size=8)")
58+
db.execute("insert into v(rowid, a) values (1, ?)", [_f32([0.1, 0.2])])
59+
db.execute("ALTER TABLE v RENAME TO v2")
60+
61+
# Insert into renamed table
62+
db.execute("insert into v2(rowid, a) values (2, ?)", [_f32([0.3, 0.4])])
63+
64+
rows = db.execute(
65+
"select rowid from v2 where a match ? and k=10",
66+
[_f32([0.3, 0.4])],
67+
).fetchall()
68+
assert len(rows) == 2
69+
assert rows[0][0] == 2
70+
71+
72+
def test_rename_delete_after(db):
73+
"""Deletes should work after rename."""
74+
db.execute("create virtual table v using vec0(a float[2], chunk_size=8)")
75+
db.execute("insert into v(rowid, a) values (1, ?)", [_f32([0.1, 0.2])])
76+
db.execute("insert into v(rowid, a) values (2, ?)", [_f32([0.3, 0.4])])
77+
db.execute("ALTER TABLE v RENAME TO v2")
78+
79+
db.execute("delete from v2 where rowid = 1")
80+
rows = db.execute(
81+
"select rowid from v2 where a match ? and k=10",
82+
[_f32([0.3, 0.4])],
83+
).fetchall()
84+
assert len(rows) == 1
85+
assert rows[0][0] == 2
86+
87+
88+
def test_rename_with_auxiliary(db):
89+
"""Rename should also rename the _auxiliary shadow table."""
90+
db.execute(
91+
"create virtual table v using vec0(a float[2], +name text, chunk_size=8)"
92+
)
93+
db.execute(
94+
"insert into v(rowid, a, name) values (1, ?, 'hello')",
95+
[_f32([0.1, 0.2])],
96+
)
97+
98+
assert _shadow_tables(db, "v") == [
99+
"v_auxiliary",
100+
"v_chunks",
101+
"v_info",
102+
"v_rowids",
103+
"v_vector_chunks00",
104+
]
105+
106+
db.execute("ALTER TABLE v RENAME TO v2")
107+
108+
# Auxiliary data should be accessible
109+
rows = db.execute(
110+
"select rowid, name from v2 where a match ? and k=10",
111+
[_f32([0.1, 0.2])],
112+
).fetchall()
113+
assert rows[0][0] == 1
114+
assert rows[0][1] == "hello"
115+
116+
assert _shadow_tables(db, "v2") == [
117+
"v2_auxiliary",
118+
"v2_chunks",
119+
"v2_info",
120+
"v2_rowids",
121+
"v2_vector_chunks00",
122+
]
123+
assert _shadow_tables(db, "v") == []
124+
125+
126+
def test_rename_with_metadata(db):
127+
"""Rename should also rename metadata shadow tables."""
128+
db.execute(
129+
"create virtual table v using vec0(a float[2], tag text, chunk_size=8)"
130+
)
131+
db.execute(
132+
"insert into v(rowid, a, tag) values (1, ?, 'a')",
133+
[_f32([0.1, 0.2])],
134+
)
135+
136+
assert _shadow_tables(db, "v") == [
137+
"v_chunks",
138+
"v_info",
139+
"v_metadatachunks00",
140+
"v_metadatatext00",
141+
"v_rowids",
142+
"v_vector_chunks00",
143+
]
144+
145+
db.execute("ALTER TABLE v RENAME TO v2")
146+
147+
rows = db.execute(
148+
"select rowid, tag from v2 where a match ? and k=10",
149+
[_f32([0.1, 0.2])],
150+
).fetchall()
151+
assert rows[0][0] == 1
152+
assert rows[0][1] == "a"
153+
154+
assert _shadow_tables(db, "v2") == [
155+
"v2_chunks",
156+
"v2_info",
157+
"v2_metadatachunks00",
158+
"v2_metadatatext00",
159+
"v2_rowids",
160+
"v2_vector_chunks00",
161+
]
162+
assert _shadow_tables(db, "v") == []
163+
164+
165+
def test_rename_drop_after(db):
166+
"""DROP TABLE should work on a renamed table."""
167+
db.execute("create virtual table v using vec0(a float[2], chunk_size=8)")
168+
db.execute("insert into v(rowid, a) values (1, ?)", [_f32([0.1, 0.2])])
169+
db.execute("ALTER TABLE v RENAME TO v2")
170+
db.execute("DROP TABLE v2")
171+
172+
# Nothing should remain
173+
assert _shadow_tables(db, "v2") == []

0 commit comments

Comments
 (0)