fix: DROP INDEX/TABLE clean up FTS/HNSW indexes and their schema keys
This commit is contained in:
@@ -859,6 +859,15 @@ proc executeQueryImpl(ctx: ExecutionContext, astNode: Node, params: seq[WireValu
|
|||||||
for idxName in ctx.btrees.keys.toSeq():
|
for idxName in ctx.btrees.keys.toSeq():
|
||||||
if idxName.startsWith(dropName & "."): toDelete.add(idxName)
|
if idxName.startsWith(dropName & "."): toDelete.add(idxName)
|
||||||
for idxName in toDelete: ctx.btrees.del(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
|
# Remove durable schema entry
|
||||||
dropTableSchema(ctx, dropName)
|
dropTableSchema(ctx, dropName)
|
||||||
# Remove row data for this table
|
# Remove row data for this table
|
||||||
@@ -869,6 +878,14 @@ proc executeQueryImpl(ctx: ExecutionContext, astNode: Node, params: seq[WireValu
|
|||||||
dataKeys.add(key)
|
dataKeys.add(key)
|
||||||
for key in dataKeys:
|
for key in dataKeys:
|
||||||
ctx.db.delete(key)
|
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
|
# Drop orphan legacy schema keys that mentioned this table
|
||||||
var legacyKeys: seq[string] = @[]
|
var legacyKeys: seq[string] = @[]
|
||||||
for (key, value) in ctx.db.scanAll():
|
for (key, value) in ctx.db.scanAll():
|
||||||
@@ -1376,6 +1393,35 @@ proc executeQueryImpl(ctx: ExecutionContext, astNode: Node, params: seq[WireValu
|
|||||||
if found:
|
if found:
|
||||||
ctx.btrees.del(targetKey)
|
ctx.btrees.del(targetKey)
|
||||||
return okResult(msg="DROP INDEX " & stmt.diName)
|
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:
|
else:
|
||||||
# Also remove from schema storage
|
# Also remove from schema storage
|
||||||
let idxKey = "_schema:indexes:" & stmt.diName
|
let idxKey = "_schema:indexes:" & stmt.diName
|
||||||
|
|||||||
@@ -193,6 +193,109 @@ suite "Schema persistence":
|
|||||||
db2.close()
|
db2.close()
|
||||||
removeDir(dir)
|
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":
|
test "Stable schema key format":
|
||||||
check tableSchemaKey("users") == "_schema:tables:users"
|
check tableSchemaKey("users") == "_schema:tables:users"
|
||||||
check serializeTableDdl(TableDef(
|
check serializeTableDdl(TableDef(
|
||||||
|
|||||||
Reference in New Issue
Block a user