1use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
25use std::sync::{Arc, Mutex, MutexGuard};
26
27use nodedb_types::Namespace;
28
29use crate::error::LiteError;
30use crate::nodedb::LockExt;
31use crate::storage::engine::{StorageEngine, WriteOp};
32
33use super::catalog::{IndexDef, IndexEngine};
34use super::key;
35use super::maintain::IndexWriteOp;
36use super::sorted_def::SortedIndexDef;
37
38struct Dirty {
42 seq: u64,
43 value: Option<Vec<u8>>,
44}
45
46#[derive(Default)]
48pub(crate) struct IndexState {
49 pub(super) defs: BTreeMap<String, Arc<IndexDef>>,
51 pub(super) sorted: BTreeMap<String, Arc<SortedIndexDef>>,
54 pub(super) entries: BTreeSet<Vec<u8>>,
56 pub(super) postings: HashMap<(String, String), BTreeSet<Vec<u8>>>,
60 pub(super) tombstoned: HashSet<(String, String)>,
63 dirty: HashMap<Vec<u8>, Dirty>,
64 next_seq: u64,
65 sink: Option<Vec<WriteOp>>,
68}
69
70impl IndexState {
71 pub(super) fn defs_on(&self, collection: &str, engine: IndexEngine) -> Vec<Arc<IndexDef>> {
73 self.defs
74 .values()
75 .filter(|d| d.collection == collection && d.engine == engine)
76 .cloned()
77 .collect()
78 }
79
80 fn mark(&mut self, key: Vec<u8>, value: Option<Vec<u8>>) {
81 if let Some(sink) = &mut self.sink {
82 sink.push(match value {
83 Some(value) => WriteOp::Put {
84 ns: Namespace::Meta,
85 key,
86 value,
87 },
88 None => WriteOp::Delete {
89 ns: Namespace::Meta,
90 key,
91 },
92 });
93 return;
94 }
95 self.next_seq += 1;
96 let seq = self.next_seq;
97 self.dirty.insert(key, Dirty { seq, value });
98 }
99
100 pub(super) fn scratch(&self, defs: &[Arc<IndexDef>]) -> IndexState {
104 let mut scratch = IndexState::default();
105 for def in defs {
106 scratch.defs.insert(def.name.clone(), Arc::clone(def));
107 let prefix = def.entry_prefix();
108 for entry in self
109 .entries
110 .range(prefix.clone()..)
111 .take_while(|k| k.starts_with(&prefix))
112 {
113 if let Some(parsed) = key::parse_entry(entry) {
114 scratch
115 .postings
116 .entry((parsed.collection.to_string(), parsed.doc_id.to_string()))
117 .or_default()
118 .insert(entry.clone());
119 }
120 scratch.entries.insert(entry.clone());
121 }
122 }
123 scratch
124 }
125
126 pub(super) fn collecting<R>(
130 &mut self,
131 durable: bool,
132 f: impl FnOnce(&mut Self) -> R,
133 ) -> (R, Vec<WriteOp>) {
134 if !durable {
135 return (f(self), Vec::new());
136 }
137 let outer = self.sink.replace(Vec::new());
138 let result = f(self);
139 let ops = self.sink.take().unwrap_or_default();
140 self.sink = outer;
141 (result, ops)
142 }
143
144 pub(super) fn apply(&mut self, collection: &str, doc_id: &str, ops: Vec<IndexWriteOp>) {
146 if ops.is_empty() {
147 return;
148 }
149 let posting_key = (collection.to_string(), doc_id.to_string());
150 for op in ops {
151 match op {
152 IndexWriteOp::Put(entry) => {
153 self.postings
154 .entry(posting_key.clone())
155 .or_default()
156 .insert(entry.clone());
157 self.entries.insert(entry.clone());
158 self.mark(entry, Some(Vec::new()));
159 }
160 IndexWriteOp::Delete(entry) => {
161 if let Some(keys) = self.postings.get_mut(&posting_key) {
162 keys.remove(&entry);
163 if keys.is_empty() {
164 self.postings.remove(&posting_key);
165 }
166 }
167 self.entries.remove(&entry);
168 self.mark(entry, None);
169 }
170 }
171 }
172 }
173
174 pub(super) fn clear_prefix(&mut self, prefix: &[u8]) {
176 let doomed: Vec<Vec<u8>> = self
177 .entries
178 .range(prefix.to_vec()..)
179 .take_while(|k| k.starts_with(prefix))
180 .cloned()
181 .collect();
182 for entry in doomed {
183 if let Some(parsed) = key::parse_entry(&entry) {
184 let posting_key = (parsed.collection.to_string(), parsed.doc_id.to_string());
185 if let Some(keys) = self.postings.get_mut(&posting_key) {
186 keys.remove(&entry);
187 if keys.is_empty() {
188 self.postings.remove(&posting_key);
189 }
190 }
191 }
192 self.entries.remove(&entry);
193 self.mark(entry, None);
194 }
195 }
196
197 pub(super) fn put_def(&mut self, def: Arc<IndexDef>) -> Result<(), LiteError> {
199 let bytes = def.encode()?;
200 self.mark(key::def_key(&def.collection, &def.name), Some(bytes));
201 self.defs.insert(def.name.clone(), def);
202 Ok(())
203 }
204
205 pub(super) fn remove_def(&mut self, name: &str) -> Option<Arc<IndexDef>> {
207 let def = self.defs.remove(name)?;
208 self.clear_prefix(&def.entry_prefix());
209 self.mark(key::def_key(&def.collection, &def.name), None);
210 Some(def)
211 }
212}
213
214pub(crate) struct IndexFlush {
216 written: Vec<(Vec<u8>, u64)>,
217}
218
219pub(crate) struct LoadOutcome {
221 pub rebuild_all: bool,
224}
225
226#[derive(Default)]
231pub struct IndexCatalog {
232 state: Mutex<IndexState>,
233 pub(crate) durable_ddl: tokio::sync::RwLock<()>,
238 pub(crate) durable_rows: tokio::sync::Mutex<()>,
242 pub(crate) bitemporal_build: tokio::sync::RwLock<()>,
246}
247
248impl IndexCatalog {
249 pub fn new() -> Self {
250 Self::default()
251 }
252
253 pub(super) fn lock(&self) -> MutexGuard<'_, IndexState> {
254 self.state.lock_or_recover()
255 }
256
257 pub(crate) async fn load<S: StorageEngine>(
260 &self,
261 storage: &S,
262 ) -> Result<LoadOutcome, LiteError> {
263 let format = storage.get(Namespace::Meta, &key::format_key()).await?;
264 let defs = storage
265 .scan_prefix(Namespace::Meta, &key::defs_prefix())
266 .await?;
267 let entries = storage
268 .scan_prefix(Namespace::Meta, &key::entries_prefix())
269 .await?;
270 let sorted = storage
271 .scan_prefix(Namespace::Meta, &key::sorted_defs_prefix())
272 .await?
273 .iter()
274 .map(|(_, bytes)| SortedIndexDef::decode(bytes))
275 .collect::<Result<Vec<_>, _>>()?;
276
277 let current = format.as_deref() == Some(key::FORMAT_VERSION.to_be_bytes().as_slice());
278 let stored_nothing = defs.is_empty() && entries.is_empty();
279 let decoded = defs
280 .iter()
281 .map(|(_, bytes)| IndexDef::decode(bytes))
282 .collect::<Result<Vec<_>, _>>()?;
283 let (rebuild_all, fresh) = {
284 let mut state = self.lock();
285 for def in decoded {
286 state.defs.insert(def.name.clone(), Arc::new(def));
287 }
288 for def in sorted {
289 state.sorted.insert(def.name.clone(), Arc::new(def));
290 }
291 for (entry, _) in entries {
292 if current && let Some(parsed) = key::parse_entry(&entry) {
293 state
294 .postings
295 .entry((parsed.collection.to_string(), parsed.doc_id.to_string()))
296 .or_default()
297 .insert(entry.clone());
298 state.entries.insert(entry);
299 } else {
300 state.mark(entry, None);
303 }
304 }
305 let rebuild_all = !current && !state.defs.is_empty();
306 let fresh = !current && stored_nothing;
307 if !current && !fresh {
308 state.mark(
310 key::format_key(),
311 Some(key::FORMAT_VERSION.to_be_bytes().to_vec()),
312 );
313 }
314 (rebuild_all, fresh)
315 };
316 if fresh {
317 storage
321 .put(
322 Namespace::Meta,
323 &key::format_key(),
324 &key::FORMAT_VERSION.to_be_bytes(),
325 )
326 .await?;
327 }
328 Ok(LoadOutcome { rebuild_all })
329 }
330
331 pub(crate) fn stage_flush(&self, ops: &mut Vec<WriteOp>) -> IndexFlush {
334 let state = self.lock();
335 let mut written = Vec::with_capacity(state.dirty.len());
336 for (k, dirty) in &state.dirty {
337 ops.push(match &dirty.value {
338 Some(value) => WriteOp::Put {
339 ns: Namespace::Meta,
340 key: k.clone(),
341 value: value.clone(),
342 },
343 None => WriteOp::Delete {
344 ns: Namespace::Meta,
345 key: k.clone(),
346 },
347 });
348 written.push((k.clone(), dirty.seq));
349 }
350 IndexFlush { written }
351 }
352
353 pub(crate) fn mark_flushed(&self, flush: IndexFlush) {
356 let mut state = self.lock();
357 for (k, seq) in flush.written {
358 if state.dirty.get(&k).is_some_and(|d| d.seq == seq) {
359 state.dirty.remove(&k);
360 }
361 }
362 }
363
364 pub(crate) fn is_indexed(&self, collection: &str) -> bool {
366 self.has_defs(collection, IndexEngine::Document)
367 }
368
369 pub(crate) fn has_defs(&self, collection: &str, engine: IndexEngine) -> bool {
371 self.lock()
372 .defs
373 .values()
374 .any(|d| d.collection == collection && d.engine == engine)
375 }
376
377 pub(crate) fn defs(&self) -> Vec<Arc<IndexDef>> {
379 self.lock().defs.values().cloned().collect()
380 }
381
382 pub(crate) fn def_named(&self, name: &str) -> Option<Arc<IndexDef>> {
384 self.lock().defs.get(name).cloned()
385 }
386
387 pub(crate) fn def_on_field(&self, collection: &str, field_spec: &str) -> Option<Arc<IndexDef>> {
390 self.lock()
391 .defs
392 .values()
393 .find(|d| d.collection == collection && d.field_spec() == field_spec)
394 .cloned()
395 }
396
397 pub(crate) fn sorted_on(&self, collection: &str) -> Vec<Arc<SortedIndexDef>> {
399 self.lock()
400 .sorted
401 .values()
402 .filter(|d| d.collection == collection)
403 .cloned()
404 .collect()
405 }
406
407 pub(crate) fn sorted_named(&self, name: &str) -> Option<Arc<SortedIndexDef>> {
409 self.lock().sorted.get(name).cloned()
410 }
411
412 pub(crate) fn has_sorted(&self, collection: &str) -> bool {
414 self.lock()
415 .sorted
416 .values()
417 .any(|d| d.collection == collection)
418 }
419
420 pub(crate) fn sorted_by_collection(&self) -> HashMap<String, Vec<Arc<SortedIndexDef>>> {
422 let mut grouped: HashMap<String, Vec<Arc<SortedIndexDef>>> = HashMap::new();
423 for def in self.lock().sorted.values() {
424 grouped
425 .entry(def.collection.clone())
426 .or_default()
427 .push(Arc::clone(def));
428 }
429 grouped
430 }
431
432 pub(crate) fn planner_specs(&self, collection: &str) -> Vec<nodedb_sql::types::IndexSpec> {
435 self.lock()
436 .defs
437 .values()
438 .filter(|d| {
439 d.collection == collection
440 && matches!(d.engine, IndexEngine::Document | IndexEngine::Strict)
441 })
442 .map(|d| d.planner_spec())
443 .collect()
444 }
445}