diff --git a/src/barabadb/query/executor.nim b/src/barabadb/query/executor.nim index d6390f2..9d4ae7a 100644 --- a/src/barabadb/query/executor.nim +++ b/src/barabadb/query/executor.nim @@ -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 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 diff --git a/tests/test_schema_persist.nim b/tests/test_schema_persist.nim index ea049dd..2367127 100644 --- a/tests/test_schema_persist.nim +++ b/tests/test_schema_persist.nim @@ -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(