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, usize)> = lexer.collect();
55
56 let iid_terms_hashed: LinkedHashSet<StoreTermHashed> =
57 LinkedHashSet::from_iter(iid_terms_hashed_vec.iter().copied());
58
59 let remaining_terms: LinkedHashSet<StoreTermHashed> = iid_terms_hashed
60 .difference(&LinkedHashSet::from_iter(
61 pop_terms.iter().map(|item| item.1),
62 ))
63 .copied()
64 .collect();
65
66 tracing::debug!(
67 "got pop executor terms remaining terms: {:?} for iid: {}",
68 remaining_terms,
69 iid
70 );
71
72 count_popped = (iid_terms_hashed.len() - remaining_terms.len()) as u32;
73
74 if count_popped > 0 {
75 if remaining_terms.is_empty() {
76 tracing::info!("nuke whole bucket for pop executor");
77
78 // Flush bucket (batch operation, as it is shared w/ other \
79 // executors)
80 executor_ensure_op!(kv_action.batch_flush_bucket(
81 iid,
82 oid,
83 &iid_terms_hashed_vec
84 ));
85 } else {
86 tracing::info!("nuke only certain terms for pop executor");
87
88 // Nuke IID in Term-to-IIDs list
89 for (pop_term, pop_term_hashed, _) in &pop_terms {
90 // Check that term is linked to IID (and should be removed)
91 if iid_terms_hashed.contains(pop_term_hashed) {
92 if let Ok(Some(mut pop_term_iids)) =
93 kv_action.get_term_to_iids(*pop_term_hashed)
94 {
95 // Remove IID from list of IIDs to be popped
96 pop_term_iids.retain(|cur_iid| cur_iid != &iid);
97
98 if pop_term_iids.is_empty() {
99 // IIDs list was empty, delete whole key
100 executor_ensure_op!(
101 kv_action
102 .delete_term_to_iids(*pop_term_hashed)
103 );
104
105 // Pop from FST graph (does not exist anymore)
106 if fst_action.pop_word(pop_term) {
107 tracing::debug!(
108 "pop term hash nuked from graph: {}",
109 pop_term_hashed
110 );
111 }
112 } else {
113 // Re-build IIDs list w/o current IID
114 executor_ensure_op!(
115 kv_action.set_term_to_iids(
116 *pop_term_hashed,
117 &pop_term_iids,
118 )
119 );
120 }
121 } else {
122 tracing::error!(
123 "failed getting term-to-iids in pop executor"
124 );
125 }
126 }
127 }
128
129 // Bump IID-to-Terms list
130 let remaining_terms_vec: Vec<StoreTermHashed> =
131 Vec::from_iter(remaining_terms);
132
133 executor_ensure_op!(
134 kv_action.set_iid_to_terms(iid, &remaining_terms_vec)
135 );
136 }
137 }
138 } else {
139 tracing::error!("failed getting iid-to-terms in pop executor");
140 }
141 }
142
143 return Ok(count_popped);
144 }
145 }
146 }
147
148 Err(())
149 }
150}