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, 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}