(
&mut self,
task: &ExecutionTask,
tid: u64,
collection: &str,
vector_id: u32,
)
| 234 | } |
| 235 | |
| 236 | pub(in crate::data::executor) fn execute_vector_delete( |
| 237 | &mut self, |
| 238 | task: &ExecutionTask, |
| 239 | tid: u64, |
| 240 | collection: &str, |
| 241 | vector_id: u32, |
| 242 | ) -> Response { |
| 243 | debug!(core = self.core_id, %collection, vector_id, "vector delete"); |
| 244 | // Resolve the actual index key. Legacy `CREATE VECTOR INDEX` uses |
| 245 | // an empty field segment; vector-primary collections use |
| 246 | // `"{collection}:{field}"`. Try the legacy key first, then scan |
| 247 | // for any field-suffixed key under the same (tenant, collection). |
| 248 | let tenant = TenantId::new(tid); |
| 249 | let plain_key = (tenant, collection.to_string()); |
| 250 | let prefix = format!("{collection}:"); |
| 251 | let resolved_key = if self.vector_collections.contains_key(&plain_key) { |
| 252 | Some(plain_key) |
| 253 | } else { |
| 254 | self.vector_collections |
| 255 | .keys() |
| 256 | .find(|(t, c)| *t == tenant && c.starts_with(&prefix)) |
| 257 | .cloned() |
| 258 | }; |
| 259 | let Some(index_key) = resolved_key else { |
| 260 | return self.response_error(task, ErrorCode::NotFound); |
| 261 | }; |
| 262 | |
| 263 | // Capture the surrogate before deletion so we can fetch the |
| 264 | // payload row from the sparse store and update the bitmap. The |
| 265 | // bitmap stores node-id -> field-value membership; without the |
| 266 | // original field values we cannot remove the entries cleanly. |
| 267 | // |
| 268 | // Asymmetric with the insert path (`vector_upsert`): insert |
| 269 | // atomically rolls back the HNSW node if the sparse write fails, |
| 270 | // because a phantom node would be returned by future searches. |
| 271 | // Delete is best-effort cleanup — if the sparse read or decode |
| 272 | // fails we still drop the HNSW node and skip bitmap cleanup. |
| 273 | // Phantom bitmap entries are safe (the bitmap is filtered against |
| 274 | // live node ids on read), so leaving them is preferable to |
| 275 | // aborting the delete and leaking the vector. |
| 276 | let surrogate_opt = self |
| 277 | .vector_collections |
| 278 | .get(&index_key) |
| 279 | .and_then(|c| c.get_surrogate(vector_id)); |
| 280 | |
| 281 | if let Some(surrogate) = surrogate_opt { |
| 282 | let row_key = format!("{:08x}", surrogate.as_u32()); |
| 283 | let fields = match self.sparse.get(tid, collection, &row_key) { |
| 284 | Ok(Some(bytes)) => decode_payload_lowercased(&bytes).ok(), |
| 285 | _ => None, |
| 286 | }; |
| 287 | if let Some(fields) = fields |
| 288 | && let Some(coll) = self.vector_collections.get_mut(&index_key) |
| 289 | { |
| 290 | coll.payload.delete_row(vector_id, &fields); |
| 291 | } |
| 292 | } |
| 293 |
no test coverage detected