Skip to main content

sonic/executor/
push.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::{StoreMetaKey, StoreMetaValue, StoreTermHashed};
15use crate::store::kv::{StoreKVAcquireMode, StoreKVActionBuilder};
16
17impl super::Executor {
18    pub fn push(&self, item: StoreItem, lexer: TokenLexer) -> Result<(), ()> {
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.acquire(StoreKVAcquireMode::Any, collection),
27                self.fst_pool.acquire(collection, bucket),
28            ) {
29                // Important: acquire bucket store write lock
30                executor_kv_lock_write!(kv_store);
31
32                let (kv_action, fst_action) = (
33                    StoreKVActionBuilder::access(bucket, kv_store),
34                    StoreFSTActionBuilder::access(fst_store),
35                );
36
37                // Try to resolve existing OID to IID, otherwise initialize IID (store the \
38                //   bi-directional relationship)
39                let oid = object.as_str();
40                let iid = kv_action.get_oid_to_iid(oid).unwrap_or(None).or_else(|| {
41                    tracing::info!("must initialize push executor oid-to-iid and iid-to-oid");
42
43                    if let Ok(iid_incr) = kv_action.get_meta_to_value(StoreMetaKey::IIDIncr) {
44                        let iid_incr = if let Some(iid_incr) = iid_incr {
45                            match iid_incr {
46                                StoreMetaValue::IIDIncr(iid_incr) => iid_incr + 1,
47                            }
48                        } else {
49                            0
50                        };
51
52                        // Bump last stored increment
53                        if kv_action
54                            .set_meta_to_value(
55                                StoreMetaKey::IIDIncr,
56                                StoreMetaValue::IIDIncr(iid_incr),
57                            )
58                            .is_ok()
59                        {
60                            // Associate OID <> IID (bidirectional)
61                            executor_ensure_op!(kv_action.set_oid_to_iid(oid, iid_incr));
62                            executor_ensure_op!(kv_action.set_iid_to_oid(iid_incr, oid));
63
64                            Some(iid_incr)
65                        } else {
66                            tracing::error!(
67                                "failed updating push executor meta-to-value iid increment"
68                            );
69
70                            None
71                        }
72                    } else {
73                        tracing::error!("failed getting push executor meta-to-value iid increment");
74
75                        None
76                    }
77                });
78
79                if let Some(iid) = iid {
80                    let mut has_commits = false;
81
82                    // Acquire list of terms for IID
83                    let mut iid_terms_hashed: LinkedHashSet<StoreTermHashed> =
84                        LinkedHashSet::from_iter(
85                            kv_action
86                                .get_iid_to_terms(iid)
87                                .unwrap_or(None)
88                                .unwrap_or_default(),
89                        );
90
91                    tracing::debug!(
92                        "got push executor stored iid-to-terms: {:?}",
93                        iid_terms_hashed
94                    );
95
96                    for (term, term_hashed, _) in lexer {
97                        // Check that term is not already linked to IID
98                        if !iid_terms_hashed.contains(&term_hashed) {
99                            if let Ok(term_iids) = kv_action.get_term_to_iids(term_hashed) {
100                                has_commits = true;
101
102                                // Add IID in first position in list for terms
103                                let mut term_iids = term_iids.unwrap_or_default();
104
105                                // Remove IID from list of IIDs to be popped before inserting in \
106                                //   first position?
107                                if term_iids.contains(&iid) {
108                                    term_iids.retain(|cur_iid| cur_iid != &iid);
109                                }
110
111                                tracing::info!("has push executor term-to-iids: {}", iid);
112
113                                term_iids.insert(0, iid);
114
115                                // Truncate IIDs linked to term? (ie. storage is too long)
116                                let truncate_limit = self.app_conf.store.kv.retain_word_objects;
117
118                                if term_iids.len() > truncate_limit {
119                                    tracing::info!(
120                                        "push executor term-to-iids object too long (limit: {})",
121                                        truncate_limit
122                                    );
123
124                                    // Drain overflowing IIDs (ie. oldest ones that overflow)
125                                    let term_iids_drain = term_iids.drain(truncate_limit..);
126
127                                    executor_ensure_op!(
128                                        kv_action
129                                            .batch_truncate_object(term_hashed, term_iids_drain)
130                                    );
131                                }
132
133                                executor_ensure_op!(
134                                    kv_action.set_term_to_iids(term_hashed, &term_iids)
135                                );
136
137                                // Insert term into IID to terms map
138                                iid_terms_hashed.insert(term_hashed);
139                            } else {
140                                tracing::error!("failed getting push executor term-to-iids");
141                            }
142                        }
143
144                        // Push to FST graph? (this consumes the term; to avoid sub-clones)
145                        if fst_action.push_word(&term, &self.app_conf.store.fst) {
146                            tracing::debug!("push term committed to graph: {}", term);
147                        }
148                    }
149
150                    // Commit updated list of terms for IID? (if any commit made)
151                    if has_commits {
152                        let collected_iids: Vec<StoreTermHashed> =
153                            iid_terms_hashed.into_iter().collect();
154
155                        tracing::info!(
156                            "has push executor iid-to-terms commits: {:?}",
157                            collected_iids
158                        );
159
160                        executor_ensure_op!(kv_action.set_iid_to_terms(iid, &collected_iids));
161                    }
162
163                    return Ok(());
164                }
165            }
166        }
167
168        Err(())
169    }
170}