Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions gitnexus/src/core/lbug/lbug-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2294,6 +2294,11 @@ export const deleteNodesForFiles = async (
if (!conn) {
throw new Error('LadybugDB not initialized. Call initLbug first.');
}
// A persisted HNSW index makes even ordinary CodeEmbedding DELETEs depend
// on VECTOR in every new connection. Keep this mutation path offline-only:
// if VECTOR is unavailable, the delete below still works for an unindexed
// table and surfaces LadybugDB's real error for an indexed one.
await loadVectorExtension(undefined, { policy: 'load-only' });
const targetConn = conn;
let warnedMissingEmbeddingTable = false;
for (let i = 0; i < filePaths.length; i += DELETE_FILES_CHUNK_SIZE) {
Expand Down
31 changes: 31 additions & 0 deletions gitnexus/test/integration/lbug-vector-extension.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,37 @@ withTestLbugDB('vector-index-creation', (handle) => {
const rows = await adapter.executeQuery('CALL SHOW_INDEXES() RETURN *');
expect(rows.filter((row: any) => row.index_name === 'code_embedding_idx')).toHaveLength(1);
});

it('deletes embedding rows from a persisted HNSW index after close and re-open', async () => {
const adapter = await import('../../src/core/lbug/lbug-adapter.js');
const { batchInsertEmbeddings } =
await import('../../src/core/embeddings/embedding-pipeline.js');
const { EMBEDDING_DIMS } = await import('../../src/core/lbug/schema.js');
const filePath = 'src/vector-reopen-delete.ts';
const nodeId = `Function:${filePath}:target:1`;

await adapter.executeQuery(
`CREATE (:Function {id: '${nodeId}', name: 'target', filePath: '${filePath}', startLine: 1, endLine: 3, isExported: true, content: '', description: ''})`,
);
await batchInsertEmbeddings(adapter.executeWithReusedStatement, [
{
nodeId,
chunkIndex: 0,
startLine: 1,
endLine: 3,
embedding: new Array(EMBEDDING_DIMS).fill(0),
},
]);
await adapter.createVectorIndex();
await adapter.closeLbug();
await adapter.initLbug(handle.dbPath);

await expect(adapter.deleteNodesForFiles([filePath])).resolves.toBeUndefined();
const rows = (await adapter.executeQuery(
`MATCH (e:CodeEmbedding) WHERE e.nodeId = '${nodeId}' RETURN count(e) AS count`,
)) as Array<{ count: number | bigint }>;
expect(Number(rows[0]?.count ?? 0)).toBe(0);
});
});
});

Expand Down
Loading