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, TriggerSpec};
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 create_trigger(&self, spec: &TriggerSpec) -> Result<mongreldb_core::StoredTrigger> {
317 let trigger = core_trigger(spec)?;
318 self.inner.create_trigger(trigger).map_err(KitError::from)
319 }
320
321 pub fn replace_trigger(&self, spec: &TriggerSpec) -> Result<mongreldb_core::StoredTrigger> {
322 let trigger = core_trigger(spec)?;
323 self.inner
324 .create_or_replace_trigger(trigger)
325 .map_err(KitError::from)
326 }
327
328 pub fn drop_trigger(&self, name: &str) -> Result<()> {
329 self.inner.drop_trigger(name).map_err(KitError::from)
330 }
331
332 pub fn triggers(&self) -> Vec<mongreldb_core::StoredTrigger> {
333 self.inner.triggers()
334 }
335
336 pub fn trigger(&self, name: &str) -> Option<mongreldb_core::StoredTrigger> {
337 self.inner.trigger(name)
338 }
339
340 pub fn allocate_sequence(&self, name: &str, count: i64) -> Result<i64> {
346 use crate::internal::cols;
347 let mut attempt = 0;
348 loop {
349 let mut txn = self.inner.begin();
350 let snapshot = txn.read_snapshot();
351 let existing = self
352 .visible_core_rows_at(crate::internal::SEQUENCES, snapshot)?
353 .into_iter()
354 .find(|r| internal_bytes(r, cols::SEQ_NAME) == Some(name.to_string()));
355
356 let now = crate::internal::iso_now();
357 let (start, next, old_row_id) = match &existing {
361 Some(row) => {
362 let current = match row.columns.get(&cols::SEQ_NEXT) {
363 Some(CoreValue::Int64(i)) => *i,
364 _ => 1,
365 };
366 (current, current + count, Some(row.row_id))
367 }
368 None => (1, 1 + count, None),
369 };
370
371 if let Some(rid) = old_row_id {
372 txn.delete(crate::internal::SEQUENCES, rid)
373 .map_err(KitError::from)?;
374 }
375 txn.put(
376 crate::internal::SEQUENCES,
377 vec![
378 (cols::SEQ_NAME, CoreValue::Bytes(name.as_bytes().to_vec())),
379 (cols::SEQ_NEXT, CoreValue::Int64(next)),
380 (cols::SEQ_UPDATED, CoreValue::Bytes(now.into_bytes())),
381 ],
382 )
383 .map_err(KitError::from)?;
384 match txn.commit() {
385 Ok(_) => return Ok(start),
386 Err(mongreldb_core::MongrelError::Conflict(_)) if attempt < 10_000 => {
387 attempt += 1;
388 std::thread::yield_now();
389 continue;
390 }
391 Err(e) => return Err(KitError::from(e)),
392 }
393 }
394 }
395
396 pub fn transaction<T, F>(&self, max_retries: usize, mut f: F) -> Result<T>
399 where
400 F: FnMut(&mut crate::txn::Transaction<'_>) -> Result<T>,
401 {
402 let mut attempt = 0;
403 loop {
404 let mut txn = self.begin()?;
405 match f(&mut txn) {
406 Ok(value) => match txn.commit() {
407 Ok(()) => return Ok(value),
408 Err(KitError::Conflict(_)) if attempt < max_retries => {
409 attempt += 1;
410 continue;
411 }
412 Err(e) => return Err(e),
413 },
414 Err(KitError::Conflict(_)) if attempt < max_retries => {
415 txn.rollback();
416 attempt += 1;
417 continue;
418 }
419 Err(e) => {
420 txn.rollback();
421 return Err(e);
422 }
423 }
424 }
425 }
426
427 pub fn table(&self, name: &str) -> Option<&KitTable> {
429 self.schema.table(name)
430 }
431
432 pub fn schema(&self) -> &KitSchema {
434 &self.schema
435 }
436
437 pub fn begin(&self) -> Result<crate::txn::Transaction<'_>> {
439 let core_txn = self.inner.begin();
440 Ok(crate::txn::Transaction::new(self, core_txn))
441 }
442
443 pub fn set_schema(&mut self, schema: KitSchema) {
445 self.schema = schema;
446 }
447
448 pub fn check_internal_tables(&self) -> Result<()> {
451 let schema_file = self.root.join(SCHEMA_FILE);
452 if !schema_file.exists() {
453 return Err(KitError::Integrity(format!(
454 "schema file {} is missing",
455 schema_file.display()
456 )));
457 }
458 for (name, _) in internal_tables_core() {
459 if self.inner.table_id(name).is_err() {
460 return Err(KitError::Integrity(format!(
461 "internal table {name} is missing"
462 )));
463 }
464 }
465 Ok(())
466 }
467
468 pub fn gc(&self) -> Result<usize> {
471 self.inner.gc().map_err(KitError::from)
472 }
473
474 pub fn check(&self) -> Vec<serde_json::Value> {
477 self.inner
478 .check()
479 .into_iter()
480 .map(|i| {
481 serde_json::json!({
482 "table_id": i.table_id,
483 "table_name": i.table_name,
484 "severity": i.severity,
485 "description": i.description,
486 })
487 })
488 .collect()
489 }
490
491 pub fn doctor(&self) -> Result<Vec<u64>> {
493 self.inner.doctor().map_err(KitError::from)
494 }
495
496 pub fn snapshot_epoch(&self) -> u64 {
500 self.inner.snapshot().0.epoch.0
501 }
502
503 pub fn export_tsv(&self, table: &str) -> Result<String> {
507 let t = self
508 .schema
509 .tables
510 .iter()
511 .find(|t| t.name == table)
512 .ok_or_else(|| KitError::Validation(format!("unknown table '{table}'")))?
513 .clone();
514 let tx = self.begin()?;
515 let rows = tx.all_rows(table)?;
516 Ok(crate::tsv::rows_to_tsv(&t, &rows))
517 }
518
519 pub fn import_tsv(&self, table: &str, text: &str) -> Result<usize> {
523 let t = self
524 .schema
525 .tables
526 .iter()
527 .find(|t| t.name == table)
528 .ok_or_else(|| KitError::Validation(format!("unknown table '{table}'")))?
529 .clone();
530 let rows = crate::tsv::tsv_to_rows(&t, text)?;
531 let n = rows.len();
532 self.transaction(1, |tx| {
533 tx.insert_many(table, rows.clone())?;
534 Ok(())
535 })?;
536 Ok(n)
537 }
538
539 pub fn explain(
544 &self,
545 table: &str,
546 predicate: &mongreldb_kit_core::query::Expr,
547 ) -> Result<ExplainPlan> {
548 let t = self
549 .schema
550 .tables
551 .iter()
552 .find(|t| t.name == table)
553 .ok_or_else(|| KitError::Validation(format!("unknown table '{table}'")))?;
554 Ok(match crate::pushdown::translate_predicate(t, predicate) {
555 Some(p) => ExplainPlan {
556 index_accelerated: p.can_push(),
557 exact: p.fully_translated,
558 pushed_conditions: p.conditions.iter().map(condition_label).collect(),
559 },
560 None => ExplainPlan {
561 index_accelerated: false,
562 exact: false,
563 pushed_conditions: Vec::new(),
564 },
565 })
566 }
567
568 pub fn rows_at_epoch(&self, table: &str, epoch: u64) -> Result<Vec<crate::schema::Row>> {
574 let t = self
575 .schema
576 .tables
577 .iter()
578 .find(|t| t.name == table)
579 .ok_or_else(|| KitError::Validation(format!("unknown table '{table}'")))?;
580 let current = self.snapshot_epoch();
581 if epoch > current {
582 return Err(KitError::Validation(format!(
583 "epoch {epoch} is in the future (current committed epoch is {current})"
584 )));
585 }
586 let snap = Snapshot::at(mongreldb_core::epoch::Epoch(epoch));
587 let rows = self.visible_core_rows_at(table, snap)?;
588 rows.iter()
589 .map(|r| crate::schema::core_row_to_json(r, t))
590 .collect()
591 }
592
593 pub fn approx_aggregate(
599 &self,
600 table: &str,
601 column: Option<&str>,
602 agg: ApproxAggKind,
603 z: f64,
604 ) -> Result<Option<ApproxAggregate>> {
605 let t = self
606 .schema
607 .tables
608 .iter()
609 .find(|t| t.name == table)
610 .ok_or_else(|| KitError::Validation(format!("unknown table '{table}'")))?;
611 if matches!(agg, ApproxAggKind::Sum | ApproxAggKind::Avg) && column.is_none() {
612 return Err(KitError::Validation(
613 "approx sum/avg requires a column".into(),
614 ));
615 }
616 let cid = match column {
617 Some(name) => Some(
618 t.columns
619 .iter()
620 .find(|c| c.name == name)
621 .ok_or_else(|| KitError::Validation(format!("unknown column '{name}'")))?
622 .id as u16,
623 ),
624 None => None,
625 };
626 let core_agg = match agg {
627 ApproxAggKind::Count => ApproxAgg::Count,
628 ApproxAggKind::Sum => ApproxAgg::Sum,
629 ApproxAggKind::Avg => ApproxAgg::Avg,
630 };
631 let handle = self.inner.table(table).map_err(KitError::from)?;
632 let mut guard = handle.lock();
633 let res = guard
634 .approx_aggregate(&[], cid, core_agg, z)
635 .map_err(KitError::from)?;
636 Ok(res.map(|r| ApproxAggregate {
637 point: r.point,
638 ci_low: r.ci_low,
639 ci_high: r.ci_high,
640 n_population: r.n_population,
641 n_sample_live: r.n_sample_live,
642 n_passing: r.n_passing,
643 }))
644 }
645
646 pub fn scan_batched<F>(&self, table: &str, batch_size: usize, mut f: F) -> Result<()>
652 where
653 F: FnMut(&[serde_json::Map<String, Value>]) -> Result<()>,
654 {
655 let kit_t = self
656 .schema
657 .tables
658 .iter()
659 .find(|t| t.name == table)
660 .ok_or_else(|| KitError::Validation(format!("unknown table '{table}'")))?;
661 let batch_size = batch_size.max(1);
662 let (snapshot, _pin) = self.inner.snapshot();
665 let handle = self.inner.table(table).map_err(KitError::from)?;
666 let guard = handle.lock();
667
668 let mut projection: Vec<(u16, mongreldb_core::schema::TypeId)> = Vec::new();
670 let mut meta: Vec<(String, mongreldb_kit_core::schema::ColumnType)> = Vec::new();
671 for c in &guard.schema().columns {
672 if let Some(kc) = kit_t.columns.iter().find(|kc| kc.id as u16 == c.id) {
673 projection.push((c.id, c.ty));
674 meta.push((kc.name.clone(), kc.storage_type));
675 }
676 }
677
678 match guard
679 .scan_cursor(snapshot, projection, &[])
680 .map_err(KitError::from)?
681 {
682 Some(mut cursor) => {
683 let mut buf: Vec<serde_json::Map<String, Value>> = Vec::with_capacity(batch_size);
684 while let Some(batch) = cursor.next_batch().map_err(KitError::from)? {
685 let nrows = batch.first().map(|c| c.len()).unwrap_or(0);
686 for j in 0..nrows {
687 let mut m = serde_json::Map::new();
688 for (ci, (name, ty)) in meta.iter().enumerate() {
689 let cv = batch
690 .get(ci)
691 .and_then(|col| col.value_at(j))
692 .unwrap_or(CoreValue::Null);
693 m.insert(name.clone(), crate::schema::core_to_json(&cv, *ty)?);
694 }
695 buf.push(m);
696 if buf.len() >= batch_size {
697 f(&buf)?;
698 buf.clear();
699 }
700 }
701 }
702 if !buf.is_empty() {
703 f(&buf)?;
704 }
705 Ok(())
706 }
707 None => {
708 drop(guard);
709 let rows = self.visible_core_rows_at(table, snapshot)?;
710 let maps: Vec<serde_json::Map<String, Value>> = rows
711 .iter()
712 .map(|r| crate::schema::core_row_to_json(r, kit_t).map(|row| row.values))
713 .collect::<Result<Vec<_>>>()?;
714 for chunk in maps.chunks(batch_size) {
715 f(chunk)?;
716 }
717 Ok(())
718 }
719 }
720 }
721
722 pub fn set_similarity(
731 &self,
732 table: &str,
733 column: &str,
734 query: &[String],
735 k: usize,
736 ) -> Result<Vec<SimilarRow>> {
737 let t = self
738 .schema
739 .tables
740 .iter()
741 .find(|t| t.name == table)
742 .ok_or_else(|| KitError::Validation(format!("unknown table '{table}'")))?;
743 let col = t.columns.iter().find(|c| c.name == column).ok_or_else(|| {
744 KitError::Validation(format!("unknown column '{column}' on table '{table}'"))
745 })?;
746 let query_set: std::collections::HashSet<String> = query.iter().cloned().collect();
747
748 let has_minhash = t.indexes.iter().any(|idx| {
749 idx.kind == KitIndexKind::MinHash && idx.columns.iter().any(|c| c == column)
750 });
751 let rows = if has_minhash {
752 let query_hashes: Vec<u64> = query
754 .iter()
755 .map(|s| mongreldb_core::index::minhash_token_hash(s))
756 .collect();
757 let cand_k = k.saturating_mul(8).max(k + 64);
759 let cond = mongreldb_core::query::Condition::MinHashSimilar {
760 column_id: col.id as u16,
761 query: query_hashes,
762 k: cand_k,
763 };
764 let (snapshot, _pin) = self.inner.snapshot();
765 let core_rows = self.query_core_rows_at(table, &[cond], snapshot)?;
766 core_rows
767 .iter()
768 .map(|r| crate::schema::core_row_to_json(r, t))
769 .collect::<Result<Vec<_>>>()?
770 } else {
771 let tx = self.begin()?;
772 tx.all_rows(table)?
773 };
774
775 let mut scored: Vec<SimilarRow> = Vec::new();
776 for row in rows {
777 let set = parse_string_set(row.values.get(column));
778 let inter = set.iter().filter(|x| query_set.contains(*x)).count();
779 let union = set.len() + query_set.len() - inter;
780 let sim = if union == 0 {
781 0.0
782 } else {
783 inter as f64 / union as f64
784 };
785 if sim > 0.0 {
786 scored.push(SimilarRow {
787 row,
788 similarity: sim,
789 });
790 }
791 }
792 scored.sort_by(|a, b| {
793 b.similarity
794 .partial_cmp(&a.similarity)
795 .unwrap_or(std::cmp::Ordering::Equal)
796 });
797 scored.truncate(k);
798 Ok(scored)
799 }
800
801 pub fn flush(&self) -> Result<()> {
805 for name in self.inner.table_names() {
806 let handle = self.inner.table(&name).map_err(KitError::from)?;
807 let mut guard = handle.lock();
808 guard.flush().map_err(KitError::from)?;
809 }
810 Ok(())
811 }
812
813 pub fn incremental_aggregate(
825 &self,
826 table: &str,
827 column: Option<&str>,
828 agg: IncrementalAggKind,
829 filter: Option<&mongreldb_kit_core::query::Expr>,
830 ) -> Result<IncrementalAggregate> {
831 let t = self
832 .schema
833 .tables
834 .iter()
835 .find(|t| t.name == table)
836 .ok_or_else(|| KitError::Validation(format!("unknown table '{table}'")))?;
837 if !matches!(agg, IncrementalAggKind::Count) && column.is_none() {
838 return Err(KitError::Validation(
839 "sum/min/max/avg incremental aggregate requires a column".into(),
840 ));
841 }
842 let cid = match column {
843 Some(name) => Some(
844 t.columns
845 .iter()
846 .find(|c| c.name == name)
847 .ok_or_else(|| KitError::Validation(format!("unknown column '{name}'")))?
848 .id as u16,
849 ),
850 None => None,
851 };
852 let conditions = match filter {
853 Some(expr) => {
854 let plan = crate::pushdown::translate_predicate(t, expr).ok_or_else(|| {
855 KitError::Validation(
856 "filter is not index-translatable for an incremental aggregate".into(),
857 )
858 })?;
859 if !plan.fully_translated {
860 return Err(KitError::Validation(
861 "filter has a residual that an incremental aggregate cannot apply exactly"
862 .into(),
863 ));
864 }
865 plan.conditions
866 }
867 None => Vec::new(),
868 };
869 let core_agg = match agg {
870 IncrementalAggKind::Count => NativeAgg::Count,
871 IncrementalAggKind::Sum => NativeAgg::Sum,
872 IncrementalAggKind::Min => NativeAgg::Min,
873 IncrementalAggKind::Max => NativeAgg::Max,
874 IncrementalAggKind::Avg => NativeAgg::Avg,
875 };
876 let cache_key = incremental_cache_key(t.id, cid, agg, &conditions);
877 let handle = self.inner.table(table).map_err(KitError::from)?;
878 let mut guard = handle.lock();
879 let res = guard
880 .aggregate_incremental(cache_key, &conditions, cid, core_agg)
881 .map_err(KitError::from)?;
882 Ok(IncrementalAggregate {
883 value: agg_state_value(&res.state),
884 incremental: res.incremental,
885 delta_rows: res.delta_rows,
886 })
887 }
888
889 pub fn applied_migrations(&self) -> Result<Vec<mongreldb_kit_core::migrations::Migration>> {
891 crate::migrate::load_applied_migrations(&self.inner)
892 }
893
894 pub(crate) fn core_db(&self) -> &CoreDatabase {
895 &self.inner
896 }
897
898 pub fn close(&self) -> Result<()> {
903 self.inner.close().map_err(KitError::from)
904 }
905
906 pub fn compact_all(&self) -> Result<(usize, usize)> {
911 self.inner.compact().map_err(KitError::from)
912 }
913
914 pub fn compact_table(&self, name: &str) -> Result<bool> {
917 self.inner.compact_table(name).map_err(KitError::from)
918 }
919
920 pub(crate) fn lookup_row_id(&self, table: &str, key: &[u8]) -> Result<Option<RowId>> {
924 let handle = self.inner.table(table).map_err(KitError::from)?;
925 let mut guard = handle.lock();
926 guard.ensure_indexes_complete()?;
927 Ok(guard.lookup_pk(key))
928 }
929
930 pub(crate) fn root(&self) -> &Path {
931 &self.root
932 }
933
934 pub(crate) fn visible_core_rows_at(
938 &self,
939 table_name: &str,
940 snapshot: Snapshot,
941 ) -> Result<Vec<CoreRow>> {
942 let handle = self.inner.table(table_name).map_err(KitError::from)?;
943 let guard = handle.lock();
944 guard.visible_rows(snapshot).map_err(KitError::from)
945 }
946
947 pub(crate) fn query_core_rows_at(
954 &self,
955 table_name: &str,
956 conditions: &[mongreldb_core::query::Condition],
957 snapshot: Snapshot,
958 ) -> Result<Vec<CoreRow>> {
959 if conditions.is_empty() {
960 return self.visible_core_rows_at(table_name, snapshot);
961 }
962 let handle = self.inner.table(table_name).map_err(KitError::from)?;
963 let mut guard = handle.lock();
964 let q = mongreldb_core::query::Query {
965 conditions: conditions.to_vec(),
966 };
967 guard.query(&q).map_err(KitError::from)
968 }
969
970 pub(crate) fn flush_table(&self, table_name: &str) -> Result<()> {
978 let handle = self.inner.table(table_name).map_err(KitError::from)?;
979 handle.lock().flush().map_err(KitError::from)?;
980 Ok(())
981 }
982
983 pub(crate) fn count_core_rows_at(
993 &self,
994 table_name: &str,
995 conditions: &[mongreldb_core::query::Condition],
996 snapshot: Snapshot,
997 ) -> Result<Option<u64>> {
998 let handle = self.inner.table(table_name).map_err(KitError::from)?;
999 let mut guard = handle.lock();
1000 if guard.snapshot().epoch != snapshot.epoch {
1001 return Ok(None); }
1003 guard
1004 .count_conditions(conditions, snapshot)
1005 .map_err(KitError::from)
1006 }
1007
1008 pub(crate) fn aggregate_core_at(
1017 &self,
1018 table_name: &str,
1019 column: Option<u16>,
1020 conditions: &[mongreldb_core::query::Condition],
1021 agg: NativeAgg,
1022 snapshot: Snapshot,
1023 ) -> Result<Option<NativeAggResult>> {
1024 let handle = self.inner.table(table_name).map_err(KitError::from)?;
1025 let guard = handle.lock();
1026 if guard.snapshot().epoch != snapshot.epoch {
1027 return Ok(None); }
1029 guard
1030 .aggregate_native(snapshot, column, conditions, agg)
1031 .map_err(KitError::from)
1032 }
1033
1034 pub(crate) fn count_distinct_core_at(
1043 &self,
1044 table_name: &str,
1045 column_id: u16,
1046 snapshot: Snapshot,
1047 ) -> Result<Option<u64>> {
1048 let handle = self.inner.table(table_name).map_err(KitError::from)?;
1049 let mut guard = handle.lock();
1050 if guard.snapshot().epoch != snapshot.epoch {
1051 return Ok(None); }
1053 guard
1054 .count_distinct_from_bitmap(column_id)
1055 .map_err(KitError::from)
1056 }
1057
1058 #[allow(dead_code)]
1060 pub(crate) fn get_core_row(&self, table_name: &str, row_id: u64) -> Result<Option<CoreRow>> {
1061 let handle = self.inner.table(table_name).map_err(KitError::from)?;
1062 let guard = handle.lock();
1063 let snapshot = guard.snapshot();
1064 Ok(guard.get(mongreldb_core::RowId(row_id), snapshot))
1065 }
1066}
1067
1068pub(crate) fn create_core_table(db: &CoreDatabase, name: &str, schema: CoreSchema) -> Result<()> {
1069 if db.table_id(name).is_ok() {
1070 return Ok(());
1071 }
1072 db.create_table(name, schema).map_err(KitError::from)?;
1073 Ok(())
1074}
1075
1076fn core_procedure(spec: &ProcedureSpec) -> Result<mongreldb_core::StoredProcedure> {
1077 let parsed: mongreldb_core::StoredProcedure =
1078 serde_json::from_value(spec.json.clone()).map_err(KitError::from)?;
1079 mongreldb_core::StoredProcedure::new(parsed.name, parsed.mode, parsed.params, parsed.body, 0)
1080 .map_err(KitError::from)
1081}
1082
1083fn core_trigger(spec: &TriggerSpec) -> Result<mongreldb_core::StoredTrigger> {
1084 let parsed: mongreldb_core::StoredTrigger =
1085 serde_json::from_value(spec.json.clone()).map_err(KitError::from)?;
1086 mongreldb_core::StoredTrigger::new(
1087 parsed.name,
1088 mongreldb_core::TriggerDefinition {
1089 target: parsed.target,
1090 timing: parsed.timing,
1091 event: parsed.event,
1092 update_of: parsed.update_of,
1093 target_columns: parsed.target_columns,
1094 when: parsed.when,
1095 program: parsed.program,
1096 },
1097 0,
1098 )
1099 .map_err(KitError::from)
1100}
1101
1102fn json_to_core_value(value: &Value) -> Result<CoreValue> {
1103 match value {
1104 Value::Null => Ok(CoreValue::Null),
1105 Value::Bool(value) => Ok(CoreValue::Bool(*value)),
1106 Value::Number(value) => {
1107 if let Some(value) = value.as_i64() {
1108 Ok(CoreValue::Int64(value))
1109 } else if let Some(value) = value.as_f64() {
1110 Ok(CoreValue::Float64(value))
1111 } else {
1112 Err(KitError::Validation("unsupported JSON number".into()))
1113 }
1114 }
1115 Value::String(value) => Ok(CoreValue::Bytes(value.as_bytes().to_vec())),
1116 Value::Array(_) | Value::Object(_) => Err(KitError::Validation(
1117 "procedure args only support scalar JSON values".into(),
1118 )),
1119 }
1120}
1121
1122pub(crate) fn internal_bytes(row: &CoreRow, col_id: u16) -> Option<String> {
1124 match row.columns.get(&col_id) {
1125 Some(CoreValue::Bytes(b)) => String::from_utf8(b.clone()).ok(),
1126 _ => None,
1127 }
1128}
1129
1130fn reap_rotated_wal_segments(db: &CoreDatabase) {
1145 let _ = db.gc();
1146}
1147
1148pub(crate) fn load_schema(path: &Path) -> Result<KitSchema> {
1149 let file = path.join(SCHEMA_FILE);
1150 let json = std::fs::read_to_string(&file)
1151 .map_err(|e| KitError::Migration(format!("cannot read schema file: {e}")))?;
1152 let schema: KitSchema = serde_json::from_str(&json)?;
1153 Ok(schema)
1154}
1155
1156pub(crate) fn store_schema(path: &Path, schema: &KitSchema) -> Result<()> {
1157 let file = path.join(SCHEMA_FILE);
1158 let json = serde_json::to_string_pretty(schema)?;
1159 std::fs::write(&file, json)?;
1160 Ok(())
1161}
1162
1163pub(crate) fn persist_schema(db: &Database, schema: &KitSchema) -> Result<()> {
1165 store_schema(&db.root, schema)
1166}