1use crate::columnar;
11use crate::cursor::NativePageCursor;
12use crate::encryption::Kek;
13use crate::encryption::DEK_LEN;
14use crate::epoch::{Epoch, EpochAuthority, Snapshot};
15use crate::global_idx;
16use crate::index::{
17 AnnIndex, BitmapIndex, ColumnLearnedRange, FmIndex, HotIndex, MinHashIndex, SparseIndex,
18};
19use crate::manifest::{self, Manifest, RunRef, TtlPolicy};
20use crate::memtable::{Memtable, Row, Value};
21use crate::mutable_run::MutableRun;
22use crate::row_id_set::RowIdSet;
23use crate::rowid::{RowId, RowIdAllocator};
24use crate::schema::{AlterColumn, ColumnDef, ColumnFlags, IndexDef, IndexKind, Schema, TypeId};
25use crate::sorted_run::{RunReader, RunWriter};
26use crate::txn::{GroupCommit, OwnedRow};
27use crate::wal::{Op, SharedWal, Wal};
28use crate::{MongrelError, Result};
29use std::collections::{BTreeMap, HashMap, HashSet};
30use std::path::{Path, PathBuf};
31use std::sync::atomic::AtomicBool;
32use std::sync::Arc;
33use zeroize::Zeroizing;
34
35pub const WAL_DIR: &str = "_wal";
36pub const RUNS_DIR: &str = "_runs";
37pub const CACHE_DIR: &str = "_cache";
38pub const META_DIR: &str = "_meta";
39pub const RCACHE_DIR: &str = "_rcache";
40pub const KEYS_FILENAME: &str = "keys";
41pub const SCHEMA_FILENAME: &str = "schema.json";
42
43fn iso_now_bytes() -> Vec<u8> {
46 let secs = std::time::SystemTime::now()
47 .duration_since(std::time::UNIX_EPOCH)
48 .map(|d| d.as_secs() as i64)
49 .unwrap_or(0);
50 let days = secs.div_euclid(86_400);
51 let rem = secs.rem_euclid(86_400);
52 let (hour, minute, second) = (rem / 3600, (rem % 3600) / 60, rem % 60);
53 let (year, month, day) = civil_from_days(days);
54 format!("{year:04}-{month:02}-{day:02}T{hour:02}:{minute:02}:{second:02}Z").into_bytes()
55}
56
57pub(crate) fn unix_nanos_now() -> i64 {
58 std::time::SystemTime::now()
59 .duration_since(std::time::UNIX_EPOCH)
60 .map(|d| d.as_nanos().min(i64::MAX as u128) as i64)
61 .unwrap_or(0)
62}
63
64fn civil_from_days(z: i64) -> (i64, u32, u32) {
65 let z = z + 719_468;
66 let era = if z >= 0 { z } else { z - 146_096 } / 146_097;
67 let doe = z - era * 146_097;
68 let yoe = (doe - doe / 1460 + doe / 36_524 - doe / 146_096) / 365;
69 let y = yoe + era * 400;
70 let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
71 let mp = (5 * doy + 2) / 153;
72 let d = (doy - (153 * mp + 2) / 5 + 1) as u32;
73 let m = if mp < 10 { mp + 3 } else { mp - 9 } as u32;
74 (if m <= 2 { y + 1 } else { y }, m, d)
75}
76
77const DEFAULT_SYNC_BYTE_THRESHOLD: u64 = 0; pub(crate) const PAGE_CACHE_CAPACITY: u64 = 64 * 1024 * 1024; pub(crate) const DECODED_CACHE_CAPACITY: u64 = 64 * 1024 * 1024; const DEFAULT_MUTABLE_RUN_SPILL_BYTES: u64 = 8 * 1024 * 1024;
84
85#[derive(Clone, Copy, Debug)]
100struct AutoIncState {
101 column_id: u16,
102 next: i64,
103 seeded: bool,
104}
105
106type FilledAutoIncRow = (Vec<(u16, Value)>, Option<i64>);
107
108fn resolve_auto_inc(schema: &Schema) -> Option<AutoIncState> {
111 schema.auto_increment_column().map(|c| AutoIncState {
112 column_id: c.id,
113 next: 0,
114 seeded: false,
115 })
116}
117
118#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
131pub enum IndexBuildPolicy {
132 #[default]
134 Deferred,
135 Eager,
137}
138
139pub struct Table {
141 dir: PathBuf,
142 table_id: u64,
143 name: String,
147 auth: Option<Arc<dyn crate::auth_state::TableAuthChecker>>,
152 read_only: bool,
155 wal: WalSink,
156 memtable: Memtable,
157 mutable_run: MutableRun,
162 mutable_run_spill_bytes: u64,
164 compaction_zstd_level: i32,
167 allocator: RowIdAllocator,
168 epoch: Arc<EpochAuthority>,
169 persisted_epoch: u64,
172 data_generation: u64,
175 schema: Schema,
176 hot: HotIndex,
177 kek: Option<Arc<Kek>>,
180 column_keys: HashMap<u16, ([u8; 32], u8)>,
184 run_refs: Vec<RunRef>,
185 retiring: Vec<crate::manifest::RetiredRun>,
188 next_run_id: u64,
189 sync_byte_threshold: u64,
190 current_txn_id: u64,
195 bitmap: HashMap<u16, BitmapIndex>,
196 ann: HashMap<u16, AnnIndex>,
197 fm: HashMap<u16, FmIndex>,
198 sparse: HashMap<u16, SparseIndex>,
199 minhash: HashMap<u16, MinHashIndex>,
200 learned_range: HashMap<u16, ColumnLearnedRange>,
203 pk_by_row: HashMap<RowId, Vec<u8>>,
205 pinned: BTreeMap<Epoch, usize>,
208 pub(crate) live_count: u64,
211 reservoir: crate::reservoir::Reservoir,
214 reservoir_complete: bool,
222 had_deletes: bool,
226 agg_cache: HashMap<u64, CachedAgg>,
230 global_idx_epoch: u64,
234 indexes_complete: bool,
239 index_build_policy: IndexBuildPolicy,
241 pk_by_row_complete: bool,
248 flushed_epoch: u64,
251 page_cache: Arc<crate::cache::Sharded<crate::cache::PageCache>>,
254 snapshots: Arc<crate::retention::SnapshotRegistry>,
257 commit_lock: Arc<parking_lot::Mutex<()>>,
259 decoded_cache: Arc<crate::cache::Sharded<crate::cache::DecodedPageCache>>,
262 verified_runs: Arc<parking_lot::Mutex<std::collections::HashSet<u128>>>,
272 result_cache: Arc<parking_lot::Mutex<ResultCache>>,
281 wal_dek: Option<Zeroizing<[u8; DEK_LEN]>>,
283 pending_delete_rids: roaring::RoaringBitmap,
286 pending_put_cols: std::collections::HashSet<u16>,
289 pending_rows: Vec<Row>,
295 pending_rows_auto_inc: Vec<bool>,
296 pending_dels: Vec<RowId>,
299 pending_truncate: Option<Epoch>,
303 auto_inc: Option<AutoIncState>,
306 ttl: Option<TtlPolicy>,
309}
310
311const _: () = {
318 const fn assert_sync<T: ?Sized + Sync>() {}
319 assert_sync::<Table>();
320};
321
322enum CachedData {
328 Rows(Arc<Vec<Row>>),
329 Columns(Arc<Vec<(u16, columnar::NativeColumn)>>),
330}
331
332impl CachedData {
333 fn approx_bytes(&self) -> u64 {
334 match self {
335 CachedData::Rows(r) => r.iter().map(|r| r.estimated_bytes()).sum::<u64>(),
336 CachedData::Columns(c) => c
337 .iter()
338 .map(|(_, c)| c.approx_bytes())
339 .sum::<u64>()
340 .saturating_add(c.len() as u64 * 16),
341 }
342 }
343}
344
345struct CachedEntry {
349 data: CachedData,
350 footprint: roaring::RoaringBitmap,
351 condition_cols: Vec<u16>,
352}
353
354struct ResultCache {
365 entries: std::collections::HashMap<u64, CachedEntry>,
366 order: std::collections::VecDeque<u64>,
367 bytes: u64,
368 max_bytes: u64,
369 dir: Option<std::path::PathBuf>,
370 #[allow(dead_code)]
371 cache_dek: Option<Zeroizing<[u8; DEK_LEN]>>,
372}
373
374#[derive(serde::Serialize, serde::Deserialize)]
376struct SerializedEntry {
377 condition_cols: Vec<u16>,
378 footprint_bits: Vec<u32>,
379 data: SerializedData,
380}
381
382#[derive(serde::Serialize, serde::Deserialize)]
383enum SerializedData {
384 Rows(Vec<Row>),
385 Columns(Vec<(u16, columnar::NativeColumn)>),
386}
387
388impl SerializedEntry {
389 fn from_entry(entry: &CachedEntry) -> Self {
390 let footprint_bits: Vec<u32> = entry.footprint.iter().collect();
391 let data = match &entry.data {
392 CachedData::Rows(r) => SerializedData::Rows((**r).clone()),
393 CachedData::Columns(c) => SerializedData::Columns((**c).clone()),
394 };
395 Self {
396 condition_cols: entry.condition_cols.clone(),
397 footprint_bits,
398 data,
399 }
400 }
401
402 fn into_entry(self) -> Option<CachedEntry> {
403 let footprint: roaring::RoaringBitmap = self.footprint_bits.into_iter().collect();
404 let data = match self.data {
405 SerializedData::Rows(r) => CachedData::Rows(Arc::new(r)),
406 SerializedData::Columns(c) => {
407 if !c.iter().all(|(_, col)| col.validate()) {
410 return None;
411 }
412 CachedData::Columns(Arc::new(c))
413 }
414 };
415 Some(CachedEntry {
416 data,
417 footprint,
418 condition_cols: self.condition_cols,
419 })
420 }
421}
422
423impl ResultCache {
424 const DEFAULT_MAX_BYTES: u64 = 256 * 1024 * 1024;
425
426 fn new() -> Self {
427 Self::with_max_bytes(Self::DEFAULT_MAX_BYTES)
428 }
429
430 fn with_max_bytes(max_bytes: u64) -> Self {
431 Self {
432 entries: std::collections::HashMap::new(),
433 order: std::collections::VecDeque::new(),
434 bytes: 0,
435 max_bytes,
436 dir: None,
437 cache_dek: None,
438 }
439 }
440
441 fn with_dir(mut self, dir: std::path::PathBuf) -> Self {
442 let _ = std::fs::create_dir_all(&dir);
443 self.dir = Some(dir);
444 self
445 }
446
447 fn with_cache_dek(mut self, dek: Option<Zeroizing<[u8; DEK_LEN]>>) -> Self {
448 self.cache_dek = dek;
449 self
450 }
451
452 fn disk_path(&self, key: u64) -> Option<std::path::PathBuf> {
453 self.dir.as_ref().map(|d| d.join(format!("{key:016x}.bin")))
454 }
455
456 fn store_to_disk(&self, key: u64, entry: &CachedEntry) {
460 let Some(path) = self.disk_path(key) else {
461 return;
462 };
463 let serialized = match bincode::serialize(&SerializedEntry::from_entry(entry)) {
464 Ok(s) => s,
465 Err(_) => return,
466 };
467 let on_disk = if let Some(dek) = &self.cache_dek {
469 match self.encrypt_cache(&serialized, dek) {
470 Some(b) => b,
471 None => return,
472 }
473 } else {
474 serialized
475 };
476 let tmp = path.with_extension("tmp");
477 use std::io::Write;
478 let write = || -> std::io::Result<()> {
479 let mut f = std::fs::File::create(&tmp)?;
480 f.write_all(&on_disk)?;
481 f.flush()?;
482 Ok(())
483 };
484 if write().is_err() {
485 let _ = std::fs::remove_file(&tmp);
486 return;
487 }
488 let _ = std::fs::rename(&tmp, &path);
489 }
490
491 fn load_from_disk(&self, key: u64) -> Option<CachedEntry> {
493 let path = self.disk_path(key)?;
494 let bytes = std::fs::read(&path).ok()?;
495 let plaintext = if let Some(dek) = &self.cache_dek {
496 self.decrypt_cache(&bytes, dek)?
497 } else {
498 bytes
499 };
500 let serialized: SerializedEntry = bincode::deserialize(&plaintext).ok()?;
501 serialized.into_entry()
502 }
503
504 fn remove_from_disk(&self, key: u64) {
506 if let Some(path) = self.disk_path(key) {
507 let _ = std::fs::remove_file(&path);
508 }
509 }
510
511 #[cfg(feature = "encryption")]
513 fn encrypt_cache(&self, plaintext: &[u8], dek: &Zeroizing<[u8; DEK_LEN]>) -> Option<Vec<u8>> {
514 use crate::encryption::Cipher;
515 let cipher = crate::encryption::AesCipher::new(&dek[..]).ok()?;
516 let mut nonce = [0u8; 12];
517 crate::encryption::fill_random(&mut nonce);
518 let ct = cipher.encrypt_page(&nonce, plaintext).ok()?;
519 let mut out = Vec::with_capacity(12 + ct.len());
520 out.extend_from_slice(&nonce);
521 out.extend_from_slice(&ct);
522 Some(out)
523 }
524
525 #[cfg(not(feature = "encryption"))]
526 fn encrypt_cache(&self, _plaintext: &[u8], _dek: &Zeroizing<[u8; DEK_LEN]>) -> Option<Vec<u8>> {
527 None
528 }
529
530 #[cfg(feature = "encryption")]
532 fn decrypt_cache(&self, bytes: &[u8], dek: &Zeroizing<[u8; DEK_LEN]>) -> Option<Vec<u8>> {
533 use crate::encryption::Cipher;
534 if bytes.len() < 28 {
535 return None;
536 }
537 let cipher = crate::encryption::AesCipher::new(&dek[..]).ok()?;
538 let nonce: [u8; 12] = bytes[..12].try_into().ok()?;
539 let ct = &bytes[12..];
540 cipher.decrypt_page(&nonce, ct).ok()
541 }
542
543 #[cfg(not(feature = "encryption"))]
544 fn decrypt_cache(&self, _bytes: &[u8], _dek: &Zeroizing<[u8; DEK_LEN]>) -> Option<Vec<u8>> {
545 None
546 }
547
548 fn load_persistent(&mut self) {
551 let Some(dir) = self.dir.as_ref().cloned() else {
552 return;
553 };
554 let entries = match std::fs::read_dir(&dir) {
555 Ok(e) => e,
556 Err(_) => return,
557 };
558 for entry in entries.flatten() {
559 let path = entry.path();
560 if path.extension().and_then(|e| e.to_str()) == Some("tmp") {
562 let _ = std::fs::remove_file(&path);
563 continue;
564 }
565 if path.extension().and_then(|e| e.to_str()) != Some("bin") {
566 continue;
567 }
568 let stem = match path.file_stem().and_then(|s| s.to_str()) {
569 Some(s) => s,
570 None => continue,
571 };
572 let key = match u64::from_str_radix(stem, 16) {
573 Ok(k) => k,
574 Err(_) => continue,
575 };
576 let bytes = match std::fs::read(&path) {
577 Ok(b) => b,
578 Err(_) => continue,
579 };
580 let plaintext = if let Some(dek) = &self.cache_dek {
582 match self.decrypt_cache(&bytes, dek) {
583 Some(p) => p,
584 None => {
585 let _ = std::fs::remove_file(&path);
586 continue;
587 }
588 }
589 } else {
590 bytes
591 };
592 match bincode::deserialize::<SerializedEntry>(&plaintext) {
593 Ok(serialized) => {
594 if let Some(entry) = serialized.into_entry() {
595 self.bytes = self.bytes.saturating_add(entry.data.approx_bytes());
596 self.entries.insert(key, entry);
597 self.order.push_back(key);
598 } else {
599 let _ = std::fs::remove_file(&path);
600 }
601 }
602 Err(_) => {
603 let _ = std::fs::remove_file(&path);
604 }
605 }
606 }
607 self.evict();
608 }
609
610 fn set_max_bytes(&mut self, max_bytes: u64) {
611 self.max_bytes = max_bytes;
612 self.evict();
613 }
614
615 fn touch(&mut self, key: u64) {
617 self.order.retain(|k| *k != key);
618 self.order.push_back(key);
619 }
620
621 fn get_rows(&mut self, key: u64) -> Option<Arc<Vec<Row>>> {
622 let res = self.entries.get(&key).and_then(|e| match &e.data {
623 CachedData::Rows(r) => Some(r.clone()),
624 CachedData::Columns(_) => None,
625 });
626 if res.is_some() {
627 self.touch(key);
628 return res;
629 }
630 if let Some(entry) = self.load_from_disk(key) {
632 let res = match &entry.data {
633 CachedData::Rows(r) => Some(r.clone()),
634 CachedData::Columns(_) => None,
635 };
636 if res.is_some() {
637 let approx = entry.data.approx_bytes();
638 self.bytes = self.bytes.saturating_add(approx);
639 self.entries.insert(key, entry);
640 self.order.push_back(key);
641 self.evict();
642 return res;
643 }
644 }
645 None
646 }
647
648 fn get_columns(&mut self, key: u64) -> Option<Arc<Vec<(u16, columnar::NativeColumn)>>> {
649 let res = self.entries.get(&key).and_then(|e| match &e.data {
650 CachedData::Columns(c) => Some(c.clone()),
651 CachedData::Rows(_) => None,
652 });
653 if res.is_some() {
654 self.touch(key);
655 return res;
656 }
657 if let Some(entry) = self.load_from_disk(key) {
659 let res = match &entry.data {
660 CachedData::Columns(c) => Some(c.clone()),
661 CachedData::Rows(_) => None,
662 };
663 if res.is_some() {
664 let approx = entry.data.approx_bytes();
665 self.bytes = self.bytes.saturating_add(approx);
666 self.entries.insert(key, entry);
667 self.order.push_back(key);
668 self.evict();
669 return res;
670 }
671 }
672 None
673 }
674
675 fn insert(&mut self, key: u64, entry: CachedEntry) {
676 let approx = entry.data.approx_bytes();
677 if self.entries.remove(&key).is_some() {
678 self.order.retain(|k| *k != key);
679 self.bytes = self.entries.values().map(|e| e.data.approx_bytes()).sum();
680 }
681 self.store_to_disk(key, &entry);
683 self.bytes = self.bytes.saturating_add(approx);
684 self.entries.insert(key, entry);
685 self.order.push_back(key);
686 self.evict();
687 }
688
689 fn invalidate(
698 &mut self,
699 delete_rids: &roaring::RoaringBitmap,
700 put_cols: &std::collections::HashSet<u16>,
701 ) {
702 if self.entries.is_empty() {
703 return;
704 }
705 let has_deletes = !delete_rids.is_empty();
706 let to_remove: std::collections::HashSet<u64> = self
707 .entries
708 .iter()
709 .filter(|(_, e)| {
710 let delete_hit = if e.footprint.is_empty() {
711 has_deletes
712 } else {
713 e.footprint.intersection_len(delete_rids) > 0
714 };
715 let col_hit = e.condition_cols.iter().any(|c| put_cols.contains(c));
716 delete_hit || col_hit
717 })
718 .map(|(&k, _)| k)
719 .collect();
720 for key in &to_remove {
721 if let Some(e) = self.entries.remove(key) {
722 self.bytes = self.bytes.saturating_sub(e.data.approx_bytes());
723 }
724 self.remove_from_disk(*key);
725 }
726 if !to_remove.is_empty() {
727 self.order.retain(|k| !to_remove.contains(k));
728 }
729 }
730
731 fn clear(&mut self) {
732 if let Some(dir) = &self.dir {
734 if let Ok(entries) = std::fs::read_dir(dir) {
735 for entry in entries.flatten() {
736 let path = entry.path();
737 if path.extension().and_then(|e| e.to_str()) == Some("bin") {
738 let _ = std::fs::remove_file(&path);
739 }
740 }
741 }
742 }
743 self.entries.clear();
744 self.order.clear();
745 self.bytes = 0;
746 }
747
748 fn evict(&mut self) {
749 while self.bytes > self.max_bytes {
750 let Some(k) = self.order.pop_front() else {
751 break;
752 };
753 if let Some(e) = self.entries.remove(&k) {
754 self.bytes = self.bytes.saturating_sub(e.data.approx_bytes());
755 self.remove_from_disk(k);
759 }
760 }
761 }
762}
763
764type DekaOpt = Option<Zeroizing<[u8; DEK_LEN]>>;
771
772fn derive_subkeys(kek: Option<&Kek>, _table_id: u64) -> (DekaOpt, DekaOpt) {
773 let _ = kek;
774 #[cfg(feature = "encryption")]
775 {
776 if let Some(k) = kek {
777 return (
778 Some(k.derive_table_wal_key(_table_id)),
779 Some(k.derive_cache_key()),
780 );
781 }
782 }
783 (None, None)
784}
785
786#[cfg(feature = "encryption")]
788fn make_cipher(dek: &Zeroizing<[u8; DEK_LEN]>) -> Box<dyn crate::encryption::Cipher> {
789 Box::new(crate::encryption::AesCipher::new(&dek[..]).expect("DEK is 32 bytes"))
790}
791
792#[cfg(not(feature = "encryption"))]
793fn make_cipher(_dek: &Zeroizing<[u8; DEK_LEN]>) -> Box<dyn crate::encryption::Cipher> {
794 Box::new(crate::encryption::PlaintextCipher)
795}
796
797fn build_column_keys(kek: Option<&Kek>, schema: &Schema) -> HashMap<u16, ([u8; 32], u8)> {
798 let Some(kek) = kek else {
799 return HashMap::new();
800 };
801 #[cfg(feature = "encryption")]
802 {
803 use crate::encryption::{SCHEME_HMAC_EQ, SCHEME_OPE_RANGE};
804 schema
805 .columns
806 .iter()
807 .filter(|c| c.flags.contains(ColumnFlags::ENCRYPTED_INDEXABLE))
808 .map(|c| {
809 let scheme = if schema
810 .indexes
811 .iter()
812 .any(|i| i.column_id == c.id && i.kind == IndexKind::LearnedRange)
813 {
814 SCHEME_OPE_RANGE
815 } else {
816 SCHEME_HMAC_EQ
817 };
818 let key: [u8; 32] = *kek.derive_column_key(c.id);
819 (c.id, (key, scheme))
820 })
821 .collect()
822 }
823 #[cfg(not(feature = "encryption"))]
824 {
825 let _ = (kek, schema);
826 HashMap::new()
827 }
828}
829
830pub(crate) struct SharedCtx {
835 pub epoch: Arc<EpochAuthority>,
836 pub page_cache: Arc<crate::cache::Sharded<crate::cache::PageCache>>,
837 pub decoded_cache: Arc<crate::cache::Sharded<crate::cache::DecodedPageCache>>,
838 pub snapshots: Arc<crate::retention::SnapshotRegistry>,
839 pub kek: Option<Arc<Kek>>,
840 pub commit_lock: Arc<parking_lot::Mutex<()>>,
846 pub shared: Option<SharedWalCtx>,
850 pub table_name: Option<String>,
853 pub auth: Option<Arc<dyn crate::auth_state::TableAuthChecker>>,
856 pub read_only: bool,
858}
859
860#[derive(Clone)]
866pub(crate) struct SharedWalCtx {
867 pub wal: Arc<parking_lot::Mutex<SharedWal>>,
868 pub group: Arc<GroupCommit>,
869 pub poisoned: Arc<AtomicBool>,
870 pub txn_ids: Arc<parking_lot::Mutex<u64>>,
871 pub change_wake: tokio::sync::broadcast::Sender<()>,
872}
873
874enum WalSink {
877 Private(Wal),
878 Shared(SharedWalCtx),
879}
880
881impl SharedCtx {
882 pub(crate) fn new(kek: Option<Arc<Kek>>, cache_dir: Option<PathBuf>) -> Self {
886 let n_shards = if cache_dir.is_some() {
890 1
891 } else {
892 crate::cache::CACHE_SHARDS
893 };
894 let per_shard = PAGE_CACHE_CAPACITY / n_shards as u64;
895 let page_cache = if let Some(d) = cache_dir {
896 Arc::new(crate::cache::Sharded::new(1, || {
897 crate::cache::PageCache::new(PAGE_CACHE_CAPACITY).with_persistence(d.clone())
898 }))
899 } else {
900 Arc::new(crate::cache::Sharded::new(n_shards, || {
901 crate::cache::PageCache::new(per_shard)
902 }))
903 };
904 let decoded_per_shard = DECODED_CACHE_CAPACITY / crate::cache::CACHE_SHARDS as u64;
905 let decoded_cache = Arc::new(crate::cache::Sharded::new(
906 crate::cache::CACHE_SHARDS,
907 || crate::cache::DecodedPageCache::new(decoded_per_shard),
908 ));
909 Self {
910 epoch: Arc::new(EpochAuthority::new(0)),
911 page_cache,
912 decoded_cache,
913 snapshots: Arc::new(crate::retention::SnapshotRegistry::new()),
914 kek,
915 commit_lock: Arc::new(parking_lot::Mutex::new(())),
916 shared: None,
917 table_name: None,
918 auth: None,
919 read_only: false,
920 }
921 }
922}
923
924fn condition_cost_rank(c: &crate::query::Condition) -> u8 {
928 use crate::query::Condition;
929 match c {
930 Condition::Pk(_)
932 | Condition::BitmapEq { .. }
933 | Condition::BitmapIn { .. }
934 | Condition::BytesPrefix { .. }
935 | Condition::IsNull { .. }
936 | Condition::IsNotNull { .. } => 0,
937 Condition::Range { .. } | Condition::RangeF64 { .. } | Condition::MinHashSimilar { .. } => {
939 1
940 }
941 Condition::FmContains { .. }
943 | Condition::FmContainsAll { .. }
944 | Condition::Ann { .. }
945 | Condition::SparseMatch { .. } => 2,
946 }
947}
948
949impl Table {
950 pub fn create(dir: impl AsRef<Path>, schema: Schema, table_id: u64) -> Result<Self> {
951 let dir = dir.as_ref().to_path_buf();
952 let ctx = SharedCtx::new(None, Some(dir.join(CACHE_DIR)));
953 Self::create_in(&dir, schema, table_id, ctx)
954 }
955
956 #[cfg(feature = "encryption")]
967 pub fn create_encrypted(
968 dir: impl AsRef<Path>,
969 schema: Schema,
970 table_id: u64,
971 passphrase: &str,
972 ) -> Result<Self> {
973 let dir = dir.as_ref();
974 std::fs::create_dir_all(dir.join(META_DIR))?;
975 let salt = crate::encryption::random_salt();
976 std::fs::write(dir.join(META_DIR).join(KEYS_FILENAME), salt)?;
977 let kek: Arc<Kek> = Arc::new(Kek::derive(passphrase, &salt)?);
978 let ctx = SharedCtx::new(Some(kek), Some(dir.to_path_buf().join(CACHE_DIR)));
979 Self::create_in(dir, schema, table_id, ctx)
980 }
981
982 #[cfg(feature = "encryption")]
987 pub fn create_with_key(
988 dir: impl AsRef<Path>,
989 schema: Schema,
990 table_id: u64,
991 key: &[u8],
992 ) -> Result<Self> {
993 let dir = dir.as_ref();
994 std::fs::create_dir_all(dir.join(META_DIR))?;
995 let salt = crate::encryption::random_salt();
996 std::fs::write(dir.join(META_DIR).join(KEYS_FILENAME), salt)?;
997 let kek: Arc<Kek> = Arc::new(Kek::from_raw_key(key, &salt)?);
998 let ctx = SharedCtx::new(Some(kek), Some(dir.to_path_buf().join(CACHE_DIR)));
999 Self::create_in(dir, schema, table_id, ctx)
1000 }
1001
1002 #[cfg(feature = "encryption")]
1004 pub fn open_with_key(dir: impl AsRef<Path>, key: &[u8]) -> Result<Self> {
1005 let dir = dir.as_ref();
1006 let salt_path = dir.join(META_DIR).join(KEYS_FILENAME);
1007 let salt_bytes = std::fs::read(&salt_path).map_err(|e| {
1008 MongrelError::NotFound(format!(
1009 "encryption salt file {:?}: {e} (table not encrypted, or corrupted)",
1010 salt_path
1011 ))
1012 })?;
1013 if salt_bytes.len() != crate::encryption::SALT_LEN {
1014 return Err(MongrelError::InvalidArgument(format!(
1015 "salt file is {} bytes, expected {}",
1016 salt_bytes.len(),
1017 crate::encryption::SALT_LEN
1018 )));
1019 }
1020 let mut salt = [0u8; crate::encryption::SALT_LEN];
1021 salt.copy_from_slice(&salt_bytes);
1022 let kek = Arc::new(Kek::from_raw_key(key, &salt)?);
1023 let ctx = SharedCtx::new(Some(kek), Some(dir.to_path_buf().join(CACHE_DIR)));
1024 Self::open_in(dir, ctx)
1025 }
1026
1027 pub(crate) fn create_in(
1028 dir: impl AsRef<Path>,
1029 schema: Schema,
1030 table_id: u64,
1031 ctx: SharedCtx,
1032 ) -> Result<Self> {
1033 schema.validate_auto_increment()?;
1034 schema.validate_defaults()?;
1035 schema.validate_ai()?;
1036 for index in &schema.indexes {
1037 index.validate_options()?;
1038 }
1039 let dir = dir.as_ref().to_path_buf();
1040 std::fs::create_dir_all(dir.join(RUNS_DIR))?;
1041 write_schema(&dir, &schema)?;
1042 let (wal_dek, cache_dek) = derive_subkeys(ctx.kek.as_deref(), table_id);
1043 let (wal, current_txn_id) = match ctx.shared.clone() {
1046 Some(s) => (WalSink::Shared(s), 0),
1047 None => {
1048 std::fs::create_dir_all(dir.join(WAL_DIR))?;
1049 let mut w = if let Some(ref dk) = wal_dek {
1050 Wal::create_with_cipher(
1051 dir.join(WAL_DIR).join("seg-000000.wal"),
1052 Epoch(0),
1053 Some(make_cipher(dk)),
1054 0,
1055 )?
1056 } else {
1057 Wal::create(dir.join(WAL_DIR).join("seg-000000.wal"), Epoch(0))?
1058 };
1059 w.set_sync_byte_threshold(DEFAULT_SYNC_BYTE_THRESHOLD);
1060 (WalSink::Private(w), 1)
1061 }
1062 };
1063 let mut manifest = Manifest::new(table_id, schema.schema_id);
1064 let manifest_meta_dek = crate::encryption::meta_dek_for(ctx.kek.as_deref());
1069 manifest::write_atomic(&dir, &mut manifest, manifest_meta_dek.as_ref())?;
1070 let (bitmap, ann, fm, sparse, minhash) = empty_indexes(&schema);
1071 let column_keys = build_column_keys(ctx.kek.as_deref(), &schema);
1072 let auto_inc = resolve_auto_inc(&schema);
1073 let rcache_dir = dir.join(RCACHE_DIR);
1074 Ok(Self {
1075 dir,
1076 table_id,
1077 name: ctx.table_name.unwrap_or_default(),
1078 auth: ctx.auth,
1079 read_only: ctx.read_only,
1080 wal,
1081 memtable: Memtable::new(),
1082 mutable_run: MutableRun::new(),
1083 mutable_run_spill_bytes: DEFAULT_MUTABLE_RUN_SPILL_BYTES,
1084 compaction_zstd_level: 3,
1085 allocator: RowIdAllocator::new(0),
1086 epoch: ctx.epoch,
1087 persisted_epoch: 0,
1088 data_generation: 0,
1089 schema,
1090 hot: HotIndex::new(),
1091 kek: ctx.kek,
1092 column_keys,
1093 run_refs: Vec::new(),
1094 retiring: Vec::new(),
1095 next_run_id: 1,
1096 sync_byte_threshold: DEFAULT_SYNC_BYTE_THRESHOLD,
1097 current_txn_id,
1098 bitmap,
1099 ann,
1100 fm,
1101 sparse,
1102 minhash,
1103 learned_range: HashMap::new(),
1104 pk_by_row: HashMap::new(),
1105 pinned: BTreeMap::new(),
1106 live_count: 0,
1107 reservoir: crate::reservoir::Reservoir::default(),
1108 reservoir_complete: true,
1109 had_deletes: false,
1110 agg_cache: HashMap::new(),
1111 global_idx_epoch: 0,
1112 indexes_complete: true,
1113 index_build_policy: IndexBuildPolicy::default(),
1114 pk_by_row_complete: false,
1115 flushed_epoch: 0,
1116 page_cache: ctx.page_cache,
1117 decoded_cache: ctx.decoded_cache,
1118 verified_runs: Arc::new(parking_lot::Mutex::new(std::collections::HashSet::new())),
1119 snapshots: ctx.snapshots,
1120 commit_lock: ctx.commit_lock,
1121 result_cache: Arc::new(parking_lot::Mutex::new(
1122 ResultCache::new()
1123 .with_dir(rcache_dir)
1124 .with_cache_dek(cache_dek.clone()),
1125 )),
1126 pending_delete_rids: roaring::RoaringBitmap::new(),
1127 pending_put_cols: std::collections::HashSet::new(),
1128 pending_rows: Vec::new(),
1129 pending_rows_auto_inc: Vec::new(),
1130 pending_dels: Vec::new(),
1131 pending_truncate: None,
1132 wal_dek,
1133 auto_inc,
1134 ttl: None,
1135 })
1136 }
1137
1138 pub fn open(dir: impl AsRef<Path>) -> Result<Self> {
1142 let dir = dir.as_ref();
1143 let ctx = SharedCtx::new(None, Some(dir.to_path_buf().join(CACHE_DIR)));
1144 Self::open_in(dir, ctx)
1145 }
1146
1147 #[cfg(feature = "encryption")]
1150 pub fn open_encrypted(dir: impl AsRef<Path>, passphrase: &str) -> Result<Self> {
1151 let dir = dir.as_ref();
1152 let salt_path = dir.join(META_DIR).join(KEYS_FILENAME);
1153 let salt_bytes = std::fs::read(&salt_path).map_err(|e| {
1154 MongrelError::NotFound(format!(
1155 "encryption salt file {:?}: {e} (table not encrypted, or corrupted)",
1156 salt_path
1157 ))
1158 })?;
1159 let salt_len = crate::encryption::SALT_LEN;
1160 if salt_bytes.len() != salt_len {
1161 return Err(MongrelError::InvalidArgument(format!(
1162 "encryption salt is {} bytes, expected {salt_len}",
1163 salt_bytes.len()
1164 )));
1165 }
1166 let mut salt = [0u8; 16];
1167 salt.copy_from_slice(&salt_bytes);
1168 let kek: Arc<Kek> = Arc::new(Kek::derive(passphrase, &salt)?);
1169 let ctx = SharedCtx::new(Some(kek), Some(dir.to_path_buf().join(CACHE_DIR)));
1170 let t = Self::open_in(dir, ctx)?;
1171 Ok(t)
1172 }
1173
1174 pub(crate) fn open_in(dir: impl AsRef<Path>, ctx: SharedCtx) -> Result<Self> {
1175 let dir = dir.as_ref().to_path_buf();
1176 let manifest_meta_dek = crate::encryption::meta_dek_for(ctx.kek.as_deref());
1177 let manifest = manifest::read(&dir, manifest_meta_dek.as_ref())?;
1178 let schema: Schema = read_schema(&dir)?;
1179 schema.validate_ai()?;
1180 for index in &schema.indexes {
1181 index.validate_options()?;
1182 }
1183 let replay_epoch = Epoch(manifest.current_epoch);
1184 let (wal_dek, cache_dek) = derive_subkeys(ctx.kek.as_deref(), manifest.table_id);
1185 let (wal, replayed, current_txn_id) = match ctx.shared.clone() {
1189 Some(s) => (WalSink::Shared(s), Vec::new(), 0),
1190 None => {
1191 let active = latest_wal_segment(&dir.join(WAL_DIR))?;
1192 let replayed = match &active {
1194 Some(path) => {
1195 let cipher = wal_dek.as_ref().map(|dk| make_cipher(dk));
1196 crate::wal::replay_with_cipher(path, cipher)?
1197 }
1198 None => Vec::new(),
1199 };
1200 let mut w = match &active {
1201 Some(path) => Wal::create_with_cipher(
1202 path,
1203 replay_epoch,
1204 wal_dek.as_ref().map(|dk| make_cipher(dk)),
1205 0,
1206 )?,
1207 None => Wal::create_with_cipher(
1208 dir.join(WAL_DIR).join("seg-000000.wal"),
1209 replay_epoch,
1210 wal_dek.as_ref().map(|dk| make_cipher(dk)),
1211 0,
1212 )?,
1213 };
1214 w.set_sync_byte_threshold(DEFAULT_SYNC_BYTE_THRESHOLD);
1215 (WalSink::Private(w), replayed, 1)
1216 }
1217 };
1218
1219 let mut memtable = Memtable::new();
1220 let mut allocator = RowIdAllocator::new(manifest.next_row_id);
1221 let persisted_epoch = manifest.current_epoch;
1222 let mut auto_inc = resolve_auto_inc(&schema).map(|mut s| {
1229 s.next = manifest.auto_inc_next;
1230 s.seeded = manifest.auto_inc_next > 0;
1231 s
1232 });
1233
1234 let mut staged_puts: HashMap<u64, Vec<Row>> = HashMap::new();
1241 let mut staged_deletes: HashMap<u64, Vec<RowId>> = HashMap::new();
1242 let mut replayed_puts: std::collections::BTreeMap<Epoch, Vec<Row>> =
1243 std::collections::BTreeMap::new();
1244 let mut replayed_deletes: Vec<(RowId, Epoch)> = Vec::new();
1245 let mut saw_delete = false;
1246 for record in replayed {
1247 let txn_id = record.txn_id;
1248 match record.op {
1249 Op::Put { rows, .. } => {
1250 let rows: Vec<Row> = bincode::deserialize(&rows)?;
1251 for row in &rows {
1252 allocator.advance_to(row.row_id);
1253 if let Some(ai) = auto_inc.as_mut() {
1254 if let Some(Value::Int64(n)) = row.columns.get(&ai.column_id) {
1255 if *n + 1 > ai.next {
1256 ai.next = *n + 1;
1257 }
1258 }
1259 }
1260 }
1261 staged_puts.entry(txn_id).or_default().extend(rows);
1262 }
1263 Op::Delete { row_ids, .. } => {
1264 staged_deletes.entry(txn_id).or_default().extend(row_ids);
1265 }
1266 Op::TxnCommit { epoch, .. } => {
1267 let commit_epoch = Epoch(epoch);
1268 if let Some(puts) = staged_puts.remove(&txn_id) {
1269 if commit_epoch.0 > manifest.flushed_epoch {
1270 for row in &puts {
1271 memtable.upsert(row.clone());
1272 }
1273 replayed_puts.entry(commit_epoch).or_default().extend(puts);
1274 }
1275 }
1276 if let Some(dels) = staged_deletes.remove(&txn_id) {
1277 saw_delete = true;
1278 if commit_epoch.0 > manifest.flushed_epoch {
1279 for rid in dels {
1280 memtable.tombstone(rid, commit_epoch);
1281 replayed_deletes.push((rid, commit_epoch));
1282 }
1283 }
1284 }
1285 }
1286 Op::TxnAbort => {
1287 staged_puts.remove(&txn_id);
1288 staged_deletes.remove(&txn_id);
1289 }
1290 Op::TruncateTable { .. }
1291 | Op::ExternalTableState { .. }
1292 | Op::Flush { .. }
1293 | Op::Ddl(_)
1294 | Op::BeforeImage { .. }
1295 | Op::CommitTimestamp { .. } => {}
1296 }
1297 }
1298
1299 let rcache_dir = dir.join(RCACHE_DIR);
1300 let column_keys = build_column_keys(ctx.kek.as_deref(), &schema);
1301 let mut db = Self {
1302 dir,
1303 table_id: manifest.table_id,
1304 name: ctx.table_name.unwrap_or_default(),
1305 auth: ctx.auth,
1306 read_only: ctx.read_only,
1307 wal,
1308 memtable,
1309 mutable_run: MutableRun::new(),
1310 mutable_run_spill_bytes: DEFAULT_MUTABLE_RUN_SPILL_BYTES,
1311 compaction_zstd_level: 3,
1312 allocator,
1313 epoch: ctx.epoch,
1314 persisted_epoch,
1315 data_generation: persisted_epoch,
1316 schema,
1317 hot: HotIndex::new(),
1318 kek: ctx.kek,
1319 column_keys,
1320 run_refs: manifest.runs.clone(),
1321 retiring: manifest.retiring.clone(),
1322 next_run_id: manifest
1323 .runs
1324 .iter()
1325 .map(|r| r.run_id as u64 + 1)
1326 .max()
1327 .unwrap_or(1),
1328 sync_byte_threshold: DEFAULT_SYNC_BYTE_THRESHOLD,
1329 current_txn_id,
1330 bitmap: HashMap::new(),
1331 ann: HashMap::new(),
1332 fm: HashMap::new(),
1333 sparse: HashMap::new(),
1334 minhash: HashMap::new(),
1335 learned_range: HashMap::new(),
1336 pk_by_row: HashMap::new(),
1337 pinned: BTreeMap::new(),
1338 live_count: manifest.live_count,
1339 reservoir: crate::reservoir::Reservoir::default(),
1340 reservoir_complete: false,
1341 had_deletes: saw_delete
1342 || manifest.runs.iter().map(|run| run.row_count).sum::<u64>()
1343 != manifest.live_count,
1344 agg_cache: HashMap::new(),
1345 global_idx_epoch: manifest.global_idx_epoch,
1346 indexes_complete: true,
1347 index_build_policy: IndexBuildPolicy::default(),
1348 pk_by_row_complete: false,
1349 flushed_epoch: manifest.flushed_epoch,
1350 page_cache: ctx.page_cache,
1351 decoded_cache: ctx.decoded_cache,
1352 verified_runs: Arc::new(parking_lot::Mutex::new(std::collections::HashSet::new())),
1353 snapshots: ctx.snapshots,
1354 commit_lock: ctx.commit_lock,
1355 result_cache: Arc::new(parking_lot::Mutex::new(
1356 ResultCache::new()
1357 .with_dir(rcache_dir)
1358 .with_cache_dek(cache_dek.clone()),
1359 )),
1360 pending_delete_rids: roaring::RoaringBitmap::new(),
1361 pending_put_cols: std::collections::HashSet::new(),
1362 pending_rows: Vec::new(),
1363 pending_rows_auto_inc: Vec::new(),
1364 pending_dels: Vec::new(),
1365 pending_truncate: None,
1366 wal_dek,
1367 auto_inc,
1368 ttl: manifest.ttl,
1369 };
1370
1371 db.epoch.advance_recovered(Epoch(db.persisted_epoch));
1374
1375 let checkpoint = global_idx::read(&db.dir, db.idx_dek().as_deref())?;
1380 let checkpoint_valid = checkpoint.as_ref().is_some_and(|c| {
1381 c.epoch_built == manifest.global_idx_epoch
1382 && manifest.global_idx_epoch > 0
1383 && manifest
1384 .runs
1385 .iter()
1386 .all(|r| r.epoch_created <= manifest.global_idx_epoch)
1387 });
1388 if let Some(loaded) = checkpoint {
1389 if checkpoint_valid {
1390 db.hot = loaded.hot;
1391 db.bitmap = loaded.bitmap;
1392 db.ann = loaded.ann;
1393 db.fm = loaded.fm;
1394 db.sparse = loaded.sparse;
1395 db.minhash = loaded.minhash;
1396 db.learned_range = loaded.learned_range;
1397 }
1400 }
1401 if !checkpoint_valid {
1402 let (bitmap, ann, fm, sparse, minhash) = empty_indexes(&db.schema);
1403 db.bitmap = bitmap;
1404 db.ann = ann;
1405 db.fm = fm;
1406 db.sparse = sparse;
1407 db.minhash = minhash;
1408 db.rebuild_indexes_from_runs()?;
1409 db.build_learned_ranges()?;
1410 }
1411
1412 for (epoch, group) in replayed_puts {
1417 let (losers, winner_pks) = db.partition_pk_winners(&group);
1418 for (key, &row_id) in &winner_pks {
1419 if let Some(old_rid) = db.hot.get(key) {
1420 if old_rid != row_id {
1421 db.tombstone_row(old_rid, epoch, false);
1422 }
1423 }
1424 }
1425 for &loser_rid in &losers {
1426 db.tombstone_row(loser_rid, epoch, false);
1427 }
1428 for (key, row_id) in winner_pks {
1429 db.insert_hot_pk(key, row_id);
1430 }
1431 if db.schema.primary_key().is_none() {
1432 for r in &group {
1433 db.hot.insert(r.row_id.0.to_be_bytes().to_vec(), r.row_id);
1434 }
1435 }
1436 for r in &group {
1437 if !losers.contains(&r.row_id) {
1438 db.index_row(r);
1439 }
1440 }
1441 }
1442 for (rid, epoch) in &replayed_deletes {
1446 db.remove_hot_for_row(*rid, *epoch);
1447 }
1448
1449 db.result_cache.lock().load_persistent();
1456 Ok(db)
1457 }
1458
1459 fn ensure_reservoir_complete(&mut self) -> Result<()> {
1465 if self.reservoir_complete {
1466 return Ok(());
1467 }
1468 self.rebuild_reservoir()?;
1469 self.reservoir_complete = true;
1470 Ok(())
1471 }
1472
1473 fn rebuild_reservoir(&mut self) -> Result<()> {
1476 let snap = self.snapshot();
1477 let rows = self.visible_rows(snap)?;
1478 self.reservoir.reset();
1479 for r in rows {
1480 self.reservoir.offer(r.row_id.0);
1481 }
1482 Ok(())
1483 }
1484
1485 pub(crate) fn rebuild_indexes_from_runs(&mut self) -> Result<()> {
1486 self.hot = HotIndex::new();
1487 self.pk_by_row.clear();
1488 let (bitmap, ann, fm, sparse, minhash) = empty_indexes(&self.schema);
1489 self.bitmap = bitmap;
1490 self.ann = ann;
1491 self.fm = fm;
1492 self.sparse = sparse;
1493 self.minhash = minhash;
1494 let snapshot = Epoch(u64::MAX);
1495 let ttl_now = unix_nanos_now();
1496 for rr in self.run_refs.clone() {
1497 let mut reader = self.open_reader(rr.run_id)?;
1498 for row in reader.visible_rows(snapshot)? {
1499 if self.row_expired_at(&row, ttl_now) {
1500 continue;
1501 }
1502 let tok_row = self.tokenized_for_indexes(&row);
1503 index_into(
1504 &self.schema,
1505 &tok_row,
1506 &mut self.hot,
1507 &mut self.bitmap,
1508 &mut self.ann,
1509 &mut self.fm,
1510 &mut self.sparse,
1511 &mut self.minhash,
1512 );
1513 }
1514 }
1515 for row in self.mutable_run.visible_versions(snapshot) {
1516 if row.deleted {
1517 self.remove_hot_for_row(row.row_id, snapshot);
1518 } else if !self.row_expired_at(&row, ttl_now) {
1519 self.index_row(&row);
1520 }
1521 }
1522 for row in self.memtable.visible_versions(snapshot) {
1523 if row.deleted {
1524 self.remove_hot_for_row(row.row_id, snapshot);
1525 } else if !self.row_expired_at(&row, ttl_now) {
1526 self.index_row(&row);
1527 }
1528 }
1529 self.refresh_pk_by_row_from_hot();
1530 Ok(())
1531 }
1532
1533 fn refresh_pk_by_row_from_hot(&mut self) {
1534 self.pk_by_row_complete = true;
1535 if self.schema.primary_key().is_none() {
1536 self.pk_by_row.clear();
1537 return;
1538 }
1539 self.pk_by_row = self
1545 .hot
1546 .entries()
1547 .into_iter()
1548 .map(|(key, row_id)| (row_id, key))
1549 .collect();
1550 }
1551
1552 fn insert_hot_pk(&mut self, key: Vec<u8>, row_id: RowId) {
1553 if self.schema.primary_key().is_some() {
1554 self.pk_by_row.insert(row_id, key.clone());
1555 }
1556 self.hot.insert(key, row_id);
1557 }
1558
1559 pub(crate) fn build_learned_ranges(&mut self) -> Result<()> {
1563 self.learned_range.clear();
1564 if self.run_refs.len() != 1 {
1565 return Ok(());
1566 }
1567 let cols: Vec<(u16, usize)> = self
1568 .schema
1569 .indexes
1570 .iter()
1571 .filter(|i| i.kind == IndexKind::LearnedRange)
1572 .map(|i| {
1573 (
1574 i.column_id,
1575 i.options
1576 .learned_range
1577 .as_ref()
1578 .map(|options| options.epsilon)
1579 .unwrap_or(16),
1580 )
1581 })
1582 .collect();
1583 if cols.is_empty() {
1584 return Ok(());
1585 }
1586 let mut reader = self.open_reader(self.run_refs[0].run_id)?;
1587 let row_ids: Vec<u64> = match reader.column_native(crate::sorted_run::SYS_ROW_ID)? {
1588 columnar::NativeColumn::Int64 { data, .. } => data.iter().map(|x| *x as u64).collect(),
1589 _ => return Ok(()),
1590 };
1591 for (cid, epsilon) in cols {
1592 let ty = self
1593 .schema
1594 .columns
1595 .iter()
1596 .find(|c| c.id == cid)
1597 .map(|c| c.ty.clone())
1598 .unwrap_or(TypeId::Int64);
1599 match ty {
1600 TypeId::Int64 | TypeId::TimestampNanos | TypeId::Date32 => {
1601 if let columnar::NativeColumn::Int64 { data, .. } = reader.column_native(cid)? {
1602 let pairs: Vec<(i64, u64)> = data
1603 .iter()
1604 .zip(row_ids.iter())
1605 .map(|(v, r)| (*v, *r))
1606 .collect();
1607 self.learned_range.insert(
1608 cid,
1609 ColumnLearnedRange::build_i64_with_epsilon(&pairs, epsilon),
1610 );
1611 }
1612 }
1613 TypeId::Float64 => {
1614 if let columnar::NativeColumn::Float64 { data, .. } =
1615 reader.column_native(cid)?
1616 {
1617 let pairs: Vec<(f64, u64)> = data
1618 .iter()
1619 .zip(row_ids.iter())
1620 .map(|(v, r)| (*v, *r))
1621 .collect();
1622 self.learned_range.insert(
1623 cid,
1624 ColumnLearnedRange::build_f64_with_epsilon(&pairs, epsilon),
1625 );
1626 }
1627 }
1628 _ => {}
1629 }
1630 }
1631 Ok(())
1632 }
1633
1634 pub fn ensure_indexes_complete(&mut self) -> Result<()> {
1641 if self.indexes_complete {
1642 crate::trace::QueryTrace::record(|t| {
1643 t.index_rebuild = crate::trace::IndexRebuild::AlreadyComplete;
1644 });
1645 return Ok(());
1646 }
1647 crate::trace::QueryTrace::record(|t| {
1648 t.index_rebuild = crate::trace::IndexRebuild::Rebuilt;
1649 });
1650 self.rebuild_indexes_from_runs()?;
1651 self.build_learned_ranges()?;
1652 self.indexes_complete = true;
1653 let epoch = self.current_epoch();
1654 self.checkpoint_indexes(epoch);
1655 Ok(())
1656 }
1657
1658 fn pending_epoch(&self) -> Epoch {
1659 Epoch(self.epoch.visible().0 + 1)
1660 }
1661
1662 fn is_shared(&self) -> bool {
1665 matches!(self.wal, WalSink::Shared(_))
1666 }
1667
1668 fn ensure_txn_id(&mut self) -> u64 {
1672 if self.current_txn_id == 0 {
1673 let id = match &self.wal {
1674 WalSink::Shared(s) => {
1675 let mut g = s.txn_ids.lock();
1676 let v = *g;
1677 *g = g.wrapping_add(1);
1678 v
1679 }
1680 WalSink::Private(_) => 1,
1681 };
1682 self.current_txn_id = id;
1683 }
1684 self.current_txn_id
1685 }
1686
1687 fn wal_append_data(&mut self, op: Op) -> Result<()> {
1690 self.ensure_writable()?;
1691 let txn_id = self.ensure_txn_id();
1692 let table_id = self.table_id;
1693 match &mut self.wal {
1694 WalSink::Private(w) => {
1695 w.append_txn(txn_id, op)?;
1696 }
1697 WalSink::Shared(s) => {
1698 s.wal.lock().append(txn_id, table_id, op)?;
1699 }
1700 }
1701 Ok(())
1702 }
1703
1704 fn ensure_writable(&self) -> Result<()> {
1705 if self.read_only {
1706 Err(MongrelError::ReadOnlyReplica)
1707 } else {
1708 Ok(())
1709 }
1710 }
1711
1712 fn require(&self, perm: crate::auth_state::RequiredPermission) -> Result<()> {
1723 match &self.auth {
1724 Some(checker) => checker.check(&self.name, perm),
1725 None => Ok(()),
1726 }
1727 }
1728 pub fn require_select(&self) -> Result<()> {
1733 self.require(crate::auth_state::RequiredPermission::Select)
1734 }
1735 fn require_insert(&self) -> Result<()> {
1736 self.require(crate::auth_state::RequiredPermission::Insert)
1737 }
1738 #[allow(dead_code)]
1742 fn require_update(&self) -> Result<()> {
1743 self.require(crate::auth_state::RequiredPermission::Update)
1744 }
1745 fn require_delete(&self) -> Result<()> {
1746 self.require(crate::auth_state::RequiredPermission::Delete)
1747 }
1748
1749 pub fn put(&mut self, columns: Vec<(u16, Value)>) -> Result<RowId> {
1752 self.require_insert()?;
1753 Ok(self.put_returning(columns)?.0)
1754 }
1755
1756 pub fn put_returning(
1761 &mut self,
1762 mut columns: Vec<(u16, Value)>,
1763 ) -> Result<(RowId, Option<i64>)> {
1764 self.require_insert()?;
1765 let assigned = self.fill_auto_inc(&mut columns)?;
1766 self.apply_defaults(&mut columns)?;
1767 self.schema.validate_values(&columns)?;
1768 let row_id = if self.schema.clustered {
1773 self.derive_clustered_row_id(&columns)?
1774 } else {
1775 self.allocator.alloc()
1776 };
1777 let epoch = self.pending_epoch();
1778 let mut row = Row::new(row_id, epoch);
1779 for (col_id, val) in columns {
1780 row.columns.insert(col_id, val);
1781 }
1782 self.commit_rows(vec![row], assigned.is_some())?;
1783 Ok((row_id, assigned))
1784 }
1785
1786 pub fn put_batch(&mut self, batch: Vec<Vec<(u16, Value)>>) -> Result<Vec<RowId>> {
1789 self.require_insert()?;
1790 Ok(self
1791 .put_batch_returning(batch)?
1792 .into_iter()
1793 .map(|(r, _)| r)
1794 .collect())
1795 }
1796
1797 pub fn put_batch_returning(
1800 &mut self,
1801 batch: Vec<Vec<(u16, Value)>>,
1802 ) -> Result<Vec<(RowId, Option<i64>)>> {
1803 let mut filled: Vec<FilledAutoIncRow> = Vec::with_capacity(batch.len());
1804 for mut cols in batch {
1805 let assigned = self.fill_auto_inc(&mut cols)?;
1806 self.apply_defaults(&mut cols)?;
1807 filled.push((cols, assigned));
1808 }
1809 for (cols, _) in &filled {
1810 self.schema.validate_values(cols)?;
1811 }
1812 let epoch = self.pending_epoch();
1813 let mut rows = Vec::with_capacity(filled.len());
1814 let mut ids = Vec::with_capacity(filled.len());
1815 for (cols, assigned) in filled {
1816 let row_id = if self.schema.clustered {
1817 self.derive_clustered_row_id(&cols)?
1818 } else {
1819 self.allocator.alloc()
1820 };
1821 let mut row = Row::new(row_id, epoch);
1822 for (c, v) in cols {
1823 row.columns.insert(c, v);
1824 }
1825 ids.push((row_id, assigned));
1826 rows.push(row);
1827 }
1828 let all_auto_generated = ids.iter().all(|(_, assigned)| assigned.is_some());
1829 self.commit_rows(rows, all_auto_generated)?;
1830 Ok(ids)
1831 }
1832
1833 pub fn fill_auto_inc(&mut self, columns: &mut Vec<(u16, Value)>) -> Result<Option<i64>> {
1839 self.ensure_writable()?;
1840 let Some(cid) = self.auto_inc.as_ref().map(|a| a.column_id) else {
1841 return Ok(None);
1842 };
1843 let pos = columns.iter().position(|(c, _)| *c == cid);
1844 let assigned = match pos {
1845 Some(i) => match &columns[i].1 {
1846 Value::Null => {
1847 let next = self.alloc_auto_inc_value()?;
1848 columns[i].1 = Value::Int64(next);
1849 Some(next)
1850 }
1851 Value::Int64(n) => {
1852 self.advance_auto_inc_past(*n)?;
1853 None
1854 }
1855 other => {
1856 return Err(MongrelError::InvalidArgument(format!(
1857 "AUTO_INCREMENT column {cid} must be Int64 or NULL, got {:?}",
1858 other
1859 )))
1860 }
1861 },
1862 None => {
1863 let next = self.alloc_auto_inc_value()?;
1864 columns.push((cid, Value::Int64(next)));
1865 Some(next)
1866 }
1867 };
1868 Ok(assigned)
1869 }
1870
1871 pub fn apply_defaults(&self, columns: &mut Vec<(u16, Value)>) -> Result<()> {
1877 for col in &self.schema.columns {
1878 let Some(expr) = &col.default_value else {
1879 continue;
1880 };
1881 if col.flags.contains(ColumnFlags::AUTO_INCREMENT) {
1883 continue;
1884 }
1885 let pos = columns.iter().position(|(c, _)| *c == col.id);
1886 let needs_default = match pos {
1887 None => true,
1888 Some(i) => matches!(columns[i].1, Value::Null),
1889 };
1890 if !needs_default {
1891 continue;
1892 }
1893 let v = match expr {
1894 crate::schema::DefaultExpr::Static(v) => v.clone(),
1895 crate::schema::DefaultExpr::Now => Value::Bytes(iso_now_bytes()),
1896 crate::schema::DefaultExpr::Uuid => {
1897 let mut buf = [0u8; 16];
1898 getrandom::getrandom(&mut buf)
1899 .map_err(|e| MongrelError::Other(format!("UUID generation failed: {e}")))?;
1900 Value::Uuid(buf)
1901 }
1902 };
1903 match pos {
1904 None => columns.push((col.id, v)),
1905 Some(i) => columns[i].1 = v,
1906 }
1907 }
1908 Ok(())
1909 }
1910
1911 fn alloc_auto_inc_value(&mut self) -> Result<i64> {
1913 self.ensure_auto_inc_seeded()?;
1914 let ai = self.auto_inc.as_mut().expect("auto-inc column present");
1916 let v = ai.next;
1917 ai.next = ai.next.saturating_add(1);
1918 Ok(v)
1919 }
1920
1921 fn advance_auto_inc_past(&mut self, used: i64) -> Result<()> {
1924 self.ensure_auto_inc_seeded()?;
1925 let ai = self.auto_inc.as_mut().expect("auto-inc column present");
1926 let floor = used.saturating_add(1).max(1);
1927 if ai.next < floor {
1928 ai.next = floor;
1929 }
1930 Ok(())
1931 }
1932
1933 fn ensure_auto_inc_seeded(&mut self) -> Result<()> {
1938 let needs_seed = match self.auto_inc {
1939 Some(ai) => !ai.seeded,
1940 None => return Ok(()),
1941 };
1942 if !needs_seed {
1943 return Ok(());
1944 }
1945 if self.seed_empty_auto_inc() {
1946 return Ok(());
1947 }
1948 let cid = self
1949 .auto_inc
1950 .as_ref()
1951 .expect("auto-inc column present")
1952 .column_id;
1953 let max = self.scan_max_int64(cid)?;
1954 let ai = self.auto_inc.as_mut().expect("auto-inc column present");
1955 let floor = max.saturating_add(1).max(1);
1956 if ai.next < floor {
1957 ai.next = floor;
1958 }
1959 ai.seeded = true;
1960 Ok(())
1961 }
1962
1963 fn alloc_auto_inc_range(&mut self, n: usize) -> Result<Option<i64>> {
1964 if n == 0 || self.auto_inc.is_none() {
1965 return Ok(None);
1966 }
1967 self.ensure_auto_inc_seeded()?;
1968 let ai = self.auto_inc.as_mut().expect("auto-inc column present");
1969 let start = ai.next;
1970 ai.next = ai.next.saturating_add(n as i64);
1971 Ok(Some(start))
1972 }
1973
1974 fn scan_max_int64(&mut self, column_id: u16) -> Result<i64> {
1978 let mut max: i64 = 0;
1979 for r in self.memtable.visible_versions(Epoch(u64::MAX)) {
1980 if let Some(Value::Int64(n)) = r.columns.get(&column_id) {
1981 if *n > max {
1982 max = *n;
1983 }
1984 }
1985 }
1986 for r in self.mutable_run.visible_versions(Epoch(u64::MAX)) {
1987 if let Some(Value::Int64(n)) = r.columns.get(&column_id) {
1988 if *n > max {
1989 max = *n;
1990 }
1991 }
1992 }
1993 for rr in self.run_refs.clone() {
1994 let reader = self.open_reader(rr.run_id)?;
1995 if let Some(stats) = reader.column_page_stats(column_id) {
1996 for s in stats {
1997 if let Some(n) = crate::sorted_run::be_i64(s.max.as_deref()) {
1998 if n > max {
1999 max = n;
2000 }
2001 }
2002 }
2003 } else if reader.has_column(column_id) {
2004 if let columnar::NativeColumn::Int64 { data, validity } =
2005 reader.column_native_shared(column_id)?
2006 {
2007 for (i, n) in data.iter().enumerate() {
2008 if (validity.is_empty() || columnar::validity_bit(&validity, i)) && *n > max
2009 {
2010 max = *n;
2011 }
2012 }
2013 }
2014 }
2015 }
2016 Ok(max)
2017 }
2018
2019 fn seed_empty_auto_inc(&mut self) -> bool {
2020 let Some(ai) = self.auto_inc.as_mut() else {
2021 return false;
2022 };
2023 if ai.seeded || self.live_count != 0 {
2024 return false;
2025 }
2026 if ai.next < 1 {
2027 ai.next = 1;
2028 }
2029 ai.seeded = true;
2030 true
2031 }
2032
2033 fn advance_auto_inc_from_native_columns(
2034 &mut self,
2035 columns: &[(u16, columnar::NativeColumn)],
2036 n: usize,
2037 live_before: u64,
2038 ) -> Result<()> {
2039 let Some(ai) = self.auto_inc.as_mut() else {
2040 return Ok(());
2041 };
2042 let Some((_, col)) = columns.iter().find(|(cid, _)| *cid == ai.column_id) else {
2043 return Ok(());
2044 };
2045 let columnar::NativeColumn::Int64 { data, validity } = col else {
2046 return Err(MongrelError::InvalidArgument(format!(
2047 "AUTO_INCREMENT column {} must be Int64",
2048 ai.column_id
2049 )));
2050 };
2051 let max = if native_int64_strictly_increasing(col, n) {
2052 data.get(n.saturating_sub(1)).copied()
2053 } else {
2054 data.iter()
2055 .take(n)
2056 .enumerate()
2057 .filter_map(|(i, v)| {
2058 if validity.is_empty() || columnar::validity_bit(validity, i) {
2059 Some(*v)
2060 } else {
2061 None
2062 }
2063 })
2064 .max()
2065 };
2066 if let Some(max) = max {
2067 let floor = max.saturating_add(1).max(1);
2068 if ai.next < floor {
2069 ai.next = floor;
2070 }
2071 if ai.seeded || live_before == 0 {
2072 ai.seeded = true;
2073 }
2074 }
2075 Ok(())
2076 }
2077
2078 fn fill_auto_inc_native_columns(
2079 &mut self,
2080 columns: &mut Vec<(u16, columnar::NativeColumn)>,
2081 n: usize,
2082 ) -> Result<()> {
2083 let Some(cid) = self.auto_inc.as_ref().map(|a| a.column_id) else {
2084 return Ok(());
2085 };
2086 let Some(pos) = columns.iter().position(|(id, _)| *id == cid) else {
2087 if let Some(start) = self.alloc_auto_inc_range(n)? {
2088 columns.push((
2089 cid,
2090 columnar::NativeColumn::Int64 {
2091 data: (start..start.saturating_add(n as i64)).collect(),
2092 validity: vec![0xFF; n.div_ceil(8)],
2093 },
2094 ));
2095 }
2096 return Ok(());
2097 };
2098
2099 let columnar::NativeColumn::Int64 { data, validity } = &mut columns[pos].1 else {
2100 return Err(MongrelError::InvalidArgument(format!(
2101 "AUTO_INCREMENT column {cid} must be Int64"
2102 )));
2103 };
2104 if data.len() < n {
2105 return Err(MongrelError::InvalidArgument(format!(
2106 "AUTO_INCREMENT column {cid} has {} rows, expected {n}",
2107 data.len()
2108 )));
2109 }
2110 if columnar::all_non_null(validity, n) {
2111 return Ok(());
2112 }
2113 if validity.iter().all(|b| *b == 0) {
2114 if let Some(start) = self.alloc_auto_inc_range(n)? {
2115 for (i, slot) in data.iter_mut().take(n).enumerate() {
2116 *slot = start.saturating_add(i as i64);
2117 }
2118 *validity = vec![0xFF; n.div_ceil(8)];
2119 }
2120 return Ok(());
2121 }
2122
2123 let new_validity = vec![0xFF; data.len().div_ceil(8)];
2124 for (i, slot) in data.iter_mut().enumerate().take(n) {
2125 if columnar::validity_bit(validity, i) {
2126 self.advance_auto_inc_past(*slot)?;
2127 } else {
2128 *slot = self.alloc_auto_inc_value()?;
2129 }
2130 }
2131 *validity = new_validity;
2132 Ok(())
2133 }
2134
2135 pub fn reserve_auto_inc(&mut self) -> Result<Option<i64>> {
2149 self.ensure_writable()?;
2150 if self.auto_inc.is_none() {
2151 return Ok(None);
2152 }
2153 Ok(Some(self.alloc_auto_inc_value()?))
2154 }
2155
2156 fn commit_rows(&mut self, rows: Vec<Row>, auto_inc_generated: bool) -> Result<()> {
2162 let payload = bincode::serialize(&rows)?;
2163 self.wal_append_data(Op::Put {
2164 table_id: self.table_id,
2165 rows: payload,
2166 })?;
2167 if self.is_shared() {
2168 self.pending_rows_auto_inc
2169 .extend(std::iter::repeat(auto_inc_generated).take(rows.len()));
2170 self.pending_rows.extend(rows);
2171 } else {
2172 self.apply_put_rows_inner(rows, !auto_inc_generated)?;
2173 }
2174 Ok(())
2175 }
2176
2177 pub(crate) fn apply_put_rows(&mut self, rows: Vec<Row>) -> Result<()> {
2182 self.apply_put_rows_inner(rows, true)
2183 }
2184
2185 fn apply_put_rows_inner(&mut self, rows: Vec<Row>, check_existing_pk: bool) -> Result<()> {
2186 if check_existing_pk {
2187 self.ensure_indexes_complete()?;
2188 }
2189 if rows.len() == 1 {
2193 let row = rows.into_iter().next().expect("len checked");
2194 return self.apply_put_row_single(row, check_existing_pk);
2195 }
2196 let pk_id = self.schema.primary_key().map(|c| c.id);
2213 let probe = match pk_id {
2214 Some(pid) => {
2215 check_existing_pk
2216 && !(self.hot.is_empty() && rows_pk_strictly_increasing(&rows, pid))
2217 }
2218 None => false,
2219 };
2220 let maintain_pk_by_row = pk_id.is_some() && self.pk_by_row_complete;
2223 for r in rows {
2224 for &cid in r.columns.keys() {
2225 self.pending_put_cols.insert(cid);
2226 }
2227 match pk_id {
2228 Some(pid) if probe || maintain_pk_by_row => {
2229 if let Some(pk_val) = r.columns.get(&pid) {
2230 let key = self.index_lookup_key(pid, pk_val);
2231 if probe {
2232 if let Some(old_rid) = self.hot.get(&key) {
2233 if old_rid != r.row_id {
2234 self.tombstone_row(old_rid, r.committed_epoch, true);
2235 }
2236 }
2237 }
2238 if maintain_pk_by_row {
2239 self.pk_by_row.insert(r.row_id, key);
2240 }
2241 }
2242 }
2243 Some(_) => {}
2244 None => {
2245 self.hot.insert(r.row_id.0.to_be_bytes().to_vec(), r.row_id);
2246 }
2247 }
2248 self.index_row(&r);
2249 self.reservoir.offer(r.row_id.0);
2250 self.memtable.upsert(r);
2251 self.live_count = self.live_count.saturating_add(1);
2254 }
2255 self.data_generation = self.data_generation.wrapping_add(1);
2256 Ok(())
2257 }
2258
2259 fn apply_put_row_single(&mut self, row: Row, check_existing_pk: bool) -> Result<()> {
2263 for &cid in row.columns.keys() {
2264 self.pending_put_cols.insert(cid);
2265 }
2266 let epoch = row.committed_epoch;
2267 if let Some(pk_col) = self.schema.primary_key() {
2268 let pk_id = pk_col.id;
2269 if let Some(pk_val) = row.columns.get(&pk_id) {
2270 let maintain_pk_by_row = self.pk_by_row_complete;
2274 if check_existing_pk || maintain_pk_by_row {
2275 let key = self.index_lookup_key(pk_id, pk_val);
2276 if check_existing_pk {
2277 if let Some(old_rid) = self.hot.get(&key) {
2278 if old_rid != row.row_id {
2279 self.tombstone_row(old_rid, epoch, true);
2280 }
2281 }
2282 }
2283 if maintain_pk_by_row {
2284 self.pk_by_row.insert(row.row_id, key);
2285 }
2286 }
2287 }
2288 } else {
2289 self.hot
2290 .insert(row.row_id.0.to_be_bytes().to_vec(), row.row_id);
2291 }
2292 self.index_row(&row);
2293 self.reservoir.offer(row.row_id.0);
2294 self.memtable.upsert(row);
2295 self.live_count = self.live_count.saturating_add(1);
2296 self.data_generation = self.data_generation.wrapping_add(1);
2297 Ok(())
2298 }
2299
2300 pub(crate) fn alloc_row_id(&mut self) -> RowId {
2303 self.allocator.alloc()
2304 }
2305
2306 fn derive_clustered_row_id(&self, columns: &[(u16, Value)]) -> Result<RowId> {
2312 let pk = self.schema.primary_key().ok_or_else(|| {
2313 MongrelError::Schema("clustered table requires a single-column primary key".into())
2314 })?;
2315 let pk_val = columns
2316 .iter()
2317 .find(|(id, _)| *id == pk.id)
2318 .map(|(_, v)| v)
2319 .ok_or_else(|| {
2320 MongrelError::Schema(format!(
2321 "clustered table missing primary key column {} ({})",
2322 pk.id, pk.name
2323 ))
2324 })?;
2325 let key_bytes = pk_val.encode_key();
2326 let mut hash: u64 = 0xcbf29ce484222325;
2328 for &b in &key_bytes {
2329 hash ^= b as u64;
2330 hash = hash.wrapping_mul(0x100000001b3);
2331 }
2332 Ok(RowId(hash.max(1)))
2335 }
2336
2337 pub(crate) fn apply_run_metadata(&mut self, rows: &[Row]) -> Result<()> {
2345 self.ensure_indexes_complete()?;
2346 let n = rows.len();
2347 for r in rows {
2348 for &cid in r.columns.keys() {
2349 self.pending_put_cols.insert(cid);
2350 }
2351 }
2352 let (losers, winner_pks) = self.partition_pk_winners(rows);
2353 let epoch = rows.first().map(|r| r.committed_epoch).unwrap_or(Epoch(0));
2354 for (key, &row_id) in &winner_pks {
2356 if let Some(old_rid) = self.hot.get(key) {
2357 if old_rid != row_id {
2358 self.tombstone_row(old_rid, epoch, true);
2359 }
2360 }
2361 }
2362 for &loser_rid in &losers {
2365 self.tombstone_row(loser_rid, epoch, false);
2366 }
2367 for (key, row_id) in winner_pks {
2369 self.insert_hot_pk(key, row_id);
2370 }
2371 if self.schema.primary_key().is_none() {
2372 for r in rows {
2373 self.hot.insert(r.row_id.0.to_be_bytes().to_vec(), r.row_id);
2374 }
2375 }
2376 for r in rows {
2377 self.allocator.advance_to(r.row_id);
2378 if !losers.contains(&r.row_id) {
2379 self.index_row(r);
2380 }
2381 }
2382 for r in rows {
2383 if !losers.contains(&r.row_id) {
2384 self.reservoir.offer(r.row_id.0);
2385 }
2386 }
2387 self.live_count = self.live_count.saturating_add((n - losers.len()) as u64);
2388 self.data_generation = self.data_generation.wrapping_add(1);
2389 Ok(())
2390 }
2391
2392 pub(crate) fn recover_apply(
2397 &mut self,
2398 rows: Vec<Row>,
2399 deletes: Vec<(RowId, Epoch)>,
2400 ) -> Result<()> {
2401 let mut by_epoch: std::collections::BTreeMap<Epoch, Vec<Row>> =
2405 std::collections::BTreeMap::new();
2406 for row in rows {
2407 self.allocator.advance_to(row.row_id);
2408 if let Some(ai) = self.auto_inc.as_mut() {
2413 if let Some(Value::Int64(n)) = row.columns.get(&ai.column_id) {
2414 if *n + 1 > ai.next {
2415 ai.next = *n + 1;
2416 }
2417 }
2418 }
2419 by_epoch.entry(row.committed_epoch).or_default().push(row);
2420 }
2421 for (epoch, group) in by_epoch {
2422 let (losers, winner_pks) = self.partition_pk_winners(&group);
2423 for (key, &row_id) in &winner_pks {
2425 if let Some(old_rid) = self.hot.get(key) {
2426 if old_rid != row_id {
2427 self.tombstone_row(old_rid, epoch, false);
2428 }
2429 }
2430 }
2431 for (key, row_id) in winner_pks {
2432 self.insert_hot_pk(key, row_id);
2433 }
2434 if self.schema.primary_key().is_none() {
2435 for r in &group {
2436 self.hot.insert(r.row_id.0.to_be_bytes().to_vec(), r.row_id);
2437 }
2438 }
2439 for r in &group {
2440 if !losers.contains(&r.row_id) {
2441 self.memtable.upsert(r.clone());
2442 self.index_row(r);
2443 }
2444 }
2445 }
2446 for (rid, epoch) in deletes {
2447 self.memtable.tombstone(rid, epoch);
2448 self.remove_hot_for_row(rid, epoch);
2449 }
2450 self.reservoir_complete = false;
2453 Ok(())
2454 }
2455
2456 pub(crate) fn flushed_epoch(&self) -> u64 {
2458 self.flushed_epoch
2459 }
2460
2461 pub(crate) fn set_flushed_epoch(&mut self, epoch: Epoch) {
2462 self.flushed_epoch = self.flushed_epoch.max(epoch.0);
2463 }
2464
2465 pub(crate) fn validate_cells_not_null(&self, cells: &[(u16, Value)]) -> Result<()> {
2467 self.schema.validate_values(cells)
2468 }
2469
2470 fn validate_columns_not_null(
2474 &self,
2475 columns: &[(u16, columnar::NativeColumn)],
2476 n: usize,
2477 ) -> Result<()> {
2478 let by_id: HashMap<u16, &columnar::NativeColumn> =
2479 columns.iter().map(|(id, c)| (*id, c)).collect();
2480 for col in &self.schema.columns {
2481 if !col.flags.contains(ColumnFlags::NULLABLE) {
2482 match by_id.get(&col.id) {
2483 None => {
2484 return Err(MongrelError::InvalidArgument(format!(
2485 "column '{}' ({}) is NOT NULL but was omitted from the bulk load",
2486 col.name, col.id
2487 )));
2488 }
2489 Some(c) => {
2490 if c.null_count(n) != 0 {
2491 return Err(MongrelError::InvalidArgument(format!(
2492 "column '{}' ({}) is NOT NULL but the bulk load contains nulls",
2493 col.name, col.id
2494 )));
2495 }
2496 }
2497 }
2498 }
2499 if let TypeId::Enum { variants } = &col.ty {
2500 let Some(columnar::NativeColumn::Bytes { .. }) = by_id.get(&col.id).copied() else {
2501 if by_id.contains_key(&col.id) {
2502 return Err(MongrelError::InvalidArgument(format!(
2503 "column '{}' ({}) enum requires a bytes column",
2504 col.name, col.id
2505 )));
2506 }
2507 continue;
2508 };
2509 for index in 0..n {
2510 let Some(value) = columnar::native_bytes_at(by_id[&col.id], index) else {
2511 continue;
2512 };
2513 if !variants.iter().any(|variant| variant.as_bytes() == value) {
2514 return Err(MongrelError::InvalidArgument(format!(
2515 "column '{}' ({}) enum value {:?} is not one of {:?}",
2516 col.name,
2517 col.id,
2518 String::from_utf8_lossy(value),
2519 variants
2520 )));
2521 }
2522 }
2523 }
2524 }
2525 Ok(())
2526 }
2527
2528 fn bulk_pk_winner_indices(
2533 &self,
2534 columns: &[(u16, columnar::NativeColumn)],
2535 n: usize,
2536 ) -> Option<Vec<usize>> {
2537 let pk_col = self.schema.primary_key()?;
2538 let pk_id = pk_col.id;
2539 let pk_ty = pk_col.ty.clone();
2540 let by_id: HashMap<u16, &columnar::NativeColumn> =
2541 columns.iter().map(|(id, c)| (*id, c)).collect();
2542 let pk_native = by_id.get(&pk_id)?;
2543 if native_int64_strictly_increasing(pk_native, n) {
2544 return None;
2545 }
2546 let mut last: HashMap<Vec<u8>, usize> = HashMap::new();
2548 let mut null_pk_rows: Vec<usize> = Vec::new();
2549 for i in 0..n {
2550 match bulk_index_key(&self.column_keys, pk_id, pk_ty.clone(), pk_native, i) {
2551 Some(key) => {
2552 last.insert(key, i);
2553 }
2554 None => null_pk_rows.push(i),
2555 }
2556 }
2557 let mut winners: HashSet<usize> = last.values().copied().collect();
2558 for i in null_pk_rows {
2559 winners.insert(i);
2560 }
2561 Some((0..n).filter(|i| winners.contains(i)).collect())
2562 }
2563
2564 pub fn delete(&mut self, row_id: RowId) -> Result<()> {
2566 self.require_delete()?;
2567 let epoch = self.pending_epoch();
2568 self.wal_append_data(Op::Delete {
2569 table_id: self.table_id,
2570 row_ids: vec![row_id],
2571 })?;
2572 if self.is_shared() {
2573 self.pending_dels.push(row_id);
2574 } else {
2575 self.apply_delete(row_id, epoch);
2576 }
2577 Ok(())
2578 }
2579
2580 pub fn delete_returning(&mut self, row_id: RowId) -> Result<Option<OwnedRow>> {
2581 let pre = self.get(row_id, self.snapshot());
2582 self.delete(row_id)?;
2583 Ok(pre.map(|row| {
2584 let mut columns: Vec<_> = row.columns.into_iter().collect();
2585 columns.sort_by_key(|(id, _)| *id);
2586 OwnedRow { columns }
2587 }))
2588 }
2589
2590 pub fn truncate(&mut self) -> Result<()> {
2592 self.require_delete()?;
2593 let epoch = self.pending_epoch();
2594 self.wal_append_data(Op::TruncateTable {
2595 table_id: self.table_id,
2596 })?;
2597 self.pending_rows.clear();
2598 self.pending_rows_auto_inc.clear();
2599 self.pending_dels.clear();
2600 self.pending_truncate = Some(epoch);
2601 Ok(())
2602 }
2603
2604 pub(crate) fn apply_truncate(&mut self, _epoch: Epoch) -> Result<()> {
2606 for rr in std::mem::take(&mut self.run_refs) {
2607 let _ = std::fs::remove_file(self.run_path(rr.run_id as u64));
2608 }
2609 for r in std::mem::take(&mut self.retiring) {
2610 let _ = std::fs::remove_file(self.run_path(r.run_id as u64));
2611 }
2612 self.memtable = Memtable::new();
2613 self.mutable_run = MutableRun::new();
2614 self.hot = HotIndex::new();
2615 let (bitmap, ann, fm, sparse, minhash) = empty_indexes(&self.schema);
2616 self.bitmap = bitmap;
2617 self.ann = ann;
2618 self.fm = fm;
2619 self.sparse = sparse;
2620 self.minhash = minhash;
2621 self.learned_range.clear();
2622 self.pk_by_row.clear();
2623 self.pk_by_row_complete = false;
2624 self.live_count = 0;
2625 self.reservoir = crate::reservoir::Reservoir::default();
2626 self.reservoir_complete = true;
2627 self.had_deletes = true;
2628 self.agg_cache.clear();
2629 self.global_idx_epoch = 0;
2630 self.indexes_complete = true;
2631 self.pending_delete_rids.clear();
2632 self.pending_put_cols.clear();
2633 self.pending_rows.clear();
2634 self.pending_rows_auto_inc.clear();
2635 self.pending_dels.clear();
2636 self.clear_result_cache();
2637 self.invalidate_index_checkpoint();
2638 self.data_generation = self.data_generation.wrapping_add(1);
2639 Ok(())
2640 }
2641
2642 pub(crate) fn apply_delete(&mut self, row_id: RowId, epoch: Epoch) {
2645 self.remove_hot_for_row(row_id, epoch);
2646 self.tombstone_row(row_id, epoch, true);
2647 self.data_generation = self.data_generation.wrapping_add(1);
2648 }
2649
2650 fn tombstone_row(&mut self, row_id: RowId, epoch: Epoch, adjust_live_count: bool) {
2654 let tombstone = Row {
2655 row_id,
2656 committed_epoch: epoch,
2657 columns: std::collections::HashMap::new(),
2658 deleted: true,
2659 };
2660 self.memtable.upsert(tombstone);
2661 self.pk_by_row.remove(&row_id);
2662 if adjust_live_count {
2663 self.live_count = self.live_count.saturating_sub(1);
2664 }
2665 self.pending_delete_rids.insert(row_id.0 as u32);
2667 self.had_deletes = true;
2670 self.agg_cache.clear();
2671 }
2672
2673 fn remove_hot_for_row(&mut self, row_id: RowId, epoch: Epoch) {
2677 let Some(pk_col) = self.schema.primary_key() else {
2678 return;
2679 };
2680 if self.pk_by_row_complete {
2683 if let Some(key) = self.pk_by_row.remove(&row_id) {
2684 if self.hot.get(&key) == Some(row_id) {
2685 self.hot.remove(&key);
2686 }
2687 }
2688 return;
2689 }
2690 let lookup_epoch = Epoch(epoch.0.saturating_sub(1));
2709 if self.indexes_complete {
2710 let pk_val = self
2711 .memtable
2712 .get_version(row_id, lookup_epoch)
2713 .and_then(|(_, r)| r.columns.get(&pk_col.id).cloned())
2714 .or_else(|| {
2715 self.mutable_run
2716 .get_version(row_id, lookup_epoch)
2717 .filter(|(_, r)| !r.deleted)
2718 .and_then(|(_, r)| r.columns.get(&pk_col.id).cloned())
2719 })
2720 .or_else(|| {
2721 self.run_refs.iter().find_map(|rr| {
2722 let mut reader = self.open_reader(rr.run_id).ok()?;
2723 let (_, deleted, val) = reader
2724 .get_version_column(row_id, lookup_epoch, pk_col.id)
2725 .ok()??;
2726 if deleted {
2727 return None;
2728 }
2729 val
2730 })
2731 });
2732 if let Some(pk_val) = pk_val {
2733 let key = self.index_lookup_key(pk_col.id, &pk_val);
2734 if self.hot.get(&key) == Some(row_id) {
2735 self.hot.remove(&key);
2736 }
2737 return;
2738 }
2739 }
2740 self.refresh_pk_by_row_from_hot();
2745 if let Some(key) = self.pk_by_row.remove(&row_id) {
2746 if self.hot.get(&key) == Some(row_id) {
2747 self.hot.remove(&key);
2748 }
2749 }
2750 }
2751
2752 fn partition_pk_winners(
2757 &self,
2758 rows: &[Row],
2759 ) -> (
2760 std::collections::HashSet<RowId>,
2761 std::collections::HashMap<Vec<u8>, RowId>,
2762 ) {
2763 let mut losers = std::collections::HashSet::new();
2764 let Some(pk_col) = self.schema.primary_key() else {
2765 return (losers, std::collections::HashMap::new());
2766 };
2767 let pk_id = pk_col.id;
2768 let mut winners: std::collections::HashMap<Vec<u8>, RowId> =
2769 std::collections::HashMap::new();
2770 for r in rows {
2771 let Some(pk_val) = r.columns.get(&pk_id) else {
2772 continue;
2773 };
2774 let key = self.index_lookup_key(pk_id, pk_val);
2775 if let Some(&old_rid) = winners.get(&key) {
2776 losers.insert(old_rid);
2777 }
2778 winners.insert(key, r.row_id);
2779 }
2780 (losers, winners)
2781 }
2782
2783 fn index_row(&mut self, row: &Row) {
2784 if row.deleted {
2785 return;
2786 }
2787 let any_predicate = self
2795 .schema
2796 .indexes
2797 .iter()
2798 .any(|idx| idx.predicate.is_some());
2799 if any_predicate {
2800 let columns_map: HashMap<u16, &Value> =
2801 row.columns.iter().map(|(k, v)| (*k, v)).collect();
2802 let name_to_id: HashMap<&str, u16> = self
2803 .schema
2804 .columns
2805 .iter()
2806 .map(|c| (c.name.as_str(), c.id))
2807 .collect();
2808 for idx in &self.schema.indexes {
2809 if let Some(pred) = &idx.predicate {
2810 if !eval_partial_predicate(pred, &columns_map, &name_to_id) {
2811 continue; }
2813 }
2814 index_into_single(
2816 idx,
2817 &self.schema,
2818 row,
2819 &mut self.hot,
2820 &mut self.bitmap,
2821 &mut self.ann,
2822 &mut self.fm,
2823 &mut self.sparse,
2824 &mut self.minhash,
2825 );
2826 }
2827 return;
2828 }
2829 if self.column_keys.is_empty() {
2833 index_into(
2834 &self.schema,
2835 row,
2836 &mut self.hot,
2837 &mut self.bitmap,
2838 &mut self.ann,
2839 &mut self.fm,
2840 &mut self.sparse,
2841 &mut self.minhash,
2842 );
2843 return;
2844 }
2845 let effective_row = self.tokenized_for_indexes(row);
2846 index_into(
2847 &self.schema,
2848 &effective_row,
2849 &mut self.hot,
2850 &mut self.bitmap,
2851 &mut self.ann,
2852 &mut self.fm,
2853 &mut self.sparse,
2854 &mut self.minhash,
2855 );
2856 }
2857
2858 fn tokenized_for_indexes(&self, row: &Row) -> Row {
2864 if self.column_keys.is_empty() {
2865 return row.clone();
2866 }
2867 #[cfg(feature = "encryption")]
2868 {
2869 use crate::encryption::SCHEME_HMAC_EQ;
2870 let mut tok = row.clone();
2871 for (&cid, &(_, scheme)) in &self.column_keys {
2872 if scheme != SCHEME_HMAC_EQ {
2873 continue;
2874 }
2875 if let Some(v) = tok.columns.get(&cid).cloned() {
2876 if let Some(t) = self.tokenize_value(cid, &v) {
2877 tok.columns.insert(cid, t);
2878 }
2879 }
2880 }
2881 tok
2882 }
2883 #[cfg(not(feature = "encryption"))]
2884 {
2885 row.clone()
2886 }
2887 }
2888
2889 pub fn commit(&mut self) -> Result<Epoch> {
2894 self.ensure_writable()?;
2895 if self.is_shared() {
2896 self.commit_shared()
2897 } else {
2898 self.commit_private()
2899 }
2900 }
2901
2902 fn commit_private(&mut self) -> Result<Epoch> {
2904 let commit_lock = Arc::clone(&self.commit_lock);
2908 let _g = commit_lock.lock();
2909 let new_epoch = self.epoch.bump_assigned();
2910 let txn_id = self.current_txn_id;
2911 match &mut self.wal {
2915 WalSink::Private(w) => {
2916 w.append_txn(
2917 txn_id,
2918 Op::TxnCommit {
2919 epoch: new_epoch.0,
2920 added_runs: Vec::new(),
2921 },
2922 )?;
2923 w.sync()?;
2924 }
2925 WalSink::Shared(_) => unreachable!("commit_private on a shared sink"),
2926 }
2927 if let Some(epoch) = self.pending_truncate.take() {
2929 self.apply_truncate(epoch)?;
2930 }
2931 self.invalidate_pending_cache();
2932 self.persist_manifest(new_epoch)?;
2933 self.epoch.publish_in_order(new_epoch);
2937 self.current_txn_id += 1;
2938 self.data_generation = self.data_generation.wrapping_add(1);
2939 Ok(new_epoch)
2940 }
2941
2942 fn commit_shared(&mut self) -> Result<Epoch> {
2948 use std::sync::atomic::Ordering;
2949 let s = match &self.wal {
2950 WalSink::Shared(s) => s.clone(),
2951 WalSink::Private(_) => unreachable!("commit_shared on a private sink"),
2952 };
2953 if s.poisoned.load(Ordering::Relaxed) {
2954 return Err(MongrelError::Other(
2955 "database poisoned by fsync error".into(),
2956 ));
2957 }
2958 let commit_lock = Arc::clone(&self.commit_lock);
2965 let _g = commit_lock.lock();
2966 let txn_id = self.ensure_txn_id();
2969 let (new_epoch, commit_seq) = {
2970 let mut wal = s.wal.lock();
2971 let new_epoch = self.epoch.bump_assigned();
2972 let seq = wal.append_commit(txn_id, new_epoch, &[])?;
2973 (new_epoch, seq)
2974 };
2975 s.group
2976 .await_durable(&s.wal, commit_seq)
2977 .inspect_err(|_| s.poisoned.store(true, Ordering::Relaxed))?;
2978
2979 if self.pending_truncate.take().is_some() {
2982 self.apply_truncate(new_epoch)?;
2983 }
2984 let mut rows = std::mem::take(&mut self.pending_rows);
2985 if !rows.is_empty() {
2986 for r in &mut rows {
2987 r.committed_epoch = new_epoch;
2988 }
2989 let auto_inc_flags = std::mem::take(&mut self.pending_rows_auto_inc);
2990 let all_auto_generated =
2991 auto_inc_flags.len() == rows.len() && auto_inc_flags.iter().all(|b| *b);
2992 self.apply_put_rows_inner(rows, !all_auto_generated)?;
2993 } else {
2994 self.pending_rows_auto_inc.clear();
2995 }
2996 let dels = std::mem::take(&mut self.pending_dels);
2997 for rid in dels {
2998 self.apply_delete(rid, new_epoch);
2999 }
3000
3001 self.invalidate_pending_cache();
3002 self.persist_manifest(new_epoch)?;
3003 self.epoch.publish_in_order(new_epoch);
3004 let _ = s.change_wake.send(());
3005 self.current_txn_id = 0;
3007 self.data_generation = self.data_generation.wrapping_add(1);
3008 Ok(new_epoch)
3009 }
3010
3011 pub fn flush(&mut self) -> Result<Epoch> {
3019 self.ensure_indexes_complete()?;
3020 let epoch = self.commit()?;
3021 let rows = self.memtable.drain_sorted();
3022 if !rows.is_empty() {
3023 self.mutable_run.insert_many(rows);
3024 }
3025 if self.mutable_run.approx_bytes() >= self.mutable_run_spill_bytes {
3026 self.spill_mutable_run(epoch)?;
3027 self.mark_flushed(epoch)?;
3031 self.persist_manifest(epoch)?;
3032 self.build_learned_ranges()?;
3033 self.checkpoint_indexes(epoch);
3036 }
3037 Ok(epoch)
3040 }
3041
3042 pub fn force_flush(&mut self) -> Result<Epoch> {
3051 let saved = self.mutable_run_spill_bytes;
3052 self.mutable_run_spill_bytes = 1;
3053 let result = self.flush();
3054 self.mutable_run_spill_bytes = saved;
3055 result
3056 }
3057
3058 pub fn close(&mut self) -> Result<()> {
3065 if self.memtable_len() > 0 || self.mutable_run_len() > 0 {
3066 self.force_flush()?;
3067 }
3068 Ok(())
3069 }
3070
3071 fn mark_flushed(&mut self, epoch: Epoch) -> Result<()> {
3078 let op = Op::Flush {
3079 table_id: self.table_id,
3080 flushed_epoch: epoch.0,
3081 };
3082 match &mut self.wal {
3083 WalSink::Private(w) => {
3084 w.append_system(op)?;
3085 w.sync()?;
3086 }
3087 WalSink::Shared(s) => {
3088 s.wal.lock().append_system(op)?;
3093 }
3094 }
3095 self.flushed_epoch = epoch.0;
3096 if matches!(self.wal, WalSink::Private(_)) {
3097 self.rotate_wal(epoch)?;
3098 }
3099 Ok(())
3100 }
3101
3102 fn spill_mutable_run(&mut self, epoch: Epoch) -> Result<()> {
3106 let rows = self.mutable_run.drain_sorted();
3107 if rows.is_empty() {
3108 return Ok(());
3109 }
3110 let run_id = self.next_run_id;
3111 self.next_run_id += 1;
3112 let path = self.run_path(run_id);
3113 let mut writer = RunWriter::new(&self.schema, run_id as u128, epoch, 0);
3114 if let Some(kek) = &self.kek {
3115 writer = writer.with_encryption(kek.as_ref(), self.indexable_column_specs());
3116 }
3117 let header = writer.write(&path, &rows)?;
3118 self.run_refs.push(RunRef {
3119 run_id: run_id as u128,
3120 level: 0,
3121 epoch_created: epoch.0,
3122 row_count: header.row_count,
3123 });
3124 Ok(())
3125 }
3126
3127 pub fn set_mutable_run_spill_bytes(&mut self, bytes: u64) {
3131 self.mutable_run_spill_bytes = bytes.max(1);
3132 }
3133
3134 pub fn set_compaction_zstd_level(&mut self, level: i32) {
3138 self.compaction_zstd_level = level;
3139 }
3140
3141 pub fn set_result_cache_max_bytes(&mut self, max_bytes: u64) {
3145 self.result_cache.lock().set_max_bytes(max_bytes);
3146 }
3147
3148 pub(crate) fn clear_result_cache(&mut self) {
3152 self.result_cache.lock().clear();
3153 }
3154
3155 pub fn mutable_run_len(&self) -> usize {
3157 self.mutable_run.len()
3158 }
3159
3160 pub(crate) fn drain_mutable_run(&mut self) -> Vec<Row> {
3163 self.mutable_run.drain_sorted()
3164 }
3165
3166 pub fn bulk_load(&mut self, batch: Vec<Vec<(u16, Value)>>) -> Result<Epoch> {
3171 let epoch = self.commit()?;
3172 let n = batch.len();
3173 if n == 0 {
3174 return Ok(epoch);
3175 }
3176 for row in &batch {
3177 self.schema.validate_values(row)?;
3178 }
3179 let live_before = self.live_count;
3180 self.spill_mutable_run(epoch)?;
3184 let eager_index_build = self.index_build_policy == IndexBuildPolicy::Eager
3185 && self.indexes_complete
3186 && self.run_refs.is_empty()
3187 && self.memtable.is_empty()
3188 && self.mutable_run.is_empty();
3189 let mut user_columns: Vec<(u16, columnar::NativeColumn)> = {
3195 use rayon::prelude::*;
3196 self.schema
3197 .columns
3198 .par_iter()
3199 .map(|cdef| {
3200 (
3201 cdef.id,
3202 columnar::rows_to_native(cdef.ty.clone(), &batch, cdef.id),
3203 )
3204 })
3205 .collect::<Vec<_>>()
3206 };
3207 drop(batch);
3208 self.fill_auto_inc_native_columns(&mut user_columns, n)?;
3213 self.validate_columns_not_null(&user_columns, n)?;
3214 let winner_idx = self
3215 .bulk_pk_winner_indices(&user_columns, n)
3216 .filter(|idx| idx.len() != n);
3217 let (write_columns, write_n): (Vec<(u16, columnar::NativeColumn)>, usize) =
3218 match winner_idx.as_deref() {
3219 Some(idx) => {
3220 let compacted = user_columns
3221 .iter()
3222 .map(|(id, c)| (*id, c.gather(idx)))
3223 .collect();
3224 (compacted, idx.len())
3225 }
3226 None => (std::mem::take(&mut user_columns), n),
3227 };
3228 self.advance_auto_inc_from_native_columns(&write_columns, write_n, live_before)?;
3229 let first = self.allocator.alloc_range(write_n as u64).0;
3230 for rid in first..first + write_n as u64 {
3231 self.reservoir.offer(rid);
3232 }
3233 let run_id = self.next_run_id;
3234 self.next_run_id += 1;
3235 let path = self.run_path(run_id);
3236 let mut writer = RunWriter::new(&self.schema, run_id as u128, epoch, 0)
3237 .clean(true)
3238 .with_lz4()
3239 .with_native_endian();
3240 if let Some(kek) = &self.kek {
3241 writer = writer.with_encryption(kek.as_ref(), self.indexable_column_specs());
3242 }
3243 let header = writer.write_native(&path, &write_columns, write_n, first)?;
3244 self.run_refs.push(RunRef {
3245 run_id: run_id as u128,
3246 level: 0,
3247 epoch_created: epoch.0,
3248 row_count: header.row_count,
3249 });
3250 self.live_count = self.live_count.saturating_add(write_n as u64);
3251 if eager_index_build {
3252 let row_ids: Vec<u64> = (first..first + write_n as u64).collect();
3253 self.index_columns_bulk(&write_columns, &row_ids);
3254 self.indexes_complete = true;
3255 self.build_learned_ranges()?;
3256 } else {
3257 self.indexes_complete = false;
3258 }
3259 self.mark_flushed(epoch)?;
3260 self.persist_manifest(epoch)?;
3261 if eager_index_build {
3262 self.checkpoint_indexes(epoch);
3263 }
3264 self.clear_result_cache();
3265 Ok(epoch)
3266 }
3267
3268 fn rotate_wal(&mut self, epoch: Epoch) -> Result<()> {
3271 let segment = next_wal_segment(&self.dir.join(WAL_DIR))?;
3272 let cipher = self.wal_dek.as_ref().map(|dk| make_cipher(dk));
3273 let segment_no = segment
3276 .file_stem()
3277 .and_then(|s| s.to_str())
3278 .and_then(|s| s.strip_prefix("seg-"))
3279 .and_then(|s| s.parse::<u64>().ok())
3280 .unwrap_or(0);
3281 let mut wal = Wal::create_with_cipher(segment, epoch, cipher, segment_no)?;
3282 wal.set_sync_byte_threshold(self.sync_byte_threshold);
3283 wal.sync()?;
3284 self.wal = WalSink::Private(wal);
3285 Ok(())
3286 }
3287
3288 pub(crate) fn invalidate_pending_cache(&mut self) {
3293 self.result_cache
3294 .lock()
3295 .invalidate(&self.pending_delete_rids, &self.pending_put_cols);
3296 self.pending_delete_rids.clear();
3297 self.pending_put_cols.clear();
3298 }
3299
3300 pub(crate) fn persist_manifest(&self, epoch: Epoch) -> Result<()> {
3301 let mut m = Manifest::new(self.table_id, self.schema.schema_id);
3302 m.current_epoch = epoch.0;
3303 m.next_row_id = self.allocator.current().0;
3304 m.runs = self.run_refs.clone();
3305 m.live_count = self.live_count;
3306 m.global_idx_epoch = self.global_idx_epoch;
3307 m.flushed_epoch = self.flushed_epoch;
3308 m.retiring = self.retiring.clone();
3309 m.auto_inc_next = match self.auto_inc {
3313 Some(ai) if ai.seeded => ai.next,
3314 _ => 0,
3315 };
3316 m.ttl = self.ttl;
3317 let meta_dek = self.manifest_meta_dek();
3318 manifest::write_atomic(&self.dir, &mut m, meta_dek.as_ref())?;
3319 Ok(())
3320 }
3321
3322 pub(crate) fn checkpoint_indexes(&mut self, epoch: Epoch) {
3328 if !self.indexes_complete {
3331 return;
3332 }
3333 let snap = global_idx::IndexSnapshot {
3334 hot: &self.hot,
3335 bitmap: &self.bitmap,
3336 ann: &self.ann,
3337 fm: &self.fm,
3338 sparse: &self.sparse,
3339 minhash: &self.minhash,
3340 learned_range: &self.learned_range,
3341 };
3342 let idx_dek = self.idx_dek();
3344 if global_idx::write_atomic(&self.dir, self.table_id, epoch.0, snap, idx_dek.as_deref())
3345 .is_ok()
3346 {
3347 self.global_idx_epoch = epoch.0;
3348 let _ = self.persist_manifest(epoch);
3349 }
3350 }
3351
3352 pub(crate) fn invalidate_index_checkpoint(&mut self) {
3355 self.global_idx_epoch = 0;
3356 global_idx::remove(&self.dir);
3357 let _ = self.persist_manifest(self.epoch.visible());
3358 }
3359
3360 pub(crate) fn mark_indexes_incomplete(&mut self) {
3361 self.indexes_complete = false;
3362 self.invalidate_index_checkpoint();
3363 }
3364
3365 pub fn get(&self, row_id: RowId, snapshot: Snapshot) -> Option<Row> {
3368 let mut best: Option<(Epoch, Row)> = self.memtable.get_version(row_id, snapshot.epoch);
3369 if let Some((epoch, row)) = self.mutable_run.get_version(row_id, snapshot.epoch) {
3370 if best.as_ref().map(|(be, _)| epoch > *be).unwrap_or(true) {
3371 best = Some((epoch, row));
3372 }
3373 }
3374 for rr in &self.run_refs {
3375 let Ok(mut reader) = self.open_reader(rr.run_id) else {
3376 continue;
3377 };
3378 let Ok(Some((epoch, row))) = reader.get_version(row_id, snapshot.epoch) else {
3379 continue;
3380 };
3381 if best.as_ref().map(|(be, _)| epoch > *be).unwrap_or(true) {
3382 best = Some((epoch, row));
3383 }
3384 }
3385 let now_nanos = unix_nanos_now();
3386 match best {
3387 Some((_, r)) if r.deleted || self.row_expired_at(&r, now_nanos) => None,
3388 Some((_, r)) => Some(r),
3389 None => None,
3390 }
3391 }
3392
3393 pub fn visible_rows(&self, snapshot: Snapshot) -> Result<Vec<Row>> {
3397 let mut best: HashMap<u64, (Epoch, Row)> = HashMap::new();
3398 let mut fold = |row: Row| {
3399 best.entry(row.row_id.0)
3400 .and_modify(|e| {
3401 if row.committed_epoch > e.0 {
3402 *e = (row.committed_epoch, row.clone());
3403 }
3404 })
3405 .or_insert_with(|| (row.committed_epoch, row));
3406 };
3407 for row in self.memtable.visible_versions(snapshot.epoch) {
3408 fold(row);
3409 }
3410 for row in self.mutable_run.visible_versions(snapshot.epoch) {
3411 fold(row);
3412 }
3413 for rr in &self.run_refs {
3414 let mut reader = self.open_reader(rr.run_id)?;
3415 for row in reader.visible_versions(snapshot.epoch)? {
3416 fold(row);
3417 }
3418 }
3419 let now_nanos = unix_nanos_now();
3420 let mut out: Vec<Row> = best
3421 .into_values()
3422 .filter_map(|(_, r)| {
3423 if r.deleted || self.row_expired_at(&r, now_nanos) {
3424 None
3425 } else {
3426 Some(r)
3427 }
3428 })
3429 .collect();
3430 out.sort_by_key(|r| r.row_id);
3431 Ok(out)
3432 }
3433
3434 pub fn visible_columns(&self, snapshot: Snapshot) -> Result<Vec<(u16, Vec<Value>)>> {
3441 if self.ttl.is_none()
3442 && self.memtable.is_empty()
3443 && self.mutable_run.is_empty()
3444 && self.run_refs.len() == 1
3445 {
3446 let rr = self.run_refs[0].clone();
3447 let mut reader = self.open_reader(rr.run_id)?;
3448 let idxs = reader.visible_indices(snapshot.epoch)?;
3449 let mut cols = Vec::with_capacity(self.schema.columns.len());
3450 for cdef in &self.schema.columns {
3451 cols.push((cdef.id, reader.gather_column(cdef.id, &idxs)?));
3452 }
3453 return Ok(cols);
3454 }
3455 let rows = self.visible_rows(snapshot)?;
3457 let mut cols: Vec<(u16, Vec<Value>)> = self
3458 .schema
3459 .columns
3460 .iter()
3461 .map(|c| (c.id, Vec::with_capacity(rows.len())))
3462 .collect();
3463 for r in &rows {
3464 for (cid, vec) in cols.iter_mut() {
3465 vec.push(r.columns.get(cid).cloned().unwrap_or(Value::Null));
3466 }
3467 }
3468 Ok(cols)
3469 }
3470
3471 pub fn lookup_pk(&self, key: &[u8]) -> Option<RowId> {
3473 let row_id = self.hot.get(key)?;
3474 if self.ttl.is_none() || self.get(row_id, Snapshot::at(Epoch(u64::MAX))).is_some() {
3475 Some(row_id)
3476 } else {
3477 None
3478 }
3479 }
3480
3481 pub fn query(&mut self, q: &crate::query::Query) -> Result<Vec<Row>> {
3486 self.query_at_with_allowed(q, self.snapshot(), None)
3487 }
3488
3489 pub fn query_at_with_allowed(
3492 &mut self,
3493 q: &crate::query::Query,
3494 snapshot: Snapshot,
3495 allowed: Option<&std::collections::HashSet<RowId>>,
3496 ) -> Result<Vec<Row>> {
3497 self.require_select()?;
3498 self.ensure_indexes_complete()?;
3499 if q.conditions.len() > crate::query::MAX_HARD_CONDITIONS {
3500 return Err(MongrelError::InvalidArgument(format!(
3501 "query exceeds {} conditions",
3502 crate::query::MAX_HARD_CONDITIONS
3503 )));
3504 }
3505 if let Some(limit) = q.limit {
3506 if limit == 0 || limit > crate::query::MAX_FINAL_LIMIT {
3507 return Err(MongrelError::InvalidArgument(format!(
3508 "query limit must be between 1 and {}",
3509 crate::query::MAX_FINAL_LIMIT
3510 )));
3511 }
3512 }
3513 self.query_conditions_at(&q.conditions, snapshot, allowed, q.limit, q.offset)
3514 }
3515
3516 #[doc(hidden)]
3519 pub fn query_all_at(
3520 &mut self,
3521 conditions: &[crate::query::Condition],
3522 snapshot: Snapshot,
3523 ) -> Result<Vec<Row>> {
3524 self.require_select()?;
3525 self.ensure_indexes_complete()?;
3526 if conditions.len() > crate::query::MAX_HARD_CONDITIONS {
3527 return Err(MongrelError::InvalidArgument(format!(
3528 "query exceeds {} conditions",
3529 crate::query::MAX_HARD_CONDITIONS
3530 )));
3531 }
3532 self.query_conditions_at(conditions, snapshot, None, None, 0)
3533 }
3534
3535 fn query_conditions_at(
3536 &self,
3537 conditions: &[crate::query::Condition],
3538 snapshot: Snapshot,
3539 allowed: Option<&std::collections::HashSet<RowId>>,
3540 limit: Option<usize>,
3541 offset: usize,
3542 ) -> Result<Vec<Row>> {
3543 crate::trace::QueryTrace::record(|t| {
3544 t.run_count = self.run_refs.len();
3545 t.memtable_rows = self.memtable.len();
3546 t.mutable_run_rows = self.mutable_run.len();
3547 });
3548 if conditions.is_empty() {
3552 crate::trace::QueryTrace::record(|t| {
3553 t.scan_mode = crate::trace::ScanMode::Materialized;
3554 t.row_materialized = true;
3555 });
3556 let mut rows = self.visible_rows(snapshot)?;
3557 if let Some(allowed) = allowed {
3558 rows.retain(|row| allowed.contains(&row.row_id));
3559 }
3560 rows.drain(..offset.min(rows.len()));
3561 if let Some(limit) = limit {
3562 rows.truncate(limit);
3563 }
3564 return Ok(rows);
3565 }
3566 crate::trace::QueryTrace::record(|t| {
3567 t.conditions_pushed = conditions.len();
3568 t.scan_mode = crate::trace::ScanMode::Materialized;
3569 t.row_materialized = true;
3570 });
3571 let mut ordered: Vec<&crate::query::Condition> = conditions.iter().collect();
3578 ordered.sort_by_key(|c| condition_cost_rank(c));
3579 let mut sets: Vec<RowIdSet> = Vec::with_capacity(ordered.len());
3580 for c in &ordered {
3581 let s = self.resolve_condition_with_allowed(c, snapshot, allowed)?;
3582 let empty = s.is_empty();
3583 sets.push(s);
3584 if empty {
3585 break;
3586 }
3587 }
3588 let mut rids = RowIdSet::intersect_many(sets).into_sorted_vec();
3589 if let Some(allowed) = allowed {
3590 rids.retain(|row_id| allowed.contains(&RowId(*row_id)));
3591 }
3592 rids.drain(..offset.min(rids.len()));
3593 if let Some(limit) = limit {
3594 rids.truncate(limit);
3595 }
3596 self.rows_for_rids(&rids, snapshot)
3597 }
3598
3599 pub fn retrieve(
3601 &mut self,
3602 retriever: &crate::query::Retriever,
3603 ) -> Result<Vec<crate::query::RetrieverHit>> {
3604 self.retrieve_with_allowed(retriever, None)
3605 }
3606
3607 pub fn retrieve_at(
3608 &mut self,
3609 retriever: &crate::query::Retriever,
3610 snapshot: Snapshot,
3611 allowed: Option<&std::collections::HashSet<RowId>>,
3612 ) -> Result<Vec<crate::query::RetrieverHit>> {
3613 self.retrieve_at_with_allowed(retriever, snapshot, allowed)
3614 }
3615
3616 pub fn retrieve_with_allowed(
3619 &mut self,
3620 retriever: &crate::query::Retriever,
3621 allowed: Option<&std::collections::HashSet<RowId>>,
3622 ) -> Result<Vec<crate::query::RetrieverHit>> {
3623 self.retrieve_at_with_allowed(retriever, self.snapshot(), allowed)
3624 }
3625
3626 pub fn retrieve_at_with_allowed(
3627 &mut self,
3628 retriever: &crate::query::Retriever,
3629 snapshot: Snapshot,
3630 allowed: Option<&std::collections::HashSet<RowId>>,
3631 ) -> Result<Vec<crate::query::RetrieverHit>> {
3632 self.retrieve_at_with_allowed_and_context(retriever, snapshot, allowed, None)
3633 }
3634
3635 pub fn retrieve_at_with_allowed_and_context(
3636 &mut self,
3637 retriever: &crate::query::Retriever,
3638 snapshot: Snapshot,
3639 allowed: Option<&std::collections::HashSet<RowId>>,
3640 context: Option<&crate::query::AiExecutionContext>,
3641 ) -> Result<Vec<crate::query::RetrieverHit>> {
3642 self.require_select()?;
3643 self.ensure_indexes_complete()?;
3644 self.validate_retriever(retriever)?;
3645 self.retrieve_filtered(retriever, snapshot, None, allowed, None, context)
3646 }
3647
3648 pub fn retrieve_at_with_candidate_authorization_and_context(
3649 &mut self,
3650 retriever: &crate::query::Retriever,
3651 snapshot: Snapshot,
3652 authorization: Option<&crate::security::CandidateAuthorization<'_>>,
3653 context: Option<&crate::query::AiExecutionContext>,
3654 ) -> Result<Vec<crate::query::RetrieverHit>> {
3655 self.require_select()?;
3656 self.ensure_indexes_complete()?;
3657 self.validate_retriever(retriever)?;
3658 self.retrieve_filtered(retriever, snapshot, None, None, authorization, context)
3659 }
3660
3661 fn validate_retriever(&self, retriever: &crate::query::Retriever) -> Result<()> {
3662 use crate::query::{Retriever, MAX_RETRIEVER_K, MAX_SET_MEMBERS, MAX_SPARSE_TERMS};
3663 let (column_id, k) = match retriever {
3664 Retriever::Ann {
3665 column_id,
3666 query,
3667 k,
3668 } => {
3669 let index = self.ann.get(column_id).ok_or_else(|| {
3670 MongrelError::InvalidArgument(format!("column {column_id} has no ANN index"))
3671 })?;
3672 if query.len() != index.dim() {
3673 return Err(MongrelError::InvalidArgument(format!(
3674 "ANN query dimension must be {}, got {}",
3675 index.dim(),
3676 query.len()
3677 )));
3678 }
3679 if query.iter().any(|value| !value.is_finite()) {
3680 return Err(MongrelError::InvalidArgument(
3681 "ANN query values must be finite".into(),
3682 ));
3683 }
3684 (*column_id, *k)
3685 }
3686 Retriever::Sparse {
3687 column_id,
3688 query,
3689 k,
3690 } => {
3691 if !self.sparse.contains_key(column_id) {
3692 return Err(MongrelError::InvalidArgument(format!(
3693 "column {column_id} has no Sparse index"
3694 )));
3695 }
3696 if query.is_empty() || query.iter().any(|(_, weight)| !weight.is_finite()) {
3697 return Err(MongrelError::InvalidArgument(
3698 "Sparse query must be non-empty with finite weights".into(),
3699 ));
3700 }
3701 if query.len() > MAX_SPARSE_TERMS {
3702 return Err(MongrelError::InvalidArgument(format!(
3703 "Sparse query exceeds {MAX_SPARSE_TERMS} terms"
3704 )));
3705 }
3706 (*column_id, *k)
3707 }
3708 Retriever::MinHash {
3709 column_id,
3710 members,
3711 k,
3712 } => {
3713 if !self.minhash.contains_key(column_id) {
3714 return Err(MongrelError::InvalidArgument(format!(
3715 "column {column_id} has no MinHash index"
3716 )));
3717 }
3718 if members.is_empty() {
3719 return Err(MongrelError::InvalidArgument(
3720 "MinHash members must not be empty".into(),
3721 ));
3722 }
3723 if members.len() > MAX_SET_MEMBERS {
3724 return Err(MongrelError::InvalidArgument(format!(
3725 "MinHash query exceeds {MAX_SET_MEMBERS} members"
3726 )));
3727 }
3728 (*column_id, *k)
3729 }
3730 };
3731 if k == 0 {
3732 return Err(MongrelError::InvalidArgument(
3733 "retriever k must be > 0".into(),
3734 ));
3735 }
3736 if k > MAX_RETRIEVER_K {
3737 return Err(MongrelError::InvalidArgument(format!(
3738 "retriever k exceeds {MAX_RETRIEVER_K}"
3739 )));
3740 }
3741 debug_assert!(self
3742 .schema
3743 .columns
3744 .iter()
3745 .any(|column| column.id == column_id));
3746 Ok(())
3747 }
3748
3749 fn validate_condition(&self, condition: &crate::query::Condition) -> Result<()> {
3750 use crate::query::Condition;
3751 match condition {
3752 Condition::Ann {
3753 column_id,
3754 query,
3755 k,
3756 } => self.validate_retriever(&crate::query::Retriever::Ann {
3757 column_id: *column_id,
3758 query: query.clone(),
3759 k: *k,
3760 }),
3761 Condition::SparseMatch {
3762 column_id,
3763 query,
3764 k,
3765 } => self.validate_retriever(&crate::query::Retriever::Sparse {
3766 column_id: *column_id,
3767 query: query.clone(),
3768 k: *k,
3769 }),
3770 Condition::MinHashSimilar {
3771 column_id,
3772 query,
3773 k,
3774 } => {
3775 if !self.minhash.contains_key(column_id) {
3776 return Err(MongrelError::InvalidArgument(format!(
3777 "column {column_id} has no MinHash index"
3778 )));
3779 }
3780 if query.is_empty() || *k == 0 {
3781 return Err(MongrelError::InvalidArgument(
3782 "MinHash query must be non-empty and k must be > 0".into(),
3783 ));
3784 }
3785 if query.len() > crate::query::MAX_SET_MEMBERS || *k > crate::query::MAX_RETRIEVER_K
3786 {
3787 return Err(MongrelError::InvalidArgument(format!(
3788 "MinHash query must have <= {} members and k <= {}",
3789 crate::query::MAX_SET_MEMBERS,
3790 crate::query::MAX_RETRIEVER_K
3791 )));
3792 }
3793 Ok(())
3794 }
3795 Condition::BitmapIn { values, .. } if values.len() > crate::query::MAX_SET_MEMBERS => {
3796 Err(MongrelError::InvalidArgument(format!(
3797 "bitmap IN exceeds {} values",
3798 crate::query::MAX_SET_MEMBERS
3799 )))
3800 }
3801 Condition::FmContainsAll { patterns, .. }
3802 if patterns.len() > crate::query::MAX_HARD_CONDITIONS =>
3803 {
3804 Err(MongrelError::InvalidArgument(format!(
3805 "FM query exceeds {} patterns",
3806 crate::query::MAX_HARD_CONDITIONS
3807 )))
3808 }
3809 _ => Ok(()),
3810 }
3811 }
3812
3813 fn retrieve_filtered(
3814 &self,
3815 retriever: &crate::query::Retriever,
3816 snapshot: Snapshot,
3817 hard_filter: Option<&RowIdSet>,
3818 allowed: Option<&std::collections::HashSet<RowId>>,
3819 candidate_authorization: Option<&crate::security::CandidateAuthorization<'_>>,
3820 context: Option<&crate::query::AiExecutionContext>,
3821 ) -> Result<Vec<crate::query::RetrieverHit>> {
3822 use crate::query::{Retriever, RetrieverHit, RetrieverScore, SetMember};
3823 let started = std::time::Instant::now();
3824 let scored: Vec<(RowId, RetrieverScore)> = match retriever {
3825 Retriever::Ann {
3826 column_id,
3827 query,
3828 k,
3829 } => {
3830 let Some(index) = self.ann.get(column_id) else {
3831 return Ok(Vec::new());
3832 };
3833 let cap = index.len();
3834 if cap == 0 {
3835 return Ok(Vec::new());
3836 }
3837 let mut breadth = (*k).max(1).min(cap);
3838 let mut eligibility = std::collections::HashMap::new();
3839 let mut filtered = loop {
3840 let mut seen = std::collections::HashSet::new();
3841 if let Some(context) = context {
3842 context.checkpoint()?;
3843 }
3844 let raw = index.search_with_context(query, breadth, context)?;
3845 let unchecked: Vec<_> = raw
3846 .iter()
3847 .map(|(row_id, _)| *row_id)
3848 .filter(|row_id| !eligibility.contains_key(row_id))
3849 .filter(|row_id| {
3850 hard_filter.map_or(true, |filter| filter.contains(row_id.0))
3851 && allowed.map_or(true, |allowed| allowed.contains(row_id))
3852 })
3853 .collect();
3854 let eligible = self.eligible_and_authorized_candidate_ids(
3855 &unchecked,
3856 *column_id,
3857 snapshot,
3858 candidate_authorization,
3859 context,
3860 )?;
3861 for row_id in unchecked {
3862 eligibility.insert(row_id, eligible.contains(&row_id));
3863 }
3864 let filtered: Vec<_> = raw
3865 .into_iter()
3866 .filter(|(row_id, _)| {
3867 seen.insert(*row_id)
3868 && eligibility.get(row_id).copied().unwrap_or(false)
3869 })
3870 .map(|(row_id, score)| (row_id, RetrieverScore::AnnHammingDistance(score)))
3871 .collect();
3872 if filtered.len() >= *k || breadth >= cap {
3873 break filtered;
3874 }
3875 breadth = breadth.saturating_mul(2).min(cap);
3876 };
3877 filtered.truncate(*k);
3878 filtered
3879 }
3880 Retriever::Sparse {
3881 column_id,
3882 query,
3883 k,
3884 } => self
3885 .sparse
3886 .get(column_id)
3887 .map(|index| -> Result<Vec<_>> {
3888 let mut breadth = (*k).max(1);
3889 let mut eligibility = std::collections::HashMap::new();
3890 loop {
3891 if let Some(context) = context {
3892 context.checkpoint()?;
3893 }
3894 let raw = index.search_with_context(query, breadth, context)?;
3895 let unchecked: Vec<_> = raw
3896 .iter()
3897 .map(|(row_id, _)| *row_id)
3898 .filter(|row_id| !eligibility.contains_key(row_id))
3899 .filter(|row_id| {
3900 hard_filter.map_or(true, |filter| filter.contains(row_id.0))
3901 && allowed.map_or(true, |allowed| allowed.contains(row_id))
3902 })
3903 .collect();
3904 let eligible = self.eligible_and_authorized_candidate_ids(
3905 &unchecked,
3906 *column_id,
3907 snapshot,
3908 candidate_authorization,
3909 context,
3910 )?;
3911 for row_id in unchecked {
3912 eligibility.insert(row_id, eligible.contains(&row_id));
3913 }
3914 let filtered: Vec<_> = raw
3915 .iter()
3916 .filter(|(row_id, _)| eligibility.get(row_id).copied().unwrap_or(false))
3917 .take(*k)
3918 .map(|(row_id, score)| {
3919 (*row_id, RetrieverScore::SparseDotProduct(*score))
3920 })
3921 .collect();
3922 if filtered.len() >= *k || raw.len() < breadth {
3923 break Ok(filtered);
3924 }
3925 let next = breadth.saturating_mul(2);
3926 if next == breadth {
3927 break Ok(filtered);
3928 }
3929 breadth = next;
3930 }
3931 })
3932 .transpose()?
3933 .unwrap_or_default(),
3934 Retriever::MinHash {
3935 column_id,
3936 members,
3937 k,
3938 } => self
3939 .minhash
3940 .get(column_id)
3941 .map(|index| -> Result<Vec<_>> {
3942 let hashes: Vec<_> = members.iter().map(SetMember::hash_v1).collect();
3943 let mut breadth = (*k).max(1);
3944 let mut eligibility = std::collections::HashMap::new();
3945 loop {
3946 if let Some(context) = context {
3947 context.checkpoint()?;
3948 }
3949 let raw = index.search_with_context(&hashes, breadth, context)?;
3950 let unchecked: Vec<_> = raw
3951 .iter()
3952 .map(|(row_id, _)| *row_id)
3953 .filter(|row_id| !eligibility.contains_key(row_id))
3954 .filter(|row_id| {
3955 hard_filter.map_or(true, |filter| filter.contains(row_id.0))
3956 && allowed.map_or(true, |allowed| allowed.contains(row_id))
3957 })
3958 .collect();
3959 let eligible = self.eligible_and_authorized_candidate_ids(
3960 &unchecked,
3961 *column_id,
3962 snapshot,
3963 candidate_authorization,
3964 context,
3965 )?;
3966 for row_id in unchecked {
3967 eligibility.insert(row_id, eligible.contains(&row_id));
3968 }
3969 let filtered: Vec<_> = raw
3970 .iter()
3971 .filter(|(row_id, _)| eligibility.get(row_id).copied().unwrap_or(false))
3972 .take(*k)
3973 .map(|(row_id, score)| {
3974 (*row_id, RetrieverScore::MinHashEstimatedJaccard(*score))
3975 })
3976 .collect();
3977 if filtered.len() >= *k || raw.len() < breadth {
3978 break Ok(filtered);
3979 }
3980 let next = breadth.saturating_mul(2);
3981 if next == breadth {
3982 break Ok(filtered);
3983 }
3984 breadth = next;
3985 }
3986 })
3987 .transpose()?
3988 .unwrap_or_default(),
3989 };
3990 let elapsed = started.elapsed().as_nanos() as u64;
3991 crate::trace::QueryTrace::record(|trace| {
3992 match retriever {
3993 Retriever::Ann { .. } => {
3994 trace.ann_candidate_nanos = trace.ann_candidate_nanos.saturating_add(elapsed)
3995 }
3996 Retriever::Sparse { .. } => {
3997 trace.sparse_candidate_nanos =
3998 trace.sparse_candidate_nanos.saturating_add(elapsed)
3999 }
4000 Retriever::MinHash { .. } => {
4001 trace.minhash_candidate_nanos =
4002 trace.minhash_candidate_nanos.saturating_add(elapsed)
4003 }
4004 }
4005 trace.candidate_count = trace.candidate_count.saturating_add(scored.len());
4006 });
4007 Ok(scored
4008 .into_iter()
4009 .enumerate()
4010 .map(|(rank, (row_id, score))| RetrieverHit {
4011 row_id,
4012 rank: rank + 1,
4013 score,
4014 })
4015 .collect())
4016 }
4017
4018 fn eligible_candidate_ids(
4019 &self,
4020 candidates: &[RowId],
4021 _column_id: u16,
4022 snapshot: Snapshot,
4023 context: Option<&crate::query::AiExecutionContext>,
4024 ) -> Result<std::collections::HashSet<RowId>> {
4025 if !self.had_deletes
4026 && self.ttl.is_none()
4027 && self.pending_put_cols.is_empty()
4028 && snapshot.epoch == self.snapshot().epoch
4029 {
4030 return Ok(candidates.iter().copied().collect());
4031 }
4032 let mut readers: Vec<_> = self
4033 .run_refs
4034 .iter()
4035 .map(|run| self.open_reader(run.run_id))
4036 .collect::<Result<_>>()?;
4037 let now = context.map_or_else(unix_nanos_now, |context| context.query_time_nanos());
4038 let mut eligible = std::collections::HashSet::with_capacity(candidates.len());
4039 for &row_id in candidates {
4040 if let Some(context) = context {
4041 context.consume(1)?;
4042 }
4043 let mem = self.memtable.get_version(row_id, snapshot.epoch);
4044 let mutable = self.mutable_run.get_version(row_id, snapshot.epoch);
4045 let overlay = match (mem, mutable) {
4046 (Some(left), Some(right)) => Some(if left.0 >= right.0 { left } else { right }),
4047 (Some(value), None) | (None, Some(value)) => Some(value),
4048 (None, None) => None,
4049 };
4050 if let Some((_, row)) = overlay {
4051 if !row.deleted && !self.row_expired_at(&row, now) {
4052 eligible.insert(row_id);
4053 }
4054 continue;
4055 }
4056 let mut best: Option<(Epoch, bool, usize)> = None;
4057 for (index, reader) in readers.iter_mut().enumerate() {
4058 if let Some((epoch, deleted)) =
4059 reader.get_version_visibility(row_id, snapshot.epoch)?
4060 {
4061 if best
4062 .as_ref()
4063 .map(|(best_epoch, ..)| epoch > *best_epoch)
4064 .unwrap_or(true)
4065 {
4066 best = Some((epoch, deleted, index));
4067 }
4068 }
4069 }
4070 let Some((_, false, reader_index)) = best else {
4071 continue;
4072 };
4073 if let Some(ttl) = self.ttl {
4074 if let Some((_, _, Some(Value::Int64(timestamp)))) = readers[reader_index]
4075 .get_version_column(row_id, snapshot.epoch, ttl.column_id)?
4076 {
4077 if timestamp.saturating_add(ttl.duration_nanos as i64) <= now {
4078 continue;
4079 }
4080 }
4081 }
4082 eligible.insert(row_id);
4083 }
4084 Ok(eligible)
4085 }
4086
4087 fn eligible_and_authorized_candidate_ids(
4088 &self,
4089 candidates: &[RowId],
4090 column_id: u16,
4091 snapshot: Snapshot,
4092 authorization: Option<&crate::security::CandidateAuthorization<'_>>,
4093 context: Option<&crate::query::AiExecutionContext>,
4094 ) -> Result<std::collections::HashSet<RowId>> {
4095 let eligible = self.eligible_candidate_ids(candidates, column_id, snapshot, context)?;
4096 let Some(authorization) = authorization else {
4097 return Ok(eligible);
4098 };
4099 let candidates: Vec<_> = eligible.into_iter().collect();
4100 self.policy_allowed_candidate_ids(&candidates, snapshot, authorization, context)
4101 }
4102
4103 fn policy_allowed_candidate_ids(
4104 &self,
4105 candidates: &[RowId],
4106 snapshot: Snapshot,
4107 authorization: &crate::security::CandidateAuthorization<'_>,
4108 context: Option<&crate::query::AiExecutionContext>,
4109 ) -> Result<std::collections::HashSet<RowId>> {
4110 let started = std::time::Instant::now();
4111 if candidates.is_empty()
4112 || authorization.principal.is_admin
4113 || !authorization.security.rls_enabled(authorization.table)
4114 {
4115 return Ok(candidates.iter().copied().collect());
4116 }
4117 if let Some(context) = context {
4118 context.checkpoint()?;
4119 }
4120 let row_ids: Vec<_> = candidates.iter().map(|row_id| row_id.0).collect();
4121 let mut rows: std::collections::HashMap<RowId, Row> = candidates
4122 .iter()
4123 .map(|row_id| {
4124 (
4125 *row_id,
4126 Row {
4127 row_id: *row_id,
4128 committed_epoch: snapshot.epoch,
4129 columns: std::collections::HashMap::new(),
4130 deleted: false,
4131 },
4132 )
4133 })
4134 .collect();
4135 let columns = authorization
4136 .security
4137 .select_policy_columns(authorization.table, authorization.principal);
4138 let query_now = context.map_or_else(unix_nanos_now, |context| context.query_time_nanos());
4139 let mut decoded = 0usize;
4140 for column_id in &columns {
4141 if let Some(context) = context {
4142 context.checkpoint()?;
4143 }
4144 for (row_id, value) in self.values_for_rids_batch_at_with_context(
4145 &row_ids, *column_id, snapshot, query_now, context,
4146 )? {
4147 if let Some(row) = rows.get_mut(&row_id) {
4148 row.columns.insert(*column_id, value);
4149 decoded = decoded.saturating_add(1);
4150 }
4151 }
4152 }
4153 if let Some(context) = context {
4154 context.consume(candidates.len().saturating_add(decoded))?;
4155 }
4156 let allowed = rows
4157 .into_values()
4158 .filter_map(|row| {
4159 authorization
4160 .security
4161 .row_allowed(
4162 authorization.table,
4163 crate::security::PolicyCommand::Select,
4164 &row,
4165 authorization.principal,
4166 false,
4167 )
4168 .then_some(row.row_id)
4169 })
4170 .collect();
4171 crate::trace::QueryTrace::record(|trace| {
4172 trace.rls_rows_evaluated = trace.rls_rows_evaluated.saturating_add(candidates.len());
4173 trace.rls_policy_columns_decoded =
4174 trace.rls_policy_columns_decoded.saturating_add(decoded);
4175 trace.authorization_nanos = trace
4176 .authorization_nanos
4177 .saturating_add(started.elapsed().as_nanos() as u64);
4178 });
4179 Ok(allowed)
4180 }
4181
4182 pub fn search(
4184 &mut self,
4185 request: &crate::query::SearchRequest,
4186 ) -> Result<Vec<crate::query::SearchHit>> {
4187 self.search_with_allowed(request, None)
4188 }
4189
4190 pub fn search_at(
4191 &mut self,
4192 request: &crate::query::SearchRequest,
4193 snapshot: Snapshot,
4194 authorized: Option<&std::collections::HashSet<RowId>>,
4195 ) -> Result<Vec<crate::query::SearchHit>> {
4196 self.search_at_with_allowed(request, snapshot, authorized)
4197 }
4198
4199 pub fn search_with_allowed(
4200 &mut self,
4201 request: &crate::query::SearchRequest,
4202 authorized: Option<&std::collections::HashSet<RowId>>,
4203 ) -> Result<Vec<crate::query::SearchHit>> {
4204 self.search_at_with_allowed(request, self.snapshot(), authorized)
4205 }
4206
4207 pub fn search_at_with_allowed(
4208 &mut self,
4209 request: &crate::query::SearchRequest,
4210 snapshot: Snapshot,
4211 authorized: Option<&std::collections::HashSet<RowId>>,
4212 ) -> Result<Vec<crate::query::SearchHit>> {
4213 self.search_at_with_allowed_and_context(request, snapshot, authorized, None)
4214 }
4215
4216 pub fn search_at_with_allowed_and_context(
4217 &mut self,
4218 request: &crate::query::SearchRequest,
4219 snapshot: Snapshot,
4220 authorized: Option<&std::collections::HashSet<RowId>>,
4221 context: Option<&crate::query::AiExecutionContext>,
4222 ) -> Result<Vec<crate::query::SearchHit>> {
4223 self.search_at_with_filters_and_context(request, snapshot, authorized, None, context)
4224 }
4225
4226 pub fn search_at_with_candidate_authorization_and_context(
4227 &mut self,
4228 request: &crate::query::SearchRequest,
4229 snapshot: Snapshot,
4230 authorization: Option<&crate::security::CandidateAuthorization<'_>>,
4231 context: Option<&crate::query::AiExecutionContext>,
4232 ) -> Result<Vec<crate::query::SearchHit>> {
4233 self.search_at_with_filters_and_context(request, snapshot, None, authorization, context)
4234 }
4235
4236 fn search_at_with_filters_and_context(
4237 &mut self,
4238 request: &crate::query::SearchRequest,
4239 snapshot: Snapshot,
4240 authorized: Option<&std::collections::HashSet<RowId>>,
4241 candidate_authorization: Option<&crate::security::CandidateAuthorization<'_>>,
4242 context: Option<&crate::query::AiExecutionContext>,
4243 ) -> Result<Vec<crate::query::SearchHit>> {
4244 use crate::query::{
4245 ComponentScore, Condition, Fusion, SearchHit, MAX_FINAL_LIMIT, MAX_HARD_CONDITIONS,
4246 MAX_PROJECTION_COLUMNS, MAX_RETRIEVERS, MAX_RETRIEVER_WEIGHT,
4247 };
4248 let total_started = std::time::Instant::now();
4249 self.require_select()?;
4250 self.ensure_indexes_complete()?;
4251 if request.limit == 0 {
4252 return Err(MongrelError::InvalidArgument(
4253 "search limit must be > 0".into(),
4254 ));
4255 }
4256 if request.limit > MAX_FINAL_LIMIT {
4257 return Err(MongrelError::InvalidArgument(format!(
4258 "search limit exceeds {MAX_FINAL_LIMIT}"
4259 )));
4260 }
4261 if request.retrievers.is_empty() {
4262 return Err(MongrelError::InvalidArgument(
4263 "search requires at least one retriever".into(),
4264 ));
4265 }
4266 if request.retrievers.len() > MAX_RETRIEVERS {
4267 return Err(MongrelError::InvalidArgument(format!(
4268 "search exceeds {MAX_RETRIEVERS} retrievers"
4269 )));
4270 }
4271 if request.must.len() > MAX_HARD_CONDITIONS {
4272 return Err(MongrelError::InvalidArgument(format!(
4273 "search exceeds {MAX_HARD_CONDITIONS} hard conditions"
4274 )));
4275 }
4276 if request.must.iter().any(|condition| {
4277 matches!(
4278 condition,
4279 Condition::Ann { .. }
4280 | Condition::SparseMatch { .. }
4281 | Condition::MinHashSimilar { .. }
4282 )
4283 }) {
4284 return Err(MongrelError::InvalidArgument(
4285 "ranked ANN, Sparse, and MinHash conditions must be retrievers, not must filters"
4286 .into(),
4287 ));
4288 }
4289 let mut names = std::collections::HashSet::new();
4290 for named in &request.retrievers {
4291 if named.name.is_empty() || !names.insert(named.name.as_str()) {
4292 return Err(MongrelError::InvalidArgument(
4293 "retriever names must be non-empty and unique".into(),
4294 ));
4295 }
4296 if !named.weight.is_finite()
4297 || named.weight < 0.0
4298 || named.weight > MAX_RETRIEVER_WEIGHT
4299 {
4300 return Err(MongrelError::InvalidArgument(format!(
4301 "retriever weight must be finite, non-negative, and <= {MAX_RETRIEVER_WEIGHT}"
4302 )));
4303 }
4304 self.validate_retriever(&named.retriever)?;
4305 }
4306 let projection = request
4307 .projection
4308 .clone()
4309 .unwrap_or_else(|| self.schema.columns.iter().map(|column| column.id).collect());
4310 if projection.len() > MAX_PROJECTION_COLUMNS {
4311 return Err(MongrelError::InvalidArgument(format!(
4312 "projection exceeds {MAX_PROJECTION_COLUMNS} columns"
4313 )));
4314 }
4315 for column_id in &projection {
4316 if !self
4317 .schema
4318 .columns
4319 .iter()
4320 .any(|column| column.id == *column_id)
4321 {
4322 return Err(MongrelError::ColumnNotFound(column_id.to_string()));
4323 }
4324 }
4325 if let Some(crate::query::Rerank::ExactVector {
4326 embedding_column,
4327 query,
4328 candidate_limit,
4329 weight,
4330 ..
4331 }) = &request.rerank
4332 {
4333 if *candidate_limit < request.limit || *candidate_limit > crate::query::MAX_RETRIEVER_K
4334 {
4335 return Err(MongrelError::InvalidArgument(format!(
4336 "rerank candidate_limit must be between search limit and {}",
4337 crate::query::MAX_RETRIEVER_K
4338 )));
4339 }
4340 if !weight.is_finite() || *weight < 0.0 || *weight > MAX_RETRIEVER_WEIGHT {
4341 return Err(MongrelError::InvalidArgument(format!(
4342 "rerank weight must be finite, non-negative, and <= {MAX_RETRIEVER_WEIGHT}"
4343 )));
4344 }
4345 let column = self
4346 .schema
4347 .columns
4348 .iter()
4349 .find(|column| column.id == *embedding_column)
4350 .ok_or_else(|| MongrelError::ColumnNotFound(embedding_column.to_string()))?;
4351 let crate::schema::TypeId::Embedding { dim } = column.ty else {
4352 return Err(MongrelError::InvalidArgument(format!(
4353 "rerank column {embedding_column} is not an embedding"
4354 )));
4355 };
4356 if query.len() != dim as usize || query.iter().any(|value| !value.is_finite()) {
4357 return Err(MongrelError::InvalidArgument(format!(
4358 "rerank query must contain {dim} finite values"
4359 )));
4360 }
4361 }
4362
4363 let hard_filter_started = std::time::Instant::now();
4364 let hard_filter = if request.must.is_empty() {
4365 None
4366 } else {
4367 let mut sets = Vec::with_capacity(request.must.len());
4368 for condition in &request.must {
4369 if let Some(context) = context {
4370 context.checkpoint()?;
4371 }
4372 sets.push(self.resolve_condition(condition, snapshot)?);
4373 }
4374 Some(RowIdSet::intersect_many(sets))
4375 };
4376 crate::trace::QueryTrace::record(|trace| {
4377 trace.hard_filter_nanos = trace
4378 .hard_filter_nanos
4379 .saturating_add(hard_filter_started.elapsed().as_nanos() as u64);
4380 });
4381 if hard_filter.as_ref().is_some_and(RowIdSet::is_empty) {
4382 return Ok(Vec::new());
4383 }
4384
4385 let constant = match request.fusion {
4386 Fusion::ReciprocalRank { constant } => constant,
4387 };
4388 let mut retrievers: Vec<_> = request.retrievers.iter().collect();
4389 retrievers.sort_by(|a, b| a.name.cmp(&b.name));
4390 let mut fusion_nanos = 0u64;
4391 let mut fused: std::collections::HashMap<RowId, (f64, Vec<ComponentScore>)> =
4392 std::collections::HashMap::new();
4393 for named in retrievers {
4394 if named.weight == 0.0 {
4395 continue;
4396 }
4397 if let Some(context) = context {
4398 context.checkpoint()?;
4399 }
4400 let hits = self.retrieve_filtered(
4401 &named.retriever,
4402 snapshot,
4403 hard_filter.as_ref(),
4404 authorized,
4405 candidate_authorization,
4406 context,
4407 )?;
4408 let fusion_started = std::time::Instant::now();
4409 for hit in hits {
4410 if let Some(context) = context {
4411 context.consume(1)?;
4412 }
4413 let contribution = named.weight / (constant as f64 + hit.rank as f64);
4414 if !contribution.is_finite() {
4415 return Err(MongrelError::InvalidArgument(
4416 "retriever contribution must be finite".into(),
4417 ));
4418 }
4419 let max_fused_candidates = context.map_or(
4420 crate::query::MAX_FUSED_CANDIDATES,
4421 crate::query::AiExecutionContext::max_fused_candidates,
4422 );
4423 if !fused.contains_key(&hit.row_id) && fused.len() >= max_fused_candidates {
4424 return Err(MongrelError::WorkBudgetExceeded);
4425 }
4426 let entry = fused.entry(hit.row_id).or_default();
4427 entry.0 += contribution;
4428 if !entry.0.is_finite() {
4429 return Err(MongrelError::InvalidArgument(
4430 "fused score must be finite".into(),
4431 ));
4432 }
4433 entry.1.push(ComponentScore {
4434 retriever_name: named.name.clone(),
4435 rank: hit.rank,
4436 raw_score: hit.score,
4437 contribution,
4438 });
4439 }
4440 fusion_nanos = fusion_nanos.saturating_add(fusion_started.elapsed().as_nanos() as u64);
4441 }
4442 let union_size = fused.len();
4443 let mut ranked: Vec<_> = fused
4444 .into_iter()
4445 .map(|(row_id, (fused_score, components))| {
4446 (row_id, fused_score, components, None, fused_score)
4447 })
4448 .collect();
4449 let order = |(a_row, _, _, _, a_score): &(
4450 RowId,
4451 f64,
4452 Vec<ComponentScore>,
4453 Option<f32>,
4454 f64,
4455 ),
4456 (b_row, _, _, _, b_score): &(
4457 RowId,
4458 f64,
4459 Vec<ComponentScore>,
4460 Option<f32>,
4461 f64,
4462 )| { b_score.total_cmp(a_score).then_with(|| a_row.cmp(b_row)) };
4463 if let Some(crate::query::Rerank::ExactVector {
4464 embedding_column,
4465 query,
4466 metric,
4467 candidate_limit,
4468 weight,
4469 }) = &request.rerank
4470 {
4471 let fused_order = |(a_row, a_score, ..): &(
4472 RowId,
4473 f64,
4474 Vec<ComponentScore>,
4475 Option<f32>,
4476 f64,
4477 ),
4478 (b_row, b_score, ..): &(
4479 RowId,
4480 f64,
4481 Vec<ComponentScore>,
4482 Option<f32>,
4483 f64,
4484 )| {
4485 b_score.total_cmp(a_score).then_with(|| a_row.cmp(b_row))
4486 };
4487 let selection_started = std::time::Instant::now();
4488 if let Some(context) = context {
4489 context.consume(ranked.len())?;
4490 }
4491 if ranked.len() > *candidate_limit {
4492 let (_, _, _) = ranked.select_nth_unstable_by(*candidate_limit, fused_order);
4493 ranked.truncate(*candidate_limit);
4494 }
4495 ranked.sort_by(fused_order);
4496 fusion_nanos =
4497 fusion_nanos.saturating_add(selection_started.elapsed().as_nanos() as u64);
4498 let row_ids: Vec<_> = ranked.iter().map(|(row_id, ..)| row_id.0).collect();
4499 if let Some(context) = context {
4500 context.consume(row_ids.len())?;
4501 }
4502 let query_now =
4503 context.map_or_else(unix_nanos_now, |context| context.query_time_nanos());
4504 let gather_started = std::time::Instant::now();
4505 let vectors = self.values_for_rids_batch_at_with_context(
4506 &row_ids,
4507 *embedding_column,
4508 snapshot,
4509 query_now,
4510 context,
4511 )?;
4512 let gather_nanos = gather_started.elapsed().as_nanos() as u64;
4513 let query_norm = query
4514 .iter()
4515 .map(|value| f64::from(*value).powi(2))
4516 .sum::<f64>()
4517 .sqrt();
4518 let score_started = std::time::Instant::now();
4519 let mut scores = std::collections::HashMap::with_capacity(vectors.len());
4520 for (row_id, value) in vectors {
4521 if let Some(context) = context {
4522 context.consume(1)?;
4523 }
4524 let Value::Embedding(vector) = value else {
4525 continue;
4526 };
4527 let dot = query
4528 .iter()
4529 .zip(&vector)
4530 .map(|(left, right)| f64::from(*left) * f64::from(*right))
4531 .sum::<f64>();
4532 let score = match metric {
4533 crate::query::VectorMetric::DotProduct => dot,
4534 crate::query::VectorMetric::Cosine => {
4535 let norm = vector
4536 .iter()
4537 .map(|value| f64::from(*value).powi(2))
4538 .sum::<f64>()
4539 .sqrt();
4540 if query_norm == 0.0 || norm == 0.0 {
4541 0.0
4542 } else {
4543 dot / (query_norm * norm)
4544 }
4545 }
4546 crate::query::VectorMetric::Euclidean => query
4547 .iter()
4548 .zip(&vector)
4549 .map(|(left, right)| (f64::from(*left) - f64::from(*right)).powi(2))
4550 .sum::<f64>()
4551 .sqrt(),
4552 };
4553 if !score.is_finite() {
4554 return Err(MongrelError::InvalidArgument(
4555 "exact rerank score must be finite".into(),
4556 ));
4557 }
4558 scores.insert(row_id, score as f32);
4559 }
4560 let mut reranked = Vec::with_capacity(ranked.len());
4561 for (row_id, fused_score, components, _, _) in ranked.drain(..) {
4562 let Some(score) = scores.get(&row_id).copied() else {
4563 continue;
4564 };
4565 let ordering_score = match metric {
4566 crate::query::VectorMetric::Euclidean => -f64::from(score),
4567 crate::query::VectorMetric::Cosine | crate::query::VectorMetric::DotProduct => {
4568 f64::from(score)
4569 }
4570 };
4571 let final_score = fused_score + *weight * ordering_score;
4572 if !final_score.is_finite() {
4573 return Err(MongrelError::InvalidArgument(
4574 "final rerank score must be finite".into(),
4575 ));
4576 }
4577 reranked.push((row_id, fused_score, components, Some(score), final_score));
4578 }
4579 ranked = reranked;
4580 ranked.sort_by(order);
4581 crate::trace::QueryTrace::record(|trace| {
4582 trace.exact_vector_gather_nanos =
4583 trace.exact_vector_gather_nanos.saturating_add(gather_nanos);
4584 trace.exact_vector_score_nanos = trace
4585 .exact_vector_score_nanos
4586 .saturating_add(score_started.elapsed().as_nanos() as u64);
4587 });
4588 }
4589 let projection_started = std::time::Instant::now();
4590 let sentinel = projection
4591 .first()
4592 .copied()
4593 .or_else(|| self.schema.columns.first().map(|column| column.id));
4594 let query_now = context.map_or_else(unix_nanos_now, |context| context.query_time_nanos());
4595 let mut out = Vec::with_capacity(request.limit.min(ranked.len()));
4596 let mut projection_rows = 0usize;
4597 let mut projection_cells = 0usize;
4598 while out.len() < request.limit && !ranked.is_empty() {
4599 if let Some(context) = context {
4600 context.checkpoint()?;
4601 context.consume(ranked.len())?;
4602 }
4603 let needed = request.limit - out.len();
4604 let window_size = ranked
4605 .len()
4606 .min(needed.saturating_mul(2).max(needed.saturating_add(8)));
4607 let selection_started = std::time::Instant::now();
4608 let mut remainder = if ranked.len() > window_size {
4609 let (_, _, _) = ranked.select_nth_unstable_by(window_size, order);
4610 ranked.split_off(window_size)
4611 } else {
4612 Vec::new()
4613 };
4614 ranked.sort_by(order);
4615 fusion_nanos =
4616 fusion_nanos.saturating_add(selection_started.elapsed().as_nanos() as u64);
4617 let row_ids: Vec<_> = ranked.iter().map(|(row_id, ..)| row_id.0).collect();
4618 let gathered_columns = projection.len().max(usize::from(sentinel.is_some()));
4619 if let Some(context) = context {
4620 context.consume(row_ids.len().saturating_mul(gathered_columns))?;
4621 }
4622 projection_rows = projection_rows.saturating_add(row_ids.len());
4623 projection_cells =
4624 projection_cells.saturating_add(row_ids.len().saturating_mul(gathered_columns));
4625 let mut cells: std::collections::HashMap<RowId, std::collections::HashMap<u16, Value>> =
4626 std::collections::HashMap::new();
4627 if let Some(column_id) = sentinel {
4628 for (row_id, value) in self.values_for_rids_batch_at_with_context(
4629 &row_ids, column_id, snapshot, query_now, context,
4630 )? {
4631 cells.entry(row_id).or_default().insert(column_id, value);
4632 }
4633 }
4634 for &column_id in &projection {
4635 if Some(column_id) == sentinel {
4636 continue;
4637 }
4638 for (row_id, value) in self.values_for_rids_batch_at_with_context(
4639 &row_ids, column_id, snapshot, query_now, context,
4640 )? {
4641 cells.entry(row_id).or_default().insert(column_id, value);
4642 }
4643 }
4644 for (row_id, fused_score, mut components, exact_rerank_score, final_score) in
4645 ranked.drain(..)
4646 {
4647 let Some(row_cells) = cells.remove(&row_id) else {
4648 continue;
4649 };
4650 components.sort_by(|a, b| a.retriever_name.cmp(&b.retriever_name));
4651 let final_rank = out.len() + 1;
4652 out.push(SearchHit {
4653 row_id,
4654 cells: projection
4655 .iter()
4656 .filter_map(|column_id| {
4657 row_cells
4658 .get(column_id)
4659 .cloned()
4660 .map(|value| (*column_id, value))
4661 })
4662 .collect(),
4663 components,
4664 fused_score,
4665 exact_rerank_score,
4666 final_score,
4667 final_rank,
4668 });
4669 if out.len() == request.limit {
4670 break;
4671 }
4672 }
4673 ranked.append(&mut remainder);
4674 }
4675 crate::trace::QueryTrace::record(|trace| {
4676 trace.union_size = union_size;
4677 trace.fusion_nanos = trace.fusion_nanos.saturating_add(fusion_nanos);
4678 trace.projection_nanos = trace
4679 .projection_nanos
4680 .saturating_add(projection_started.elapsed().as_nanos() as u64);
4681 trace.total_nanos = trace
4682 .total_nanos
4683 .saturating_add(total_started.elapsed().as_nanos() as u64);
4684 trace.projection_rows = trace.projection_rows.saturating_add(projection_rows);
4685 trace.projection_cells = trace.projection_cells.saturating_add(projection_cells);
4686 if let Some(context) = context {
4687 trace.work_consumed = trace.work_consumed.saturating_add(context.consumed_work());
4688 }
4689 });
4690 Ok(out)
4691 }
4692
4693 pub fn set_similarity(
4696 &mut self,
4697 request: &crate::query::SetSimilarityRequest,
4698 ) -> Result<Vec<crate::query::SetSimilarityHit>> {
4699 self.set_similarity_with_allowed(request, None)
4700 }
4701
4702 pub fn set_similarity_at(
4703 &mut self,
4704 request: &crate::query::SetSimilarityRequest,
4705 snapshot: Snapshot,
4706 allowed: Option<&std::collections::HashSet<RowId>>,
4707 ) -> Result<Vec<crate::query::SetSimilarityHit>> {
4708 self.set_similarity_explained_at(request, snapshot, allowed)
4709 .map(|(hits, _)| hits)
4710 }
4711
4712 pub fn ann_rerank(
4714 &mut self,
4715 request: &crate::query::AnnRerankRequest,
4716 ) -> Result<Vec<crate::query::AnnRerankHit>> {
4717 self.ann_rerank_with_allowed(request, None)
4718 }
4719
4720 pub fn ann_rerank_with_allowed(
4721 &mut self,
4722 request: &crate::query::AnnRerankRequest,
4723 allowed: Option<&std::collections::HashSet<RowId>>,
4724 ) -> Result<Vec<crate::query::AnnRerankHit>> {
4725 self.ann_rerank_at(request, self.snapshot(), allowed)
4726 }
4727
4728 pub fn ann_rerank_at(
4729 &mut self,
4730 request: &crate::query::AnnRerankRequest,
4731 snapshot: Snapshot,
4732 allowed: Option<&std::collections::HashSet<RowId>>,
4733 ) -> Result<Vec<crate::query::AnnRerankHit>> {
4734 self.ann_rerank_at_with_context(request, snapshot, allowed, None)
4735 }
4736
4737 pub fn ann_rerank_at_with_context(
4738 &mut self,
4739 request: &crate::query::AnnRerankRequest,
4740 snapshot: Snapshot,
4741 allowed: Option<&std::collections::HashSet<RowId>>,
4742 context: Option<&crate::query::AiExecutionContext>,
4743 ) -> Result<Vec<crate::query::AnnRerankHit>> {
4744 self.ann_rerank_at_with_filters_and_context(request, snapshot, allowed, None, context)
4745 }
4746
4747 pub fn ann_rerank_at_with_candidate_authorization_and_context(
4748 &mut self,
4749 request: &crate::query::AnnRerankRequest,
4750 snapshot: Snapshot,
4751 authorization: Option<&crate::security::CandidateAuthorization<'_>>,
4752 context: Option<&crate::query::AiExecutionContext>,
4753 ) -> Result<Vec<crate::query::AnnRerankHit>> {
4754 self.ann_rerank_at_with_filters_and_context(request, snapshot, None, authorization, context)
4755 }
4756
4757 fn ann_rerank_at_with_filters_and_context(
4758 &mut self,
4759 request: &crate::query::AnnRerankRequest,
4760 snapshot: Snapshot,
4761 allowed: Option<&std::collections::HashSet<RowId>>,
4762 candidate_authorization: Option<&crate::security::CandidateAuthorization<'_>>,
4763 context: Option<&crate::query::AiExecutionContext>,
4764 ) -> Result<Vec<crate::query::AnnRerankHit>> {
4765 use crate::query::{
4766 AnnRerankHit, Retriever, RetrieverScore, VectorMetric, MAX_FINAL_LIMIT, MAX_RETRIEVER_K,
4767 };
4768 if request.candidate_k == 0 || request.limit == 0 {
4769 return Err(MongrelError::InvalidArgument(
4770 "candidate_k and limit must be > 0".into(),
4771 ));
4772 }
4773 if request.candidate_k > MAX_RETRIEVER_K || request.limit > MAX_FINAL_LIMIT {
4774 return Err(MongrelError::InvalidArgument(format!(
4775 "candidate_k must be <= {MAX_RETRIEVER_K} and limit <= {MAX_FINAL_LIMIT}"
4776 )));
4777 }
4778 let retriever = Retriever::Ann {
4779 column_id: request.column_id,
4780 query: request.query.clone(),
4781 k: request.candidate_k,
4782 };
4783 self.require_select()?;
4784 self.ensure_indexes_complete()?;
4785 self.validate_retriever(&retriever)?;
4786 let hits = self.retrieve_filtered(
4787 &retriever,
4788 snapshot,
4789 None,
4790 allowed,
4791 candidate_authorization,
4792 context,
4793 )?;
4794 let distances: std::collections::HashMap<_, _> = hits
4795 .iter()
4796 .filter_map(|hit| match hit.score {
4797 RetrieverScore::AnnHammingDistance(distance) => Some((hit.row_id, distance)),
4798 _ => None,
4799 })
4800 .collect();
4801 let row_ids: Vec<_> = hits.iter().map(|hit| hit.row_id.0).collect();
4802 if let Some(context) = context {
4803 context.consume(row_ids.len())?;
4804 }
4805 let gather_started = std::time::Instant::now();
4806 let query_now = context.map_or_else(unix_nanos_now, |context| context.query_time_nanos());
4807 let values = self.values_for_rids_batch_at_with_context(
4808 &row_ids,
4809 request.column_id,
4810 snapshot,
4811 query_now,
4812 context,
4813 )?;
4814 let gather_nanos = gather_started.elapsed().as_nanos() as u64;
4815 let score_started = std::time::Instant::now();
4816 let query_norm = request
4817 .query
4818 .iter()
4819 .map(|value| f64::from(*value).powi(2))
4820 .sum::<f64>()
4821 .sqrt();
4822 let mut reranked = Vec::with_capacity(values.len().min(request.limit));
4823 for (row_id, value) in values {
4824 if let Some(context) = context {
4825 context.consume(1)?;
4826 }
4827 let Value::Embedding(vector) = value else {
4828 continue;
4829 };
4830 let dot = request
4831 .query
4832 .iter()
4833 .zip(&vector)
4834 .map(|(left, right)| f64::from(*left) * f64::from(*right))
4835 .sum::<f64>();
4836 let exact_score = match request.metric {
4837 VectorMetric::DotProduct => dot,
4838 VectorMetric::Cosine => {
4839 let norm = vector
4840 .iter()
4841 .map(|value| f64::from(*value).powi(2))
4842 .sum::<f64>()
4843 .sqrt();
4844 if query_norm == 0.0 || norm == 0.0 {
4845 0.0
4846 } else {
4847 dot / (query_norm * norm)
4848 }
4849 }
4850 VectorMetric::Euclidean => request
4851 .query
4852 .iter()
4853 .zip(&vector)
4854 .map(|(left, right)| (f64::from(*left) - f64::from(*right)).powi(2))
4855 .sum::<f64>()
4856 .sqrt(),
4857 };
4858 let exact_score = exact_score as f32;
4859 if !exact_score.is_finite() {
4860 return Err(MongrelError::InvalidArgument(
4861 "exact ANN score must be finite".into(),
4862 ));
4863 }
4864 reranked.push(AnnRerankHit {
4865 row_id,
4866 hamming_distance: distances.get(&row_id).copied().unwrap_or_default(),
4867 exact_score,
4868 });
4869 }
4870 reranked.sort_by(|left, right| {
4871 let score = match request.metric {
4872 VectorMetric::Euclidean => left.exact_score.total_cmp(&right.exact_score),
4873 VectorMetric::Cosine | VectorMetric::DotProduct => {
4874 right.exact_score.total_cmp(&left.exact_score)
4875 }
4876 };
4877 score.then_with(|| left.row_id.cmp(&right.row_id))
4878 });
4879 reranked.truncate(request.limit);
4880 crate::trace::QueryTrace::record(|trace| {
4881 trace.exact_vector_gather_nanos =
4882 trace.exact_vector_gather_nanos.saturating_add(gather_nanos);
4883 trace.exact_vector_score_nanos = trace
4884 .exact_vector_score_nanos
4885 .saturating_add(score_started.elapsed().as_nanos() as u64);
4886 });
4887 Ok(reranked)
4888 }
4889
4890 pub fn set_similarity_with_allowed(
4891 &mut self,
4892 request: &crate::query::SetSimilarityRequest,
4893 allowed: Option<&std::collections::HashSet<RowId>>,
4894 ) -> Result<Vec<crate::query::SetSimilarityHit>> {
4895 self.set_similarity_explained_at(request, self.snapshot(), allowed)
4896 .map(|(hits, _)| hits)
4897 }
4898
4899 pub fn set_similarity_explained(
4900 &mut self,
4901 request: &crate::query::SetSimilarityRequest,
4902 ) -> Result<(
4903 Vec<crate::query::SetSimilarityHit>,
4904 crate::query::SetSimilarityTrace,
4905 )> {
4906 self.set_similarity_explained_at(request, self.snapshot(), None)
4907 }
4908
4909 fn set_similarity_explained_at(
4910 &mut self,
4911 request: &crate::query::SetSimilarityRequest,
4912 snapshot: Snapshot,
4913 allowed: Option<&std::collections::HashSet<RowId>>,
4914 ) -> Result<(
4915 Vec<crate::query::SetSimilarityHit>,
4916 crate::query::SetSimilarityTrace,
4917 )> {
4918 self.set_similarity_explained_at_with_context(request, snapshot, allowed, None, None)
4919 }
4920
4921 pub fn set_similarity_at_with_context(
4922 &mut self,
4923 request: &crate::query::SetSimilarityRequest,
4924 snapshot: Snapshot,
4925 allowed: Option<&std::collections::HashSet<RowId>>,
4926 context: Option<&crate::query::AiExecutionContext>,
4927 ) -> Result<Vec<crate::query::SetSimilarityHit>> {
4928 self.set_similarity_explained_at_with_context(request, snapshot, allowed, None, context)
4929 .map(|(hits, _)| hits)
4930 }
4931
4932 pub fn set_similarity_at_with_candidate_authorization_and_context(
4933 &mut self,
4934 request: &crate::query::SetSimilarityRequest,
4935 snapshot: Snapshot,
4936 authorization: Option<&crate::security::CandidateAuthorization<'_>>,
4937 context: Option<&crate::query::AiExecutionContext>,
4938 ) -> Result<Vec<crate::query::SetSimilarityHit>> {
4939 self.set_similarity_explained_at_with_context(
4940 request,
4941 snapshot,
4942 None,
4943 authorization,
4944 context,
4945 )
4946 .map(|(hits, _)| hits)
4947 }
4948
4949 fn set_similarity_explained_at_with_context(
4950 &mut self,
4951 request: &crate::query::SetSimilarityRequest,
4952 snapshot: Snapshot,
4953 allowed: Option<&std::collections::HashSet<RowId>>,
4954 candidate_authorization: Option<&crate::security::CandidateAuthorization<'_>>,
4955 context: Option<&crate::query::AiExecutionContext>,
4956 ) -> Result<(
4957 Vec<crate::query::SetSimilarityHit>,
4958 crate::query::SetSimilarityTrace,
4959 )> {
4960 use crate::query::{
4961 Retriever, RetrieverScore, SetSimilarityHit, MAX_FINAL_LIMIT, MAX_RETRIEVER_K,
4962 MAX_SET_MEMBERS,
4963 };
4964 let mut trace = crate::query::SetSimilarityTrace::default();
4965 if request.members.is_empty() {
4966 return Ok((Vec::new(), trace));
4967 }
4968 if request.candidate_k == 0 || request.limit == 0 {
4969 return Err(MongrelError::InvalidArgument(
4970 "candidate_k and limit must be > 0".into(),
4971 ));
4972 }
4973 if request.candidate_k > MAX_RETRIEVER_K
4974 || request.limit > MAX_FINAL_LIMIT
4975 || request.members.len() > MAX_SET_MEMBERS
4976 {
4977 return Err(MongrelError::InvalidArgument(format!(
4978 "candidate_k must be <= {MAX_RETRIEVER_K}, limit <= {MAX_FINAL_LIMIT}, and members <= {MAX_SET_MEMBERS}"
4979 )));
4980 }
4981 if !request.min_jaccard.is_finite() || !(0.0..=1.0).contains(&request.min_jaccard) {
4982 return Err(MongrelError::InvalidArgument(
4983 "min_jaccard must be finite and between 0 and 1".into(),
4984 ));
4985 }
4986 let started = std::time::Instant::now();
4987 let retriever = Retriever::MinHash {
4988 column_id: request.column_id,
4989 members: request.members.clone(),
4990 k: request.candidate_k,
4991 };
4992 self.require_select()?;
4993 self.ensure_indexes_complete()?;
4994 self.validate_retriever(&retriever)?;
4995 let hits = self.retrieve_filtered(
4996 &retriever,
4997 snapshot,
4998 None,
4999 allowed,
5000 candidate_authorization,
5001 context,
5002 )?;
5003 trace.candidate_generation_us = started.elapsed().as_micros() as u64;
5004 trace.candidate_count = hits.len();
5005 let row_ids: Vec<_> = hits.iter().map(|hit| hit.row_id.0).collect();
5006 if let Some(context) = context {
5007 context.consume(row_ids.len())?;
5008 }
5009 let started = std::time::Instant::now();
5010 let query_now = context.map_or_else(unix_nanos_now, |context| context.query_time_nanos());
5011 let values = self.values_for_rids_batch_at_with_context(
5012 &row_ids,
5013 request.column_id,
5014 snapshot,
5015 query_now,
5016 context,
5017 )?;
5018 trace.gather_us = started.elapsed().as_micros() as u64;
5019 let query: std::collections::HashSet<_> = request.members.iter().cloned().collect();
5020 let estimates: std::collections::HashMap<_, _> = hits
5021 .into_iter()
5022 .filter_map(|hit| match hit.score {
5023 RetrieverScore::MinHashEstimatedJaccard(score) => Some((hit.row_id, score)),
5024 _ => None,
5025 })
5026 .collect();
5027 let started = std::time::Instant::now();
5028 let parsed: Vec<_> = values
5029 .into_iter()
5030 .filter_map(|(row_id, value)| {
5031 let Value::Bytes(bytes) = value else {
5032 return None;
5033 };
5034 let Ok(serde_json::Value::Array(members)) = serde_json::from_slice(&bytes) else {
5035 return None;
5036 };
5037 let stored = members
5038 .into_iter()
5039 .filter_map(|member| match member {
5040 serde_json::Value::String(value) => {
5041 Some(crate::query::SetMember::String(value))
5042 }
5043 serde_json::Value::Number(value) => {
5044 Some(crate::query::SetMember::Number(value))
5045 }
5046 serde_json::Value::Bool(value) => {
5047 Some(crate::query::SetMember::Boolean(value))
5048 }
5049 _ => None,
5050 })
5051 .collect::<std::collections::HashSet<_>>();
5052 Some((row_id, stored))
5053 })
5054 .collect();
5055 trace.parse_us = started.elapsed().as_micros() as u64;
5056 trace.verified_count = parsed.len();
5057 let started = std::time::Instant::now();
5058 let mut exact = Vec::new();
5059 for (row_id, stored) in parsed {
5060 if let Some(context) = context {
5061 context.consume(1)?;
5062 }
5063 let union = query.union(&stored).count();
5064 let score = if union == 0 {
5065 1.0
5066 } else {
5067 query.intersection(&stored).count() as f32 / union as f32
5068 };
5069 if score >= request.min_jaccard {
5070 exact.push(SetSimilarityHit {
5071 row_id,
5072 estimated_jaccard: estimates.get(&row_id).copied().unwrap_or_default(),
5073 exact_jaccard: score,
5074 });
5075 }
5076 }
5077 exact.sort_by(|a, b| {
5078 b.exact_jaccard
5079 .total_cmp(&a.exact_jaccard)
5080 .then_with(|| a.row_id.cmp(&b.row_id))
5081 });
5082 exact.truncate(request.limit);
5083 trace.score_us = started.elapsed().as_micros() as u64;
5084 crate::trace::QueryTrace::record(|query_trace| {
5085 query_trace.exact_set_gather_nanos = query_trace
5086 .exact_set_gather_nanos
5087 .saturating_add(trace.gather_us.saturating_mul(1_000));
5088 query_trace.exact_set_parse_nanos = query_trace
5089 .exact_set_parse_nanos
5090 .saturating_add(trace.parse_us.saturating_mul(1_000));
5091 query_trace.exact_set_score_nanos = query_trace
5092 .exact_set_score_nanos
5093 .saturating_add(trace.score_us.saturating_mul(1_000));
5094 });
5095 Ok((exact, trace))
5096 }
5097
5098 fn values_for_rids_batch_at(
5100 &self,
5101 row_ids: &[u64],
5102 column_id: u16,
5103 snapshot: Snapshot,
5104 now: i64,
5105 ) -> Result<Vec<(RowId, Value)>> {
5106 if self.ttl.is_none()
5107 && self.memtable.is_empty()
5108 && self.mutable_run.is_empty()
5109 && self.run_refs.len() == 1
5110 {
5111 let mut reader = self.open_reader(self.run_refs[0].run_id)?;
5112 let (positions, visible_row_ids) =
5113 reader.visible_positions_with_rids(snapshot.epoch)?;
5114 let requested: Vec<(RowId, usize)> = row_ids
5115 .iter()
5116 .filter_map(|raw| {
5117 visible_row_ids
5118 .binary_search(&(*raw as i64))
5119 .ok()
5120 .map(|index| (RowId(*raw), positions[index]))
5121 })
5122 .collect();
5123 let values = reader.gather_column(
5124 column_id,
5125 &requested
5126 .iter()
5127 .map(|(_, position)| *position)
5128 .collect::<Vec<_>>(),
5129 )?;
5130 return Ok(requested
5131 .into_iter()
5132 .zip(values)
5133 .map(|((row_id, _), value)| (row_id, value))
5134 .collect());
5135 }
5136 self.values_for_rids_at(row_ids, column_id, snapshot, now)
5137 }
5138
5139 fn values_for_rids_batch_at_with_context(
5140 &self,
5141 row_ids: &[u64],
5142 column_id: u16,
5143 snapshot: Snapshot,
5144 now: i64,
5145 context: Option<&crate::query::AiExecutionContext>,
5146 ) -> Result<Vec<(RowId, Value)>> {
5147 let Some(context) = context else {
5148 return self.values_for_rids_batch_at(row_ids, column_id, snapshot, now);
5149 };
5150 let mut values = Vec::with_capacity(row_ids.len());
5151 for chunk in row_ids.chunks(256) {
5152 context.checkpoint()?;
5153 values.extend(self.values_for_rids_batch_at(chunk, column_id, snapshot, now)?);
5154 }
5155 Ok(values)
5156 }
5157
5158 fn values_for_rids_at(
5160 &self,
5161 row_ids: &[u64],
5162 column_id: u16,
5163 snapshot: Snapshot,
5164 now: i64,
5165 ) -> Result<Vec<(RowId, Value)>> {
5166 let mut readers: Vec<_> = self
5167 .run_refs
5168 .iter()
5169 .map(|run| self.open_reader(run.run_id))
5170 .collect::<Result<_>>()?;
5171 let mut out = Vec::with_capacity(row_ids.len());
5172 for &raw_row_id in row_ids {
5173 let row_id = RowId(raw_row_id);
5174 let mem = self.memtable.get_version(row_id, snapshot.epoch);
5175 let mutable = self.mutable_run.get_version(row_id, snapshot.epoch);
5176 let overlay = match (mem, mutable) {
5177 (Some((a_epoch, a)), Some((b_epoch, b))) => Some(if a_epoch >= b_epoch {
5178 (a_epoch, a)
5179 } else {
5180 (b_epoch, b)
5181 }),
5182 (Some(value), None) | (None, Some(value)) => Some(value),
5183 (None, None) => None,
5184 };
5185 if let Some((_, row)) = overlay {
5186 if !row.deleted && !self.row_expired_at(&row, now) {
5187 if let Some(value) = row.columns.get(&column_id) {
5188 out.push((row_id, value.clone()));
5189 }
5190 }
5191 continue;
5192 }
5193
5194 let mut best: Option<(Epoch, bool, Option<Value>, usize)> = None;
5195 for (index, reader) in readers.iter_mut().enumerate() {
5196 if let Some((epoch, deleted, value)) =
5197 reader.get_version_column(row_id, snapshot.epoch, column_id)?
5198 {
5199 if best
5200 .as_ref()
5201 .map(|(best_epoch, ..)| epoch > *best_epoch)
5202 .unwrap_or(true)
5203 {
5204 best = Some((epoch, deleted, value, index));
5205 }
5206 }
5207 }
5208 let Some((_, false, Some(value), reader_index)) = best else {
5209 continue;
5210 };
5211 if let Some(ttl) = self.ttl {
5212 if ttl.column_id != column_id {
5213 if let Some((_, _, Some(Value::Int64(timestamp)))) = readers[reader_index]
5214 .get_version_column(row_id, snapshot.epoch, ttl.column_id)?
5215 {
5216 if timestamp.saturating_add(ttl.duration_nanos as i64) <= now {
5217 continue;
5218 }
5219 }
5220 } else if let Value::Int64(timestamp) = value {
5221 if timestamp.saturating_add(ttl.duration_nanos as i64) <= now {
5222 continue;
5223 }
5224 }
5225 }
5226 out.push((row_id, value));
5227 }
5228 Ok(out)
5229 }
5230
5231 pub fn rows_for_rids(&self, rids: &[u64], snapshot: Snapshot) -> Result<Vec<Row>> {
5236 self.rows_for_rids_at_time(rids, snapshot, unix_nanos_now())
5237 }
5238
5239 pub fn rows_for_rids_with_context(
5240 &self,
5241 rids: &[u64],
5242 snapshot: Snapshot,
5243 context: &crate::query::AiExecutionContext,
5244 ) -> Result<Vec<Row>> {
5245 context.consume(rids.len().saturating_mul(self.schema.columns.len()))?;
5246 self.rows_for_rids_at_time(rids, snapshot, context.query_time_nanos())
5247 }
5248
5249 fn rows_for_rids_at_time(
5250 &self,
5251 rids: &[u64],
5252 snapshot: Snapshot,
5253 ttl_now: i64,
5254 ) -> Result<Vec<Row>> {
5255 use std::collections::HashMap;
5256 let mut rows = Vec::with_capacity(rids.len());
5257 let tier_size = self.memtable.len() + self.mutable_run.len();
5274 let mut overlay: HashMap<u64, Row> = HashMap::with_capacity(rids.len());
5275 if rids.len().saturating_mul(24) < tier_size {
5276 for &rid in rids {
5277 let mem = self.memtable.get_version(RowId(rid), snapshot.epoch);
5278 let mrun = self.mutable_run.get_version(RowId(rid), snapshot.epoch);
5279 let newest = match (mem, mrun) {
5280 (Some((me, mr)), Some((re, rr))) => Some(if me >= re { mr } else { rr }),
5281 (Some((_, mr)), None) => Some(mr),
5282 (None, Some((_, rr))) => Some(rr),
5283 (None, None) => None,
5284 };
5285 if let Some(row) = newest {
5286 overlay.insert(rid, row);
5287 }
5288 }
5289 } else {
5290 let fold_newest = |row: Row, overlay: &mut HashMap<u64, Row>| {
5291 overlay
5292 .entry(row.row_id.0)
5293 .and_modify(|e| {
5294 if row.committed_epoch > e.committed_epoch {
5295 *e = row.clone();
5296 }
5297 })
5298 .or_insert(row);
5299 };
5300 for row in self.memtable.visible_versions(snapshot.epoch) {
5301 fold_newest(row, &mut overlay);
5302 }
5303 for row in self.mutable_run.visible_versions(snapshot.epoch) {
5304 fold_newest(row, &mut overlay);
5305 }
5306 }
5307 if self.run_refs.len() == 1 {
5308 let mut reader = self.open_reader(self.run_refs[0].run_id)?;
5309 if rids.len().saturating_mul(24) < reader.row_count() {
5317 for &rid in rids {
5318 if let Some(r) = overlay.get(&rid) {
5319 if !r.deleted {
5320 rows.push(r.clone());
5321 }
5322 continue;
5323 }
5324 if let Some((_, row)) = reader.get_version(RowId(rid), snapshot.epoch)? {
5325 if !row.deleted {
5326 rows.push(row);
5327 }
5328 }
5329 }
5330 rows.retain(|row| !self.row_expired_at(row, ttl_now));
5331 return Ok(rows);
5332 }
5333 let (positions, vis_rids) = reader.visible_positions_with_rids(snapshot.epoch)?;
5342 enum Src {
5345 Overlay,
5346 Run,
5347 }
5348 let mut plan: Vec<Src> = Vec::with_capacity(rids.len());
5349 let mut fetch: Vec<usize> = Vec::with_capacity(rids.len());
5350 for rid in rids {
5351 if overlay.contains_key(rid) {
5352 plan.push(Src::Overlay);
5353 continue;
5354 }
5355 match vis_rids.binary_search(&(*rid as i64)) {
5356 Ok(i) => {
5357 plan.push(Src::Run);
5358 fetch.push(positions[i]);
5359 }
5360 Err(_) => { }
5361 }
5362 }
5363 let fetched = reader.materialize_batch(&fetch)?;
5364 let mut fetched_iter = fetched.into_iter();
5365 for (rid, src) in rids.iter().zip(plan) {
5366 match src {
5367 Src::Overlay => {
5368 if let Some(r) = overlay.get(rid) {
5369 if !r.deleted {
5370 rows.push(r.clone());
5371 }
5372 }
5373 }
5374 Src::Run => {
5375 if let Some(row) = fetched_iter.next() {
5376 if !row.deleted {
5377 rows.push(row);
5378 }
5379 }
5380 }
5381 }
5382 }
5383 rows.retain(|row| !self.row_expired_at(row, ttl_now));
5384 return Ok(rows);
5385 }
5386 let mut readers: Vec<_> = self
5390 .run_refs
5391 .iter()
5392 .map(|rr| self.open_reader(rr.run_id))
5393 .collect::<Result<Vec<_>>>()?;
5394 for rid in rids {
5395 if let Some(r) = overlay.get(rid) {
5396 if !r.deleted {
5397 rows.push(r.clone());
5398 }
5399 continue;
5400 }
5401 let mut best: Option<(Epoch, Row)> = None;
5402 for reader in readers.iter_mut() {
5403 if let Ok(Some((epoch, row))) = reader.get_version(RowId(*rid), snapshot.epoch) {
5404 if best.as_ref().map(|(be, _)| epoch > *be).unwrap_or(true) {
5405 best = Some((epoch, row));
5406 }
5407 }
5408 }
5409 if let Some((_, r)) = best {
5410 if !r.deleted {
5411 rows.push(r);
5412 }
5413 }
5414 }
5415 rows.retain(|row| !self.row_expired_at(row, ttl_now));
5416 Ok(rows)
5417 }
5418
5419 pub fn indexes_complete(&self) -> bool {
5429 self.indexes_complete
5430 }
5431
5432 pub fn index_build_policy(&self) -> IndexBuildPolicy {
5434 self.index_build_policy
5435 }
5436
5437 pub fn set_index_build_policy(&mut self, policy: IndexBuildPolicy) {
5441 self.index_build_policy = policy;
5442 }
5443
5444 pub fn broadcast_join_values(&self, column_id: u16, pk_db: &Table) -> Option<Vec<Vec<u8>>> {
5449 if !self.indexes_complete {
5453 return None;
5454 }
5455 let b = self.bitmap.get(&column_id)?;
5456 let result: Vec<Vec<u8>> = b
5457 .keys()
5458 .into_iter()
5459 .filter(|k| pk_db.hot.get(k.as_slice()).is_some())
5460 .cloned()
5461 .collect();
5462 Some(result)
5463 }
5464
5465 pub fn fk_join_row_ids(
5466 &self,
5467 fk_column_id: u16,
5468 pk_values: &[Vec<u8>],
5469 fk_conditions: &[crate::query::Condition],
5470 snapshot: Snapshot,
5471 ) -> Result<Vec<u64>> {
5472 let Some(b) = self.bitmap.get(&fk_column_id) else {
5473 return Ok(Vec::new());
5474 };
5475 let mut join_set = {
5476 let mut acc = roaring::RoaringBitmap::new();
5477 for v in pk_values {
5478 acc |= b.get(v);
5479 }
5480 RowIdSet::from_roaring(acc)
5481 };
5482 if !fk_conditions.is_empty() {
5483 let mut sets: Vec<RowIdSet> = Vec::with_capacity(fk_conditions.len() + 1);
5484 sets.push(join_set);
5485 for c in fk_conditions {
5486 sets.push(self.resolve_condition(c, snapshot)?);
5487 }
5488 join_set = RowIdSet::intersect_many(sets);
5489 }
5490 Ok(join_set.into_sorted_vec())
5491 }
5492
5493 pub fn fk_join_count(
5499 &self,
5500 fk_column_id: u16,
5501 pk_values: &[Vec<u8>],
5502 fk_conditions: &[crate::query::Condition],
5503 snapshot: Snapshot,
5504 ) -> Result<u64> {
5505 let Some(b) = self.bitmap.get(&fk_column_id) else {
5506 return Ok(0);
5507 };
5508 let mut acc = roaring::RoaringBitmap::new();
5509 for v in pk_values {
5510 acc |= b.get(v);
5511 }
5512 if fk_conditions.is_empty() {
5513 return Ok(acc.len());
5514 }
5515 let mut sets: Vec<RowIdSet> = Vec::with_capacity(fk_conditions.len() + 1);
5516 sets.push(RowIdSet::from_roaring(acc));
5517 for c in fk_conditions {
5518 sets.push(self.resolve_condition(c, snapshot)?);
5519 }
5520 Ok(RowIdSet::intersect_many(sets).len() as u64)
5521 }
5522
5523 fn resolve_condition(
5528 &self,
5529 c: &crate::query::Condition,
5530 snapshot: Snapshot,
5531 ) -> Result<RowIdSet> {
5532 self.resolve_condition_with_allowed(c, snapshot, None)
5533 }
5534
5535 fn resolve_condition_with_allowed(
5536 &self,
5537 c: &crate::query::Condition,
5538 snapshot: Snapshot,
5539 allowed: Option<&std::collections::HashSet<RowId>>,
5540 ) -> Result<RowIdSet> {
5541 use crate::query::Condition;
5542 self.validate_condition(c)?;
5543 Ok(match c {
5544 Condition::Pk(key) => {
5545 let lookup = self
5546 .schema
5547 .primary_key()
5548 .map(|pk| self.index_lookup_key_bytes(pk.id, key))
5549 .unwrap_or_else(|| key.clone());
5550 self.hot
5551 .get(&lookup)
5552 .map(|r| RowIdSet::one(r.0))
5553 .unwrap_or_else(RowIdSet::empty)
5554 }
5555 Condition::BitmapEq { column_id, value } => {
5556 let lookup = self.index_lookup_key_bytes(*column_id, value);
5557 self.bitmap
5558 .get(column_id)
5559 .map(|b| RowIdSet::from_roaring(b.get(&lookup)))
5560 .unwrap_or_else(RowIdSet::empty)
5561 }
5562 Condition::BitmapIn { column_id, values } => {
5563 let bm = self.bitmap.get(column_id);
5564 let mut acc = roaring::RoaringBitmap::new();
5565 if let Some(b) = bm {
5566 for v in values {
5567 let lookup = self.index_lookup_key_bytes(*column_id, v);
5568 acc |= b.get(&lookup);
5569 }
5570 }
5571 RowIdSet::from_roaring(acc)
5572 }
5573 Condition::BytesPrefix { column_id, prefix } => {
5574 if let Some(b) = self.bitmap.get(column_id) {
5579 let lookup_prefix = self.index_lookup_key_bytes(*column_id, prefix);
5580 let mut acc = roaring::RoaringBitmap::new();
5581 for key in b.keys() {
5582 if key.starts_with(&lookup_prefix) {
5583 acc |= b.get(key);
5584 }
5585 }
5586 RowIdSet::from_roaring(acc)
5587 } else {
5588 RowIdSet::empty()
5589 }
5590 }
5591 Condition::FmContains { column_id, pattern } => self
5592 .fm
5593 .get(column_id)
5594 .map(|f| {
5595 RowIdSet::from_unsorted(f.locate(pattern).into_iter().map(|r| r.0).collect())
5596 })
5597 .unwrap_or_else(RowIdSet::empty),
5598 Condition::FmContainsAll {
5599 column_id,
5600 patterns,
5601 } => {
5602 if let Some(f) = self.fm.get(column_id) {
5605 let sets: Vec<RowIdSet> = patterns
5606 .iter()
5607 .map(|pat| {
5608 RowIdSet::from_unsorted(
5609 f.locate(pat).into_iter().map(|r| r.0).collect(),
5610 )
5611 })
5612 .collect();
5613 RowIdSet::intersect_many(sets)
5614 } else {
5615 RowIdSet::empty()
5616 }
5617 }
5618 Condition::Ann {
5619 column_id,
5620 query,
5621 k,
5622 } => RowIdSet::from_unsorted(
5623 self.retrieve_filtered(
5624 &crate::query::Retriever::Ann {
5625 column_id: *column_id,
5626 query: query.clone(),
5627 k: *k,
5628 },
5629 snapshot,
5630 None,
5631 allowed,
5632 None,
5633 None,
5634 )?
5635 .into_iter()
5636 .map(|hit| hit.row_id.0)
5637 .collect(),
5638 ),
5639 Condition::SparseMatch {
5640 column_id,
5641 query,
5642 k,
5643 } => RowIdSet::from_unsorted(
5644 self.retrieve_filtered(
5645 &crate::query::Retriever::Sparse {
5646 column_id: *column_id,
5647 query: query.clone(),
5648 k: *k,
5649 },
5650 snapshot,
5651 None,
5652 allowed,
5653 None,
5654 None,
5655 )?
5656 .into_iter()
5657 .map(|hit| hit.row_id.0)
5658 .collect(),
5659 ),
5660 Condition::MinHashSimilar {
5661 column_id,
5662 query,
5663 k,
5664 } => match self.minhash.get(column_id) {
5665 Some(index) => {
5666 let candidates = index.candidate_row_ids(query);
5667 let eligible =
5668 self.eligible_candidate_ids(&candidates, *column_id, snapshot, None)?;
5669 RowIdSet::from_unsorted(
5670 index
5671 .search_filtered(query, *k, |row_id| {
5672 eligible.contains(&row_id)
5673 && allowed.map_or(true, |allowed| allowed.contains(&row_id))
5674 })
5675 .into_iter()
5676 .map(|(row_id, _)| row_id.0)
5677 .collect(),
5678 )
5679 }
5680 None => RowIdSet::empty(),
5681 },
5682 Condition::Range { column_id, lo, hi } => {
5683 let mut set = if let Some(li) = self.learned_range.get(column_id) {
5692 RowIdSet::from_unsorted(li.range(*lo, *hi).into_iter().collect())
5693 } else if self.run_refs.len() == 1 {
5694 let mut r = self.open_reader(self.run_refs[0].run_id)?;
5695 r.range_row_id_set_i64(*column_id, *lo, *hi)?
5696 } else {
5697 return self.range_scan_i64(*column_id, *lo, *hi, snapshot);
5698 };
5699 set.remove_many(self.overlay_rid_set(snapshot));
5700 self.range_scan_overlay_i64(&mut set, *column_id, *lo, *hi, snapshot);
5701 set
5702 }
5703 Condition::RangeF64 {
5704 column_id,
5705 lo,
5706 lo_inclusive,
5707 hi,
5708 hi_inclusive,
5709 } => {
5710 let mut set = if let Some(li) = self.learned_range.get(column_id) {
5713 RowIdSet::from_unsorted(
5714 li.range_f64(*lo, *lo_inclusive, *hi, *hi_inclusive)
5715 .into_iter()
5716 .collect(),
5717 )
5718 } else if self.run_refs.len() == 1 {
5719 let mut r = self.open_reader(self.run_refs[0].run_id)?;
5720 r.range_row_id_set_f64(*column_id, *lo, *lo_inclusive, *hi, *hi_inclusive)?
5721 } else {
5722 return self.range_scan_f64(
5723 *column_id,
5724 *lo,
5725 *lo_inclusive,
5726 *hi,
5727 *hi_inclusive,
5728 snapshot,
5729 );
5730 };
5731 set.remove_many(self.overlay_rid_set(snapshot));
5732 self.range_scan_overlay_f64(
5733 &mut set,
5734 *column_id,
5735 *lo,
5736 *lo_inclusive,
5737 *hi,
5738 *hi_inclusive,
5739 snapshot,
5740 );
5741 set
5742 }
5743 Condition::IsNull { column_id } => {
5744 let mut set = if self.run_refs.len() == 1 {
5745 let mut r = self.open_reader(self.run_refs[0].run_id)?;
5746 r.null_row_id_set(*column_id, true)?
5747 } else {
5748 return self.null_scan(*column_id, true, snapshot);
5749 };
5750 set.remove_many(self.overlay_rid_set(snapshot));
5751 self.null_scan_overlay(&mut set, *column_id, true, snapshot);
5752 set
5753 }
5754 Condition::IsNotNull { column_id } => {
5755 let mut set = if self.run_refs.len() == 1 {
5756 let mut r = self.open_reader(self.run_refs[0].run_id)?;
5757 r.null_row_id_set(*column_id, false)?
5758 } else {
5759 return self.null_scan(*column_id, false, snapshot);
5760 };
5761 set.remove_many(self.overlay_rid_set(snapshot));
5762 self.null_scan_overlay(&mut set, *column_id, false, snapshot);
5763 set
5764 }
5765 })
5766 }
5767
5768 fn range_scan_i64(
5776 &self,
5777 column_id: u16,
5778 lo: i64,
5779 hi: i64,
5780 snapshot: Snapshot,
5781 ) -> Result<RowIdSet> {
5782 let mut row_ids = Vec::new();
5783 let overlay_rids = self.overlay_rid_set(snapshot);
5784 for rr in &self.run_refs {
5785 let mut reader = self.open_reader(rr.run_id)?;
5786 let matched = reader.range_row_ids_visible_i64(column_id, lo, hi, snapshot.epoch)?;
5787 for rid in matched {
5788 if !overlay_rids.contains(&rid) {
5789 row_ids.push(rid);
5790 }
5791 }
5792 }
5793 let mut s = RowIdSet::from_unsorted(row_ids);
5794 self.range_scan_overlay_i64(&mut s, column_id, lo, hi, snapshot);
5795 Ok(s)
5796 }
5797
5798 fn range_scan_f64(
5801 &self,
5802 column_id: u16,
5803 lo: f64,
5804 lo_inclusive: bool,
5805 hi: f64,
5806 hi_inclusive: bool,
5807 snapshot: Snapshot,
5808 ) -> Result<RowIdSet> {
5809 let mut row_ids = Vec::new();
5810 let overlay_rids = self.overlay_rid_set(snapshot);
5811 for rr in &self.run_refs {
5812 let mut reader = self.open_reader(rr.run_id)?;
5813 let matched = reader.range_row_ids_visible_f64(
5814 column_id,
5815 lo,
5816 lo_inclusive,
5817 hi,
5818 hi_inclusive,
5819 snapshot.epoch,
5820 )?;
5821 for rid in matched {
5822 if !overlay_rids.contains(&rid) {
5823 row_ids.push(rid);
5824 }
5825 }
5826 }
5827 let mut s = RowIdSet::from_unsorted(row_ids);
5828 self.range_scan_overlay_f64(
5829 &mut s,
5830 column_id,
5831 lo,
5832 lo_inclusive,
5833 hi,
5834 hi_inclusive,
5835 snapshot,
5836 );
5837 Ok(s)
5838 }
5839
5840 fn overlay_rid_set(&self, snapshot: Snapshot) -> HashSet<u64> {
5842 let mut s = HashSet::new();
5843 for row in self.memtable.visible_versions(snapshot.epoch) {
5844 s.insert(row.row_id.0);
5845 }
5846 for row in self.mutable_run.visible_versions(snapshot.epoch) {
5847 s.insert(row.row_id.0);
5848 }
5849 s
5850 }
5851
5852 fn range_scan_overlay_i64(
5853 &self,
5854 s: &mut RowIdSet,
5855 column_id: u16,
5856 lo: i64,
5857 hi: i64,
5858 snapshot: Snapshot,
5859 ) {
5860 let mut newest: HashMap<u64, &Row> = HashMap::new();
5865 let mutable = self.mutable_run.visible_versions(snapshot.epoch);
5866 let memtable = self.memtable.visible_versions(snapshot.epoch);
5867 for r in &mutable {
5868 newest.entry(r.row_id.0).or_insert(r);
5869 }
5870 for r in &memtable {
5871 newest.insert(r.row_id.0, r);
5872 }
5873 for row in newest.values() {
5874 if !row.deleted {
5875 if let Some(Value::Int64(v)) = row.columns.get(&column_id) {
5876 if *v >= lo && *v <= hi {
5877 s.insert(row.row_id.0);
5878 }
5879 }
5880 }
5881 }
5882 }
5883
5884 #[allow(clippy::too_many_arguments)]
5885 fn range_scan_overlay_f64(
5886 &self,
5887 s: &mut RowIdSet,
5888 column_id: u16,
5889 lo: f64,
5890 lo_inclusive: bool,
5891 hi: f64,
5892 hi_inclusive: bool,
5893 snapshot: Snapshot,
5894 ) {
5895 let mut newest: HashMap<u64, &Row> = HashMap::new();
5898 let mutable = self.mutable_run.visible_versions(snapshot.epoch);
5899 let memtable = self.memtable.visible_versions(snapshot.epoch);
5900 for r in &mutable {
5901 newest.entry(r.row_id.0).or_insert(r);
5902 }
5903 for r in &memtable {
5904 newest.insert(r.row_id.0, r);
5905 }
5906 for row in newest.values() {
5907 if !row.deleted {
5908 if let Some(Value::Float64(v)) = row.columns.get(&column_id) {
5909 let ok_lo = if lo_inclusive { *v >= lo } else { *v > lo };
5910 let ok_hi = if hi_inclusive { *v <= hi } else { *v < hi };
5911 if ok_lo && ok_hi {
5912 s.insert(row.row_id.0);
5913 }
5914 }
5915 }
5916 }
5917 }
5918
5919 fn null_scan(&self, column_id: u16, want_nulls: bool, snapshot: Snapshot) -> Result<RowIdSet> {
5922 let mut row_ids = Vec::new();
5923 let overlay_rids = self.overlay_rid_set(snapshot);
5924 for rr in &self.run_refs {
5925 let mut reader = self.open_reader(rr.run_id)?;
5926 let matched = reader.null_row_ids_visible(column_id, want_nulls, snapshot.epoch)?;
5927 for rid in matched {
5928 if !overlay_rids.contains(&rid) {
5929 row_ids.push(rid);
5930 }
5931 }
5932 }
5933 let mut s = RowIdSet::from_unsorted(row_ids);
5934 self.null_scan_overlay(&mut s, column_id, want_nulls, snapshot);
5935 Ok(s)
5936 }
5937
5938 fn null_scan_overlay(
5942 &self,
5943 s: &mut RowIdSet,
5944 column_id: u16,
5945 want_nulls: bool,
5946 snapshot: Snapshot,
5947 ) {
5948 let mut newest: HashMap<u64, &Row> = HashMap::new();
5949 let mutable = self.mutable_run.visible_versions(snapshot.epoch);
5950 let memtable = self.memtable.visible_versions(snapshot.epoch);
5951 for r in &mutable {
5952 newest.entry(r.row_id.0).or_insert(r);
5953 }
5954 for r in &memtable {
5955 newest.insert(r.row_id.0, r);
5956 }
5957 for row in newest.values() {
5958 if row.deleted {
5959 continue;
5960 }
5961 let is_null = !row.columns.contains_key(&column_id)
5962 || matches!(row.columns.get(&column_id), Some(Value::Null) | None);
5963 if is_null == want_nulls {
5964 s.insert(row.row_id.0);
5965 }
5966 }
5967 }
5968
5969 pub fn snapshot(&self) -> Snapshot {
5970 Snapshot::at(self.epoch.visible())
5971 }
5972
5973 pub fn data_generation(&self) -> u64 {
5975 self.data_generation
5976 }
5977
5978 pub fn pin_snapshot(&mut self) -> Snapshot {
5981 let e = self.epoch.visible();
5982 *self.pinned.entry(e).or_insert(0) += 1;
5983 Snapshot::at(e)
5984 }
5985
5986 pub fn unpin_snapshot(&mut self, snap: Snapshot) {
5988 if let Some(count) = self.pinned.get_mut(&snap.epoch) {
5989 *count -= 1;
5990 if *count == 0 {
5991 self.pinned.remove(&snap.epoch);
5992 }
5993 }
5994 }
5995
5996 pub(crate) fn min_active_snapshot(&self) -> Option<Epoch> {
6006 let local = self.pinned.keys().next().copied();
6007 let global = self.snapshots.min_pinned();
6008 let history = self.snapshots.history_floor(self.current_epoch());
6009 [local, global, history].into_iter().flatten().min()
6010 }
6011
6012 pub fn set_ttl(&mut self, column_name: &str, duration_nanos: u64) -> Result<()> {
6016 self.ensure_writable()?;
6017 let policy = self.prepare_ttl_policy(column_name, duration_nanos)?;
6018 self.apply_ttl_policy_at(Some(policy), self.current_epoch())
6019 }
6020
6021 pub fn clear_ttl(&mut self) -> Result<()> {
6022 self.ensure_writable()?;
6023 self.apply_ttl_policy_at(None, self.current_epoch())
6024 }
6025
6026 pub fn ttl(&self) -> Option<TtlPolicy> {
6027 self.ttl
6028 }
6029
6030 pub(crate) fn prepare_ttl_policy(
6031 &self,
6032 column_name: &str,
6033 duration_nanos: u64,
6034 ) -> Result<TtlPolicy> {
6035 if duration_nanos == 0 || duration_nanos > i64::MAX as u64 {
6036 return Err(MongrelError::InvalidArgument(
6037 "TTL duration must be between 1 and i64::MAX nanoseconds".into(),
6038 ));
6039 }
6040 let column = self
6041 .schema
6042 .columns
6043 .iter()
6044 .find(|column| column.name == column_name)
6045 .ok_or_else(|| MongrelError::Schema(format!("unknown TTL column {column_name}")))?;
6046 if column.ty != TypeId::TimestampNanos {
6047 return Err(MongrelError::Schema(format!(
6048 "TTL column {column_name} must be TimestampNanos, is {:?}",
6049 column.ty
6050 )));
6051 }
6052 Ok(TtlPolicy {
6053 column_id: column.id,
6054 duration_nanos,
6055 })
6056 }
6057
6058 pub(crate) fn apply_ttl_policy_at(
6059 &mut self,
6060 policy: Option<TtlPolicy>,
6061 epoch: Epoch,
6062 ) -> Result<()> {
6063 if let Some(policy) = policy {
6064 let column = self
6065 .schema
6066 .columns
6067 .iter()
6068 .find(|column| column.id == policy.column_id)
6069 .ok_or_else(|| {
6070 MongrelError::Schema(format!("unknown TTL column id {}", policy.column_id))
6071 })?;
6072 if column.ty != TypeId::TimestampNanos
6073 || policy.duration_nanos == 0
6074 || policy.duration_nanos > i64::MAX as u64
6075 {
6076 return Err(MongrelError::Schema("invalid TTL policy".into()));
6077 }
6078 }
6079 self.ttl = policy;
6080 self.agg_cache.clear();
6081 self.clear_result_cache();
6082 let _ = std::fs::remove_dir_all(self.dir.join("_shadow"));
6083 self.persist_manifest(epoch)
6084 }
6085
6086 pub(crate) fn row_expired_at(&self, row: &Row, now_nanos: i64) -> bool {
6087 let Some(policy) = self.ttl else {
6088 return false;
6089 };
6090 let Some(Value::Int64(timestamp)) = row.columns.get(&policy.column_id) else {
6091 return false;
6092 };
6093 timestamp.saturating_add(policy.duration_nanos as i64) <= now_nanos
6094 }
6095
6096 pub fn current_epoch(&self) -> Epoch {
6097 self.epoch.visible()
6098 }
6099
6100 pub fn memtable_len(&self) -> usize {
6101 self.memtable.len()
6102 }
6103
6104 pub fn count(&self) -> u64 {
6107 if self.ttl.is_none()
6108 && self.pending_put_cols.is_empty()
6109 && self.pending_delete_rids.is_empty()
6110 && self.pending_rows.is_empty()
6111 && self.pending_dels.is_empty()
6112 && self.pending_truncate.is_none()
6113 {
6114 self.live_count
6115 } else {
6116 self.visible_rows(self.snapshot())
6117 .map(|rows| rows.len() as u64)
6118 .unwrap_or(self.live_count)
6119 }
6120 }
6121
6122 pub fn count_conditions(
6126 &mut self,
6127 conditions: &[crate::query::Condition],
6128 snapshot: Snapshot,
6129 ) -> Result<Option<u64>> {
6130 use crate::query::Condition;
6131 if self.ttl.is_some() {
6132 if conditions.is_empty() {
6133 return Ok(Some(self.visible_rows(snapshot)?.len() as u64));
6134 }
6135 let mut sets = Vec::with_capacity(conditions.len());
6136 for condition in conditions {
6137 sets.push(self.resolve_condition(condition, snapshot)?);
6138 }
6139 let survivors = RowIdSet::intersect_many(sets);
6140 let rows = self.visible_rows(snapshot)?;
6141 return Ok(Some(
6142 rows.into_iter()
6143 .filter(|row| survivors.contains(row.row_id.0))
6144 .count() as u64,
6145 ));
6146 }
6147 if conditions.is_empty() {
6148 return Ok(Some(self.count()));
6149 }
6150 let served = |c: &Condition| {
6151 matches!(
6152 c,
6153 Condition::Pk(_)
6154 | Condition::BitmapEq { .. }
6155 | Condition::BitmapIn { .. }
6156 | Condition::BytesPrefix { .. }
6157 | Condition::FmContains { .. }
6158 | Condition::FmContainsAll { .. }
6159 | Condition::Ann { .. }
6160 | Condition::Range { .. }
6161 | Condition::RangeF64 { .. }
6162 | Condition::SparseMatch { .. }
6163 | Condition::MinHashSimilar { .. }
6164 | Condition::IsNull { .. }
6165 | Condition::IsNotNull { .. }
6166 )
6167 };
6168 if !conditions.iter().all(served) {
6169 return Ok(None);
6170 }
6171 self.ensure_indexes_complete()?;
6172 if !self.pending_put_cols.is_empty()
6173 || !self.pending_delete_rids.is_empty()
6174 || !self.pending_rows.is_empty()
6175 || !self.pending_dels.is_empty()
6176 || self.pending_truncate.is_some()
6177 {
6178 let mut sets = Vec::with_capacity(conditions.len());
6179 for condition in conditions {
6180 sets.push(self.resolve_condition(condition, snapshot)?);
6181 }
6182 let rids = RowIdSet::intersect_many(sets).into_sorted_vec();
6183 return Ok(Some(self.rows_for_rids(&rids, snapshot)?.len() as u64));
6184 }
6185 let mut sets = Vec::with_capacity(conditions.len());
6186 for condition in conditions {
6187 sets.push(self.resolve_condition(condition, snapshot)?);
6188 }
6189 let mut rids = RowIdSet::intersect_many(sets);
6190 if !self.memtable.is_empty() || !self.mutable_run.is_empty() {
6200 rids.remove_many(self.overlay_tombstoned_rids(snapshot));
6201 }
6202 let count = rids.len() as u64;
6203 crate::trace::QueryTrace::record(|t| {
6204 t.scan_mode = crate::trace::ScanMode::CountSurvivors;
6205 t.survivor_count = Some(count as usize);
6206 t.conditions_pushed = conditions.len();
6207 });
6208 Ok(Some(count))
6209 }
6210
6211 fn overlay_tombstoned_rids(&self, snapshot: Snapshot) -> Vec<u64> {
6216 let mut out = Vec::new();
6217 for row in self.memtable.visible_versions(snapshot.epoch) {
6218 if row.deleted {
6219 out.push(row.row_id.0);
6220 }
6221 }
6222 for row in self.mutable_run.visible_versions(snapshot.epoch) {
6223 if row.deleted {
6224 out.push(row.row_id.0);
6225 }
6226 }
6227 out
6228 }
6229
6230 pub fn bulk_load_columns(
6239 &mut self,
6240 user_columns: Vec<(u16, columnar::NativeColumn)>,
6241 ) -> Result<Epoch> {
6242 self.bulk_load_columns_with(user_columns, 3, false, true)
6243 }
6244
6245 pub fn bulk_load_fast(
6252 &mut self,
6253 user_columns: Vec<(u16, columnar::NativeColumn)>,
6254 ) -> Result<Epoch> {
6255 self.bulk_load_columns_with(user_columns, -1, true, false)
6256 }
6257
6258 fn bulk_load_columns_with(
6259 &mut self,
6260 mut user_columns: Vec<(u16, columnar::NativeColumn)>,
6261 zstd_level: i32,
6262 force_plain: bool,
6263 lz4: bool,
6264 ) -> Result<Epoch> {
6265 let epoch = self.commit()?;
6266 let n = user_columns.first().map(|(_, c)| c.len()).unwrap_or(0);
6267 if n == 0 {
6268 return Ok(epoch);
6269 }
6270 let live_before = self.live_count;
6271 self.spill_mutable_run(epoch)?;
6273 let eager_index_build = self.index_build_policy == IndexBuildPolicy::Eager
6274 && self.indexes_complete
6275 && self.run_refs.is_empty()
6276 && self.memtable.is_empty()
6277 && self.mutable_run.is_empty();
6278 self.fill_auto_inc_native_columns(&mut user_columns, n)?;
6281 self.validate_columns_not_null(&user_columns, n)?;
6282 let winner_idx = self
6283 .bulk_pk_winner_indices(&user_columns, n)
6284 .filter(|idx| idx.len() != n);
6285 let (write_columns, write_n): (Vec<(u16, columnar::NativeColumn)>, usize) =
6286 match winner_idx.as_deref() {
6287 Some(idx) => {
6288 let compacted = user_columns
6289 .iter()
6290 .map(|(id, c)| (*id, c.gather(idx)))
6291 .collect();
6292 (compacted, idx.len())
6293 }
6294 None => (user_columns, n),
6295 };
6296 self.advance_auto_inc_from_native_columns(&write_columns, write_n, live_before)?;
6297 let first = self.allocator.alloc_range(write_n as u64).0;
6298 for rid in first..first + write_n as u64 {
6299 self.reservoir.offer(rid);
6300 }
6301 let run_id = self.next_run_id;
6302 self.next_run_id += 1;
6303 let path = self.run_path(run_id);
6304 let mut writer =
6305 RunWriter::new(&self.schema, run_id as u128, epoch, 0).with_native_endian();
6306 if force_plain {
6307 writer = writer.with_plain();
6308 } else if lz4 {
6309 writer = writer.with_lz4();
6312 } else {
6313 writer = writer.with_zstd_level(zstd_level);
6314 }
6315 if let Some(kek) = &self.kek {
6316 writer = writer.with_encryption(kek.as_ref(), self.indexable_column_specs());
6317 }
6318 let header = writer.write_native(&path, &write_columns, write_n, first)?;
6319 self.run_refs.push(RunRef {
6320 run_id: run_id as u128,
6321 level: 0,
6322 epoch_created: epoch.0,
6323 row_count: header.row_count,
6324 });
6325 self.live_count = self.live_count.saturating_add(write_n as u64);
6326 if eager_index_build {
6327 let row_ids: Vec<u64> = (first..first + write_n as u64).collect();
6328 self.index_columns_bulk(&write_columns, &row_ids);
6329 self.indexes_complete = true;
6330 self.build_learned_ranges()?;
6331 } else {
6332 self.indexes_complete = false;
6336 }
6337 self.mark_flushed(epoch)?;
6338 self.persist_manifest(epoch)?;
6339 if eager_index_build {
6340 self.checkpoint_indexes(epoch);
6341 }
6342 self.clear_result_cache();
6343 self.data_generation = self.data_generation.wrapping_add(1);
6344 Ok(epoch)
6345 }
6346
6347 fn index_columns_bulk(&mut self, columns: &[(u16, columnar::NativeColumn)], row_ids: &[u64]) {
6365 let n = row_ids.len();
6366 if n == 0 {
6367 return;
6368 }
6369 let by_id: std::collections::HashMap<u16, &columnar::NativeColumn> =
6370 columns.iter().map(|(id, c)| (*id, c)).collect();
6371 let ty_of: std::collections::HashMap<u16, TypeId> = self
6372 .schema
6373 .columns
6374 .iter()
6375 .map(|c| (c.id, c.ty.clone()))
6376 .collect();
6377 let pk_id = self.schema.primary_key().map(|c| c.id);
6378
6379 for (i, &rid) in row_ids.iter().enumerate() {
6380 let row_id = RowId(rid);
6381 if let Some(pid) = pk_id {
6382 if let Some(col) = by_id.get(&pid) {
6383 let ty = ty_of.get(&pid).cloned().unwrap_or(TypeId::Int64);
6384 if let Some(key) = bulk_index_key(&self.column_keys, pid, ty, col, i) {
6385 self.insert_hot_pk(key, row_id);
6386 }
6387 }
6388 }
6389 for idef in &self.schema.indexes {
6390 let Some(col) = by_id.get(&idef.column_id) else {
6391 continue;
6392 };
6393 let ty = ty_of.get(&idef.column_id).cloned().unwrap_or(TypeId::Int64);
6394 match idef.kind {
6395 IndexKind::Bitmap => {
6396 if let Some(b) = self.bitmap.get_mut(&idef.column_id) {
6397 if let Some(key) =
6398 bulk_index_key(&self.column_keys, idef.column_id, ty, col, i)
6399 {
6400 b.insert(key, row_id);
6401 }
6402 }
6403 }
6404 IndexKind::FmIndex => {
6405 if let Some(f) = self.fm.get_mut(&idef.column_id) {
6406 if let Some(bytes) = columnar::native_bytes_at(col, i) {
6407 f.insert(bytes.to_vec(), row_id);
6408 }
6409 }
6410 }
6411 IndexKind::Sparse => {
6412 if let Some(s) = self.sparse.get_mut(&idef.column_id) {
6413 if let Some(bytes) = columnar::native_bytes_at(col, i) {
6414 if let Ok(terms) = bincode::deserialize::<Vec<(u32, f32)>>(bytes) {
6415 s.insert(&terms, row_id);
6416 }
6417 }
6418 }
6419 }
6420 IndexKind::MinHash => {
6421 if let Some(mh) = self.minhash.get_mut(&idef.column_id) {
6422 if let Some(bytes) = columnar::native_bytes_at(col, i) {
6423 let tokens = crate::index::token_hashes_from_bytes(bytes);
6424 mh.insert(&tokens, row_id);
6425 }
6426 }
6427 }
6428 _ => {}
6429 }
6430 }
6431 }
6432 }
6433
6434 pub fn visible_columns_native(
6439 &self,
6440 snapshot: Snapshot,
6441 projection: Option<&[u16]>,
6442 ) -> Result<Vec<(u16, columnar::NativeColumn)>> {
6443 let wanted: Vec<u16> = match projection {
6444 Some(p) => p.to_vec(),
6445 None => self.schema.columns.iter().map(|c| c.id).collect(),
6446 };
6447 if self.ttl.is_none()
6448 && self.memtable.is_empty()
6449 && self.mutable_run.is_empty()
6450 && self.run_refs.len() == 1
6451 {
6452 let rr = self.run_refs[0].clone();
6453 let mut reader = self.open_reader(rr.run_id)?;
6454 let idxs = reader.visible_indices_native(snapshot.epoch)?;
6455 let all_visible = idxs.len() == reader.row_count();
6456 if reader.has_mmap() {
6462 use rayon::prelude::*;
6463 let valid: Vec<u16> = wanted
6466 .iter()
6467 .filter(|cid| self.schema.columns.iter().any(|c| c.id == **cid))
6468 .copied()
6469 .collect();
6470 let decoded: Vec<(u16, columnar::NativeColumn)> = valid
6472 .par_iter()
6473 .filter_map(|cid| {
6474 reader
6475 .column_native_shared(*cid)
6476 .ok()
6477 .map(|col| (*cid, col))
6478 })
6479 .collect();
6480 let cols = decoded
6481 .into_iter()
6482 .map(|(id, col)| (id, if all_visible { col } else { col.gather(&idxs) }))
6483 .collect();
6484 return Ok(cols);
6485 }
6486 let mut cols = Vec::with_capacity(wanted.len());
6487 for cid in &wanted {
6488 let cdef = match self.schema.columns.iter().find(|c| c.id == *cid) {
6489 Some(c) => c,
6490 None => continue,
6491 };
6492 let col = reader.column_native(cdef.id)?;
6493 cols.push((cdef.id, if all_visible { col } else { col.gather(&idxs) }));
6494 }
6495 return Ok(cols);
6496 }
6497 let vcols = self.visible_columns(snapshot)?;
6498 let want_set: std::collections::HashSet<u16> = wanted.iter().copied().collect();
6499 let out: Vec<(u16, columnar::NativeColumn)> = vcols
6500 .into_iter()
6501 .filter(|(id, _)| want_set.contains(id))
6502 .map(|(id, vals)| {
6503 let ty = self
6504 .schema
6505 .columns
6506 .iter()
6507 .find(|c| c.id == id)
6508 .map(|c| c.ty.clone())
6509 .unwrap_or(TypeId::Bytes);
6510 (id, columnar::values_to_native(ty, &vals))
6511 })
6512 .collect();
6513 Ok(out)
6514 }
6515
6516 pub fn run_count(&self) -> usize {
6517 self.run_refs.len()
6518 }
6519
6520 pub fn memtable_is_empty(&self) -> bool {
6522 self.memtable.is_empty()
6523 }
6524
6525 pub fn page_cache_stats(&self) -> crate::cache::CacheStats {
6529 self.page_cache.stats()
6530 }
6531
6532 pub fn reset_page_cache_stats(&self) {
6534 self.page_cache.reset_stats();
6535 }
6536
6537 pub fn run_ids(&self) -> Vec<u128> {
6540 self.run_refs.iter().map(|r| r.run_id).collect()
6541 }
6542
6543 pub fn single_run_is_clean(&self) -> bool {
6547 if self.ttl.is_some() || self.run_refs.len() != 1 {
6548 return false;
6549 }
6550 self.open_reader(self.run_refs[0].run_id)
6551 .map(|r| r.is_clean())
6552 .unwrap_or(false)
6553 }
6554
6555 fn resolve_footprint(
6562 &self,
6563 conditions: &[crate::query::Condition],
6564 snapshot: Snapshot,
6565 ) -> roaring::RoaringBitmap {
6566 if !self.memtable.is_empty() || !self.mutable_run.is_empty() {
6567 return roaring::RoaringBitmap::new();
6568 }
6569 if self.run_refs.is_empty() {
6570 return roaring::RoaringBitmap::new();
6571 }
6572 if self.run_refs.len() == 1 {
6574 if let Ok(mut reader) = self.open_reader(self.run_refs[0].run_id) {
6575 if let Ok(rids) = self.resolve_survivor_rids(conditions, &mut reader, snapshot) {
6576 return rids.to_roaring_lossy();
6577 }
6578 }
6579 }
6580 roaring::RoaringBitmap::new()
6581 }
6582
6583 pub fn query_columns_native_cached(
6594 &mut self,
6595 conditions: &[crate::query::Condition],
6596 projection: Option<&[u16]>,
6597 snapshot: Snapshot,
6598 ) -> Result<Option<Vec<(u16, columnar::NativeColumn)>>> {
6599 if self.ttl.is_some() {
6602 return self.query_columns_native(conditions, projection, snapshot);
6603 }
6604 if conditions.is_empty() {
6605 return self.query_columns_native(conditions, projection, snapshot);
6606 }
6607 let key = crate::query::canonical_query_key(conditions, projection, snapshot.epoch.0);
6611 if let Some(hit) = self.result_cache.lock().get_columns(key) {
6612 crate::trace::QueryTrace::record(|t| {
6613 t.result_cache_hit = true;
6614 t.scan_mode = crate::trace::ScanMode::NativePushdown;
6615 });
6616 return Ok(Some((*hit).clone()));
6617 }
6618 let res = self.query_columns_native(conditions, projection, snapshot)?;
6619 if let Some(cols) = &res {
6620 let footprint = self.resolve_footprint(conditions, snapshot);
6621 let condition_cols = crate::query::condition_columns(conditions);
6622 self.result_cache.lock().insert(
6623 key,
6624 CachedEntry {
6625 data: CachedData::Columns(Arc::new(cols.clone())),
6626 footprint,
6627 condition_cols,
6628 },
6629 );
6630 }
6631 Ok(res)
6632 }
6633
6634 pub fn query_cached(&mut self, q: &crate::query::Query) -> Result<Vec<Row>> {
6639 if self.ttl.is_some() {
6640 return self.query(q);
6641 }
6642 if q.conditions.is_empty() {
6643 return self.query(q);
6644 }
6645 let key = crate::query::canonical_query_key(&q.conditions, None, 0)
6646 ^ (q.limit.unwrap_or(usize::MAX) as u64).wrapping_mul(0x9E37_79B9_7F4A_7C15)
6647 ^ (q.offset as u64).wrapping_mul(0xC2B2_AE3D_27D4_EB4F);
6648 if let Some(hit) = self.result_cache.lock().get_rows(key) {
6649 crate::trace::QueryTrace::record(|t| {
6650 t.result_cache_hit = true;
6651 t.scan_mode = crate::trace::ScanMode::Materialized;
6652 });
6653 return Ok((*hit).clone());
6654 }
6655 let rows = self.query(q)?;
6656 let footprint = rows.iter().map(|r| r.row_id.0 as u32).collect();
6657 let condition_cols = crate::query::condition_columns(&q.conditions);
6658 self.result_cache.lock().insert(
6659 key,
6660 CachedEntry {
6661 data: CachedData::Rows(Arc::new(rows.clone())),
6662 footprint,
6663 condition_cols,
6664 },
6665 );
6666 Ok(rows)
6667 }
6668
6669 #[allow(clippy::type_complexity)]
6684 pub fn query_columns_native_traced(
6685 &mut self,
6686 conditions: &[crate::query::Condition],
6687 projection: Option<&[u16]>,
6688 snapshot: Snapshot,
6689 ) -> Result<(
6690 Option<Vec<(u16, columnar::NativeColumn)>>,
6691 crate::trace::QueryTrace,
6692 )> {
6693 let (result, trace) = crate::trace::QueryTrace::capture(|| {
6694 self.query_columns_native(conditions, projection, snapshot)
6695 });
6696 Ok((result?, trace))
6697 }
6698
6699 #[allow(clippy::type_complexity)]
6702 pub fn query_columns_native_cached_traced(
6703 &mut self,
6704 conditions: &[crate::query::Condition],
6705 projection: Option<&[u16]>,
6706 snapshot: Snapshot,
6707 ) -> Result<(
6708 Option<Vec<(u16, columnar::NativeColumn)>>,
6709 crate::trace::QueryTrace,
6710 )> {
6711 let (result, trace) = crate::trace::QueryTrace::capture(|| {
6712 self.query_columns_native_cached(conditions, projection, snapshot)
6713 });
6714 Ok((result?, trace))
6715 }
6716
6717 pub fn native_page_cursor_traced(
6719 &self,
6720 snapshot: Snapshot,
6721 projection: Vec<(u16, TypeId)>,
6722 conditions: &[crate::query::Condition],
6723 ) -> Result<(Option<NativePageCursor>, crate::trace::QueryTrace)> {
6724 let (result, trace) = crate::trace::QueryTrace::capture(|| {
6725 self.native_page_cursor(snapshot, projection, conditions)
6726 });
6727 Ok((result?, trace))
6728 }
6729
6730 pub fn native_multi_run_cursor_traced(
6732 &self,
6733 snapshot: Snapshot,
6734 projection: Vec<(u16, TypeId)>,
6735 conditions: &[crate::query::Condition],
6736 ) -> Result<(
6737 Option<crate::cursor::MultiRunCursor>,
6738 crate::trace::QueryTrace,
6739 )> {
6740 let (result, trace) = crate::trace::QueryTrace::capture(|| {
6741 self.native_multi_run_cursor(snapshot, projection, conditions)
6742 });
6743 Ok((result?, trace))
6744 }
6745
6746 pub fn count_conditions_traced(
6748 &mut self,
6749 conditions: &[crate::query::Condition],
6750 snapshot: Snapshot,
6751 ) -> Result<(Option<u64>, crate::trace::QueryTrace)> {
6752 let (result, trace) =
6753 crate::trace::QueryTrace::capture(|| self.count_conditions(conditions, snapshot));
6754 Ok((result?, trace))
6755 }
6756
6757 pub fn query_traced(
6759 &mut self,
6760 q: &crate::query::Query,
6761 ) -> Result<(Vec<Row>, crate::trace::QueryTrace)> {
6762 let (result, trace) = crate::trace::QueryTrace::capture(|| self.query(q));
6763 Ok((result?, trace))
6764 }
6765
6766 pub fn query_columns_native(
6771 &mut self,
6772 conditions: &[crate::query::Condition],
6773 projection: Option<&[u16]>,
6774 snapshot: Snapshot,
6775 ) -> Result<Option<Vec<(u16, columnar::NativeColumn)>>> {
6776 use crate::query::Condition;
6777 if self.ttl.is_some() {
6780 return Ok(None);
6781 }
6782 if conditions.is_empty() {
6783 return Ok(None);
6784 }
6785 self.ensure_indexes_complete()?;
6786
6787 let served = |c: &Condition| {
6792 matches!(
6793 c,
6794 Condition::Pk(_)
6795 | Condition::BitmapEq { .. }
6796 | Condition::BitmapIn { .. }
6797 | Condition::BytesPrefix { .. }
6798 | Condition::FmContains { .. }
6799 | Condition::FmContainsAll { .. }
6800 | Condition::Ann { .. }
6801 | Condition::Range { .. }
6802 | Condition::RangeF64 { .. }
6803 | Condition::SparseMatch { .. }
6804 | Condition::MinHashSimilar { .. }
6805 | Condition::IsNull { .. }
6806 | Condition::IsNotNull { .. }
6807 )
6808 };
6809 if !conditions.iter().all(served) {
6810 return Ok(None);
6811 }
6812 let fast_path =
6813 self.memtable.is_empty() && self.mutable_run.is_empty() && self.run_refs.len() == 1;
6814 crate::trace::QueryTrace::record(|t| {
6815 t.run_count = self.run_refs.len();
6816 t.memtable_rows = self.memtable.len();
6817 t.mutable_run_rows = self.mutable_run.len();
6818 t.conditions_pushed = conditions.len();
6819 t.learned_range_used = conditions.iter().any(|c| match c {
6820 Condition::Range { column_id, .. } | Condition::RangeF64 { column_id, .. } => {
6821 self.learned_range.contains_key(column_id)
6822 }
6823 _ => false,
6824 });
6825 });
6826 let col_ids: Vec<u16> = projection
6828 .map(|p| p.to_vec())
6829 .unwrap_or_else(|| self.schema.columns.iter().map(|c| c.id).collect());
6830 let proj_pairs: Vec<(u16, TypeId)> = col_ids
6831 .iter()
6832 .map(|&cid| {
6833 let ty = self
6834 .schema
6835 .columns
6836 .iter()
6837 .find(|c| c.id == cid)
6838 .map(|c| c.ty.clone())
6839 .unwrap_or(TypeId::Bytes);
6840 (cid, ty)
6841 })
6842 .collect();
6843
6844 if fast_path {
6850 let needs_column = conditions.iter().any(|c| match c {
6853 Condition::Range { column_id, .. } => !self.learned_range.contains_key(column_id),
6854 Condition::RangeF64 { column_id, .. } => {
6855 !self.learned_range.contains_key(column_id)
6856 }
6857 _ => false,
6858 });
6859 let mut reader_opt: Option<RunReader> = if needs_column {
6860 Some(self.open_reader(self.run_refs[0].run_id)?)
6861 } else {
6862 None
6863 };
6864 let mut sets: Vec<RowIdSet> = Vec::new();
6865 for c in conditions {
6866 let s = match c {
6867 Condition::Range { column_id, lo, hi }
6868 if !self.learned_range.contains_key(column_id) =>
6869 {
6870 if reader_opt.is_none() {
6871 reader_opt = Some(self.open_reader(self.run_refs[0].run_id)?);
6872 }
6873 reader_opt
6874 .as_mut()
6875 .expect("reader opened for range")
6876 .range_row_id_set_i64(*column_id, *lo, *hi)?
6877 }
6878 Condition::RangeF64 {
6879 column_id,
6880 lo,
6881 lo_inclusive,
6882 hi,
6883 hi_inclusive,
6884 } if !self.learned_range.contains_key(column_id) => {
6885 if reader_opt.is_none() {
6886 reader_opt = Some(self.open_reader(self.run_refs[0].run_id)?);
6887 }
6888 reader_opt
6889 .as_mut()
6890 .expect("reader opened for range")
6891 .range_row_id_set_f64(
6892 *column_id,
6893 *lo,
6894 *lo_inclusive,
6895 *hi,
6896 *hi_inclusive,
6897 )?
6898 }
6899 _ => self.resolve_condition(c, snapshot)?,
6900 };
6901 sets.push(s);
6902 }
6903 let candidates = RowIdSet::intersect_many(sets);
6904 crate::trace::QueryTrace::record(|t| {
6905 t.survivor_count = Some(candidates.len());
6906 });
6907 if candidates.is_empty() {
6908 let cols: Vec<(u16, columnar::NativeColumn)> = col_ids
6909 .iter()
6910 .map(|&id| {
6911 (
6912 id,
6913 columnar::null_native(
6914 proj_pairs
6915 .iter()
6916 .find(|(c, _)| c == &id)
6917 .map(|(_, t)| t.clone())
6918 .unwrap_or(TypeId::Bytes),
6919 0,
6920 ),
6921 )
6922 })
6923 .collect();
6924 return Ok(Some(cols));
6925 }
6926 let mut reader = match reader_opt.take() {
6927 Some(r) => r,
6928 None => self.open_reader(self.run_refs[0].run_id)?,
6929 };
6930 let candidate_ids = candidates.into_sorted_vec();
6931 let (positions, fast_rid) = if let Some(positions) =
6932 reader.positions_for_row_ids_fast(&candidate_ids)
6933 {
6934 (positions, true)
6935 } else {
6936 let col = reader.column_native(crate::sorted_run::SYS_ROW_ID)?;
6937 match col {
6938 columnar::NativeColumn::Int64 { data, .. } => {
6939 let mut p: Vec<usize> = candidate_ids
6940 .iter()
6941 .filter_map(|rid| data.binary_search(&(*rid as i64)).ok())
6942 .collect();
6943 p.sort_unstable();
6944 (p, false)
6945 }
6946 _ => return Err(MongrelError::InvalidArgument("sys row_id not int64".into())),
6947 }
6948 };
6949 crate::trace::QueryTrace::record(|t| {
6950 t.scan_mode = crate::trace::ScanMode::NativePushdown;
6951 t.fast_row_id_map = fast_rid;
6952 });
6953 let mut cols = Vec::with_capacity(col_ids.len());
6954 for cid in &col_ids {
6955 let col = reader.column_native(*cid)?;
6956 cols.push((*cid, col.gather(&positions)));
6957 }
6958 return Ok(Some(cols));
6959 }
6960
6961 if !self.run_refs.is_empty() {
6974 use crate::cursor::{drain_cursor_to_columns, Cursor};
6975 let remaining: usize;
6976 let mut cursor: Box<dyn crate::cursor::Cursor> = if self.run_refs.len() == 1 {
6977 let c = self
6978 .native_page_cursor(snapshot, proj_pairs.clone(), conditions)?
6979 .expect("single-run cursor should build when run_refs.len() == 1");
6980 remaining = c.remaining_rows();
6981 Box::new(c)
6982 } else {
6983 let c = self
6984 .native_multi_run_cursor(snapshot, proj_pairs.clone(), conditions)?
6985 .expect("multi-run cursor should build when run_refs.len() >= 1");
6986 remaining = c.remaining_rows();
6987 Box::new(c)
6988 };
6989 crate::trace::QueryTrace::record(|t| {
6990 if t.survivor_count.is_none() {
6991 t.survivor_count = Some(remaining);
6992 }
6993 });
6994 let cols = drain_cursor_to_columns(cursor.as_mut(), &proj_pairs)?;
6995 return Ok(Some(cols));
6996 }
6997
6998 crate::trace::QueryTrace::record(|t| {
7003 t.scan_mode = crate::trace::ScanMode::Materialized;
7004 t.row_materialized = true;
7005 });
7006 let mut sets: Vec<RowIdSet> = Vec::with_capacity(conditions.len());
7007 for c in conditions {
7008 sets.push(self.resolve_condition(c, snapshot)?);
7009 }
7010 let rids = RowIdSet::intersect_many(sets).into_sorted_vec();
7011 let rows = self.rows_for_rids(&rids, snapshot)?;
7012 let mut cols: Vec<(u16, columnar::NativeColumn)> = Vec::with_capacity(col_ids.len());
7013 for (cid, ty) in &proj_pairs {
7014 let vals: Vec<Value> = rows
7015 .iter()
7016 .map(|r| r.columns.get(cid).cloned().unwrap_or(Value::Null))
7017 .collect();
7018 cols.push((*cid, columnar::values_to_native(ty.clone(), &vals)));
7019 }
7020 Ok(Some(cols))
7021 }
7022
7023 pub fn native_page_cursor(
7038 &self,
7039 snapshot: Snapshot,
7040 projection: Vec<(u16, TypeId)>,
7041 conditions: &[crate::query::Condition],
7042 ) -> Result<Option<NativePageCursor>> {
7043 use crate::cursor::build_page_plans;
7044 if self.ttl.is_some() {
7045 return Ok(None);
7046 }
7047 if !conditions.is_empty() && !self.indexes_complete {
7050 return Ok(None);
7051 }
7052 if self.run_refs.len() != 1 {
7053 return Ok(None);
7054 }
7055 let mut reader = self.open_reader(self.run_refs[0].run_id)?;
7056 let (positions, rids) = reader.visible_positions_with_rids(snapshot.epoch)?;
7057
7058 let overlay_rids: HashSet<u64> = {
7061 let mut s = HashSet::new();
7062 for row in self.memtable.visible_versions(snapshot.epoch) {
7063 s.insert(row.row_id.0);
7064 }
7065 for row in self.mutable_run.visible_versions(snapshot.epoch) {
7066 s.insert(row.row_id.0);
7067 }
7068 s
7069 };
7070
7071 let survivors = if conditions.is_empty() {
7075 None
7076 } else {
7077 Some(self.resolve_survivor_rids(conditions, &mut reader, snapshot)?)
7078 };
7079
7080 let run_survivors: Option<RowIdSet> = if overlay_rids.is_empty() {
7087 survivors.clone()
7088 } else if let Some(s) = &survivors {
7089 let mut run_set = s.clone();
7090 run_set.remove_many(overlay_rids.iter().copied());
7091 Some(run_set)
7092 } else {
7093 Some(RowIdSet::from_unsorted(
7094 rids.iter()
7095 .map(|&r| r as u64)
7096 .filter(|r| !overlay_rids.contains(r))
7097 .collect(),
7098 ))
7099 };
7100
7101 let overlay_rows = if overlay_rids.is_empty() {
7102 Vec::new()
7103 } else {
7104 let bound = Self::overlay_materialization_bound(conditions, &survivors);
7105 self.overlay_visible_rows(snapshot, bound)
7106 };
7107
7108 let plans = if positions.is_empty() {
7110 Vec::new()
7111 } else {
7112 let page_rows = reader.page_row_counts(crate::sorted_run::SYS_ROW_ID)?;
7113 build_page_plans(&positions, &rids, &page_rows, run_survivors.as_ref())
7114 };
7115
7116 let overlay = if overlay_rows.is_empty() {
7118 None
7119 } else {
7120 let filtered =
7121 self.filter_overlay_rows(overlay_rows, conditions, survivors.as_ref(), snapshot)?;
7122 if filtered.is_empty() {
7123 None
7124 } else {
7125 Some(self.materialize_overlay(&filtered, &projection))
7126 }
7127 };
7128
7129 let overlay_row_count = overlay
7130 .as_ref()
7131 .map(|c| c.first().map(|c| c.len()).unwrap_or(0))
7132 .unwrap_or(0);
7133 crate::trace::QueryTrace::record(|t| {
7134 t.scan_mode = crate::trace::ScanMode::NativePageCursor;
7135 t.run_count = self.run_refs.len();
7136 t.memtable_rows = self.memtable.len();
7137 t.mutable_run_rows = self.mutable_run.len();
7138 t.overlay_rows = overlay_row_count;
7139 t.conditions_pushed = conditions.len();
7140 t.pages_decoded = plans
7141 .iter()
7142 .map(|p| p.positions.len())
7143 .sum::<usize>()
7144 .min(1);
7145 });
7146
7147 Ok(Some(NativePageCursor::new_with_overlay(
7148 reader, projection, plans, overlay,
7149 )))
7150 }
7151 #[allow(clippy::type_complexity)]
7161 pub fn native_multi_run_cursor(
7162 &self,
7163 snapshot: Snapshot,
7164 projection: Vec<(u16, TypeId)>,
7165 conditions: &[crate::query::Condition],
7166 ) -> Result<Option<crate::cursor::MultiRunCursor>> {
7167 use crate::cursor::{MultiRunCursor, RunStream};
7168 use crate::sorted_run::SYS_ROW_ID;
7169 use std::collections::{BinaryHeap, HashMap, HashSet};
7170 if self.ttl.is_some() {
7171 return Ok(None);
7172 }
7173 if !conditions.is_empty() && !self.indexes_complete {
7176 return Ok(None);
7177 }
7178 if self.run_refs.is_empty() {
7179 return Ok(None);
7180 }
7181
7182 let mut run_meta: Vec<(RunReader, Vec<i64>, Vec<i64>, Vec<u8>, Vec<usize>)> =
7184 Vec::with_capacity(self.run_refs.len());
7185 for rr in &self.run_refs {
7186 let mut reader = self.open_reader(rr.run_id)?;
7187 let (rids, eps, del) = reader.system_columns_native()?;
7188 let page_rows = reader.page_row_counts(SYS_ROW_ID)?;
7189 run_meta.push((reader, rids, eps, del, page_rows));
7190 }
7191
7192 let mut best: HashMap<u64, (u64, usize, usize, bool)> = HashMap::new();
7196 for (run_idx, (_, rids, eps, del, _)) in run_meta.iter().enumerate() {
7197 for i in 0..rids.len() {
7198 let rid = rids[i] as u64;
7199 let e = eps[i] as u64;
7200 if e > snapshot.epoch.0 {
7201 continue;
7202 }
7203 let is_del = del[i] != 0;
7204 best.entry(rid)
7205 .and_modify(|cur| {
7206 if e > cur.0 {
7207 *cur = (e, run_idx, i, is_del);
7208 }
7209 })
7210 .or_insert((e, run_idx, i, is_del));
7211 }
7212 }
7213
7214 let overlay_rids: HashSet<u64> = {
7216 let mut s = HashSet::new();
7217 for row in self.memtable.visible_versions(snapshot.epoch) {
7218 s.insert(row.row_id.0);
7219 }
7220 for row in self.mutable_run.visible_versions(snapshot.epoch) {
7221 s.insert(row.row_id.0);
7222 }
7223 s
7224 };
7225
7226 let survivors: Option<RowIdSet> = if conditions.is_empty() {
7228 None
7229 } else {
7230 let mut sets: Vec<RowIdSet> = Vec::with_capacity(conditions.len());
7231 for c in conditions {
7232 sets.push(self.resolve_condition(c, snapshot)?);
7233 }
7234 Some(RowIdSet::intersect_many(sets))
7235 };
7236
7237 let mut per_run: Vec<Vec<(u64, usize)>> = vec![Vec::new(); run_meta.len()];
7241 for (rid, (_, run_idx, pos, deleted)) in &best {
7242 if *deleted {
7243 continue;
7244 }
7245 if overlay_rids.contains(rid) {
7246 continue;
7247 }
7248 if let Some(s) = &survivors {
7249 if !s.contains(*rid) {
7250 continue;
7251 }
7252 }
7253 per_run[*run_idx].push((*rid, *pos));
7254 }
7255 for v in per_run.iter_mut() {
7256 v.sort_unstable_by_key(|&(rid, _)| rid);
7257 }
7258
7259 let mut streams = Vec::with_capacity(run_meta.len());
7261 let mut heap: BinaryHeap<std::cmp::Reverse<(u64, usize)>> = BinaryHeap::new();
7262 let mut total = 0usize;
7263 for (run_idx, (reader, _, _, _, page_rows)) in run_meta.into_iter().enumerate() {
7264 let mut starts = Vec::with_capacity(page_rows.len());
7265 let mut acc = 0usize;
7266 for &r in &page_rows {
7267 starts.push(acc);
7268 acc += r;
7269 }
7270 let mut survivors_vec: Vec<(u64, usize, usize)> =
7271 Vec::with_capacity(per_run[run_idx].len());
7272 for &(rid, pos) in &per_run[run_idx] {
7273 let page_seq = match starts.partition_point(|&s| s <= pos) {
7274 0 => continue,
7275 p => p - 1,
7276 };
7277 let within = pos - starts[page_seq];
7278 survivors_vec.push((rid, page_seq, within));
7279 }
7280 total += survivors_vec.len();
7281 if let Some(&(rid, _, _)) = survivors_vec.first() {
7282 heap.push(std::cmp::Reverse((rid, run_idx)));
7283 }
7284 streams.push(RunStream::new(reader, survivors_vec, page_rows));
7285 }
7286
7287 let overlay_rows = if overlay_rids.is_empty() {
7289 Vec::new()
7290 } else {
7291 let bound = Self::overlay_materialization_bound(conditions, &survivors);
7292 self.overlay_visible_rows(snapshot, bound)
7293 };
7294 let overlay = if overlay_rows.is_empty() {
7295 None
7296 } else {
7297 let filtered =
7298 self.filter_overlay_rows(overlay_rows, conditions, survivors.as_ref(), snapshot)?;
7299 if filtered.is_empty() {
7300 None
7301 } else {
7302 Some(self.materialize_overlay(&filtered, &projection))
7303 }
7304 };
7305
7306 let overlay_row_count = overlay
7307 .as_ref()
7308 .map(|c| c.first().map(|c| c.len()).unwrap_or(0))
7309 .unwrap_or(0);
7310 crate::trace::QueryTrace::record(|t| {
7311 t.scan_mode = crate::trace::ScanMode::MultiRunCursor;
7312 t.run_count = self.run_refs.len();
7313 t.memtable_rows = self.memtable.len();
7314 t.mutable_run_rows = self.mutable_run.len();
7315 t.overlay_rows = overlay_row_count;
7316 t.conditions_pushed = conditions.len();
7317 t.survivor_count = Some(total);
7318 });
7319
7320 Ok(Some(MultiRunCursor::new(
7321 streams, projection, heap, total, overlay,
7322 )))
7323 }
7324
7325 fn overlay_materialization_bound<'a>(
7337 conditions: &[crate::query::Condition],
7338 survivors: &'a Option<RowIdSet>,
7339 ) -> Option<&'a RowIdSet> {
7340 use crate::query::Condition;
7341 let has_range = conditions
7342 .iter()
7343 .any(|c| matches!(c, Condition::Range { .. } | Condition::RangeF64 { .. }));
7344 if has_range {
7345 None
7346 } else {
7347 survivors.as_ref()
7348 }
7349 }
7350
7351 fn overlay_visible_rows(&self, snapshot: Snapshot, bound: Option<&RowIdSet>) -> Vec<Row> {
7363 let mut best: HashMap<u64, (Epoch, Row)> = HashMap::new();
7364 let mut fold = |row: Row| {
7365 if let Some(b) = bound {
7366 if !b.contains(row.row_id.0) {
7367 return;
7368 }
7369 }
7370 best.entry(row.row_id.0)
7371 .and_modify(|(be, br)| {
7372 if row.committed_epoch > *be {
7373 *be = row.committed_epoch;
7374 *br = row.clone();
7375 }
7376 })
7377 .or_insert_with(|| (row.committed_epoch, row));
7378 };
7379 for row in self.memtable.visible_versions(snapshot.epoch) {
7380 fold(row);
7381 }
7382 for row in self.mutable_run.visible_versions(snapshot.epoch) {
7383 fold(row);
7384 }
7385 let mut out: Vec<Row> = best
7386 .into_values()
7387 .filter_map(|(_, r)| if r.deleted { None } else { Some(r) })
7388 .collect();
7389 out.sort_by_key(|r| r.row_id);
7390 out
7391 }
7392
7393 fn filter_overlay_rows(
7401 &self,
7402 rows: Vec<Row>,
7403 conditions: &[crate::query::Condition],
7404 survivors: Option<&RowIdSet>,
7405 snapshot: Snapshot,
7406 ) -> Result<Vec<Row>> {
7407 if conditions.is_empty() {
7408 return Ok(rows);
7409 }
7410 use crate::query::Condition;
7411 let all_index_served = !conditions
7415 .iter()
7416 .any(|c| matches!(c, Condition::Range { .. } | Condition::RangeF64 { .. }));
7417 if all_index_served {
7418 return Ok(rows
7419 .into_iter()
7420 .filter(|r| survivors.map_or(true, |s| s.contains(r.row_id.0)))
7421 .collect());
7422 }
7423 let mut per_cond_sets: Vec<RowIdSet> = Vec::with_capacity(conditions.len());
7426 for c in conditions {
7427 let s = match c {
7428 Condition::Range { .. } | Condition::RangeF64 { .. } => RowIdSet::empty(),
7429 _ => self.resolve_condition(c, snapshot)?,
7430 };
7431 per_cond_sets.push(s);
7432 }
7433 Ok(rows
7434 .into_iter()
7435 .filter(|row| {
7436 conditions.iter().enumerate().all(|(i, c)| match c {
7437 Condition::Range { column_id, lo, hi } => {
7438 matches!(row.columns.get(column_id), Some(Value::Int64(v)) if *v >= *lo && *v <= *hi)
7439 }
7440 Condition::RangeF64 { column_id, lo, lo_inclusive, hi, hi_inclusive } => {
7441 match row.columns.get(column_id) {
7442 Some(Value::Float64(v)) => {
7443 let lo_ok = if *lo_inclusive { *v >= *lo } else { *v > *lo };
7444 let hi_ok = if *hi_inclusive { *v <= *hi } else { *v < *hi };
7445 lo_ok && hi_ok
7446 }
7447 _ => false,
7448 }
7449 }
7450 _ => per_cond_sets[i].contains(row.row_id.0),
7451 })
7452 })
7453 .collect())
7454 }
7455
7456 fn materialize_overlay(
7459 &self,
7460 rows: &[Row],
7461 projection: &[(u16, TypeId)],
7462 ) -> Vec<columnar::NativeColumn> {
7463 if projection.is_empty() {
7464 return vec![columnar::null_native(TypeId::Int64, rows.len())];
7465 }
7466 let mut cols = Vec::with_capacity(projection.len());
7467 for (cid, ty) in projection {
7468 let vals: Vec<Value> = rows
7469 .iter()
7470 .map(|r| r.columns.get(cid).cloned().unwrap_or(Value::Null))
7471 .collect();
7472 cols.push(columnar::values_to_native(ty.clone(), &vals));
7473 }
7474 cols
7475 }
7476
7477 fn resolve_survivor_rids(
7482 &self,
7483 conditions: &[crate::query::Condition],
7484 reader: &mut RunReader,
7485 snapshot: Snapshot,
7486 ) -> Result<RowIdSet> {
7487 use crate::query::Condition;
7488 let mut sets: Vec<RowIdSet> = Vec::new();
7489 for c in conditions {
7490 self.validate_condition(c)?;
7491 let s: RowIdSet = match c {
7492 Condition::Pk(key) => {
7493 let lookup = self
7494 .schema
7495 .primary_key()
7496 .map(|pk| self.index_lookup_key_bytes(pk.id, key))
7497 .unwrap_or_else(|| key.clone());
7498 self.hot
7499 .get(&lookup)
7500 .map(|r| RowIdSet::one(r.0))
7501 .unwrap_or_else(RowIdSet::empty)
7502 }
7503 Condition::BitmapEq { column_id, value } => {
7504 let lookup = self.index_lookup_key_bytes(*column_id, value);
7505 self.bitmap
7506 .get(column_id)
7507 .map(|b| RowIdSet::from_roaring(b.get(&lookup)))
7508 .unwrap_or_else(RowIdSet::empty)
7509 }
7510 Condition::BitmapIn { column_id, values } => {
7511 let bm = self.bitmap.get(column_id);
7512 let mut acc = roaring::RoaringBitmap::new();
7513 if let Some(b) = bm {
7514 for v in values {
7515 let lookup = self.index_lookup_key_bytes(*column_id, v);
7516 acc |= b.get(&lookup);
7517 }
7518 }
7519 RowIdSet::from_roaring(acc)
7520 }
7521 Condition::BytesPrefix { column_id, prefix } => {
7522 if let Some(b) = self.bitmap.get(column_id) {
7523 let lookup_prefix = self.index_lookup_key_bytes(*column_id, prefix);
7524 let mut acc = roaring::RoaringBitmap::new();
7525 for key in b.keys() {
7526 if key.starts_with(&lookup_prefix) {
7527 acc |= b.get(key);
7528 }
7529 }
7530 RowIdSet::from_roaring(acc)
7531 } else {
7532 RowIdSet::empty()
7533 }
7534 }
7535 Condition::FmContains { column_id, pattern } => self
7536 .fm
7537 .get(column_id)
7538 .map(|f| {
7539 RowIdSet::from_unsorted(
7540 f.locate(pattern).into_iter().map(|r| r.0).collect(),
7541 )
7542 })
7543 .unwrap_or_else(RowIdSet::empty),
7544 Condition::FmContainsAll {
7545 column_id,
7546 patterns,
7547 } => {
7548 if let Some(f) = self.fm.get(column_id) {
7549 let sets: Vec<RowIdSet> = patterns
7550 .iter()
7551 .map(|pat| {
7552 RowIdSet::from_unsorted(
7553 f.locate(pat).into_iter().map(|r| r.0).collect(),
7554 )
7555 })
7556 .collect();
7557 RowIdSet::intersect_many(sets)
7558 } else {
7559 RowIdSet::empty()
7560 }
7561 }
7562 Condition::Ann {
7563 column_id,
7564 query,
7565 k,
7566 } => RowIdSet::from_unsorted(
7567 self.retrieve_filtered(
7568 &crate::query::Retriever::Ann {
7569 column_id: *column_id,
7570 query: query.clone(),
7571 k: *k,
7572 },
7573 snapshot,
7574 None,
7575 None,
7576 None,
7577 None,
7578 )?
7579 .into_iter()
7580 .map(|hit| hit.row_id.0)
7581 .collect(),
7582 ),
7583 Condition::SparseMatch {
7584 column_id,
7585 query,
7586 k,
7587 } => RowIdSet::from_unsorted(
7588 self.retrieve_filtered(
7589 &crate::query::Retriever::Sparse {
7590 column_id: *column_id,
7591 query: query.clone(),
7592 k: *k,
7593 },
7594 snapshot,
7595 None,
7596 None,
7597 None,
7598 None,
7599 )?
7600 .into_iter()
7601 .map(|hit| hit.row_id.0)
7602 .collect(),
7603 ),
7604 Condition::MinHashSimilar {
7605 column_id,
7606 query,
7607 k,
7608 } => match self.minhash.get(column_id) {
7609 Some(index) => {
7610 let candidates = index.candidate_row_ids(query);
7611 let eligible =
7612 self.eligible_candidate_ids(&candidates, *column_id, snapshot, None)?;
7613 RowIdSet::from_unsorted(
7614 index
7615 .search_filtered(query, *k, |row_id| eligible.contains(&row_id))
7616 .into_iter()
7617 .map(|(row_id, _)| row_id.0)
7618 .collect(),
7619 )
7620 }
7621 None => RowIdSet::empty(),
7622 },
7623 Condition::Range { column_id, lo, hi } => {
7624 if let Some(li) = self.learned_range.get(column_id) {
7625 RowIdSet::from_unsorted(li.range(*lo, *hi).into_iter().collect())
7626 } else {
7627 reader.range_row_id_set_i64(*column_id, *lo, *hi)?
7628 }
7629 }
7630 Condition::RangeF64 {
7631 column_id,
7632 lo,
7633 lo_inclusive,
7634 hi,
7635 hi_inclusive,
7636 } => {
7637 if let Some(li) = self.learned_range.get(column_id) {
7638 RowIdSet::from_unsorted(
7639 li.range_f64(*lo, *lo_inclusive, *hi, *hi_inclusive)
7640 .into_iter()
7641 .collect(),
7642 )
7643 } else {
7644 reader.range_row_id_set_f64(
7645 *column_id,
7646 *lo,
7647 *lo_inclusive,
7648 *hi,
7649 *hi_inclusive,
7650 )?
7651 }
7652 }
7653 Condition::IsNull { column_id } => reader.null_row_id_set(*column_id, true)?,
7654 Condition::IsNotNull { column_id } => reader.null_row_id_set(*column_id, false)?,
7655 };
7656 sets.push(s);
7657 }
7658 Ok(RowIdSet::intersect_many(sets))
7659 }
7660
7661 pub fn scan_cursor(
7682 &self,
7683 snapshot: Snapshot,
7684 projection: Vec<(u16, TypeId)>,
7685 conditions: &[crate::query::Condition],
7686 ) -> Result<Option<Box<dyn crate::cursor::Cursor>>> {
7687 if self.ttl.is_some() {
7688 return Ok(None);
7689 }
7690 if !conditions.is_empty() && !self.indexes_complete {
7696 return Ok(None);
7697 }
7698 if self.run_refs.len() == 1 {
7699 Ok(self
7700 .native_page_cursor(snapshot, projection, conditions)?
7701 .map(|c| Box::new(c) as Box<dyn crate::cursor::Cursor>))
7702 } else {
7703 Ok(self
7704 .native_multi_run_cursor(snapshot, projection, conditions)?
7705 .map(|c| Box::new(c) as Box<dyn crate::cursor::Cursor>))
7706 }
7707 }
7708
7709 pub fn aggregate_native(
7723 &self,
7724 snapshot: Snapshot,
7725 column: Option<u16>,
7726 conditions: &[crate::query::Condition],
7727 agg: NativeAgg,
7728 ) -> Result<Option<NativeAggResult>> {
7729 if self.ttl.is_some() {
7730 return Ok(None);
7731 }
7732 if self.run_refs.len() == 1 && conditions.is_empty() {
7734 if let Some(res) = self.aggregate_from_stats(snapshot, column, agg)? {
7735 return Ok(Some(res));
7736 }
7737 }
7738 if matches!(agg, NativeAgg::Count) && column.is_none() {
7740 return Ok(self
7741 .scan_cursor(snapshot, Vec::new(), conditions)?
7742 .map(|c| NativeAggResult::Count(c.remaining_rows() as u64)));
7743 }
7744 let cid = match column {
7747 Some(c) => c,
7748 None => return Ok(None),
7749 };
7750 let ty = self.column_type(cid);
7751 let Some(mut cursor) = self.scan_cursor(snapshot, vec![(cid, ty.clone())], conditions)?
7752 else {
7753 return Ok(None);
7754 };
7755 match ty {
7756 TypeId::Int64 | TypeId::TimestampNanos | TypeId::Date32 => {
7757 let (count, sum, mn, mx) = accumulate_int(cursor.as_mut())?;
7758 Ok(Some(pack_int(agg, count, sum, mn, mx)))
7759 }
7760 TypeId::Float64 => {
7761 let (count, sum, mn, mx) = accumulate_float(cursor.as_mut())?;
7762 Ok(Some(pack_float(agg, count, sum, mn, mx)))
7763 }
7764 _ => Ok(None),
7765 }
7766 }
7767
7768 fn aggregate_from_stats(
7776 &self,
7777 snapshot: Snapshot,
7778 column: Option<u16>,
7779 agg: NativeAgg,
7780 ) -> Result<Option<NativeAggResult>> {
7781 let cid = match (agg, column) {
7782 (NativeAgg::Count | NativeAgg::Min | NativeAgg::Max, Some(c)) => c,
7783 _ => return Ok(None), };
7785 let Some(stats) = self.exact_column_stats(snapshot, &[cid])? else {
7786 return Ok(None);
7787 };
7788 let Some(cs) = stats.get(&cid) else {
7789 return Ok(None);
7790 };
7791 match agg {
7792 NativeAgg::Count => Ok(Some(NativeAggResult::Count(
7794 self.live_count.saturating_sub(cs.null_count),
7795 ))),
7796 NativeAgg::Min | NativeAgg::Max => {
7797 let bound = if agg == NativeAgg::Min {
7798 &cs.min
7799 } else {
7800 &cs.max
7801 };
7802 match bound {
7803 Some(Value::Int64(x)) => Ok(Some(NativeAggResult::Int(*x))),
7804 Some(Value::Float64(x)) => Ok(Some(NativeAggResult::Float(*x))),
7805 Some(_) => Ok(None), None if cs.null_count >= self.live_count => Ok(Some(NativeAggResult::Null)),
7810 None => Ok(None),
7811 }
7812 }
7813 _ => Ok(None),
7814 }
7815 }
7816
7817 pub fn count_distinct_from_bitmap(&mut self, column_id: u16) -> Result<Option<u64>> {
7826 if self.ttl.is_some() {
7827 return Ok(None);
7828 }
7829 if !(self.memtable.is_empty() && self.mutable_run.is_empty() && self.run_refs.len() == 1) {
7830 return Ok(None);
7831 }
7832 self.ensure_indexes_complete()?;
7835 let reader = self.open_reader(self.run_refs[0].run_id)?;
7836 if self.live_count != reader.row_count() as u64 {
7837 return Ok(None);
7838 }
7839 let Some(bm) = self.bitmap.get(&column_id) else {
7840 return Ok(None); };
7842 let mut distinct = bm.value_count() as u64;
7843 if !bm.get(&Value::Null.encode_key()).is_empty() {
7846 distinct = distinct.saturating_sub(1);
7847 }
7848 Ok(Some(distinct))
7849 }
7850
7851 pub fn aggregate_incremental(
7863 &mut self,
7864 cache_key: u64,
7865 conditions: &[crate::query::Condition],
7866 column: Option<u16>,
7867 agg: NativeAgg,
7868 ) -> Result<IncrementalAggResult> {
7869 let snap = self.snapshot();
7870 let cur_wm = self.allocator.current().0;
7871 let cur_epoch = snap.epoch.0;
7872 let incremental_ok = self.ttl.is_none()
7879 && !self.had_deletes
7880 && self.memtable.is_empty()
7881 && self.mutable_run.is_empty();
7882
7883 if incremental_ok {
7886 if let Some(cached) = self.agg_cache.get(&cache_key).cloned() {
7887 if cached.epoch == cur_epoch {
7888 return Ok(IncrementalAggResult {
7889 state: cached.state,
7890 incremental: true,
7891 delta_rows: 0,
7892 });
7893 }
7894 if cached.epoch < cur_epoch && cached.watermark <= cur_wm {
7895 let delta_rids: Vec<u64> = (cached.watermark..cur_wm).collect();
7896 let delta_rows = self.rows_for_rids(&delta_rids, snap)?;
7897 let index_sets = self.resolve_index_conditions(conditions, snap)?;
7898 let delta_state = agg_state_from_rows(
7899 &delta_rows,
7900 conditions,
7901 &index_sets,
7902 column,
7903 agg,
7904 &self.schema,
7905 )?;
7906 let merged = cached.state.merge(delta_state);
7907 let delta_n = delta_rids.len() as u64;
7908 self.agg_cache.insert(
7909 cache_key,
7910 CachedAgg {
7911 state: merged.clone(),
7912 watermark: cur_wm,
7913 epoch: cur_epoch,
7914 },
7915 );
7916 return Ok(IncrementalAggResult {
7917 state: merged,
7918 incremental: true,
7919 delta_rows: delta_n,
7920 });
7921 }
7922 }
7923 }
7924
7925 let cursor_ok =
7930 self.memtable.is_empty() && self.mutable_run.is_empty() && self.run_refs.len() == 1;
7931 let state = if cursor_ok && agg != NativeAgg::Avg {
7932 match self.aggregate_native(snap, column, conditions, agg)? {
7933 Some(result) => {
7934 AggState::from_native(result, agg, column.map(|c| self.column_type(c)))
7935 }
7936 None => self.agg_state_full_scan(conditions, column, agg, snap)?,
7937 }
7938 } else {
7939 self.agg_state_full_scan(conditions, column, agg, snap)?
7940 };
7941 if incremental_ok {
7943 self.agg_cache.insert(
7944 cache_key,
7945 CachedAgg {
7946 state: state.clone(),
7947 watermark: cur_wm,
7948 epoch: cur_epoch,
7949 },
7950 );
7951 }
7952 Ok(IncrementalAggResult {
7953 state,
7954 incremental: false,
7955 delta_rows: 0,
7956 })
7957 }
7958
7959 fn agg_state_full_scan(
7962 &self,
7963 conditions: &[crate::query::Condition],
7964 column: Option<u16>,
7965 agg: NativeAgg,
7966 snap: Snapshot,
7967 ) -> Result<AggState> {
7968 let rows = self.visible_rows(snap)?;
7969 let index_sets = self.resolve_index_conditions(conditions, snap)?;
7970 agg_state_from_rows(&rows, conditions, &index_sets, column, agg, &self.schema)
7971 }
7972
7973 fn resolve_index_conditions(
7976 &self,
7977 conditions: &[crate::query::Condition],
7978 snapshot: Snapshot,
7979 ) -> Result<Vec<RowIdSet>> {
7980 use crate::query::Condition;
7981 let mut sets = Vec::new();
7982 for c in conditions {
7983 if matches!(
7984 c,
7985 Condition::Ann { .. }
7986 | Condition::SparseMatch { .. }
7987 | Condition::MinHashSimilar { .. }
7988 ) {
7989 sets.push(self.resolve_condition(c, snapshot)?);
7990 }
7991 }
7992 Ok(sets)
7993 }
7994
7995 fn column_type(&self, cid: u16) -> TypeId {
7996 self.schema
7997 .columns
7998 .iter()
7999 .find(|c| c.id == cid)
8000 .map(|c| c.ty.clone())
8001 .unwrap_or(TypeId::Bytes)
8002 }
8003
8004 pub fn approx_aggregate(
8013 &mut self,
8014 conditions: &[crate::query::Condition],
8015 column: Option<u16>,
8016 agg: ApproxAgg,
8017 z: f64,
8018 ) -> Result<Option<ApproxResult>> {
8019 use crate::query::Condition;
8020 self.ensure_reservoir_complete()?;
8021 let snapshot = self.snapshot();
8022 let n_pop = self.count();
8023 let sample_rids: Vec<u64> = self.reservoir.row_ids().to_vec();
8024 if sample_rids.is_empty() {
8025 return Ok(None);
8026 }
8027 let live_sample = self.rows_for_rids(&sample_rids, snapshot)?;
8029 let s = live_sample.len();
8030 if s == 0 {
8031 return Ok(None);
8032 }
8033
8034 let mut index_sets: Vec<RowIdSet> = Vec::new();
8037 for c in conditions {
8038 if matches!(
8039 c,
8040 Condition::Ann { .. }
8041 | Condition::SparseMatch { .. }
8042 | Condition::MinHashSimilar { .. }
8043 ) {
8044 index_sets.push(self.resolve_condition(c, snapshot)?);
8045 }
8046 }
8047
8048 let cid = match (agg, column) {
8050 (ApproxAgg::Count, _) => None,
8051 (_, Some(c)) => Some(c),
8052 _ => return Ok(None),
8053 };
8054 let mut passing_vals: Vec<f64> = Vec::with_capacity(s);
8055 for r in &live_sample {
8056 if !conditions
8058 .iter()
8059 .all(|c| condition_matches_row(c, r, &self.schema))
8060 {
8061 continue;
8062 }
8063 if !index_sets.iter().all(|set| set.contains(r.row_id.0)) {
8065 continue;
8066 }
8067 if let Some(cid) = cid {
8068 if let Some(v) = as_f64(r.columns.get(&cid)) {
8069 passing_vals.push(v);
8070 } } else {
8072 passing_vals.push(0.0); }
8074 }
8075 let m = passing_vals.len();
8076
8077 let (point, half) = match agg {
8078 ApproxAgg::Count => {
8079 let p = m as f64 / s as f64;
8081 let point = n_pop as f64 * p;
8082 let var = if s > 1 {
8083 n_pop as f64 * n_pop as f64 * p * (1.0 - p) / s as f64
8084 * (1.0 - s as f64 / n_pop as f64).max(0.0)
8085 } else {
8086 0.0
8087 };
8088 (point, z * var.sqrt())
8089 }
8090 ApproxAgg::Sum => {
8091 let y: Vec<f64> = live_sample
8093 .iter()
8094 .map(|r| {
8095 let passes_row = conditions
8096 .iter()
8097 .all(|c| condition_matches_row(c, r, &self.schema))
8098 && index_sets.iter().all(|set| set.contains(r.row_id.0));
8099 if passes_row {
8100 cid.and_then(|c| as_f64(r.columns.get(&c))).unwrap_or(0.0)
8101 } else {
8102 0.0
8103 }
8104 })
8105 .collect();
8106 let mean_y = y.iter().sum::<f64>() / s as f64;
8107 let point = n_pop as f64 * mean_y;
8108 let var = if s > 1 {
8109 let ss: f64 = y.iter().map(|v| (v - mean_y).powi(2)).sum();
8110 let var_y = ss / (s - 1) as f64;
8111 n_pop as f64 * n_pop as f64 * var_y / s as f64
8112 * (1.0 - s as f64 / n_pop as f64).max(0.0)
8113 } else {
8114 0.0
8115 };
8116 (point, z * var.sqrt())
8117 }
8118 ApproxAgg::Avg => {
8119 if m == 0 {
8120 return Ok(Some(ApproxResult {
8121 point: 0.0,
8122 ci_low: 0.0,
8123 ci_high: 0.0,
8124 n_population: n_pop,
8125 n_sample_live: s,
8126 n_passing: 0,
8127 }));
8128 }
8129 let mean = passing_vals.iter().sum::<f64>() / m as f64;
8130 let half = if m > 1 {
8131 let ss: f64 = passing_vals.iter().map(|v| (v - mean).powi(2)).sum();
8132 let sd = (ss / (m - 1) as f64).sqrt();
8133 let fpc = (1.0 - s as f64 / n_pop as f64).max(0.0);
8134 z * sd / (m as f64).sqrt() * fpc.sqrt()
8135 } else {
8136 0.0
8137 };
8138 (mean, half)
8139 }
8140 };
8141
8142 Ok(Some(ApproxResult {
8143 point,
8144 ci_low: point - half,
8145 ci_high: point + half,
8146 n_population: n_pop,
8147 n_sample_live: s,
8148 n_passing: m,
8149 }))
8150 }
8151
8152 pub fn exact_column_stats(
8160 &self,
8161 _snapshot: Snapshot,
8162 projection: &[u16],
8163 ) -> Result<Option<HashMap<u16, ColumnStat>>> {
8164 if self.ttl.is_some()
8165 || !(self.memtable.is_empty()
8166 && self.mutable_run.is_empty()
8167 && self.run_refs.len() == 1)
8168 {
8169 return Ok(None);
8170 }
8171 let reader = self.open_reader(self.run_refs[0].run_id)?;
8172 if self.live_count != reader.row_count() as u64 {
8173 return Ok(None);
8174 }
8175 let mut out = HashMap::new();
8176 for &cid in projection {
8177 let cdef = match self.schema.columns.iter().find(|c| c.id == cid) {
8178 Some(c) => c,
8179 None => continue,
8180 };
8181 let Some(stats) = reader.column_page_stats(cid) else {
8183 out.insert(
8184 cid,
8185 ColumnStat {
8186 min: None,
8187 max: None,
8188 null_count: self.live_count,
8189 },
8190 );
8191 continue;
8192 };
8193 let stat = match cdef.ty {
8194 TypeId::Int64 | TypeId::TimestampNanos | TypeId::Date32 => {
8195 agg_int(stats, crate::sorted_run::be_i64).map(|(mn, mx, n)| ColumnStat {
8196 min: mn.map(Value::Int64),
8197 max: mx.map(Value::Int64),
8198 null_count: n,
8199 })
8200 }
8201 TypeId::Float64 => {
8202 agg_float(stats, crate::sorted_run::be_f64).map(|(mn, mx, n)| ColumnStat {
8203 min: mn.map(Value::Float64),
8204 max: mx.map(Value::Float64),
8205 null_count: n,
8206 })
8207 }
8208 _ => None,
8209 };
8210 if let Some(s) = stat {
8211 out.insert(cid, s);
8212 }
8213 }
8214 Ok(Some(out))
8215 }
8216
8217 pub fn dir(&self) -> &Path {
8218 &self.dir
8219 }
8220
8221 pub fn schema(&self) -> &Schema {
8222 &self.schema
8223 }
8224
8225 pub(crate) fn set_catalog_name(&mut self, name: String) {
8226 self.name = name;
8227 }
8228
8229 pub(crate) fn prepare_alter_column(
8230 &mut self,
8231 column_name: &str,
8232 change: &AlterColumn,
8233 ) -> Result<ColumnDef> {
8234 if !self.pending_rows.is_empty() || !self.pending_dels.is_empty() {
8235 return Err(MongrelError::InvalidArgument(
8236 "ALTER COLUMN requires committing staged writes first".into(),
8237 ));
8238 }
8239 let old = self
8240 .schema
8241 .columns
8242 .iter()
8243 .find(|c| c.name == column_name)
8244 .cloned()
8245 .ok_or_else(|| MongrelError::Schema(format!("unknown column {column_name}")))?;
8246 let mut next = old.clone();
8247
8248 if let Some(name) = &change.name {
8249 let trimmed = name.trim();
8250 if trimmed.is_empty() {
8251 return Err(MongrelError::InvalidArgument(
8252 "ALTER COLUMN name must not be empty".into(),
8253 ));
8254 }
8255 if trimmed != old.name && self.schema.columns.iter().any(|c| c.name == trimmed) {
8256 return Err(MongrelError::Schema(format!(
8257 "column {trimmed} already exists"
8258 )));
8259 }
8260 next.name = trimmed.to_string();
8261 }
8262
8263 if let Some(ty) = &change.ty {
8264 next.ty = ty.clone();
8265 }
8266 if let Some(flags) = change.flags {
8267 validate_alter_column_flags(old.flags, flags)?;
8268 next.flags = flags;
8269 }
8270
8271 if let Some(default_change) = &change.default_value {
8272 next.default_value = default_change.clone();
8273 }
8274
8275 validate_alter_column_type(&self.schema, &old, &next, self.has_stored_versions())?;
8276 if old.flags.contains(ColumnFlags::NULLABLE)
8277 && !next.flags.contains(ColumnFlags::NULLABLE)
8278 && self.column_has_nulls(old.id)?
8279 {
8280 return Err(MongrelError::InvalidArgument(format!(
8281 "column '{}' contains NULL values",
8282 old.name
8283 )));
8284 }
8285 Ok(next)
8286 }
8287
8288 pub(crate) fn apply_altered_column(&mut self, column: ColumnDef) -> Result<()> {
8289 let idx = self
8290 .schema
8291 .columns
8292 .iter()
8293 .position(|c| c.id == column.id)
8294 .ok_or_else(|| MongrelError::Schema(format!("unknown column {}", column.id)))?;
8295 if self.schema.columns[idx] == column {
8296 return Ok(());
8297 }
8298 self.schema.columns[idx] = column;
8299 self.schema.schema_id = self.schema.schema_id.saturating_add(1);
8300 self.schema.validate_auto_increment()?;
8301 self.schema.validate_defaults()?;
8302 self.auto_inc = resolve_auto_inc(&self.schema);
8303 self.column_keys = build_column_keys(self.kek.as_deref(), &self.schema);
8304 write_schema(&self.dir, &self.schema)?;
8305 self.clear_result_cache();
8306 let _ = std::fs::remove_dir_all(self.dir.join("_shadow"));
8307 self.persist_manifest(self.current_epoch())?;
8308 Ok(())
8309 }
8310
8311 pub fn alter_column(&mut self, column_name: &str, change: AlterColumn) -> Result<ColumnDef> {
8312 self.ensure_writable()?;
8313 let column = self.prepare_alter_column(column_name, &change)?;
8314 self.apply_altered_column(column.clone())?;
8315 Ok(column)
8316 }
8317
8318 fn column_has_nulls(&mut self, column_id: u16) -> Result<bool> {
8319 if self.live_count == 0 {
8320 return Ok(false);
8321 }
8322 let snap = self.snapshot();
8323 let columns = self.visible_columns_native(snap, Some(&[column_id]))?;
8324 Ok(columns
8325 .first()
8326 .map(|(_, col)| col.null_count(col.len()) != 0)
8327 .unwrap_or(true))
8328 }
8329
8330 fn has_stored_versions(&self) -> bool {
8331 !self.memtable.is_empty()
8332 || !self.mutable_run.is_empty()
8333 || self.run_refs.iter().any(|r| r.row_count > 0)
8334 || !self.retiring.is_empty()
8335 }
8336
8337 pub fn add_column(
8342 &mut self,
8343 name: &str,
8344 ty: TypeId,
8345 flags: ColumnFlags,
8346 default_value: Option<crate::schema::DefaultExpr>,
8347 ) -> Result<u16> {
8348 self.add_column_with_id(name, ty, flags, default_value, None)
8349 }
8350
8351 pub fn add_column_with_id(
8352 &mut self,
8353 name: &str,
8354 ty: TypeId,
8355 flags: ColumnFlags,
8356 default_value: Option<crate::schema::DefaultExpr>,
8357 requested_id: Option<u16>,
8358 ) -> Result<u16> {
8359 self.ensure_writable()?;
8360 if self.schema.columns.iter().any(|c| c.name == name) {
8361 return Err(MongrelError::Schema(format!(
8362 "column {name} already exists"
8363 )));
8364 }
8365 let id = if let Some(id) = requested_id.filter(|id| *id != 0) {
8366 if self.schema.columns.iter().any(|c| c.id == id) {
8367 return Err(MongrelError::Schema(format!(
8368 "column id {id} already exists"
8369 )));
8370 }
8371 id
8372 } else {
8373 self.schema
8374 .columns
8375 .iter()
8376 .map(|c| c.id)
8377 .max()
8378 .unwrap_or(0)
8379 .checked_add(1)
8380 .ok_or_else(|| MongrelError::Schema("column id space exhausted".into()))?
8381 };
8382 self.schema.columns.push(ColumnDef {
8383 id,
8384 name: name.to_string(),
8385 ty,
8386 flags,
8387 default_value,
8388 });
8389 self.schema.schema_id = self.schema.schema_id.saturating_add(1);
8390 self.schema.validate_auto_increment()?;
8391 self.schema.validate_defaults()?;
8392 if flags.contains(ColumnFlags::AUTO_INCREMENT) {
8393 self.auto_inc = resolve_auto_inc(&self.schema);
8394 }
8395 write_schema(&self.dir, &self.schema)?;
8396 self.clear_result_cache();
8397 let _ = std::fs::remove_dir_all(self.dir.join("_shadow"));
8399 self.persist_manifest(self.current_epoch())?;
8400 Ok(id)
8401 }
8402
8403 pub fn add_learned_range_index(&mut self, column_name: &str) -> Result<()> {
8412 self.ensure_writable()?;
8413 let cid = self
8414 .schema
8415 .columns
8416 .iter()
8417 .find(|c| c.name == column_name)
8418 .map(|c| c.id)
8419 .ok_or_else(|| MongrelError::Schema(format!("unknown column {column_name}")))?;
8420 let ty = self
8421 .schema
8422 .columns
8423 .iter()
8424 .find(|c| c.id == cid)
8425 .map(|c| c.ty.clone())
8426 .unwrap_or(TypeId::Int64);
8427 if !matches!(
8428 ty,
8429 TypeId::Int64 | TypeId::Float64 | TypeId::TimestampNanos | TypeId::Date32
8430 ) {
8431 return Err(MongrelError::Schema(format!(
8432 "LearnedRange requires a numeric column; {column_name} is {ty:?}"
8433 )));
8434 }
8435 if self
8436 .schema
8437 .indexes
8438 .iter()
8439 .any(|i| i.column_id == cid && i.kind == IndexKind::LearnedRange)
8440 {
8441 return Ok(()); }
8443 self.schema.indexes.push(IndexDef {
8444 name: format!("{}_learned_range", column_name),
8445 column_id: cid,
8446 kind: IndexKind::LearnedRange,
8447 predicate: None,
8448 options: Default::default(),
8449 });
8450 self.schema.schema_id = self.schema.schema_id.saturating_add(1);
8451 write_schema(&self.dir, &self.schema)?;
8452 self.build_learned_ranges()?;
8453 Ok(())
8454 }
8455
8456 pub fn set_sync_byte_threshold(&mut self, threshold: u64) {
8459 self.sync_byte_threshold = threshold;
8460 if let WalSink::Private(w) = &mut self.wal {
8461 w.set_sync_byte_threshold(threshold);
8462 }
8463 }
8464
8465 pub fn page_cache_flush(&self) {
8469 self.page_cache.flush_to_disk();
8470 }
8471
8472 pub fn page_cache_len(&self) -> usize {
8474 self.page_cache.len()
8475 }
8476
8477 pub fn decoded_cache_len(&self) -> usize {
8480 self.decoded_cache.len()
8481 }
8482
8483 pub fn drain_memtable_sorted(&mut self) -> Vec<Row> {
8486 self.memtable.drain_sorted()
8487 }
8488
8489 pub(crate) fn run_path(&self, run_id: u64) -> PathBuf {
8490 self.dir.join(RUNS_DIR).join(format!("r-{run_id}.sr"))
8491 }
8492
8493 pub(crate) fn table_dir(&self) -> &Path {
8494 &self.dir
8495 }
8496
8497 pub(crate) fn schema_ref(&self) -> &crate::schema::Schema {
8498 &self.schema
8499 }
8500
8501 pub(crate) fn alloc_run_id(&mut self) -> u64 {
8502 let id = self.next_run_id;
8503 self.next_run_id += 1;
8504 id
8505 }
8506
8507 pub(crate) fn link_run(&mut self, run_ref: crate::manifest::RunRef) {
8508 self.run_refs.push(run_ref);
8509 }
8510
8511 pub(crate) fn retire_run(&mut self, run_id: u128, retire_epoch: u64) {
8521 self.retiring.push(crate::manifest::RetiredRun {
8522 run_id,
8523 retire_epoch,
8524 });
8525 }
8526
8527 pub(crate) fn reap_retiring(
8531 &mut self,
8532 min_active: Epoch,
8533 backup_pinned: &std::collections::HashSet<u128>,
8534 ) -> Result<usize> {
8535 if self.retiring.is_empty() {
8536 return Ok(0);
8537 }
8538 let mut reaped = 0;
8539 let mut kept: Vec<crate::manifest::RetiredRun> = Vec::new();
8540 for r in std::mem::take(&mut self.retiring) {
8546 if min_active.0 >= r.retire_epoch && !backup_pinned.contains(&r.run_id) {
8547 let _ = std::fs::remove_file(self.run_path(r.run_id as u64));
8548 reaped += 1;
8549 } else {
8550 kept.push(r);
8551 }
8552 }
8553 self.retiring = kept;
8554 if reaped > 0 {
8555 self.persist_manifest(self.current_epoch())?;
8556 }
8557 Ok(reaped)
8558 }
8559
8560 pub(crate) fn recover_spilled_run(&mut self, run_ref: crate::manifest::RunRef) -> bool {
8561 if self.run_refs.iter().any(|r| r.run_id == run_ref.run_id) {
8562 return false;
8563 }
8564 self.live_count = self.live_count.saturating_add(run_ref.row_count);
8565 self.run_refs.push(run_ref);
8566 self.indexes_complete = false;
8567 true
8568 }
8569
8570 pub(crate) fn kek_ref(&self) -> Option<&Arc<Kek>> {
8571 self.kek.as_ref()
8572 }
8573
8574 pub(crate) fn open_reader(&self, run_id: u128) -> Result<RunReader> {
8575 let mut reader = RunReader::open_with_cache(
8576 self.dir.join(RUNS_DIR).join(format!("r-{run_id}.sr")),
8577 self.schema.clone(),
8578 self.kek.clone(),
8579 Some(self.page_cache.clone()),
8580 Some(self.decoded_cache.clone()),
8581 self.table_id,
8582 Some(&self.verified_runs),
8583 )?;
8584 if let Some(rr) = self.run_refs.iter().find(|r| r.run_id == run_id) {
8588 reader.set_uniform_epoch(Epoch(rr.epoch_created));
8589 }
8590 Ok(reader)
8591 }
8592
8593 pub(crate) fn run_refs(&self) -> &[RunRef] {
8594 &self.run_refs
8595 }
8596
8597 pub(crate) fn retiring_run_ids(&self) -> impl Iterator<Item = u128> + '_ {
8598 self.retiring.iter().map(|run| run.run_id)
8599 }
8600
8601 pub(crate) fn runs_dir(&self) -> PathBuf {
8602 self.dir.join(RUNS_DIR)
8603 }
8604
8605 pub(crate) fn wal_dir(&self) -> PathBuf {
8606 self.dir.join(WAL_DIR)
8607 }
8608
8609 pub(crate) fn set_run_refs(&mut self, refs: Vec<RunRef>) {
8610 self.run_refs = refs;
8611 }
8612
8613 pub(crate) fn next_run_id(&self) -> u64 {
8614 self.next_run_id
8615 }
8616
8617 pub(crate) fn compaction_zstd_level(&self) -> i32 {
8618 self.compaction_zstd_level
8619 }
8620
8621 pub(crate) fn bump_next_run_id(&mut self) {
8622 self.next_run_id += 1;
8623 }
8624
8625 pub(crate) fn kek(&self) -> Option<Arc<Kek>> {
8626 self.kek.clone()
8627 }
8628
8629 #[cfg(feature = "encryption")]
8633 fn idx_dek(&self) -> Option<Zeroizing<[u8; DEK_LEN]>> {
8634 self.kek.as_ref().map(|k| k.derive_idx_key())
8635 }
8636
8637 #[cfg(not(feature = "encryption"))]
8638 fn idx_dek(&self) -> Option<Zeroizing<[u8; DEK_LEN]>> {
8639 None
8640 }
8641
8642 #[cfg(feature = "encryption")]
8646 fn manifest_meta_dek(&self) -> Option<[u8; DEK_LEN]> {
8647 self.kek.as_ref().map(|k| *k.derive_meta_key())
8648 }
8649
8650 #[cfg(not(feature = "encryption"))]
8651 fn manifest_meta_dek(&self) -> Option<[u8; DEK_LEN]> {
8652 None
8653 }
8654
8655 pub(crate) fn indexable_column_specs(&self) -> Vec<(u16, u8)> {
8658 self.column_keys
8659 .iter()
8660 .map(|(&id, &(_, scheme))| (id, scheme))
8661 .collect()
8662 }
8663
8664 #[cfg(feature = "encryption")]
8669 fn tokenize_value(&self, column_id: u16, v: &Value) -> Option<Value> {
8670 self.tokenize_value_enc(column_id, v)
8671 }
8672
8673 #[cfg(feature = "encryption")]
8674 fn tokenize_value_enc(&self, column_id: u16, v: &Value) -> Option<Value> {
8675 use crate::encryption::{hmac_token, ope_token_f64, ope_token_i64, SCHEME_HMAC_EQ};
8676 let (key, scheme) = self.column_keys.get(&column_id)?;
8677 let token: Vec<u8> = match (*scheme, v) {
8678 (SCHEME_HMAC_EQ, _) => hmac_token(key, &v.encode_key()).to_vec(),
8679 (_, Value::Int64(x)) => ope_token_i64(key, *x).to_vec(),
8680 (_, Value::Float64(x)) => ope_token_f64(key, *x).to_vec(),
8681 _ => hmac_token(key, &v.encode_key()).to_vec(),
8682 };
8683 Some(Value::Bytes(token))
8684 }
8685
8686 fn index_lookup_key(&self, column_id: u16, v: &Value) -> Vec<u8> {
8688 self.index_lookup_key_bytes(column_id, &v.encode_key())
8689 }
8690
8691 fn index_lookup_key_bytes(&self, column_id: u16, encoded: &[u8]) -> Vec<u8> {
8694 #[cfg(feature = "encryption")]
8695 {
8696 use crate::encryption::{hmac_token, SCHEME_HMAC_EQ};
8697 if let Some((key, scheme)) = self.column_keys.get(&column_id) {
8698 if *scheme == SCHEME_HMAC_EQ {
8699 return hmac_token(key, encoded).to_vec();
8700 }
8701 }
8702 }
8703 let _ = column_id;
8704 encoded.to_vec()
8705 }
8706}
8707
8708fn native_int64_strictly_increasing(col: &columnar::NativeColumn, n: usize) -> bool {
8709 let columnar::NativeColumn::Int64 { data, validity } = col else {
8710 return false;
8711 };
8712 if data.len() < n || !columnar::all_non_null(validity, n) {
8713 return false;
8714 }
8715 data.iter()
8716 .take(n)
8717 .zip(data.iter().skip(1))
8718 .all(|(a, b)| a < b)
8719}
8720
8721#[derive(Debug, Clone)]
8725pub struct ColumnStat {
8726 pub min: Option<Value>,
8727 pub max: Option<Value>,
8728 pub null_count: u64,
8729}
8730
8731#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8733pub enum NativeAgg {
8734 Count,
8735 Sum,
8736 Min,
8737 Max,
8738 Avg,
8739}
8740
8741#[derive(Debug, Clone, PartialEq)]
8743pub enum NativeAggResult {
8744 Count(u64),
8745 Int(i64),
8746 Float(f64),
8747 Null,
8749}
8750
8751#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8753pub enum ApproxAgg {
8754 Count,
8755 Sum,
8756 Avg,
8757}
8758
8759#[derive(Debug, Clone)]
8763pub struct ApproxResult {
8764 pub point: f64,
8766 pub ci_low: f64,
8768 pub ci_high: f64,
8770 pub n_population: u64,
8772 pub n_sample_live: usize,
8774 pub n_passing: usize,
8776}
8777
8778#[derive(Debug, Clone, PartialEq)]
8783pub enum AggState {
8784 Count(u64),
8786 SumI {
8788 sum: i128,
8789 count: u64,
8790 },
8791 SumF {
8793 sum: f64,
8794 count: u64,
8795 },
8796 AvgI {
8798 sum: i128,
8799 count: u64,
8800 },
8801 AvgF {
8803 sum: f64,
8804 count: u64,
8805 },
8806 MinI(i64),
8808 MaxI(i64),
8809 MinF(f64),
8811 MaxF(f64),
8812 Empty,
8814}
8815
8816impl AggState {
8817 pub fn merge(self, other: AggState) -> AggState {
8819 use AggState::*;
8820 match (self, other) {
8821 (Empty, x) | (x, Empty) => x,
8822 (Count(a), Count(b)) => Count(a + b),
8823 (SumI { sum: sa, count: ca }, SumI { sum: sb, count: cb }) => SumI {
8824 sum: sa + sb,
8825 count: ca + cb,
8826 },
8827 (SumF { sum: sa, count: ca }, SumF { sum: sb, count: cb }) => SumF {
8828 sum: sa + sb,
8829 count: ca + cb,
8830 },
8831 (AvgI { sum: sa, count: ca }, AvgI { sum: sb, count: cb }) => AvgI {
8832 sum: sa + sb,
8833 count: ca + cb,
8834 },
8835 (AvgF { sum: sa, count: ca }, AvgF { sum: sb, count: cb }) => AvgF {
8836 sum: sa + sb,
8837 count: ca + cb,
8838 },
8839 (MinI(a), MinI(b)) => MinI(a.min(b)),
8840 (MaxI(a), MaxI(b)) => MaxI(a.max(b)),
8841 (MinF(a), MinF(b)) => MinF(a.min(b)),
8842 (MaxF(a), MaxF(b)) => MaxF(a.max(b)),
8843 _ => Empty, }
8845 }
8846
8847 pub fn point(&self) -> Option<f64> {
8849 match self {
8850 AggState::Count(n) => Some(*n as f64),
8851 AggState::SumI { sum, .. } => Some(*sum as f64),
8852 AggState::SumF { sum, .. } => Some(*sum),
8853 AggState::AvgI { sum, count } if *count > 0 => Some(*sum as f64 / *count as f64),
8854 AggState::AvgF { sum, count } if *count > 0 => Some(*sum / *count as f64),
8855 AggState::MinI(n) => Some(*n as f64),
8856 AggState::MaxI(n) => Some(*n as f64),
8857 AggState::MinF(n) => Some(*n),
8858 AggState::MaxF(n) => Some(*n),
8859 AggState::AvgI { .. } | AggState::AvgF { .. } | AggState::Empty => None,
8860 }
8861 }
8862
8863 pub fn from_native(result: NativeAggResult, agg: NativeAgg, ty: Option<TypeId>) -> Self {
8867 let is_float = matches!(ty, Some(TypeId::Float64));
8868 match (agg, result) {
8869 (NativeAgg::Count, NativeAggResult::Count(n)) => AggState::Count(n),
8870 (NativeAgg::Sum, NativeAggResult::Int(x)) => AggState::SumI {
8871 sum: x as i128,
8872 count: 1, },
8874 (NativeAgg::Sum, NativeAggResult::Float(x)) => AggState::SumF { sum: x, count: 1 },
8875 (NativeAgg::Avg, NativeAggResult::Float(x)) => AggState::AvgF { sum: x, count: 1 },
8876 (NativeAgg::Min, NativeAggResult::Int(x)) => AggState::MinI(x),
8877 (NativeAgg::Max, NativeAggResult::Int(x)) => AggState::MaxI(x),
8878 (NativeAgg::Min, NativeAggResult::Float(x)) => AggState::MinF(x),
8879 (NativeAgg::Max, NativeAggResult::Float(x)) => AggState::MaxF(x),
8880 (NativeAgg::Count, _) => AggState::Empty,
8881 (_, NativeAggResult::Null) => AggState::Empty,
8882 _ => {
8883 let _ = is_float;
8884 AggState::Empty
8885 }
8886 }
8887 }
8888}
8889
8890#[derive(Debug, Clone)]
8893pub struct CachedAgg {
8894 pub state: AggState,
8895 pub watermark: u64,
8896 pub epoch: u64,
8897}
8898
8899#[derive(Debug, Clone)]
8901pub struct IncrementalAggResult {
8902 pub state: AggState,
8904 pub incremental: bool,
8907 pub delta_rows: u64,
8909}
8910
8911fn agg_state_from_rows(
8915 rows: &[Row],
8916 conditions: &[crate::query::Condition],
8917 index_sets: &[RowIdSet],
8918 column: Option<u16>,
8919 agg: NativeAgg,
8920 schema: &Schema,
8921) -> Result<AggState> {
8922 let mut count: u64 = 0;
8923 let mut sum_i: i128 = 0;
8924 let mut sum_f: f64 = 0.0;
8925 let mut mn_i: i64 = i64::MAX;
8926 let mut mx_i: i64 = i64::MIN;
8927 let mut mn_f: f64 = f64::INFINITY;
8928 let mut mx_f: f64 = f64::NEG_INFINITY;
8929 let mut saw_int = false;
8930 let mut saw_float = false;
8931 for r in rows {
8932 if !conditions
8933 .iter()
8934 .all(|c| condition_matches_row(c, r, schema))
8935 {
8936 continue;
8937 }
8938 if !index_sets.iter().all(|s| s.contains(r.row_id.0)) {
8939 continue;
8940 }
8941 match agg {
8942 NativeAgg::Count => match column {
8943 None => count += 1,
8945 Some(cid) => match r.columns.get(&cid) {
8948 None | Some(Value::Null) => {}
8949 Some(_) => count += 1,
8950 },
8951 },
8952 _ => match column.and_then(|cid| r.columns.get(&cid)) {
8953 Some(Value::Int64(n)) => {
8954 count += 1;
8955 sum_i += *n as i128;
8956 mn_i = mn_i.min(*n);
8957 mx_i = mx_i.max(*n);
8958 saw_int = true;
8959 }
8960 Some(Value::Float64(f)) => {
8961 count += 1;
8962 sum_f += f;
8963 mn_f = mn_f.min(*f);
8964 mx_f = mx_f.max(*f);
8965 saw_float = true;
8966 }
8967 _ => {}
8968 },
8969 }
8970 }
8971 Ok(match agg {
8972 NativeAgg::Count => {
8973 if count == 0 {
8974 AggState::Empty
8975 } else {
8976 AggState::Count(count)
8977 }
8978 }
8979 NativeAgg::Sum => {
8980 if count == 0 {
8981 AggState::Empty
8982 } else if saw_int {
8983 AggState::SumI { sum: sum_i, count }
8984 } else {
8985 AggState::SumF { sum: sum_f, count }
8986 }
8987 }
8988 NativeAgg::Avg => {
8989 if count == 0 {
8990 AggState::Empty
8991 } else if saw_int {
8992 AggState::AvgI { sum: sum_i, count }
8993 } else {
8994 AggState::AvgF { sum: sum_f, count }
8995 }
8996 }
8997 NativeAgg::Min => {
8998 if !saw_int && !saw_float {
8999 AggState::Empty
9000 } else if saw_int {
9001 AggState::MinI(mn_i)
9002 } else {
9003 AggState::MinF(mn_f)
9004 }
9005 }
9006 NativeAgg::Max => {
9007 if !saw_int && !saw_float {
9008 AggState::Empty
9009 } else if saw_int {
9010 AggState::MaxI(mx_i)
9011 } else {
9012 AggState::MaxF(mx_f)
9013 }
9014 }
9015 })
9016}
9017
9018fn condition_matches_row(c: &crate::query::Condition, row: &Row, schema: &Schema) -> bool {
9022 use crate::query::Condition;
9023 match c {
9024 Condition::Pk(key) => match schema.primary_key() {
9025 Some(pk) => row
9026 .columns
9027 .get(&pk.id)
9028 .map(|v| v.encode_key() == *key)
9029 .unwrap_or(false),
9030 None => false,
9031 },
9032 Condition::BitmapEq { column_id, value } => row
9033 .columns
9034 .get(column_id)
9035 .map(|v| v.encode_key() == *value)
9036 .unwrap_or(false),
9037 Condition::BitmapIn { column_id, values } => {
9038 let key = row.columns.get(column_id).map(|v| v.encode_key());
9039 match key {
9040 Some(k) => values.contains(&k),
9041 None => false,
9042 }
9043 }
9044 Condition::BytesPrefix { column_id, prefix } => row
9045 .columns
9046 .get(column_id)
9047 .map(|v| v.encode_key().starts_with(prefix))
9048 .unwrap_or(false),
9049 Condition::Range { column_id, lo, hi } => match row.columns.get(column_id) {
9050 Some(Value::Int64(n)) => *n >= *lo && *n <= *hi,
9051 _ => false,
9052 },
9053 Condition::RangeF64 {
9054 column_id,
9055 lo,
9056 lo_inclusive,
9057 hi,
9058 hi_inclusive,
9059 } => match row.columns.get(column_id) {
9060 Some(Value::Float64(n)) => {
9061 let lo_ok = if *lo_inclusive { *n >= *lo } else { *n > *lo };
9062 let hi_ok = if *hi_inclusive { *n <= *hi } else { *n < *hi };
9063 lo_ok && hi_ok
9064 }
9065 _ => false,
9066 },
9067 Condition::FmContains { column_id, pattern } => match row.columns.get(column_id) {
9068 Some(Value::Bytes(b)) => {
9069 !pattern.is_empty() && b.windows(pattern.len()).any(|w| w == &pattern[..])
9070 }
9071 _ => false,
9072 },
9073 Condition::FmContainsAll {
9074 column_id,
9075 patterns,
9076 } => match row.columns.get(column_id) {
9077 Some(Value::Bytes(b)) => patterns
9078 .iter()
9079 .all(|pat| !pat.is_empty() && b.windows(pat.len()).any(|w| w == &pat[..])),
9080 _ => false,
9081 },
9082 Condition::Ann { .. }
9083 | Condition::SparseMatch { .. }
9084 | Condition::MinHashSimilar { .. } => true,
9085 Condition::IsNull { column_id } => {
9086 matches!(row.columns.get(column_id), Some(Value::Null) | None)
9087 }
9088 Condition::IsNotNull { column_id } => {
9089 !matches!(row.columns.get(column_id), Some(Value::Null) | None)
9090 }
9091 }
9092}
9093
9094fn as_f64(v: Option<&Value>) -> Option<f64> {
9096 match v {
9097 Some(Value::Int64(n)) => Some(*n as f64),
9098 Some(Value::Float64(f)) => Some(*f),
9099 _ => None,
9100 }
9101}
9102
9103fn accumulate_int(cursor: &mut dyn crate::cursor::Cursor) -> Result<(u64, i128, i64, i64)> {
9107 let mut count: u64 = 0;
9108 let mut sum: i128 = 0;
9109 let mut mn: i64 = i64::MAX;
9110 let mut mx: i64 = i64::MIN;
9111 while let Some(cols) = cursor.next_batch()? {
9112 if let Some(crate::columnar::NativeColumn::Int64 { data, validity }) = cols.first() {
9113 if crate::columnar::all_non_null(validity, data.len()) {
9114 count += data.len() as u64;
9116 sum += data.iter().map(|&v| v as i128).sum::<i128>();
9117 mn = mn.min(*data.iter().min().unwrap_or(&mn));
9118 mx = mx.max(*data.iter().max().unwrap_or(&mx));
9119 } else {
9120 for (i, &v) in data.iter().enumerate() {
9121 if crate::columnar::validity_bit(validity, i) {
9122 count += 1;
9123 sum += v as i128;
9124 mn = mn.min(v);
9125 mx = mx.max(v);
9126 }
9127 }
9128 }
9129 }
9130 }
9131 Ok((count, sum, mn, mx))
9132}
9133
9134fn accumulate_float(cursor: &mut dyn crate::cursor::Cursor) -> Result<(u64, f64, f64, f64)> {
9136 let mut count: u64 = 0;
9137 let mut sum: f64 = 0.0;
9138 let mut mn: f64 = f64::INFINITY;
9139 let mut mx: f64 = f64::NEG_INFINITY;
9140 while let Some(cols) = cursor.next_batch()? {
9141 if let Some(crate::columnar::NativeColumn::Float64 { data, validity }) = cols.first() {
9142 if crate::columnar::all_non_null(validity, data.len()) {
9143 count += data.len() as u64;
9144 sum += data.iter().sum::<f64>();
9145 mn = mn.min(data.iter().copied().fold(f64::INFINITY, f64::min));
9146 mx = mx.max(data.iter().copied().fold(f64::NEG_INFINITY, f64::max));
9147 } else {
9148 for (i, &v) in data.iter().enumerate() {
9149 if crate::columnar::validity_bit(validity, i) {
9150 count += 1;
9151 sum += v;
9152 mn = mn.min(v);
9153 mx = mx.max(v);
9154 }
9155 }
9156 }
9157 }
9158 }
9159 Ok((count, sum, mn, mx))
9160}
9161
9162fn pack_int(agg: NativeAgg, count: u64, sum: i128, mn: i64, mx: i64) -> NativeAggResult {
9163 if count == 0 && !matches!(agg, NativeAgg::Count) {
9164 return NativeAggResult::Null;
9165 }
9166 match agg {
9167 NativeAgg::Count => NativeAggResult::Count(count),
9168 NativeAgg::Sum => match sum.try_into() {
9171 Ok(v) => NativeAggResult::Int(v),
9172 Err(_) => NativeAggResult::Null,
9173 },
9174 NativeAgg::Min => NativeAggResult::Int(mn),
9175 NativeAgg::Max => NativeAggResult::Int(mx),
9176 NativeAgg::Avg => NativeAggResult::Float((sum as f64) / (count as f64)),
9177 }
9178}
9179
9180fn pack_float(agg: NativeAgg, count: u64, sum: f64, mn: f64, mx: f64) -> NativeAggResult {
9181 if count == 0 && !matches!(agg, NativeAgg::Count) {
9182 return NativeAggResult::Null;
9183 }
9184 match agg {
9185 NativeAgg::Count => NativeAggResult::Count(count),
9186 NativeAgg::Sum => NativeAggResult::Float(sum),
9187 NativeAgg::Min => NativeAggResult::Float(mn),
9188 NativeAgg::Max => NativeAggResult::Float(mx),
9189 NativeAgg::Avg => NativeAggResult::Float(sum / (count as f64)),
9190 }
9191}
9192
9193fn agg_int(
9196 stats: &[crate::page::PageStat],
9197 decode: fn(Option<&[u8]>) -> Option<i64>,
9198) -> Option<(Option<i64>, Option<i64>, u64)> {
9199 let (mut mn, mut mx, mut nulls) = (i64::MAX, i64::MIN, 0u64);
9200 let mut any = false;
9201 for s in stats {
9202 if let Some(v) = decode(s.min.as_deref()) {
9203 mn = mn.min(v);
9204 any = true;
9205 }
9206 if let Some(v) = decode(s.max.as_deref()) {
9207 mx = mx.max(v);
9208 any = true;
9209 }
9210 nulls += s.null_count;
9211 }
9212 any.then_some((Some(mn), Some(mx), nulls))
9213}
9214
9215fn agg_float(
9217 stats: &[crate::page::PageStat],
9218 decode: fn(Option<&[u8]>) -> Option<f64>,
9219) -> Option<(Option<f64>, Option<f64>, u64)> {
9220 let (mut mn, mut mx, mut nulls) = (f64::INFINITY, f64::NEG_INFINITY, 0u64);
9221 let mut any = false;
9222 for s in stats {
9223 if let Some(v) = decode(s.min.as_deref()) {
9224 mn = mn.min(v);
9225 any = true;
9226 }
9227 if let Some(v) = decode(s.max.as_deref()) {
9228 mx = mx.max(v);
9229 any = true;
9230 }
9231 nulls += s.null_count;
9232 }
9233 any.then_some((Some(mn), Some(mx), nulls))
9234}
9235
9236type SecondaryIndexes = (
9238 HashMap<u16, BitmapIndex>,
9239 HashMap<u16, AnnIndex>,
9240 HashMap<u16, FmIndex>,
9241 HashMap<u16, SparseIndex>,
9242 HashMap<u16, MinHashIndex>,
9243);
9244
9245fn empty_indexes(schema: &Schema) -> SecondaryIndexes {
9246 let mut bitmap = HashMap::new();
9247 let mut ann = HashMap::new();
9248 let mut fm = HashMap::new();
9249 let mut sparse = HashMap::new();
9250 let mut minhash = HashMap::new();
9251 for idef in &schema.indexes {
9252 match idef.kind {
9253 IndexKind::Bitmap => {
9254 bitmap.insert(idef.column_id, BitmapIndex::new());
9255 }
9256 IndexKind::Ann => {
9257 let dim = schema
9258 .columns
9259 .iter()
9260 .find(|c| c.id == idef.column_id)
9261 .and_then(|c| match c.ty {
9262 TypeId::Embedding { dim } => Some(dim as usize),
9263 _ => None,
9264 })
9265 .unwrap_or(0);
9266 let options = idef.options.ann.clone().unwrap_or_default();
9267 ann.insert(
9268 idef.column_id,
9269 AnnIndex::with_options(
9270 dim,
9271 options.m,
9272 options.ef_construction,
9273 options.ef_search,
9274 ),
9275 );
9276 }
9277 IndexKind::FmIndex => {
9278 fm.insert(idef.column_id, FmIndex::new());
9279 }
9280 IndexKind::Sparse => {
9281 sparse.insert(idef.column_id, SparseIndex::new());
9282 }
9283 IndexKind::MinHash => {
9284 let options = idef.options.minhash.clone().unwrap_or_default();
9285 minhash.insert(
9286 idef.column_id,
9287 MinHashIndex::with_options(options.permutations, options.bands),
9288 );
9289 }
9290 _ => {}
9291 }
9292 }
9293 (bitmap, ann, fm, sparse, minhash)
9294}
9295
9296const ALTER_COLUMN_PROTECTED_FLAGS: u32 = ColumnFlags::PRIMARY_KEY
9297 | ColumnFlags::AUTO_INCREMENT
9298 | ColumnFlags::ENCRYPTED
9299 | ColumnFlags::ENCRYPTED_INDEXABLE
9300 | ColumnFlags::EMBEDDING_BINARY_QUANTIZED;
9301
9302fn validate_alter_column_flags(old: ColumnFlags, new: ColumnFlags) -> Result<()> {
9303 if (old.bits() ^ new.bits()) & ALTER_COLUMN_PROTECTED_FLAGS != 0 {
9304 return Err(MongrelError::Schema(
9305 "ALTER COLUMN may only change NULLABLE; primary key, auto-increment, encryption, and embedding flags are immutable".into(),
9306 ));
9307 }
9308 Ok(())
9309}
9310
9311fn validate_alter_column_type(
9312 schema: &Schema,
9313 old: &ColumnDef,
9314 next: &ColumnDef,
9315 has_stored_versions: bool,
9316) -> Result<()> {
9317 if old.ty == next.ty {
9318 return Ok(());
9319 }
9320 if schema.indexes.iter().any(|i| i.column_id == old.id) {
9321 return Err(MongrelError::Schema(format!(
9322 "ALTER COLUMN TYPE is not supported for indexed column '{}'",
9323 old.name
9324 )));
9325 }
9326 if !has_stored_versions || storage_compatible_type_change(old.ty.clone(), next.ty.clone()) {
9327 return Ok(());
9328 }
9329 Err(MongrelError::Schema(format!(
9330 "ALTER COLUMN TYPE from {:?} to {:?} requires an empty column or a representation-compatible type",
9331 old.ty, next.ty
9332 )))
9333}
9334
9335fn storage_compatible_type_change(old: TypeId, new: TypeId) -> bool {
9336 matches!(
9337 (old, new),
9338 (TypeId::Int64, TypeId::TimestampNanos) | (TypeId::TimestampNanos, TypeId::Int64)
9339 )
9340}
9341
9342fn rows_pk_strictly_increasing(rows: &[Row], pk_id: u16) -> bool {
9348 let mut prev: Option<i64> = None;
9349 for r in rows {
9350 match r.columns.get(&pk_id) {
9351 Some(Value::Int64(v)) => {
9352 if prev.is_some_and(|p| p >= *v) {
9353 return false;
9354 }
9355 prev = Some(*v);
9356 }
9357 _ => return false,
9358 }
9359 }
9360 true
9361}
9362
9363#[allow(clippy::too_many_arguments)]
9364fn index_into(
9365 schema: &Schema,
9366 row: &Row,
9367 hot: &mut HotIndex,
9368 bitmap: &mut HashMap<u16, BitmapIndex>,
9369 ann: &mut HashMap<u16, AnnIndex>,
9370 fm: &mut HashMap<u16, FmIndex>,
9371 sparse: &mut HashMap<u16, SparseIndex>,
9372 minhash: &mut HashMap<u16, MinHashIndex>,
9373) {
9374 for idef in &schema.indexes {
9375 let Some(val) = row.columns.get(&idef.column_id) else {
9376 continue;
9377 };
9378 match idef.kind {
9379 IndexKind::Bitmap => {
9380 if let Some(b) = bitmap.get_mut(&idef.column_id) {
9381 b.insert(val.encode_key(), row.row_id);
9382 }
9383 }
9384 IndexKind::Ann => {
9385 if let (Some(a), Value::Embedding(v)) = (ann.get_mut(&idef.column_id), val) {
9386 a.insert_validated(v, row.row_id);
9387 }
9388 }
9389 IndexKind::FmIndex => {
9390 if let (Some(f), Value::Bytes(b)) = (fm.get_mut(&idef.column_id), val) {
9391 f.insert(b.clone(), row.row_id);
9392 }
9393 }
9394 IndexKind::Sparse => {
9395 if let (Some(s), Value::Bytes(b)) = (sparse.get_mut(&idef.column_id), val) {
9396 if let Ok(terms) = bincode::deserialize::<Vec<(u32, f32)>>(b) {
9399 s.insert(&terms, row.row_id);
9400 }
9401 }
9402 }
9403 IndexKind::MinHash => {
9404 if let (Some(mh), Value::Bytes(b)) = (minhash.get_mut(&idef.column_id), val) {
9405 let tokens = crate::index::token_hashes_from_bytes(b);
9408 mh.insert(&tokens, row.row_id);
9409 }
9410 }
9411 _ => {}
9412 }
9413 }
9414 if let Some(pk_col) = schema.primary_key() {
9415 if let Some(pk_val) = row.columns.get(&pk_col.id) {
9416 hot.insert(pk_val.encode_key(), row.row_id);
9417 }
9418 }
9419}
9420
9421#[allow(clippy::too_many_arguments)]
9424fn index_into_single(
9425 idef: &IndexDef,
9426 _schema: &Schema,
9427 row: &Row,
9428 _hot: &mut HotIndex,
9429 bitmap: &mut HashMap<u16, BitmapIndex>,
9430 ann: &mut HashMap<u16, AnnIndex>,
9431 fm: &mut HashMap<u16, FmIndex>,
9432 sparse: &mut HashMap<u16, SparseIndex>,
9433 minhash: &mut HashMap<u16, MinHashIndex>,
9434) {
9435 let Some(val) = row.columns.get(&idef.column_id) else {
9436 return;
9437 };
9438 match idef.kind {
9439 IndexKind::Bitmap => {
9440 if let Some(b) = bitmap.get_mut(&idef.column_id) {
9441 b.insert(val.encode_key(), row.row_id);
9442 }
9443 }
9444 IndexKind::Ann => {
9445 if let (Some(a), Value::Embedding(v)) = (ann.get_mut(&idef.column_id), val) {
9446 a.insert_validated(v, row.row_id);
9447 }
9448 }
9449 IndexKind::FmIndex => {
9450 if let (Some(f), Value::Bytes(b)) = (fm.get_mut(&idef.column_id), val) {
9451 f.insert(b.clone(), row.row_id);
9452 }
9453 }
9454 IndexKind::Sparse => {
9455 if let (Some(s), Value::Bytes(b)) = (sparse.get_mut(&idef.column_id), val) {
9456 if let Ok(terms) = bincode::deserialize::<Vec<(u32, f32)>>(b) {
9457 s.insert(&terms, row.row_id);
9458 }
9459 }
9460 }
9461 IndexKind::MinHash => {
9462 if let (Some(mh), Value::Bytes(b)) = (minhash.get_mut(&idef.column_id), val) {
9463 let tokens = crate::index::token_hashes_from_bytes(b);
9464 mh.insert(&tokens, row.row_id);
9465 }
9466 }
9467 _ => {}
9468 }
9469}
9470
9471fn eval_partial_predicate(
9477 pred: &str,
9478 columns_map: &HashMap<u16, &Value>,
9479 name_to_id: &HashMap<&str, u16>,
9480) -> bool {
9481 let lower = pred.trim().to_ascii_lowercase();
9482 if let Some(rest) = lower.strip_suffix(" is not null") {
9484 let col_name = rest.trim();
9485 if let Some(col_id) = name_to_id.get(col_name) {
9486 return columns_map
9487 .get(col_id)
9488 .is_some_and(|v| !matches!(v, Value::Null));
9489 }
9490 }
9491 if let Some(rest) = lower.strip_suffix(" is null") {
9493 let col_name = rest.trim();
9494 if let Some(col_id) = name_to_id.get(col_name) {
9495 return columns_map
9496 .get(col_id)
9497 .map_or(true, |v| matches!(v, Value::Null));
9498 }
9499 }
9500 true
9503}
9504
9505#[allow(dead_code)]
9511fn bulk_index_key(
9512 column_keys: &HashMap<u16, ([u8; 32], u8)>,
9513 column_id: u16,
9514 ty: TypeId,
9515 col: &columnar::NativeColumn,
9516 i: usize,
9517) -> Option<Vec<u8>> {
9518 let encoded = columnar::encode_key_native(ty, col, i)?;
9519 #[cfg(feature = "encryption")]
9520 {
9521 use crate::encryption::{hmac_token, ope_token_f64, ope_token_i64, SCHEME_HMAC_EQ};
9522 if let Some((key, scheme)) = column_keys.get(&column_id) {
9523 return Some(match (*scheme, col) {
9524 (SCHEME_HMAC_EQ, _) => hmac_token(key, &encoded).to_vec(),
9525 (_, columnar::NativeColumn::Int64 { data, .. }) => {
9526 ope_token_i64(key, data[i]).to_vec()
9527 }
9528 (_, columnar::NativeColumn::Float64 { data, .. }) => {
9529 ope_token_f64(key, data[i]).to_vec()
9530 }
9531 _ => hmac_token(key, &encoded).to_vec(),
9532 });
9533 }
9534 }
9535 #[cfg(not(feature = "encryption"))]
9536 {
9537 let _ = (column_id, column_keys, col);
9538 }
9539 Some(encoded)
9540}
9541
9542pub(crate) fn write_schema(dir: &Path, schema: &Schema) -> Result<()> {
9543 let json = serde_json::to_string_pretty(schema)
9544 .map_err(|e| MongrelError::Schema(format!("encode schema: {e}")))?;
9545 std::fs::write(dir.join(SCHEMA_FILENAME), json)?;
9546 Ok(())
9547}
9548
9549fn read_schema(dir: &Path) -> Result<Schema> {
9550 serde_json::from_str(&std::fs::read_to_string(dir.join(SCHEMA_FILENAME))?)
9551 .map_err(|e| MongrelError::Schema(format!("decode schema: {e}")))
9552}
9553
9554fn next_wal_segment(wal_dir: &Path) -> Result<PathBuf> {
9555 Ok(wal_dir.join(format!("seg-{:06}.wal", next_wal_number(wal_dir)?)))
9556}
9557
9558fn latest_wal_segment(wal_dir: &Path) -> Result<Option<PathBuf>> {
9559 let n = list_wal_numbers(wal_dir)?;
9560 Ok(n.map(|max| wal_dir.join(format!("seg-{max:06}.wal"))))
9561}
9562
9563fn next_wal_number(wal_dir: &Path) -> Result<u32> {
9564 Ok(list_wal_numbers(wal_dir)?.map(|m| m + 1).unwrap_or(0))
9565}
9566
9567fn list_wal_numbers(wal_dir: &Path) -> Result<Option<u32>> {
9568 let _ = std::fs::create_dir_all(wal_dir);
9569 let mut max_n = None;
9570 for entry in std::fs::read_dir(wal_dir)? {
9571 let entry = entry?;
9572 let fname = entry.file_name();
9573 let Some(s) = fname.to_str() else {
9574 continue;
9575 };
9576 let Some(stripped) = s.strip_prefix("seg-") else {
9577 continue;
9578 };
9579 let Some(stripped) = stripped.strip_suffix(".wal") else {
9580 continue;
9581 };
9582 if let Ok(n) = stripped.parse::<u32>() {
9583 max_n = Some(max_n.map(|m: u32| m.max(n)).unwrap_or(n));
9584 }
9585 }
9586 Ok(max_n)
9587}