fix: DROP INDEX/TABLE clean up FTS/HNSW indexes and their schema keys

This commit is contained in:
2026-07-30 16:20:23 +03:00
parent ce6e7aa707
commit 214b9cf346
2 changed files with 149 additions and 0 deletions
+46
View File
@@ -859,6 +859,15 @@ proc executeQueryImpl(ctx: ExecutionContext, astNode: Node, params: seq[WireValu
for idxName in ctx.btrees.keys.toSeq():
if idxName.startsWith(dropName & "."): toDelete.add(idxName)
for idxName in toDelete: ctx.btrees.del(idxName)
# Drop FTS/HNSW engine indexes for this table (in-memory entries)
var ftsToDelete: seq[string] = @[]
for key in ctx.ftsIndexes.keys.toSeq():
if key.startsWith(dropName & "."): ftsToDelete.add(key)
for key in ftsToDelete: ctx.ftsIndexes.del(key)
var vecToDelete: seq[string] = @[]
for key in ctx.vectorIndexes.keys.toSeq():
if key.startsWith(dropName & "."): vecToDelete.add(key)
for key in vecToDelete: ctx.vectorIndexes.del(key)
# Remove durable schema entry
dropTableSchema(ctx, dropName)
# Remove row data for this table
@@ -869,6 +878,14 @@ proc executeQueryImpl(ctx: ExecutionContext, astNode: Node, params: seq[WireValu
dataKeys.add(key)
for key in dataKeys:
ctx.db.delete(key)
# Remove persisted FTS/HNSW index schema keys for this table
var engineKeys: seq[string] = @[]
for (key, _) in ctx.db.scanAll():
if key.startsWith(SchemaFtsIndexPrefix & prefix) or
key.startsWith(SchemaVecIndexPrefix & prefix):
engineKeys.add(key)
for key in engineKeys:
ctx.db.delete(key)
# Drop orphan legacy schema keys that mentioned this table
var legacyKeys: seq[string] = @[]
for (key, value) in ctx.db.scanAll():
@@ -1376,6 +1393,35 @@ proc executeQueryImpl(ctx: ExecutionContext, astNode: Node, params: seq[WireValu
if found:
ctx.btrees.del(targetKey)
return okResult(msg="DROP INDEX " & stmt.diName)
# FTS/HNSW engine indexes: in-memory maps are keyed by table.col, and a
# custom index name only appears in the persisted DDL — match it against
# the stored "CREATE INDEX <name> ON" text as well.
var ftsKey = ""
for key in ctx.ftsIndexes.keys.toSeq():
if key == stmt.diName or key.endsWith("." & stmt.diName):
ftsKey = key
break
let (hasDdl, ddl) = ctx.db.get(SchemaFtsIndexPrefix & key)
if hasDdl and cast[string](ddl).startsWith("CREATE INDEX " & stmt.diName & " ON "):
ftsKey = key
break
if ftsKey.len > 0:
ctx.ftsIndexes.del(ftsKey)
ctx.db.delete(SchemaFtsIndexPrefix & ftsKey)
return okResult(msg="DROP INDEX " & stmt.diName)
var vecKey = ""
for key in ctx.vectorIndexes.keys.toSeq():
if key == stmt.diName or key.endsWith("." & stmt.diName):
vecKey = key
break
let (hasDdl, ddl) = ctx.db.get(SchemaVecIndexPrefix & key)
if hasDdl and cast[string](ddl).startsWith("CREATE INDEX " & stmt.diName & " ON "):
vecKey = key
break
if vecKey.len > 0:
ctx.vectorIndexes.del(vecKey)
ctx.db.delete(SchemaVecIndexPrefix & vecKey)
return okResult(msg="DROP INDEX " & stmt.diName)
else:
# Also remove from schema storage
let idxKey = "_schema:indexes:" & stmt.diName
+103
View File
@@ -193,6 +193,109 @@ suite "Schema persistence":
db2.close()
removeDir(dir)
test "DROP INDEX removes FTS index and its schema key (custom name)":
let dir = "/tmp/baradb_schema_persist_dropfts"
removeDir(dir)
block:
var db = newLSMTree(dir)
var ctx = newExecutionContext(db)
check execSql(ctx, "CREATE TABLE docs (id INTEGER PRIMARY KEY, content TEXT)").success
check execSql(ctx, "INSERT INTO docs (id, content) VALUES (1, 'quick brown fox')").success
check execSql(ctx, "CREATE INDEX docs_fts ON docs (content) USING FTS").success
check ctx.ftsIndexes.hasKey("docs.content")
# Drop by the custom index name — the in-memory key is table.col
let d = execSql(ctx, "DROP INDEX docs_fts")
check d.success
check "docs.content" notin ctx.ftsIndexes
let (found, _) = db.get(SchemaFtsIndexPrefix & "docs.content")
check not found
db.close()
block:
var db2 = newLSMTree(dir)
var ctx2 = newExecutionContext(db2)
check "docs.content" notin ctx2.ftsIndexes # no ghost rebuild
db2.close()
removeDir(dir)
test "DROP INDEX removes FTS index by column key":
let dir = "/tmp/baradb_schema_persist_dropfts_col"
removeDir(dir)
block:
var db = newLSMTree(dir)
var ctx = newExecutionContext(db)
check execSql(ctx, "CREATE TABLE docs (id INTEGER PRIMARY KEY, content TEXT)").success
check execSql(ctx, "INSERT INTO docs (id, content) VALUES (1, 'quick brown fox')").success
# No custom name — idxName defaults to colKey (docs.content)
check execSql(ctx, "CREATE INDEX ON docs (content) USING FTS").success
check ctx.ftsIndexes.hasKey("docs.content")
# Drop by column name — matches endsWith(".content")
let d = execSql(ctx, "DROP INDEX content")
check d.success
check "docs.content" notin ctx.ftsIndexes
let (found, _) = db.get(SchemaFtsIndexPrefix & "docs.content")
check not found
db.close()
block:
var db2 = newLSMTree(dir)
var ctx2 = newExecutionContext(db2)
check "docs.content" notin ctx2.ftsIndexes # no ghost rebuild
db2.close()
removeDir(dir)
test "DROP INDEX removes HNSW index and its schema key":
let dir = "/tmp/baradb_schema_persist_drophnsw"
removeDir(dir)
block:
var db = newLSMTree(dir)
var ctx = newExecutionContext(db)
check execSql(ctx, "CREATE TABLE vecs (id INTEGER PRIMARY KEY, embedding TEXT)").success
check execSql(ctx, "INSERT INTO vecs (id, embedding) VALUES (1, '[1.0, 0.0, 0.0]')").success
check execSql(ctx, "CREATE INDEX vecs_hnsw ON vecs (embedding) USING HNSW").success
check ctx.vectorIndexes.hasKey("vecs.embedding")
let d = execSql(ctx, "DROP INDEX vecs_hnsw")
check d.success
check "vecs.embedding" notin ctx.vectorIndexes
let (found, _) = db.get(SchemaVecIndexPrefix & "vecs.embedding")
check not found
db.close()
block:
var db2 = newLSMTree(dir)
var ctx2 = newExecutionContext(db2)
check "vecs.embedding" notin ctx2.vectorIndexes # no ghost rebuild
db2.close()
removeDir(dir)
test "DROP TABLE removes engine indexes for that table":
let dir = "/tmp/baradb_schema_persist_droptbl"
removeDir(dir)
block:
var db = newLSMTree(dir)
var ctx = newExecutionContext(db)
check execSql(ctx, "CREATE TABLE docs (id INTEGER PRIMARY KEY, content TEXT)").success
check execSql(ctx, "INSERT INTO docs (id, content) VALUES (1, 'quick brown fox')").success
check execSql(ctx, "CREATE INDEX docs_fts ON docs (content) USING FTS").success
check execSql(ctx, "CREATE TABLE vecs (id INTEGER PRIMARY KEY, embedding TEXT)").success
check execSql(ctx, "INSERT INTO vecs (id, embedding) VALUES (1, '[1.0, 0.0, 0.0]')").success
check execSql(ctx, "CREATE INDEX vecs_hnsw ON vecs (embedding) USING HNSW").success
check ctx.ftsIndexes.hasKey("docs.content")
check ctx.vectorIndexes.hasKey("vecs.embedding")
check execSql(ctx, "DROP TABLE docs").success
check "docs.content" notin ctx.ftsIndexes
check execSql(ctx, "DROP TABLE vecs").success
check "vecs.embedding" notin ctx.vectorIndexes
let (ftsFound, _) = db.get(SchemaFtsIndexPrefix & "docs.content")
check not ftsFound
let (vecFound, _) = db.get(SchemaVecIndexPrefix & "vecs.embedding")
check not vecFound
db.close()
block:
var db2 = newLSMTree(dir)
var ctx2 = newExecutionContext(db2)
check "docs.content" notin ctx2.ftsIndexes # no ghost rebuild
check "vecs.embedding" notin ctx2.vectorIndexes
db2.close()
removeDir(dir)
test "Stable schema key format":
check tableSchemaKey("users") == "_schema:tables:users"
check serializeTableDdl(TableDef(