1use crate::error::{KitError, Result};
4use crate::internal::{ensure_internal_tables, internal_tables_core};
5use crate::schema::to_core_schema;
6use mongreldb_core::epoch::Snapshot;
7use mongreldb_core::memtable::Row as CoreRow;
8use mongreldb_core::memtable::Value as CoreValue;
9use mongreldb_core::schema::Schema as CoreSchema;
10use mongreldb_core::Database as CoreDatabase;
11use mongreldb_core::{AggState, ApproxAgg, NativeAgg, NativeAggResult, RowId};
12use mongreldb_kit_core::schema::IndexKind as KitIndexKind;
13use mongreldb_kit_core::schema::Schema as KitSchema;
14use mongreldb_kit_core::schema::Table as KitTable;
15use serde_json::Value;
16
17use std::collections::HashMap;
18use std::path::{Path, PathBuf};
19
20const SCHEMA_FILE: &str = "kit_schema.json";
21
22pub type DefaultProvider = Box<dyn Fn() -> Value + Send + Sync>;
24
25#[derive(Debug, Clone)]
28pub struct ExplainPlan {
29 pub index_accelerated: bool,
31 pub exact: bool,
34 pub pushed_conditions: Vec<String>,
36}
37
38#[derive(Debug, Clone)]
40pub struct SimilarRow {
41 pub row: crate::schema::Row,
42 pub similarity: f64,
43}
44
45fn parse_string_set(value: Option<&Value>) -> std::collections::HashSet<String> {
49 let arr = match value {
50 Some(Value::Array(a)) => Some(a.clone()),
51 Some(Value::String(s)) => serde_json::from_str::<Value>(s)
52 .ok()
53 .and_then(|v| v.as_array().cloned()),
54 _ => None,
55 };
56 arr.into_iter()
57 .flatten()
58 .filter_map(|v| match v {
59 Value::String(s) => Some(s),
60 Value::Number(n) => Some(n.to_string()),
61 Value::Bool(b) => Some(b.to_string()),
62 _ => None,
63 })
64 .collect()
65}
66
67#[derive(Debug, Clone, Copy, PartialEq, Eq)]
69pub enum IncrementalAggKind {
70 Count,
71 Sum,
72 Min,
73 Max,
74 Avg,
75}
76
77#[derive(Debug, Clone)]
79pub struct IncrementalAggregate {
80 pub value: Value,
83 pub incremental: bool,
87 pub delta_rows: u64,
89}
90
91fn incremental_cache_key(
95 table_id: u32,
96 column: Option<u16>,
97 agg: IncrementalAggKind,
98 conditions: &[mongreldb_core::query::Condition],
99) -> u64 {
100 use std::hash::{Hash, Hasher};
101 let mut h = std::collections::hash_map::DefaultHasher::new();
102 table_id.hash(&mut h);
103 column.hash(&mut h);
104 (agg as u8).hash(&mut h);
105 format!("{conditions:?}").hash(&mut h);
107 h.finish()
108}
109
110fn agg_state_value(s: &AggState) -> Value {
114 let num_f64 = |x: f64| {
115 serde_json::Number::from_f64(x)
116 .map(Value::Number)
117 .unwrap_or(Value::Null)
118 };
119 match s {
120 AggState::Count(n) => Value::from(*n),
121 AggState::SumI { sum, .. } => i64::try_from(*sum)
122 .map(Value::from)
123 .unwrap_or_else(|_| num_f64(*sum as f64)),
124 AggState::SumF { sum, .. } => num_f64(*sum),
125 AggState::AvgI { sum, count } if *count > 0 => num_f64(*sum as f64 / *count as f64),
126 AggState::AvgF { sum, count } if *count > 0 => num_f64(*sum / *count as f64),
127 AggState::AvgI { .. } | AggState::AvgF { .. } => Value::Null,
128 AggState::MinI(n) | AggState::MaxI(n) => Value::from(*n),
129 AggState::MinF(f) | AggState::MaxF(f) => num_f64(*f),
130 AggState::Empty => Value::Null,
131 }
132}
133
134#[derive(Debug, Clone, Copy, PartialEq, Eq)]
136pub enum ApproxAggKind {
137 Count,
138 Sum,
139 Avg,
140}
141
142#[derive(Debug, Clone)]
146pub struct ApproxAggregate {
147 pub point: f64,
148 pub ci_low: f64,
149 pub ci_high: f64,
150 pub n_population: u64,
151 pub n_sample_live: usize,
152 pub n_passing: usize,
153}
154
155fn condition_label(c: &mongreldb_core::query::Condition) -> String {
158 let dbg = format!("{c:?}");
159 dbg.split(['(', '{', ' ']).next().unwrap_or("").to_string()
160}
161
162pub struct Database {
167 pub(crate) inner: CoreDatabase,
168 pub(crate) schema: KitSchema,
169 pub(crate) root: PathBuf,
170 pub(crate) default_providers: HashMap<String, DefaultProvider>,
172}
173
174impl Database {
175 pub fn open(path: &Path) -> Result<Self> {
177 let inner = CoreDatabase::open(path)?;
178 let schema = load_schema(path)?;
179 ensure_internal_tables(&inner)?;
181 reap_rotated_wal_segments(&inner);
182 Ok(Self {
183 inner,
184 schema,
185 root: path.to_path_buf(),
186 default_providers: HashMap::new(),
187 })
188 }
189
190 pub fn open_encrypted(path: &Path, passphrase: &str) -> Result<Self> {
192 let inner = CoreDatabase::open_encrypted(path, passphrase)?;
193 let schema = load_schema(path)?;
194 ensure_internal_tables(&inner)?;
195 reap_rotated_wal_segments(&inner);
196 Ok(Self {
197 inner,
198 schema,
199 root: path.to_path_buf(),
200 default_providers: HashMap::new(),
201 })
202 }
203
204 pub fn create_encrypted(path: &Path, schema: KitSchema, passphrase: &str) -> Result<Self> {
208 std::fs::create_dir_all(path)?;
209 let inner = CoreDatabase::create_encrypted(path, passphrase)?;
210 ensure_internal_tables(&inner)?;
211 store_schema(path, &schema)?;
212 for table in &schema.tables {
213 create_core_table(&inner, &table.name, to_core_schema(table))?;
214 }
215 Ok(Self {
216 inner,
217 schema,
218 root: path.to_path_buf(),
219 default_providers: HashMap::new(),
220 })
221 }
222
223 pub fn create(path: &Path, schema: KitSchema) -> Result<Self> {
225 std::fs::create_dir_all(path)?;
226 let inner = CoreDatabase::create(path)?;
227
228 ensure_internal_tables(&inner)?;
231
232 store_schema(path, &schema)?;
235
236 for table in &schema.tables {
238 create_core_table(&inner, &table.name, to_core_schema(table))?;
239 }
240
241 Ok(Self {
242 inner,
243 schema,
244 root: path.to_path_buf(),
245 default_providers: HashMap::new(),
246 })
247 }
248
249 pub fn register_default(
252 &mut self,
253 name: impl Into<String>,
254 provider: impl Fn() -> Value + Send + Sync + 'static,
255 ) {
256 self.default_providers
257 .insert(name.into(), Box::new(provider));
258 }
259
260 pub fn raw(&self) -> &CoreDatabase {
264 &self.inner
265 }
266
267 pub fn table_names(&self) -> Vec<String> {
269 self.schema
270 .tables
271 .iter()
272 .map(|t| t.name.clone())
273 .filter(|n| !n.starts_with("__kit_"))
274 .collect()
275 }
276
277 pub fn allocate_sequence(&self, name: &str, count: i64) -> Result<i64> {
283 use crate::internal::cols;
284 let mut attempt = 0;
285 loop {
286 let mut txn = self.inner.begin();
287 let snapshot = txn.read_snapshot();
288 let existing = self
289 .visible_core_rows_at(crate::internal::SEQUENCES, snapshot)?
290 .into_iter()
291 .find(|r| internal_bytes(r, cols::SEQ_NAME) == Some(name.to_string()));
292
293 let now = crate::internal::iso_now();
294 let (start, next, old_row_id) = match &existing {
298 Some(row) => {
299 let current = match row.columns.get(&cols::SEQ_NEXT) {
300 Some(CoreValue::Int64(i)) => *i,
301 _ => 1,
302 };
303 (current, current + count, Some(row.row_id))
304 }
305 None => (1, 1 + count, None),
306 };
307
308 if let Some(rid) = old_row_id {
309 txn.delete(crate::internal::SEQUENCES, rid)
310 .map_err(KitError::from)?;
311 }
312 txn.put(
313 crate::internal::SEQUENCES,
314 vec![
315 (cols::SEQ_NAME, CoreValue::Bytes(name.as_bytes().to_vec())),
316 (cols::SEQ_NEXT, CoreValue::Int64(next)),
317 (cols::SEQ_UPDATED, CoreValue::Bytes(now.into_bytes())),
318 ],
319 )
320 .map_err(KitError::from)?;
321 match txn.commit() {
322 Ok(_) => return Ok(start),
323 Err(mongreldb_core::MongrelError::Conflict(_)) if attempt < 10_000 => {
324 attempt += 1;
325 std::thread::yield_now();
326 continue;
327 }
328 Err(e) => return Err(KitError::from(e)),
329 }
330 }
331 }
332
333 pub fn transaction<T, F>(&self, max_retries: usize, mut f: F) -> Result<T>
336 where
337 F: FnMut(&mut crate::txn::Transaction<'_>) -> Result<T>,
338 {
339 let mut attempt = 0;
340 loop {
341 let mut txn = self.begin()?;
342 match f(&mut txn) {
343 Ok(value) => match txn.commit() {
344 Ok(()) => return Ok(value),
345 Err(KitError::Conflict(_)) if attempt < max_retries => {
346 attempt += 1;
347 continue;
348 }
349 Err(e) => return Err(e),
350 },
351 Err(KitError::Conflict(_)) if attempt < max_retries => {
352 txn.rollback();
353 attempt += 1;
354 continue;
355 }
356 Err(e) => {
357 txn.rollback();
358 return Err(e);
359 }
360 }
361 }
362 }
363
364 pub fn table(&self, name: &str) -> Option<&KitTable> {
366 self.schema.table(name)
367 }
368
369 pub fn schema(&self) -> &KitSchema {
371 &self.schema
372 }
373
374 pub fn begin(&self) -> Result<crate::txn::Transaction<'_>> {
376 let core_txn = self.inner.begin();
377 Ok(crate::txn::Transaction::new(self, core_txn))
378 }
379
380 pub fn set_schema(&mut self, schema: KitSchema) {
382 self.schema = schema;
383 }
384
385 pub fn check_internal_tables(&self) -> Result<()> {
388 let schema_file = self.root.join(SCHEMA_FILE);
389 if !schema_file.exists() {
390 return Err(KitError::Integrity(format!(
391 "schema file {} is missing",
392 schema_file.display()
393 )));
394 }
395 for (name, _) in internal_tables_core() {
396 if self.inner.table_id(name).is_err() {
397 return Err(KitError::Integrity(format!(
398 "internal table {name} is missing"
399 )));
400 }
401 }
402 Ok(())
403 }
404
405 pub fn gc(&self) -> Result<usize> {
408 self.inner.gc().map_err(KitError::from)
409 }
410
411 pub fn check(&self) -> Vec<serde_json::Value> {
414 self.inner
415 .check()
416 .into_iter()
417 .map(|i| {
418 serde_json::json!({
419 "table_id": i.table_id,
420 "table_name": i.table_name,
421 "severity": i.severity,
422 "description": i.description,
423 })
424 })
425 .collect()
426 }
427
428 pub fn doctor(&self) -> Result<Vec<u64>> {
430 self.inner.doctor().map_err(KitError::from)
431 }
432
433 pub fn snapshot_epoch(&self) -> u64 {
437 self.inner.snapshot().0.epoch.0
438 }
439
440 pub fn export_tsv(&self, table: &str) -> Result<String> {
444 let t = self
445 .schema
446 .tables
447 .iter()
448 .find(|t| t.name == table)
449 .ok_or_else(|| KitError::Validation(format!("unknown table '{table}'")))?
450 .clone();
451 let tx = self.begin()?;
452 let rows = tx.all_rows(table)?;
453 Ok(crate::tsv::rows_to_tsv(&t, &rows))
454 }
455
456 pub fn import_tsv(&self, table: &str, text: &str) -> Result<usize> {
460 let t = self
461 .schema
462 .tables
463 .iter()
464 .find(|t| t.name == table)
465 .ok_or_else(|| KitError::Validation(format!("unknown table '{table}'")))?
466 .clone();
467 let rows = crate::tsv::tsv_to_rows(&t, text)?;
468 let n = rows.len();
469 self.transaction(1, |tx| {
470 tx.insert_many(table, rows.clone())?;
471 Ok(())
472 })?;
473 Ok(n)
474 }
475
476 pub fn explain(
481 &self,
482 table: &str,
483 predicate: &mongreldb_kit_core::query::Expr,
484 ) -> Result<ExplainPlan> {
485 let t = self
486 .schema
487 .tables
488 .iter()
489 .find(|t| t.name == table)
490 .ok_or_else(|| KitError::Validation(format!("unknown table '{table}'")))?;
491 Ok(match crate::pushdown::translate_predicate(t, predicate) {
492 Some(p) => ExplainPlan {
493 index_accelerated: p.can_push(),
494 exact: p.fully_translated,
495 pushed_conditions: p.conditions.iter().map(condition_label).collect(),
496 },
497 None => ExplainPlan {
498 index_accelerated: false,
499 exact: false,
500 pushed_conditions: Vec::new(),
501 },
502 })
503 }
504
505 pub fn rows_at_epoch(&self, table: &str, epoch: u64) -> Result<Vec<crate::schema::Row>> {
511 let t = self
512 .schema
513 .tables
514 .iter()
515 .find(|t| t.name == table)
516 .ok_or_else(|| KitError::Validation(format!("unknown table '{table}'")))?;
517 let current = self.snapshot_epoch();
518 if epoch > current {
519 return Err(KitError::Validation(format!(
520 "epoch {epoch} is in the future (current committed epoch is {current})"
521 )));
522 }
523 let snap = Snapshot::at(mongreldb_core::epoch::Epoch(epoch));
524 let rows = self.visible_core_rows_at(table, snap)?;
525 rows.iter()
526 .map(|r| crate::schema::core_row_to_json(r, t))
527 .collect()
528 }
529
530 pub fn approx_aggregate(
536 &self,
537 table: &str,
538 column: Option<&str>,
539 agg: ApproxAggKind,
540 z: f64,
541 ) -> Result<Option<ApproxAggregate>> {
542 let t = self
543 .schema
544 .tables
545 .iter()
546 .find(|t| t.name == table)
547 .ok_or_else(|| KitError::Validation(format!("unknown table '{table}'")))?;
548 if matches!(agg, ApproxAggKind::Sum | ApproxAggKind::Avg) && column.is_none() {
549 return Err(KitError::Validation(
550 "approx sum/avg requires a column".into(),
551 ));
552 }
553 let cid = match column {
554 Some(name) => Some(
555 t.columns
556 .iter()
557 .find(|c| c.name == name)
558 .ok_or_else(|| KitError::Validation(format!("unknown column '{name}'")))?
559 .id as u16,
560 ),
561 None => None,
562 };
563 let core_agg = match agg {
564 ApproxAggKind::Count => ApproxAgg::Count,
565 ApproxAggKind::Sum => ApproxAgg::Sum,
566 ApproxAggKind::Avg => ApproxAgg::Avg,
567 };
568 let handle = self.inner.table(table).map_err(KitError::from)?;
569 let mut guard = handle.lock();
570 let res = guard
571 .approx_aggregate(&[], cid, core_agg, z)
572 .map_err(KitError::from)?;
573 Ok(res.map(|r| ApproxAggregate {
574 point: r.point,
575 ci_low: r.ci_low,
576 ci_high: r.ci_high,
577 n_population: r.n_population,
578 n_sample_live: r.n_sample_live,
579 n_passing: r.n_passing,
580 }))
581 }
582
583 pub fn scan_batched<F>(&self, table: &str, batch_size: usize, mut f: F) -> Result<()>
589 where
590 F: FnMut(&[serde_json::Map<String, Value>]) -> Result<()>,
591 {
592 let kit_t = self
593 .schema
594 .tables
595 .iter()
596 .find(|t| t.name == table)
597 .ok_or_else(|| KitError::Validation(format!("unknown table '{table}'")))?;
598 let batch_size = batch_size.max(1);
599 let (snapshot, _pin) = self.inner.snapshot();
602 let handle = self.inner.table(table).map_err(KitError::from)?;
603 let guard = handle.lock();
604
605 let mut projection: Vec<(u16, mongreldb_core::schema::TypeId)> = Vec::new();
607 let mut meta: Vec<(String, mongreldb_kit_core::schema::ColumnType)> = Vec::new();
608 for c in &guard.schema().columns {
609 if let Some(kc) = kit_t.columns.iter().find(|kc| kc.id as u16 == c.id) {
610 projection.push((c.id, c.ty));
611 meta.push((kc.name.clone(), kc.storage_type));
612 }
613 }
614
615 match guard
616 .scan_cursor(snapshot, projection, &[])
617 .map_err(KitError::from)?
618 {
619 Some(mut cursor) => {
620 let mut buf: Vec<serde_json::Map<String, Value>> = Vec::with_capacity(batch_size);
621 while let Some(batch) = cursor.next_batch().map_err(KitError::from)? {
622 let nrows = batch.first().map(|c| c.len()).unwrap_or(0);
623 for j in 0..nrows {
624 let mut m = serde_json::Map::new();
625 for (ci, (name, ty)) in meta.iter().enumerate() {
626 let cv = batch
627 .get(ci)
628 .and_then(|col| col.value_at(j))
629 .unwrap_or(CoreValue::Null);
630 m.insert(name.clone(), crate::schema::core_to_json(&cv, *ty)?);
631 }
632 buf.push(m);
633 if buf.len() >= batch_size {
634 f(&buf)?;
635 buf.clear();
636 }
637 }
638 }
639 if !buf.is_empty() {
640 f(&buf)?;
641 }
642 Ok(())
643 }
644 None => {
645 drop(guard);
646 let rows = self.visible_core_rows_at(table, snapshot)?;
647 let maps: Vec<serde_json::Map<String, Value>> = rows
648 .iter()
649 .map(|r| crate::schema::core_row_to_json(r, kit_t).map(|row| row.values))
650 .collect::<Result<Vec<_>>>()?;
651 for chunk in maps.chunks(batch_size) {
652 f(chunk)?;
653 }
654 Ok(())
655 }
656 }
657 }
658
659 pub fn set_similarity(
668 &self,
669 table: &str,
670 column: &str,
671 query: &[String],
672 k: usize,
673 ) -> Result<Vec<SimilarRow>> {
674 let t = self
675 .schema
676 .tables
677 .iter()
678 .find(|t| t.name == table)
679 .ok_or_else(|| KitError::Validation(format!("unknown table '{table}'")))?;
680 let col = t.columns.iter().find(|c| c.name == column).ok_or_else(|| {
681 KitError::Validation(format!("unknown column '{column}' on table '{table}'"))
682 })?;
683 let query_set: std::collections::HashSet<String> = query.iter().cloned().collect();
684
685 let has_minhash = t.indexes.iter().any(|idx| {
686 idx.kind == KitIndexKind::MinHash && idx.columns.iter().any(|c| c == column)
687 });
688 let rows = if has_minhash {
689 let query_hashes: Vec<u64> = query
691 .iter()
692 .map(|s| mongreldb_core::index::minhash_token_hash(s))
693 .collect();
694 let cand_k = k.saturating_mul(8).max(k + 64);
696 let cond = mongreldb_core::query::Condition::MinHashSimilar {
697 column_id: col.id as u16,
698 query: query_hashes,
699 k: cand_k,
700 };
701 let (snapshot, _pin) = self.inner.snapshot();
702 let core_rows = self.query_core_rows_at(table, &[cond], snapshot)?;
703 core_rows
704 .iter()
705 .map(|r| crate::schema::core_row_to_json(r, t))
706 .collect::<Result<Vec<_>>>()?
707 } else {
708 let tx = self.begin()?;
709 tx.all_rows(table)?
710 };
711
712 let mut scored: Vec<SimilarRow> = Vec::new();
713 for row in rows {
714 let set = parse_string_set(row.values.get(column));
715 let inter = set.iter().filter(|x| query_set.contains(*x)).count();
716 let union = set.len() + query_set.len() - inter;
717 let sim = if union == 0 {
718 0.0
719 } else {
720 inter as f64 / union as f64
721 };
722 if sim > 0.0 {
723 scored.push(SimilarRow {
724 row,
725 similarity: sim,
726 });
727 }
728 }
729 scored.sort_by(|a, b| {
730 b.similarity
731 .partial_cmp(&a.similarity)
732 .unwrap_or(std::cmp::Ordering::Equal)
733 });
734 scored.truncate(k);
735 Ok(scored)
736 }
737
738 pub fn flush(&self) -> Result<()> {
742 for name in self.inner.table_names() {
743 let handle = self.inner.table(&name).map_err(KitError::from)?;
744 let mut guard = handle.lock();
745 guard.flush().map_err(KitError::from)?;
746 }
747 Ok(())
748 }
749
750 pub fn incremental_aggregate(
762 &self,
763 table: &str,
764 column: Option<&str>,
765 agg: IncrementalAggKind,
766 filter: Option<&mongreldb_kit_core::query::Expr>,
767 ) -> Result<IncrementalAggregate> {
768 let t = self
769 .schema
770 .tables
771 .iter()
772 .find(|t| t.name == table)
773 .ok_or_else(|| KitError::Validation(format!("unknown table '{table}'")))?;
774 if !matches!(agg, IncrementalAggKind::Count) && column.is_none() {
775 return Err(KitError::Validation(
776 "sum/min/max/avg incremental aggregate requires a column".into(),
777 ));
778 }
779 let cid = match column {
780 Some(name) => Some(
781 t.columns
782 .iter()
783 .find(|c| c.name == name)
784 .ok_or_else(|| KitError::Validation(format!("unknown column '{name}'")))?
785 .id as u16,
786 ),
787 None => None,
788 };
789 let conditions = match filter {
790 Some(expr) => {
791 let plan = crate::pushdown::translate_predicate(t, expr).ok_or_else(|| {
792 KitError::Validation(
793 "filter is not index-translatable for an incremental aggregate".into(),
794 )
795 })?;
796 if !plan.fully_translated {
797 return Err(KitError::Validation(
798 "filter has a residual that an incremental aggregate cannot apply exactly"
799 .into(),
800 ));
801 }
802 plan.conditions
803 }
804 None => Vec::new(),
805 };
806 let core_agg = match agg {
807 IncrementalAggKind::Count => NativeAgg::Count,
808 IncrementalAggKind::Sum => NativeAgg::Sum,
809 IncrementalAggKind::Min => NativeAgg::Min,
810 IncrementalAggKind::Max => NativeAgg::Max,
811 IncrementalAggKind::Avg => NativeAgg::Avg,
812 };
813 let cache_key = incremental_cache_key(t.id, cid, agg, &conditions);
814 let handle = self.inner.table(table).map_err(KitError::from)?;
815 let mut guard = handle.lock();
816 let res = guard
817 .aggregate_incremental(cache_key, &conditions, cid, core_agg)
818 .map_err(KitError::from)?;
819 Ok(IncrementalAggregate {
820 value: agg_state_value(&res.state),
821 incremental: res.incremental,
822 delta_rows: res.delta_rows,
823 })
824 }
825
826 pub fn applied_migrations(&self) -> Result<Vec<mongreldb_kit_core::migrations::Migration>> {
828 crate::migrate::load_applied_migrations(&self.inner)
829 }
830
831 pub(crate) fn core_db(&self) -> &CoreDatabase {
832 &self.inner
833 }
834
835 pub fn close(&self) -> Result<()> {
840 self.inner.close().map_err(KitError::from)
841 }
842
843 pub fn compact_all(&self) -> Result<(usize, usize)> {
848 self.inner.compact().map_err(KitError::from)
849 }
850
851 pub fn compact_table(&self, name: &str) -> Result<bool> {
854 self.inner.compact_table(name).map_err(KitError::from)
855 }
856
857 pub(crate) fn lookup_row_id(&self, table: &str, key: &[u8]) -> Result<Option<RowId>> {
861 let handle = self.inner.table(table).map_err(KitError::from)?;
862 let mut guard = handle.lock();
863 guard.ensure_indexes_complete()?;
864 Ok(guard.lookup_pk(key))
865 }
866
867 pub(crate) fn root(&self) -> &Path {
868 &self.root
869 }
870
871 pub(crate) fn visible_core_rows_at(
875 &self,
876 table_name: &str,
877 snapshot: Snapshot,
878 ) -> Result<Vec<CoreRow>> {
879 let handle = self.inner.table(table_name).map_err(KitError::from)?;
880 let guard = handle.lock();
881 guard.visible_rows(snapshot).map_err(KitError::from)
882 }
883
884 pub(crate) fn query_core_rows_at(
891 &self,
892 table_name: &str,
893 conditions: &[mongreldb_core::query::Condition],
894 snapshot: Snapshot,
895 ) -> Result<Vec<CoreRow>> {
896 if conditions.is_empty() {
897 return self.visible_core_rows_at(table_name, snapshot);
898 }
899 let handle = self.inner.table(table_name).map_err(KitError::from)?;
900 let mut guard = handle.lock();
901 let q = mongreldb_core::query::Query {
902 conditions: conditions.to_vec(),
903 };
904 guard.query(&q).map_err(KitError::from)
905 }
906
907 pub(crate) fn flush_table(&self, table_name: &str) -> Result<()> {
915 let handle = self.inner.table(table_name).map_err(KitError::from)?;
916 handle.lock().flush().map_err(KitError::from)?;
917 Ok(())
918 }
919
920 pub(crate) fn count_core_rows_at(
930 &self,
931 table_name: &str,
932 conditions: &[mongreldb_core::query::Condition],
933 snapshot: Snapshot,
934 ) -> Result<Option<u64>> {
935 let handle = self.inner.table(table_name).map_err(KitError::from)?;
936 let mut guard = handle.lock();
937 if guard.snapshot().epoch != snapshot.epoch {
938 return Ok(None); }
940 guard
941 .count_conditions(conditions, snapshot)
942 .map_err(KitError::from)
943 }
944
945 pub(crate) fn aggregate_core_at(
954 &self,
955 table_name: &str,
956 column: Option<u16>,
957 conditions: &[mongreldb_core::query::Condition],
958 agg: NativeAgg,
959 snapshot: Snapshot,
960 ) -> Result<Option<NativeAggResult>> {
961 let handle = self.inner.table(table_name).map_err(KitError::from)?;
962 let guard = handle.lock();
963 if guard.snapshot().epoch != snapshot.epoch {
964 return Ok(None); }
966 guard
967 .aggregate_native(snapshot, column, conditions, agg)
968 .map_err(KitError::from)
969 }
970
971 pub(crate) fn count_distinct_core_at(
980 &self,
981 table_name: &str,
982 column_id: u16,
983 snapshot: Snapshot,
984 ) -> Result<Option<u64>> {
985 let handle = self.inner.table(table_name).map_err(KitError::from)?;
986 let mut guard = handle.lock();
987 if guard.snapshot().epoch != snapshot.epoch {
988 return Ok(None); }
990 guard
991 .count_distinct_from_bitmap(column_id)
992 .map_err(KitError::from)
993 }
994
995 #[allow(dead_code)]
997 pub(crate) fn get_core_row(&self, table_name: &str, row_id: u64) -> Result<Option<CoreRow>> {
998 let handle = self.inner.table(table_name).map_err(KitError::from)?;
999 let guard = handle.lock();
1000 let snapshot = guard.snapshot();
1001 Ok(guard.get(mongreldb_core::RowId(row_id), snapshot))
1002 }
1003}
1004
1005pub(crate) fn create_core_table(db: &CoreDatabase, name: &str, schema: CoreSchema) -> Result<()> {
1006 if db.table_id(name).is_ok() {
1007 return Ok(());
1008 }
1009 db.create_table(name, schema).map_err(KitError::from)?;
1010 Ok(())
1011}
1012
1013pub(crate) fn internal_bytes(row: &CoreRow, col_id: u16) -> Option<String> {
1015 match row.columns.get(&col_id) {
1016 Some(CoreValue::Bytes(b)) => String::from_utf8(b.clone()).ok(),
1017 _ => None,
1018 }
1019}
1020
1021fn reap_rotated_wal_segments(db: &CoreDatabase) {
1036 let _ = db.gc();
1037}
1038
1039pub(crate) fn load_schema(path: &Path) -> Result<KitSchema> {
1040 let file = path.join(SCHEMA_FILE);
1041 let json = std::fs::read_to_string(&file)
1042 .map_err(|e| KitError::Migration(format!("cannot read schema file: {e}")))?;
1043 let schema: KitSchema = serde_json::from_str(&json)?;
1044 Ok(schema)
1045}
1046
1047pub(crate) fn store_schema(path: &Path, schema: &KitSchema) -> Result<()> {
1048 let file = path.join(SCHEMA_FILE);
1049 let json = serde_json::to_string_pretty(schema)?;
1050 std::fs::write(&file, json)?;
1051 Ok(())
1052}
1053
1054pub(crate) fn persist_schema(db: &Database, schema: &KitSchema) -> Result<()> {
1056 store_schema(&db.root, schema)
1057}