Skip to content
Merged
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
9 changes: 8 additions & 1 deletion vector/src/index/hnsw_index.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -804,6 +804,9 @@ std::unique_ptr<Index::UpdateState> OnDiskHNSWIndex::initUpdateState(main::Clien
void OnDiskHNSWIndex::update(Transaction* transaction, const common::ValueVector& nodeIDVector,
common::ValueVector& propertyVector, UpdateState& updateState) {
auto& hnswUpdateState = updateState.cast<HNSWUpdateState>();
// NOTE: The node table has already been updated with the new value when we get here
// (NodeTable::update applies the table update before updating indexes), so any embedding
// scanned from the node table during re-insertion is the new value.
commitInsert(transaction, nodeIDVector, {&propertyVector}, *hnswUpdateState.insertState);
}

Expand Down Expand Up @@ -1427,7 +1430,11 @@ void OnDiskHNSWIndex::shrinkForNode(Transaction* transaction, common::offset_t o
const auto approxMetricFunc = embeddings.getMetricFunction(config.metric);
auto& embeddingScanState = *insertState.searchState.embeddingScanState;
const auto vector = embeddings.getEmbedding(offset, embeddingScanState);
DASSERT(!vector.isNull());
if (vector.isNull()) {
// The node has no embedding (e.g. its embedding was updated to NULL while it is still
// referenced in the graph). We cannot compute distances, so we skip shrinking.
return;
}
const auto& searchState = insertState.searchState;
const auto& graph = isUpperLayer ? searchState.upperGraph : searchState.lowerGraph;
const auto relTableID = isUpperLayer ? storageInfo->cast<HNSWStorageInfo>().upperRelTableID :
Expand Down
73 changes: 73 additions & 0 deletions vector/test/test_files/update.test
Original file line number Diff line number Diff line change
Expand Up @@ -188,3 +188,76 @@ Binder exception: Cannot set property id in table embeddings because it is used
1
2
3

-CASE UpdateNullEmbeddingRowAtIndexBuild
-LOAD_DYNAMIC_EXTENSION vector
-STATEMENT CREATE NODE TABLE embeddings (id int64, vec FLOAT[8], PRIMARY KEY (id));
---- ok
-STATEMENT CALL threads=1;
---- ok
-STATEMENT UNWIND range(0, 499) AS i CREATE (:embeddings {id: i, vec: [i * 0.001, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8]});
---- ok
-STATEMENT UNWIND range(500, 549) AS i CREATE (:embeddings {id: i});
---- ok
-STATEMENT CALL CREATE_VECTOR_INDEX('embeddings', 'e_hnsw_index', 'vec', metric := 'l2');
---- ok
-STATEMENT MATCH (t:embeddings) WHERE t.id = 500 SET t.vec = [0.35, 0.25, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8];
---- ok
-STATEMENT MATCH (t:embeddings) WHERE t.id = 550 SET t.vec = [0.9, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8];
---- ok
-STATEMENT CALL QUERY_VECTOR_INDEX('embeddings', 'e_hnsw_index', [0.35, 0.25, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8], 1, efs := 50) RETURN node.id ORDER BY distance;
---- 1
500

-CASE UpdateNullEmbeddingToNullAndBack
-LOAD_DYNAMIC_EXTENSION vector
-STATEMENT CREATE NODE TABLE embeddings (id int64, vec FLOAT[8], PRIMARY KEY (id));
---- ok
-STATEMENT CALL threads=1;
---- ok
-STATEMENT UNWIND range(0, 499) AS i CREATE (:embeddings {id: i, vec: [i * 0.001, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8]});
---- ok
-STATEMENT UNWIND range(500, 549) AS i CREATE (:embeddings {id: i, vec: [i * 0.002, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8]});
---- ok
-STATEMENT CALL CREATE_VECTOR_INDEX('embeddings', 'e_hnsw_index', 'vec', metric := 'l2');
---- ok
-STATEMENT MATCH (t:embeddings) WHERE t.id = 500 SET t.vec = NULL;
---- ok
-STATEMENT MATCH (t:embeddings) WHERE t.id = 501 SET t.vec = [1.5, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8];
---- ok
-STATEMENT MATCH (t:embeddings) WHERE t.id = 500 SET t.vec = [1.5, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8];
---- ok
-STATEMENT CALL QUERY_VECTOR_INDEX('embeddings', 'e_hnsw_index', [1.5, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8], 2, efs := 50) RETURN node.id ORDER BY distance;
-CHECK_ORDER
---- 2
500
501

-CASE UpdateNullEmbeddingSurvivesCheckpointReopen
-SKIP_IN_MEM
-LOAD_DYNAMIC_EXTENSION vector
-STATEMENT CREATE NODE TABLE embeddings (id int64, vec FLOAT[8], PRIMARY KEY (id));
---- ok
-STATEMENT CALL threads=1;
---- ok
-STATEMENT UNWIND range(0, 499) AS i CREATE (:embeddings {id: i, vec: [i * 0.001, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8]});
---- ok
-STATEMENT UNWIND range(500, 549) AS i CREATE (:embeddings {id: i});
---- ok
-STATEMENT CALL CREATE_VECTOR_INDEX('embeddings', 'e_hnsw_index', 'vec', metric := 'l2');
---- ok
-STATEMENT CHECKPOINT;
---- ok
-RELOADDB
-LOAD_DYNAMIC_EXTENSION vector
-STATEMENT MATCH (t:embeddings) WHERE t.id = 500 SET t.vec = [0.35, 0.25, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8];
---- ok
-STATEMENT MATCH (t:embeddings) WHERE t.id = 510 SET t.vec = [0.51, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8];
---- ok
-STATEMENT CHECKPOINT;
---- ok
-RELOADDB
-LOAD_DYNAMIC_EXTENSION vector
-STATEMENT CALL QUERY_VECTOR_INDEX('embeddings', 'e_hnsw_index', [0.35, 0.25, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8], 1, efs := 50) RETURN node.id ORDER BY distance;
---- 1
500
Loading