sonic/executor/pop.rs
1// Sonic
2//
3// Fast, lightweight and schema-less search backend
4// Copyright: 2019, Valerian Saliou <valerian@valeriansaliou.name>
5// Copyright: 2026, Rémi Bardon <remi@remibardon.name>
6// License: Mozilla Public License v2.0 (MPL v2.0)
7
8use linked_hash_set::LinkedHashSet;
9use std::iter::FromIterator;
10
11use crate::lexer::TokenLexer;
12use crate::store::StoreItem;
13use crate::store::fst::StoreFSTActionBuilder;
14use crate::store::identifiers::StoreTermHashed;
15use crate::store::kv::{StoreKVAcquireMode, StoreKVActionBuilder};
16
17impl super::Executor {
18 pub fn pop(&self, item: StoreItem, lexer: TokenLexer) -> Result<u32, ()> {
19 if let StoreItem(collection, Some(bucket), Some(object)) = item {
20 // Important: acquire database access read lock, and reference it in context. This \
21 // prevents the database from being erased while using it in this block.
22 let _kv_read_guard = self.kv_pool.lock_read_access();
23 let _fst_read_guard = self.fst_pool.lock_read_access();
24
25 if let (Ok(kv_store), Ok(fst_store)) = (
26 self.kv_pool
27 .acquire(StoreKVAcquireMode::OpenOnly, collection),
28 self.fst_pool.acquire(collection, bucket),
29 ) {
30 // Important: acquire bucket store write lock
31 executor_kv_lock_write!(kv_store);
32
33 let (kv_action, fst_action) = (
34 StoreKVActionBuilder::access(bucket, kv_store),
35 StoreFSTActionBuilder::access(fst_store),
36 );
37
38 // Try to resolve existing OID to IID (if it does not exist, there is nothing to \
39 // be flushed)
40 let oid = object.as_str();
41
42 if let Ok(iid_value) = kv_action.get_oid_to_iid(oid) {
43 let mut count_popped = 0;
44
45 if let Some(iid) = iid_value {
46 // Try to resolve existing search terms from IID, and perform an algebraic \
47 // AND on all popped terms to generate a list of terms to be cleaned up.
48 if let Ok(Some(iid_terms_hashed_vec)) = kv_action.get_iid_to_terms(iid) {
49 tracing::info!(
50 "got pop executor stored iid-to-terms: {:?}",
51 iid_terms_hashed_vec
52 );
53
54 let pop_terms: Vec<(String, StoreTermHashed)> = lexer
55 .map(|(token, hash, _len)| (token.into_inner(), hash))
56 .collect();
57
58 let iid_terms_hashed: LinkedHashSet<StoreTermHashed> =
59 LinkedHashSet::from_iter(iid_terms_hashed_vec.iter().copied());
60
61 let remaining_terms: LinkedHashSet<StoreTermHashed> = iid_terms_hashed
62 .difference(&LinkedHashSet::from_iter(
63 pop_terms.iter().map(|item| item.1),
64 ))
65 .copied()
66 .collect();
67
68 tracing::debug!(
69 "got pop executor terms remaining terms: {:?} for iid: {}",
70 remaining_terms,
71 iid
72 );
73
74 count_popped = (iid_terms_hashed.len() - remaining_terms.len()) as u32;
75
76 if count_popped > 0 {
77 if remaining_terms.is_empty() {
78 tracing::info!("nuke whole bucket for pop executor");
79
80 // Flush bucket (batch operation, as it is shared w/ other \
81 // executors)
82 executor_ensure_op!(kv_action.batch_flush_bucket(
83 iid,
84 oid,
85 &iid_terms_hashed_vec
86 ));
87 } else {
88 tracing::info!("nuke only certain terms for pop executor");
89
90 // Nuke IID in Term-to-IIDs list
91 for (pop_term, pop_term_hashed) in &pop_terms {
92 // Check that term is linked to IID (and should be removed)
93 if iid_terms_hashed.contains(pop_term_hashed) {
94 if let Ok(Some(mut pop_term_iids)) =
95 kv_action.get_term_to_iids(*pop_term_hashed)
96 {
97 // Remove IID from list of IIDs to be popped
98 pop_term_iids.retain(|cur_iid| cur_iid != &iid);
99
100 if pop_term_iids.is_empty() {
101 // IIDs list was empty, delete whole key
102 executor_ensure_op!(
103 kv_action
104 .delete_term_to_iids(*pop_term_hashed)
105 );
106
107 // Pop from FST graph (does not exist anymore)
108 if fst_action.pop_word(pop_term) {
109 tracing::debug!(
110 "pop term hash nuked from graph: {}",
111 pop_term_hashed
112 );
113 }
114 } else {
115 // Re-build IIDs list w/o current IID
116 executor_ensure_op!(
117 kv_action.set_term_to_iids(
118 *pop_term_hashed,
119 &pop_term_iids,
120 )
121 );
122 }
123 } else {
124 tracing::error!(
125 "failed getting term-to-iids in pop executor"
126 );
127 }
128 }
129 }
130
131 // Bump IID-to-Terms list
132 let remaining_terms_vec: Vec<StoreTermHashed> =
133 Vec::from_iter(remaining_terms);
134
135 executor_ensure_op!(
136 kv_action.set_iid_to_terms(iid, &remaining_terms_vec)
137 );
138 }
139 }
140 } else {
141 tracing::error!("failed getting iid-to-terms in pop executor");
142 }
143 }
144
145 return Ok(count_popped);
146 }
147 }
148 }
149
150 Err(())
151 }
152}