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 mongreldb_kit_core::ProcedureSpec;
16use serde_json::Value;
17
18use std::collections::HashMap;
19use std::path::{Path, PathBuf};
20
21const SCHEMA_FILE: &str = "kit_schema.json";
22
23pub type DefaultProvider = Box<dyn Fn() -> Value + Send + Sync>;
25
26#[derive(Debug, Clone)]
29pub struct ExplainPlan {
30 pub index_accelerated: bool,
32 pub exact: bool,
35 pub pushed_conditions: Vec<String>,
37}
38
39#[derive(Debug, Clone)]
41pub struct SimilarRow {
42 pub row: crate::schema::Row,
43 pub similarity: f64,
44}
45
46fn parse_string_set(value: Option<&Value>) -> std::collections::HashSet<String> {
50 let arr = match value {
51 Some(Value::Array(a)) => Some(a.clone()),
52 Some(Value::String(s)) => serde_json::from_str::<Value>(s)
53 .ok()
54 .and_then(|v| v.as_array().cloned()),
55 _ => None,
56 };
57 arr.into_iter()
58 .flatten()
59 .filter_map(|v| match v {
60 Value::String(s) => Some(s),
61 Value::Number(n) => Some(n.to_string()),
62 Value::Bool(b) => Some(b.to_string()),
63 _ => None,
64 })
65 .collect()
66}
67
68#[derive(Debug, Clone, Copy, PartialEq, Eq)]
70pub enum IncrementalAggKind {
71 Count,
72 Sum,
73 Min,
74 Max,
75 Avg,
76}
77
78#[derive(Debug, Clone)]
80pub struct IncrementalAggregate {
81 pub value: Value,
84 pub incremental: bool,
88 pub delta_rows: u64,
90}
91
92fn incremental_cache_key(
96 table_id: u32,
97 column: Option<u16>,
98 agg: IncrementalAggKind,
99 conditions: &[mongreldb_core::query::Condition],
100) -> u64 {
101 use std::hash::{Hash, Hasher};
102 let mut h = std::collections::hash_map::DefaultHasher::new();
103 table_id.hash(&mut h);
104 column.hash(&mut h);
105 (agg as u8).hash(&mut h);
106 format!("{conditions:?}").hash(&mut h);
108 h.finish()
109}
110
111fn agg_state_value(s: &AggState) -> Value {
115 let num_f64 = |x: f64| {
116 serde_json::Number::from_f64(x)
117 .map(Value::Number)
118 .unwrap_or(Value::Null)
119 };
120 match s {
121 AggState::Count(n) => Value::from(*n),
122 AggState::SumI { sum, .. } => i64::try_from(*sum)
123 .map(Value::from)
124 .unwrap_or_else(|_| num_f64(*sum as f64)),
125 AggState::SumF { sum, .. } => num_f64(*sum),
126 AggState::AvgI { sum, count } if *count > 0 => num_f64(*sum as f64 / *count as f64),
127 AggState::AvgF { sum, count } if *count > 0 => num_f64(*sum / *count as f64),
128 AggState::AvgI { .. } | AggState::AvgF { .. } => Value::Null,
129 AggState::MinI(n) | AggState::MaxI(n) => Value::from(*n),
130 AggState::MinF(f) | AggState::MaxF(f) => num_f64(*f),
131 AggState::Empty => Value::Null,
132 }
133}
134
135#[derive(Debug, Clone, Copy, PartialEq, Eq)]
137pub enum ApproxAggKind {
138 Count,
139 Sum,
140 Avg,
141}
142
143#[derive(Debug, Clone)]
147pub struct ApproxAggregate {
148 pub point: f64,
149 pub ci_low: f64,
150 pub ci_high: f64,
151 pub n_population: u64,
152 pub n_sample_live: usize,
153 pub n_passing: usize,
154}
155
156fn condition_label(c: &mongreldb_core::query::Condition) -> String {
159 let dbg = format!("{c:?}");
160 dbg.split(['(', '{', ' ']).next().unwrap_or("").to_string()
161}
162
163pub struct Database {
168 pub(crate) inner: CoreDatabase,
169 pub(crate) schema: KitSchema,
170 pub(crate) root: PathBuf,
171 pub(crate) default_providers: HashMap<String, DefaultProvider>,
173}
174
175impl Database {
176 pub fn open(path: &Path) -> Result<Self> {
178 let inner = CoreDatabase::open(path)?;
179 let schema = load_schema(path)?;
180 ensure_internal_tables(&inner)?;
182 reap_rotated_wal_segments(&inner);
183 Ok(Self {
184 inner,
185 schema,
186 root: path.to_path_buf(),
187 default_providers: HashMap::new(),
188 })
189 }
190
191 pub fn open_encrypted(path: &Path, passphrase: &str) -> Result<Self> {
193 let inner = CoreDatabase::open_encrypted(path, passphrase)?;
194 let schema = load_schema(path)?;
195 ensure_internal_tables(&inner)?;
196 reap_rotated_wal_segments(&inner);
197 Ok(Self {
198 inner,
199 schema,
200 root: path.to_path_buf(),
201 default_providers: HashMap::new(),
202 })
203 }
204
205 pub fn create_encrypted(path: &Path, schema: KitSchema, passphrase: &str) -> Result<Self> {
209 std::fs::create_dir_all(path)?;
210 let inner = CoreDatabase::create_encrypted(path, passphrase)?;
211 ensure_internal_tables(&inner)?;
212 store_schema(path, &schema)?;
213 for table in &schema.tables {
214 create_core_table(&inner, &table.name, to_core_schema(table))?;
215 }
216 Ok(Self {
217 inner,
218 schema,
219 root: path.to_path_buf(),
220 default_providers: HashMap::new(),
221 })
222 }
223
224 pub fn create(path: &Path, schema: KitSchema) -> Result<Self> {
226 std::fs::create_dir_all(path)?;
227 let inner = CoreDatabase::create(path)?;
228
229 ensure_internal_tables(&inner)?;
232
233 store_schema(path, &schema)?;
236
237 for table in &schema.tables {
239 create_core_table(&inner, &table.name, to_core_schema(table))?;
240 }
241
242 Ok(Self {
243 inner,
244 schema,
245 root: path.to_path_buf(),
246 default_providers: HashMap::new(),
247 })
248 }
249
250 pub fn register_default(
253 &mut self,
254 name: impl Into<String>,
255 provider: impl Fn() -> Value + Send + Sync + 'static,
256 ) {
257 self.default_providers
258 .insert(name.into(), Box::new(provider));
259 }
260
261 pub fn raw(&self) -> &CoreDatabase {
265 &self.inner
266 }
267
268 pub fn table_names(&self) -> Vec<String> {
270 self.schema
271 .tables
272 .iter()
273 .map(|t| t.name.clone())
274 .filter(|n| !n.starts_with("__kit_"))
275 .collect()
276 }
277
278 pub fn create_procedure(
279 &self,
280 spec: &ProcedureSpec,
281 ) -> Result<mongreldb_core::StoredProcedure> {
282 let procedure = core_procedure(spec)?;
283 self.inner
284 .create_procedure(procedure)
285 .map_err(KitError::from)
286 }
287
288 pub fn replace_procedure(
289 &self,
290 spec: &ProcedureSpec,
291 ) -> Result<mongreldb_core::StoredProcedure> {
292 let procedure = core_procedure(spec)?;
293 self.inner
294 .create_or_replace_procedure(procedure)
295 .map_err(KitError::from)
296 }
297
298 pub fn drop_procedure(&self, name: &str) -> Result<()> {
299 self.inner.drop_procedure(name).map_err(KitError::from)
300 }
301
302 pub fn call_procedure(
303 &self,
304 name: &str,
305 args: serde_json::Map<String, Value>,
306 ) -> Result<mongreldb_core::ProcedureCallResult> {
307 let args = args
308 .iter()
309 .map(|(key, value)| Ok((key.clone(), json_to_core_value(value)?)))
310 .collect::<Result<HashMap<_, _>>>()?;
311 self.inner
312 .call_procedure(name, args)
313 .map_err(KitError::from)
314 }
315
316 pub fn allocate_sequence(&self, name: &str, count: i64) -> Result<i64> {
322 use crate::internal::cols;
323 let mut attempt = 0;
324 loop {
325 let mut txn = self.inner.begin();
326 let snapshot = txn.read_snapshot();
327 let existing = self
328 .visible_core_rows_at(crate::internal::SEQUENCES, snapshot)?
329 .into_iter()
330 .find(|r| internal_bytes(r, cols::SEQ_NAME) == Some(name.to_string()));
331
332 let now = crate::internal::iso_now();
333 let (start, next, old_row_id) = match &existing {
337 Some(row) => {
338 let current = match row.columns.get(&cols::SEQ_NEXT) {
339 Some(CoreValue::Int64(i)) => *i,
340 _ => 1,
341 };
342 (current, current + count, Some(row.row_id))
343 }
344 None => (1, 1 + count, None),
345 };
346
347 if let Some(rid) = old_row_id {
348 txn.delete(crate::internal::SEQUENCES, rid)
349 .map_err(KitError::from)?;
350 }
351 txn.put(
352 crate::internal::SEQUENCES,
353 vec![
354 (cols::SEQ_NAME, CoreValue::Bytes(name.as_bytes().to_vec())),
355 (cols::SEQ_NEXT, CoreValue::Int64(next)),
356 (cols::SEQ_UPDATED, CoreValue::Bytes(now.into_bytes())),
357 ],
358 )
359 .map_err(KitError::from)?;
360 match txn.commit() {
361 Ok(_) => return Ok(start),
362 Err(mongreldb_core::MongrelError::Conflict(_)) if attempt < 10_000 => {
363 attempt += 1;
364 std::thread::yield_now();
365 continue;
366 }
367 Err(e) => return Err(KitError::from(e)),
368 }
369 }
370 }
371
372 pub fn transaction<T, F>(&self, max_retries: usize, mut f: F) -> Result<T>
375 where
376 F: FnMut(&mut crate::txn::Transaction<'_>) -> Result<T>,
377 {
378 let mut attempt = 0;
379 loop {
380 let mut txn = self.begin()?;
381 match f(&mut txn) {
382 Ok(value) => match txn.commit() {
383 Ok(()) => return Ok(value),
384 Err(KitError::Conflict(_)) if attempt < max_retries => {
385 attempt += 1;
386 continue;
387 }
388 Err(e) => return Err(e),
389 },
390 Err(KitError::Conflict(_)) if attempt < max_retries => {
391 txn.rollback();
392 attempt += 1;
393 continue;
394 }
395 Err(e) => {
396 txn.rollback();
397 return Err(e);
398 }
399 }
400 }
401 }
402
403 pub fn table(&self, name: &str) -> Option<&KitTable> {
405 self.schema.table(name)
406 }
407
408 pub fn schema(&self) -> &KitSchema {
410 &self.schema
411 }
412
413 pub fn begin(&self) -> Result<crate::txn::Transaction<'_>> {
415 let core_txn = self.inner.begin();
416 Ok(crate::txn::Transaction::new(self, core_txn))
417 }
418
419 pub fn set_schema(&mut self, schema: KitSchema) {
421 self.schema = schema;
422 }
423
424 pub fn check_internal_tables(&self) -> Result<()> {
427 let schema_file = self.root.join(SCHEMA_FILE);
428 if !schema_file.exists() {
429 return Err(KitError::Integrity(format!(
430 "schema file {} is missing",
431 schema_file.display()
432 )));
433 }
434 for (name, _) in internal_tables_core() {
435 if self.inner.table_id(name).is_err() {
436 return Err(KitError::Integrity(format!(
437 "internal table {name} is missing"
438 )));
439 }
440 }
441 Ok(())
442 }
443
444 pub fn gc(&self) -> Result<usize> {
447 self.inner.gc().map_err(KitError::from)
448 }
449
450 pub fn check(&self) -> Vec<serde_json::Value> {
453 self.inner
454 .check()
455 .into_iter()
456 .map(|i| {
457 serde_json::json!({
458 "table_id": i.table_id,
459 "table_name": i.table_name,
460 "severity": i.severity,
461 "description": i.description,
462 })
463 })
464 .collect()
465 }
466
467 pub fn doctor(&self) -> Result<Vec<u64>> {
469 self.inner.doctor().map_err(KitError::from)
470 }
471
472 pub fn snapshot_epoch(&self) -> u64 {
476 self.inner.snapshot().0.epoch.0
477 }
478
479 pub fn export_tsv(&self, table: &str) -> Result<String> {
483 let t = self
484 .schema
485 .tables
486 .iter()
487 .find(|t| t.name == table)
488 .ok_or_else(|| KitError::Validation(format!("unknown table '{table}'")))?
489 .clone();
490 let tx = self.begin()?;
491 let rows = tx.all_rows(table)?;
492 Ok(crate::tsv::rows_to_tsv(&t, &rows))
493 }
494
495 pub fn import_tsv(&self, table: &str, text: &str) -> Result<usize> {
499 let t = self
500 .schema
501 .tables
502 .iter()
503 .find(|t| t.name == table)
504 .ok_or_else(|| KitError::Validation(format!("unknown table '{table}'")))?
505 .clone();
506 let rows = crate::tsv::tsv_to_rows(&t, text)?;
507 let n = rows.len();
508 self.transaction(1, |tx| {
509 tx.insert_many(table, rows.clone())?;
510 Ok(())
511 })?;
512 Ok(n)
513 }
514
515 pub fn explain(
520 &self,
521 table: &str,
522 predicate: &mongreldb_kit_core::query::Expr,
523 ) -> Result<ExplainPlan> {
524 let t = self
525 .schema
526 .tables
527 .iter()
528 .find(|t| t.name == table)
529 .ok_or_else(|| KitError::Validation(format!("unknown table '{table}'")))?;
530 Ok(match crate::pushdown::translate_predicate(t, predicate) {
531 Some(p) => ExplainPlan {
532 index_accelerated: p.can_push(),
533 exact: p.fully_translated,
534 pushed_conditions: p.conditions.iter().map(condition_label).collect(),
535 },
536 None => ExplainPlan {
537 index_accelerated: false,
538 exact: false,
539 pushed_conditions: Vec::new(),
540 },
541 })
542 }
543
544 pub fn rows_at_epoch(&self, table: &str, epoch: u64) -> Result<Vec<crate::schema::Row>> {
550 let t = self
551 .schema
552 .tables
553 .iter()
554 .find(|t| t.name == table)
555 .ok_or_else(|| KitError::Validation(format!("unknown table '{table}'")))?;
556 let current = self.snapshot_epoch();
557 if epoch > current {
558 return Err(KitError::Validation(format!(
559 "epoch {epoch} is in the future (current committed epoch is {current})"
560 )));
561 }
562 let snap = Snapshot::at(mongreldb_core::epoch::Epoch(epoch));
563 let rows = self.visible_core_rows_at(table, snap)?;
564 rows.iter()
565 .map(|r| crate::schema::core_row_to_json(r, t))
566 .collect()
567 }
568
569 pub fn approx_aggregate(
575 &self,
576 table: &str,
577 column: Option<&str>,
578 agg: ApproxAggKind,
579 z: f64,
580 ) -> Result<Option<ApproxAggregate>> {
581 let t = self
582 .schema
583 .tables
584 .iter()
585 .find(|t| t.name == table)
586 .ok_or_else(|| KitError::Validation(format!("unknown table '{table}'")))?;
587 if matches!(agg, ApproxAggKind::Sum | ApproxAggKind::Avg) && column.is_none() {
588 return Err(KitError::Validation(
589 "approx sum/avg requires a column".into(),
590 ));
591 }
592 let cid = match column {
593 Some(name) => Some(
594 t.columns
595 .iter()
596 .find(|c| c.name == name)
597 .ok_or_else(|| KitError::Validation(format!("unknown column '{name}'")))?
598 .id as u16,
599 ),
600 None => None,
601 };
602 let core_agg = match agg {
603 ApproxAggKind::Count => ApproxAgg::Count,
604 ApproxAggKind::Sum => ApproxAgg::Sum,
605 ApproxAggKind::Avg => ApproxAgg::Avg,
606 };
607 let handle = self.inner.table(table).map_err(KitError::from)?;
608 let mut guard = handle.lock();
609 let res = guard
610 .approx_aggregate(&[], cid, core_agg, z)
611 .map_err(KitError::from)?;
612 Ok(res.map(|r| ApproxAggregate {
613 point: r.point,
614 ci_low: r.ci_low,
615 ci_high: r.ci_high,
616 n_population: r.n_population,
617 n_sample_live: r.n_sample_live,
618 n_passing: r.n_passing,
619 }))
620 }
621
622 pub fn scan_batched<F>(&self, table: &str, batch_size: usize, mut f: F) -> Result<()>
628 where
629 F: FnMut(&[serde_json::Map<String, Value>]) -> Result<()>,
630 {
631 let kit_t = self
632 .schema
633 .tables
634 .iter()
635 .find(|t| t.name == table)
636 .ok_or_else(|| KitError::Validation(format!("unknown table '{table}'")))?;
637 let batch_size = batch_size.max(1);
638 let (snapshot, _pin) = self.inner.snapshot();
641 let handle = self.inner.table(table).map_err(KitError::from)?;
642 let guard = handle.lock();
643
644 let mut projection: Vec<(u16, mongreldb_core::schema::TypeId)> = Vec::new();
646 let mut meta: Vec<(String, mongreldb_kit_core::schema::ColumnType)> = Vec::new();
647 for c in &guard.schema().columns {
648 if let Some(kc) = kit_t.columns.iter().find(|kc| kc.id as u16 == c.id) {
649 projection.push((c.id, c.ty));
650 meta.push((kc.name.clone(), kc.storage_type));
651 }
652 }
653
654 match guard
655 .scan_cursor(snapshot, projection, &[])
656 .map_err(KitError::from)?
657 {
658 Some(mut cursor) => {
659 let mut buf: Vec<serde_json::Map<String, Value>> = Vec::with_capacity(batch_size);
660 while let Some(batch) = cursor.next_batch().map_err(KitError::from)? {
661 let nrows = batch.first().map(|c| c.len()).unwrap_or(0);
662 for j in 0..nrows {
663 let mut m = serde_json::Map::new();
664 for (ci, (name, ty)) in meta.iter().enumerate() {
665 let cv = batch
666 .get(ci)
667 .and_then(|col| col.value_at(j))
668 .unwrap_or(CoreValue::Null);
669 m.insert(name.clone(), crate::schema::core_to_json(&cv, *ty)?);
670 }
671 buf.push(m);
672 if buf.len() >= batch_size {
673 f(&buf)?;
674 buf.clear();
675 }
676 }
677 }
678 if !buf.is_empty() {
679 f(&buf)?;
680 }
681 Ok(())
682 }
683 None => {
684 drop(guard);
685 let rows = self.visible_core_rows_at(table, snapshot)?;
686 let maps: Vec<serde_json::Map<String, Value>> = rows
687 .iter()
688 .map(|r| crate::schema::core_row_to_json(r, kit_t).map(|row| row.values))
689 .collect::<Result<Vec<_>>>()?;
690 for chunk in maps.chunks(batch_size) {
691 f(chunk)?;
692 }
693 Ok(())
694 }
695 }
696 }
697
698 pub fn set_similarity(
707 &self,
708 table: &str,
709 column: &str,
710 query: &[String],
711 k: usize,
712 ) -> Result<Vec<SimilarRow>> {
713 let t = self
714 .schema
715 .tables
716 .iter()
717 .find(|t| t.name == table)
718 .ok_or_else(|| KitError::Validation(format!("unknown table '{table}'")))?;
719 let col = t.columns.iter().find(|c| c.name == column).ok_or_else(|| {
720 KitError::Validation(format!("unknown column '{column}' on table '{table}'"))
721 })?;
722 let query_set: std::collections::HashSet<String> = query.iter().cloned().collect();
723
724 let has_minhash = t.indexes.iter().any(|idx| {
725 idx.kind == KitIndexKind::MinHash && idx.columns.iter().any(|c| c == column)
726 });
727 let rows = if has_minhash {
728 let query_hashes: Vec<u64> = query
730 .iter()
731 .map(|s| mongreldb_core::index::minhash_token_hash(s))
732 .collect();
733 let cand_k = k.saturating_mul(8).max(k + 64);
735 let cond = mongreldb_core::query::Condition::MinHashSimilar {
736 column_id: col.id as u16,
737 query: query_hashes,
738 k: cand_k,
739 };
740 let (snapshot, _pin) = self.inner.snapshot();
741 let core_rows = self.query_core_rows_at(table, &[cond], snapshot)?;
742 core_rows
743 .iter()
744 .map(|r| crate::schema::core_row_to_json(r, t))
745 .collect::<Result<Vec<_>>>()?
746 } else {
747 let tx = self.begin()?;
748 tx.all_rows(table)?
749 };
750
751 let mut scored: Vec<SimilarRow> = Vec::new();
752 for row in rows {
753 let set = parse_string_set(row.values.get(column));
754 let inter = set.iter().filter(|x| query_set.contains(*x)).count();
755 let union = set.len() + query_set.len() - inter;
756 let sim = if union == 0 {
757 0.0
758 } else {
759 inter as f64 / union as f64
760 };
761 if sim > 0.0 {
762 scored.push(SimilarRow {
763 row,
764 similarity: sim,
765 });
766 }
767 }
768 scored.sort_by(|a, b| {
769 b.similarity
770 .partial_cmp(&a.similarity)
771 .unwrap_or(std::cmp::Ordering::Equal)
772 });
773 scored.truncate(k);
774 Ok(scored)
775 }
776
777 pub fn flush(&self) -> Result<()> {
781 for name in self.inner.table_names() {
782 let handle = self.inner.table(&name).map_err(KitError::from)?;
783 let mut guard = handle.lock();
784 guard.flush().map_err(KitError::from)?;
785 }
786 Ok(())
787 }
788
789 pub fn incremental_aggregate(
801 &self,
802 table: &str,
803 column: Option<&str>,
804 agg: IncrementalAggKind,
805 filter: Option<&mongreldb_kit_core::query::Expr>,
806 ) -> Result<IncrementalAggregate> {
807 let t = self
808 .schema
809 .tables
810 .iter()
811 .find(|t| t.name == table)
812 .ok_or_else(|| KitError::Validation(format!("unknown table '{table}'")))?;
813 if !matches!(agg, IncrementalAggKind::Count) && column.is_none() {
814 return Err(KitError::Validation(
815 "sum/min/max/avg incremental aggregate requires a column".into(),
816 ));
817 }
818 let cid = match column {
819 Some(name) => Some(
820 t.columns
821 .iter()
822 .find(|c| c.name == name)
823 .ok_or_else(|| KitError::Validation(format!("unknown column '{name}'")))?
824 .id as u16,
825 ),
826 None => None,
827 };
828 let conditions = match filter {
829 Some(expr) => {
830 let plan = crate::pushdown::translate_predicate(t, expr).ok_or_else(|| {
831 KitError::Validation(
832 "filter is not index-translatable for an incremental aggregate".into(),
833 )
834 })?;
835 if !plan.fully_translated {
836 return Err(KitError::Validation(
837 "filter has a residual that an incremental aggregate cannot apply exactly"
838 .into(),
839 ));
840 }
841 plan.conditions
842 }
843 None => Vec::new(),
844 };
845 let core_agg = match agg {
846 IncrementalAggKind::Count => NativeAgg::Count,
847 IncrementalAggKind::Sum => NativeAgg::Sum,
848 IncrementalAggKind::Min => NativeAgg::Min,
849 IncrementalAggKind::Max => NativeAgg::Max,
850 IncrementalAggKind::Avg => NativeAgg::Avg,
851 };
852 let cache_key = incremental_cache_key(t.id, cid, agg, &conditions);
853 let handle = self.inner.table(table).map_err(KitError::from)?;
854 let mut guard = handle.lock();
855 let res = guard
856 .aggregate_incremental(cache_key, &conditions, cid, core_agg)
857 .map_err(KitError::from)?;
858 Ok(IncrementalAggregate {
859 value: agg_state_value(&res.state),
860 incremental: res.incremental,
861 delta_rows: res.delta_rows,
862 })
863 }
864
865 pub fn applied_migrations(&self) -> Result<Vec<mongreldb_kit_core::migrations::Migration>> {
867 crate::migrate::load_applied_migrations(&self.inner)
868 }
869
870 pub(crate) fn core_db(&self) -> &CoreDatabase {
871 &self.inner
872 }
873
874 pub fn close(&self) -> Result<()> {
879 self.inner.close().map_err(KitError::from)
880 }
881
882 pub fn compact_all(&self) -> Result<(usize, usize)> {
887 self.inner.compact().map_err(KitError::from)
888 }
889
890 pub fn compact_table(&self, name: &str) -> Result<bool> {
893 self.inner.compact_table(name).map_err(KitError::from)
894 }
895
896 pub(crate) fn lookup_row_id(&self, table: &str, key: &[u8]) -> Result<Option<RowId>> {
900 let handle = self.inner.table(table).map_err(KitError::from)?;
901 let mut guard = handle.lock();
902 guard.ensure_indexes_complete()?;
903 Ok(guard.lookup_pk(key))
904 }
905
906 pub(crate) fn root(&self) -> &Path {
907 &self.root
908 }
909
910 pub(crate) fn visible_core_rows_at(
914 &self,
915 table_name: &str,
916 snapshot: Snapshot,
917 ) -> Result<Vec<CoreRow>> {
918 let handle = self.inner.table(table_name).map_err(KitError::from)?;
919 let guard = handle.lock();
920 guard.visible_rows(snapshot).map_err(KitError::from)
921 }
922
923 pub(crate) fn query_core_rows_at(
930 &self,
931 table_name: &str,
932 conditions: &[mongreldb_core::query::Condition],
933 snapshot: Snapshot,
934 ) -> Result<Vec<CoreRow>> {
935 if conditions.is_empty() {
936 return self.visible_core_rows_at(table_name, snapshot);
937 }
938 let handle = self.inner.table(table_name).map_err(KitError::from)?;
939 let mut guard = handle.lock();
940 let q = mongreldb_core::query::Query {
941 conditions: conditions.to_vec(),
942 };
943 guard.query(&q).map_err(KitError::from)
944 }
945
946 pub(crate) fn flush_table(&self, table_name: &str) -> Result<()> {
954 let handle = self.inner.table(table_name).map_err(KitError::from)?;
955 handle.lock().flush().map_err(KitError::from)?;
956 Ok(())
957 }
958
959 pub(crate) fn count_core_rows_at(
969 &self,
970 table_name: &str,
971 conditions: &[mongreldb_core::query::Condition],
972 snapshot: Snapshot,
973 ) -> Result<Option<u64>> {
974 let handle = self.inner.table(table_name).map_err(KitError::from)?;
975 let mut guard = handle.lock();
976 if guard.snapshot().epoch != snapshot.epoch {
977 return Ok(None); }
979 guard
980 .count_conditions(conditions, snapshot)
981 .map_err(KitError::from)
982 }
983
984 pub(crate) fn aggregate_core_at(
993 &self,
994 table_name: &str,
995 column: Option<u16>,
996 conditions: &[mongreldb_core::query::Condition],
997 agg: NativeAgg,
998 snapshot: Snapshot,
999 ) -> Result<Option<NativeAggResult>> {
1000 let handle = self.inner.table(table_name).map_err(KitError::from)?;
1001 let guard = handle.lock();
1002 if guard.snapshot().epoch != snapshot.epoch {
1003 return Ok(None); }
1005 guard
1006 .aggregate_native(snapshot, column, conditions, agg)
1007 .map_err(KitError::from)
1008 }
1009
1010 pub(crate) fn count_distinct_core_at(
1019 &self,
1020 table_name: &str,
1021 column_id: u16,
1022 snapshot: Snapshot,
1023 ) -> Result<Option<u64>> {
1024 let handle = self.inner.table(table_name).map_err(KitError::from)?;
1025 let mut guard = handle.lock();
1026 if guard.snapshot().epoch != snapshot.epoch {
1027 return Ok(None); }
1029 guard
1030 .count_distinct_from_bitmap(column_id)
1031 .map_err(KitError::from)
1032 }
1033
1034 #[allow(dead_code)]
1036 pub(crate) fn get_core_row(&self, table_name: &str, row_id: u64) -> Result<Option<CoreRow>> {
1037 let handle = self.inner.table(table_name).map_err(KitError::from)?;
1038 let guard = handle.lock();
1039 let snapshot = guard.snapshot();
1040 Ok(guard.get(mongreldb_core::RowId(row_id), snapshot))
1041 }
1042}
1043
1044pub(crate) fn create_core_table(db: &CoreDatabase, name: &str, schema: CoreSchema) -> Result<()> {
1045 if db.table_id(name).is_ok() {
1046 return Ok(());
1047 }
1048 db.create_table(name, schema).map_err(KitError::from)?;
1049 Ok(())
1050}
1051
1052fn core_procedure(spec: &ProcedureSpec) -> Result<mongreldb_core::StoredProcedure> {
1053 let parsed: mongreldb_core::StoredProcedure =
1054 serde_json::from_value(spec.json.clone()).map_err(KitError::from)?;
1055 mongreldb_core::StoredProcedure::new(parsed.name, parsed.mode, parsed.params, parsed.body, 0)
1056 .map_err(KitError::from)
1057}
1058
1059fn json_to_core_value(value: &Value) -> Result<CoreValue> {
1060 match value {
1061 Value::Null => Ok(CoreValue::Null),
1062 Value::Bool(value) => Ok(CoreValue::Bool(*value)),
1063 Value::Number(value) => {
1064 if let Some(value) = value.as_i64() {
1065 Ok(CoreValue::Int64(value))
1066 } else if let Some(value) = value.as_f64() {
1067 Ok(CoreValue::Float64(value))
1068 } else {
1069 Err(KitError::Validation("unsupported JSON number".into()))
1070 }
1071 }
1072 Value::String(value) => Ok(CoreValue::Bytes(value.as_bytes().to_vec())),
1073 Value::Array(_) | Value::Object(_) => Err(KitError::Validation(
1074 "procedure args only support scalar JSON values".into(),
1075 )),
1076 }
1077}
1078
1079pub(crate) fn internal_bytes(row: &CoreRow, col_id: u16) -> Option<String> {
1081 match row.columns.get(&col_id) {
1082 Some(CoreValue::Bytes(b)) => String::from_utf8(b.clone()).ok(),
1083 _ => None,
1084 }
1085}
1086
1087fn reap_rotated_wal_segments(db: &CoreDatabase) {
1102 let _ = db.gc();
1103}
1104
1105pub(crate) fn load_schema(path: &Path) -> Result<KitSchema> {
1106 let file = path.join(SCHEMA_FILE);
1107 let json = std::fs::read_to_string(&file)
1108 .map_err(|e| KitError::Migration(format!("cannot read schema file: {e}")))?;
1109 let schema: KitSchema = serde_json::from_str(&json)?;
1110 Ok(schema)
1111}
1112
1113pub(crate) fn store_schema(path: &Path, schema: &KitSchema) -> Result<()> {
1114 let file = path.join(SCHEMA_FILE);
1115 let json = serde_json::to_string_pretty(schema)?;
1116 std::fs::write(&file, json)?;
1117 Ok(())
1118}
1119
1120pub(crate) fn persist_schema(db: &Database, schema: &KitSchema) -> Result<()> {
1122 store_schema(&db.root, schema)
1123}