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}