Skip to main content

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}