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, ViewSpec};
16use serde_json::Value;
17
18use std::collections::HashMap;
19use std::path::{Path, PathBuf};
20use std::sync::Arc;
21
22const SCHEMA_FILE: &str = "kit_schema.json";
23
24#[derive(Clone, Copy, Debug, Default)]
29pub struct OpenOptions {
30 pub lock_timeout_ms: u32,
36}
37
38impl OpenOptions {
39 pub fn new() -> Self {
41 Self::default()
42 }
43
44 pub fn with_lock_timeout_ms(mut self, ms: u32) -> Self {
47 self.lock_timeout_ms = ms;
48 self
49 }
50}
51
52pub type DefaultProvider = Box<dyn Fn() -> Value + Send + Sync>;
54
55#[derive(Debug, Clone)]
58pub struct ExplainPlan {
59 pub index_accelerated: bool,
61 pub exact: bool,
64 pub pushed_conditions: Vec<String>,
66}
67
68#[derive(Debug, Clone)]
70pub struct SimilarRow {
71 pub row: crate::schema::Row,
72 pub similarity: f64,
73}
74
75fn parse_string_set(value: Option<&Value>) -> std::collections::HashSet<String> {
79 let arr = match value {
80 Some(Value::Array(a)) => Some(a.clone()),
81 Some(Value::String(s)) => serde_json::from_str::<Value>(s)
82 .ok()
83 .and_then(|v| v.as_array().cloned()),
84 _ => None,
85 };
86 arr.into_iter()
87 .flatten()
88 .filter_map(|v| match v {
89 Value::String(s) => Some(s),
90 Value::Number(n) => Some(n.to_string()),
91 Value::Bool(b) => Some(b.to_string()),
92 _ => None,
93 })
94 .collect()
95}
96
97#[derive(Debug, Clone, Copy, PartialEq, Eq)]
99pub enum IncrementalAggKind {
100 Count,
101 Sum,
102 Min,
103 Max,
104 Avg,
105}
106
107#[derive(Debug, Clone)]
109pub struct IncrementalAggregate {
110 pub value: Value,
113 pub incremental: bool,
117 pub delta_rows: u64,
119}
120
121fn incremental_cache_key(
125 table_id: u32,
126 column: Option<u16>,
127 agg: IncrementalAggKind,
128 conditions: &[mongreldb_core::query::Condition],
129) -> u64 {
130 use std::hash::{Hash, Hasher};
131 let mut h = std::collections::hash_map::DefaultHasher::new();
132 table_id.hash(&mut h);
133 column.hash(&mut h);
134 (agg as u8).hash(&mut h);
135 format!("{conditions:?}").hash(&mut h);
137 h.finish()
138}
139
140fn agg_state_value(s: &AggState) -> Value {
144 let num_f64 = |x: f64| {
145 serde_json::Number::from_f64(x)
146 .map(Value::Number)
147 .unwrap_or(Value::Null)
148 };
149 match s {
150 AggState::Count(n) => Value::from(*n),
151 AggState::SumI { sum, .. } => i64::try_from(*sum)
152 .map(Value::from)
153 .unwrap_or_else(|_| num_f64(*sum as f64)),
154 AggState::SumF { sum, .. } => num_f64(*sum),
155 AggState::AvgI { sum, count } if *count > 0 => num_f64(*sum as f64 / *count as f64),
156 AggState::AvgF { sum, count } if *count > 0 => num_f64(*sum / *count as f64),
157 AggState::AvgI { .. } | AggState::AvgF { .. } => Value::Null,
158 AggState::MinI(n) | AggState::MaxI(n) => Value::from(*n),
159 AggState::MinF(f) | AggState::MaxF(f) => num_f64(*f),
160 AggState::Empty => Value::Null,
161 }
162}
163
164#[derive(Debug, Clone, Copy, PartialEq, Eq)]
166pub enum ApproxAggKind {
167 Count,
168 Sum,
169 Avg,
170}
171
172#[derive(Debug, Clone)]
176pub struct ApproxAggregate {
177 pub point: f64,
178 pub ci_low: f64,
179 pub ci_high: f64,
180 pub n_population: u64,
181 pub n_sample_live: usize,
182 pub n_passing: usize,
183}
184
185fn condition_label(c: &mongreldb_core::query::Condition) -> String {
188 let dbg = format!("{c:?}");
189 dbg.split(['(', '{', ' ']).next().unwrap_or("").to_string()
190}
191
192fn open_core_with_retry<T>(
193 timeout_ms: u32,
194 mut open: impl FnMut() -> mongreldb_core::Result<T>,
195) -> mongreldb_core::Result<T> {
196 if timeout_ms == 0 {
197 return open();
198 }
199 let deadline = std::time::Instant::now() + std::time::Duration::from_millis(timeout_ms as u64);
200 let mut next_sleep = std::time::Duration::from_millis(1);
201 loop {
202 match open() {
203 Ok(db) => return Ok(db),
204 Err(err) if is_lock_contention(&err) => {
205 let now = std::time::Instant::now();
206 if now >= deadline {
207 return Err(mongreldb_core::MongrelError::Io(std::io::Error::other(
208 format!("database lock timeout after {timeout_ms}ms: {err}"),
209 )));
210 }
211 let sleep = next_sleep.min(deadline - now);
212 std::thread::sleep(sleep);
213 next_sleep = next_sleep
214 .saturating_mul(10)
215 .min(std::time::Duration::from_millis(50));
216 }
217 Err(err) => return Err(err),
218 }
219 }
220}
221
222fn is_lock_contention(err: &mongreldb_core::MongrelError) -> bool {
223 matches!(
224 err,
225 mongreldb_core::MongrelError::Io(io)
226 if io.to_string().contains("locked by another process")
227 )
228}
229
230pub struct Database {
235 pub(crate) inner: Arc<CoreDatabase>,
236 pub(crate) schema: KitSchema,
237 pub(crate) root: PathBuf,
238 pub(crate) default_providers: HashMap<String, DefaultProvider>,
240 pub(crate) session: parking_lot::Mutex<Option<mongreldb_query::MongrelSession>>,
247}
248
249impl Database {
250 pub fn open(path: &Path) -> Result<Self> {
252 let inner = Arc::new(CoreDatabase::open(path)?);
253 let schema = load_schema(path)?;
254 ensure_internal_tables(&inner)?;
256 reap_rotated_wal_segments(&inner);
257 Ok(Self {
258 inner,
259 schema,
260 root: path.to_path_buf(),
261 default_providers: HashMap::new(),
262 session: parking_lot::Mutex::new(None),
263 })
264 }
265
266 pub fn open_with_options(path: &Path, opts: OpenOptions) -> Result<Self> {
274 let inner = Arc::new(open_core_with_retry(opts.lock_timeout_ms, || {
275 CoreDatabase::open(path)
276 })?);
277 let schema = load_schema(path)?;
278 ensure_internal_tables(&inner)?;
279 reap_rotated_wal_segments(&inner);
280 Ok(Self {
281 inner,
282 schema,
283 root: path.to_path_buf(),
284 default_providers: HashMap::new(),
285 session: parking_lot::Mutex::new(None),
286 })
287 }
288
289 pub fn open_encrypted(path: &Path, passphrase: &str) -> Result<Self> {
291 let inner = Arc::new(CoreDatabase::open_encrypted(path, passphrase)?);
292 let schema = load_schema(path)?;
293 ensure_internal_tables(&inner)?;
294 reap_rotated_wal_segments(&inner);
295 Ok(Self {
296 inner,
297 schema,
298 root: path.to_path_buf(),
299 default_providers: HashMap::new(),
300 session: parking_lot::Mutex::new(None),
301 })
302 }
303
304 pub fn open_encrypted_with_options(
308 path: &Path,
309 passphrase: &str,
310 opts: OpenOptions,
311 ) -> Result<Self> {
312 let inner = Arc::new(open_core_with_retry(opts.lock_timeout_ms, || {
313 CoreDatabase::open_encrypted(path, passphrase)
314 })?);
315 let schema = load_schema(path)?;
316 ensure_internal_tables(&inner)?;
317 reap_rotated_wal_segments(&inner);
318 Ok(Self {
319 inner,
320 schema,
321 root: path.to_path_buf(),
322 default_providers: HashMap::new(),
323 session: parking_lot::Mutex::new(None),
324 })
325 }
326
327 pub fn create_encrypted(path: &Path, schema: KitSchema, passphrase: &str) -> Result<Self> {
331 std::fs::create_dir_all(path)?;
332 let inner = Arc::new(CoreDatabase::create_encrypted(path, passphrase)?);
333 ensure_internal_tables(&inner)?;
334 store_schema(path, &schema)?;
335 for table in &schema.tables {
336 create_core_table(&inner, &table.name, to_core_schema(table))?;
337 }
338 Ok(Self {
339 inner,
340 schema,
341 root: path.to_path_buf(),
342 default_providers: HashMap::new(),
343 session: parking_lot::Mutex::new(None),
344 })
345 }
346
347 pub fn create(path: &Path, schema: KitSchema) -> Result<Self> {
349 std::fs::create_dir_all(path)?;
350 let inner = Arc::new(CoreDatabase::create(path)?);
351
352 ensure_internal_tables(&inner)?;
355
356 store_schema(path, &schema)?;
359
360 for table in &schema.tables {
362 create_core_table(&inner, &table.name, to_core_schema(table))?;
363 }
364
365 Ok(Self {
366 inner,
367 schema,
368 root: path.to_path_buf(),
369 default_providers: HashMap::new(),
370 session: parking_lot::Mutex::new(None),
371 })
372 }
373
374 pub fn open_with_credentials(path: &Path, username: &str, password: &str) -> Result<Self> {
384 let inner = Arc::new(CoreDatabase::open_with_credentials(
385 path, username, password,
386 )?);
387 let schema = load_schema(path)?;
388 ensure_internal_tables(&inner)?;
389 reap_rotated_wal_segments(&inner);
390 Ok(Self {
391 inner,
392 schema,
393 root: path.to_path_buf(),
394 default_providers: HashMap::new(),
395 session: parking_lot::Mutex::new(None),
396 })
397 }
398
399 pub fn open_with_credentials_and_options(
403 path: &Path,
404 username: &str,
405 password: &str,
406 opts: OpenOptions,
407 ) -> Result<Self> {
408 let inner = Arc::new(open_core_with_retry(opts.lock_timeout_ms, || {
409 CoreDatabase::open_with_credentials(path, username, password)
410 })?);
411 let schema = load_schema(path)?;
412 ensure_internal_tables(&inner)?;
413 reap_rotated_wal_segments(&inner);
414 Ok(Self {
415 inner,
416 schema,
417 root: path.to_path_buf(),
418 default_providers: HashMap::new(),
419 session: parking_lot::Mutex::new(None),
420 })
421 }
422
423 pub fn create_with_credentials(
429 path: &Path,
430 schema: KitSchema,
431 admin_username: &str,
432 admin_password: &str,
433 ) -> Result<Self> {
434 std::fs::create_dir_all(path)?;
435 let inner = Arc::new(CoreDatabase::create_with_credentials(
436 path,
437 admin_username,
438 admin_password,
439 )?);
440 ensure_internal_tables(&inner)?;
441 store_schema(path, &schema)?;
442 for table in &schema.tables {
443 create_core_table(&inner, &table.name, to_core_schema(table))?;
444 }
445 Ok(Self {
446 inner,
447 schema,
448 root: path.to_path_buf(),
449 default_providers: HashMap::new(),
450 session: parking_lot::Mutex::new(None),
451 })
452 }
453
454 pub fn open_encrypted_with_credentials(
457 path: &Path,
458 passphrase: &str,
459 username: &str,
460 password: &str,
461 ) -> Result<Self> {
462 let inner = Arc::new(CoreDatabase::open_encrypted_with_credentials(
463 path, passphrase, username, password,
464 )?);
465 let schema = load_schema(path)?;
466 ensure_internal_tables(&inner)?;
467 reap_rotated_wal_segments(&inner);
468 Ok(Self {
469 inner,
470 schema,
471 root: path.to_path_buf(),
472 default_providers: HashMap::new(),
473 session: parking_lot::Mutex::new(None),
474 })
475 }
476
477 pub fn open_encrypted_with_credentials_and_options(
481 path: &Path,
482 passphrase: &str,
483 username: &str,
484 password: &str,
485 opts: OpenOptions,
486 ) -> Result<Self> {
487 let inner = Arc::new(open_core_with_retry(opts.lock_timeout_ms, || {
488 CoreDatabase::open_encrypted_with_credentials(path, passphrase, username, password)
489 })?);
490 let schema = load_schema(path)?;
491 ensure_internal_tables(&inner)?;
492 reap_rotated_wal_segments(&inner);
493 Ok(Self {
494 inner,
495 schema,
496 root: path.to_path_buf(),
497 default_providers: HashMap::new(),
498 session: parking_lot::Mutex::new(None),
499 })
500 }
501
502 pub fn create_encrypted_with_credentials(
506 path: &Path,
507 schema: KitSchema,
508 passphrase: &str,
509 admin_username: &str,
510 admin_password: &str,
511 ) -> Result<Self> {
512 std::fs::create_dir_all(path)?;
513 let inner = Arc::new(CoreDatabase::create_encrypted_with_credentials(
514 path,
515 passphrase,
516 admin_username,
517 admin_password,
518 )?);
519 ensure_internal_tables(&inner)?;
520 store_schema(path, &schema)?;
521 for table in &schema.tables {
522 create_core_table(&inner, &table.name, to_core_schema(table))?;
523 }
524 Ok(Self {
525 inner,
526 schema,
527 root: path.to_path_buf(),
528 default_providers: HashMap::new(),
529 session: parking_lot::Mutex::new(None),
530 })
531 }
532
533 pub fn enable_auth(&self, admin_username: &str, admin_password: &str) -> Result<()> {
537 self.inner
538 .enable_auth(admin_username, admin_password)
539 .map_err(KitError::from)
540 }
541
542 pub fn disable_auth(&self) -> Result<()> {
546 self.inner.disable_auth().map_err(KitError::from)
547 }
548
549 pub fn require_auth_enabled(&self) -> bool {
551 self.inner.require_auth_enabled()
552 }
553
554 pub fn refresh_principal(&self) -> Result<()> {
558 self.inner.refresh_principal().map_err(KitError::from)?;
559 *self.session.lock() = None;
562 Ok(())
563 }
564
565 pub fn register_default(
568 &mut self,
569 name: impl Into<String>,
570 provider: impl Fn() -> Value + Send + Sync + 'static,
571 ) {
572 self.default_providers
573 .insert(name.into(), Box::new(provider));
574 }
575
576 pub fn raw(&self) -> &CoreDatabase {
580 &self.inner
581 }
582
583 pub fn table_names(&self) -> Vec<String> {
585 self.schema
586 .tables
587 .iter()
588 .map(|t| t.name.clone())
589 .filter(|n| !n.starts_with("__kit_"))
590 .collect()
591 }
592
593 pub fn create_procedure(
594 &self,
595 spec: &ProcedureSpec,
596 ) -> Result<mongreldb_core::StoredProcedure> {
597 let procedure = core_procedure(spec)?;
598 self.inner
599 .create_procedure(procedure)
600 .map_err(KitError::from)
601 }
602
603 pub fn replace_procedure(
604 &self,
605 spec: &ProcedureSpec,
606 ) -> Result<mongreldb_core::StoredProcedure> {
607 let procedure = core_procedure(spec)?;
608 self.inner
609 .create_or_replace_procedure(procedure)
610 .map_err(KitError::from)
611 }
612
613 pub fn drop_procedure(&self, name: &str) -> Result<()> {
614 self.inner.drop_procedure(name).map_err(KitError::from)
615 }
616
617 pub fn call_procedure(
618 &self,
619 name: &str,
620 args: serde_json::Map<String, Value>,
621 ) -> Result<mongreldb_core::ProcedureCallResult> {
622 let args = args
623 .iter()
624 .map(|(key, value)| Ok((key.clone(), json_to_core_value(value)?)))
625 .collect::<Result<HashMap<_, _>>>()?;
626 self.inner
627 .call_procedure(name, args)
628 .map_err(KitError::from)
629 }
630
631 pub fn create_trigger(&self, spec: &TriggerSpec) -> Result<mongreldb_core::StoredTrigger> {
632 let trigger = core_trigger(spec)?;
633 self.inner.create_trigger(trigger).map_err(KitError::from)
634 }
635
636 pub fn replace_trigger(&self, spec: &TriggerSpec) -> Result<mongreldb_core::StoredTrigger> {
637 let trigger = core_trigger(spec)?;
638 self.inner
639 .create_or_replace_trigger(trigger)
640 .map_err(KitError::from)
641 }
642
643 pub fn drop_trigger(&self, name: &str) -> Result<()> {
644 self.inner.drop_trigger(name).map_err(KitError::from)
645 }
646
647 pub fn triggers(&self) -> Vec<mongreldb_core::StoredTrigger> {
648 self.inner.triggers()
649 }
650
651 pub fn trigger(&self, name: &str) -> Option<mongreldb_core::StoredTrigger> {
652 self.inner.trigger(name)
653 }
654
655 pub fn allocate_sequence(&self, name: &str, count: i64) -> Result<i64> {
661 use crate::internal::cols;
662 let mut attempt = 0;
663 loop {
664 let mut txn = self.inner.begin();
665 let snapshot = txn.read_snapshot();
666 let existing = self
667 .visible_core_rows_at(crate::internal::SEQUENCES, snapshot)?
668 .into_iter()
669 .find(|r| internal_bytes(r, cols::SEQ_NAME) == Some(name.to_string()));
670
671 let now = crate::internal::iso_now();
672 let (start, next, old_row_id) = match &existing {
676 Some(row) => {
677 let current = match row.columns.get(&cols::SEQ_NEXT) {
678 Some(CoreValue::Int64(i)) => *i,
679 _ => 1,
680 };
681 (current, current + count, Some(row.row_id))
682 }
683 None => (1, 1 + count, None),
684 };
685
686 if let Some(rid) = old_row_id {
687 txn.delete(crate::internal::SEQUENCES, rid)
688 .map_err(KitError::from)?;
689 }
690 txn.put(
691 crate::internal::SEQUENCES,
692 vec![
693 (cols::SEQ_NAME, CoreValue::Bytes(name.as_bytes().to_vec())),
694 (cols::SEQ_NEXT, CoreValue::Int64(next)),
695 (cols::SEQ_UPDATED, CoreValue::Bytes(now.into_bytes())),
696 ],
697 )
698 .map_err(KitError::from)?;
699 match txn.commit() {
700 Ok(_) => return Ok(start),
701 Err(mongreldb_core::MongrelError::Conflict(_)) if attempt < 10_000 => {
702 attempt += 1;
703 std::thread::yield_now();
704 continue;
705 }
706 Err(e) => return Err(KitError::from(e)),
707 }
708 }
709 }
710
711 pub fn transaction<T, F>(&self, max_retries: usize, mut f: F) -> Result<T>
714 where
715 F: FnMut(&mut crate::txn::Transaction<'_>) -> Result<T>,
716 {
717 let mut attempt = 0;
718 loop {
719 let mut txn = self.begin()?;
720 match f(&mut txn) {
721 Ok(value) => match txn.commit() {
722 Ok(()) => return Ok(value),
723 Err(KitError::Conflict(_)) if attempt < max_retries => {
724 attempt += 1;
725 continue;
726 }
727 Err(e) => return Err(e),
728 },
729 Err(KitError::Conflict(_)) if attempt < max_retries => {
730 txn.rollback();
731 attempt += 1;
732 continue;
733 }
734 Err(e) => {
735 txn.rollback();
736 return Err(e);
737 }
738 }
739 }
740 }
741
742 pub fn table(&self, name: &str) -> Option<&KitTable> {
744 self.schema.table(name)
745 }
746
747 pub fn schema(&self) -> &KitSchema {
749 &self.schema
750 }
751
752 pub fn begin(&self) -> Result<crate::txn::Transaction<'_>> {
754 let core_txn = self.inner.begin();
755 Ok(crate::txn::Transaction::new(self, core_txn))
756 }
757
758 pub fn set_schema(&mut self, schema: KitSchema) {
760 self.schema = schema;
761 }
762
763 pub fn check_internal_tables(&self) -> Result<()> {
766 let schema_file = self.root.join(SCHEMA_FILE);
767 if !schema_file.exists() {
768 return Err(KitError::Integrity(format!(
769 "schema file {} is missing",
770 schema_file.display()
771 )));
772 }
773 for (name, _) in internal_tables_core() {
774 if self.inner.table_id(name).is_err() {
775 return Err(KitError::Integrity(format!(
776 "internal table {name} is missing"
777 )));
778 }
779 }
780 Ok(())
781 }
782
783 pub fn gc(&self) -> Result<usize> {
786 self.inner.gc().map_err(KitError::from)
787 }
788
789 pub fn check(&self) -> Vec<serde_json::Value> {
792 self.inner
793 .check()
794 .into_iter()
795 .map(|i| {
796 serde_json::json!({
797 "table_id": i.table_id,
798 "table_name": i.table_name,
799 "severity": i.severity,
800 "description": i.description,
801 })
802 })
803 .collect()
804 }
805
806 pub fn doctor(&self) -> Result<Vec<u64>> {
808 self.inner.doctor().map_err(KitError::from)
809 }
810
811 pub fn snapshot_epoch(&self) -> u64 {
815 self.inner.snapshot().0.epoch.0
816 }
817
818 pub fn export_tsv(&self, table: &str) -> Result<String> {
822 let t = self
823 .schema
824 .tables
825 .iter()
826 .find(|t| t.name == table)
827 .ok_or_else(|| KitError::Validation(format!("unknown table '{table}'")))?
828 .clone();
829 let tx = self.begin()?;
830 let rows = tx.all_rows(table)?;
831 Ok(crate::tsv::rows_to_tsv(&t, &rows))
832 }
833
834 pub fn import_tsv(&self, table: &str, text: &str) -> Result<usize> {
838 let t = self
839 .schema
840 .tables
841 .iter()
842 .find(|t| t.name == table)
843 .ok_or_else(|| KitError::Validation(format!("unknown table '{table}'")))?
844 .clone();
845 let rows = crate::tsv::tsv_to_rows(&t, text)?;
846 let n = rows.len();
847 self.transaction(1, |tx| {
848 tx.insert_many(table, rows.clone())?;
849 Ok(())
850 })?;
851 Ok(n)
852 }
853
854 pub fn explain(
859 &self,
860 table: &str,
861 predicate: &mongreldb_kit_core::query::Expr,
862 ) -> Result<ExplainPlan> {
863 let t = self
864 .schema
865 .tables
866 .iter()
867 .find(|t| t.name == table)
868 .ok_or_else(|| KitError::Validation(format!("unknown table '{table}'")))?;
869 Ok(match crate::pushdown::translate_predicate(t, predicate) {
870 Some(p) => ExplainPlan {
871 index_accelerated: p.can_push(),
872 exact: p.fully_translated,
873 pushed_conditions: p.conditions.iter().map(condition_label).collect(),
874 },
875 None => ExplainPlan {
876 index_accelerated: false,
877 exact: false,
878 pushed_conditions: Vec::new(),
879 },
880 })
881 }
882
883 pub fn rows_at_epoch(&self, table: &str, epoch: u64) -> Result<Vec<crate::schema::Row>> {
889 let t = self
890 .schema
891 .tables
892 .iter()
893 .find(|t| t.name == table)
894 .ok_or_else(|| KitError::Validation(format!("unknown table '{table}'")))?;
895 let current = self.snapshot_epoch();
896 if epoch > current {
897 return Err(KitError::Validation(format!(
898 "epoch {epoch} is in the future (current committed epoch is {current})"
899 )));
900 }
901 let snap = Snapshot::at(mongreldb_core::epoch::Epoch(epoch));
902 let rows = self.visible_core_rows_at(table, snap)?;
903 rows.iter()
904 .map(|r| crate::schema::core_row_to_json(r, t))
905 .collect()
906 }
907
908 pub fn approx_aggregate(
914 &self,
915 table: &str,
916 column: Option<&str>,
917 agg: ApproxAggKind,
918 z: f64,
919 ) -> Result<Option<ApproxAggregate>> {
920 let t = self
921 .schema
922 .tables
923 .iter()
924 .find(|t| t.name == table)
925 .ok_or_else(|| KitError::Validation(format!("unknown table '{table}'")))?;
926 if matches!(agg, ApproxAggKind::Sum | ApproxAggKind::Avg) && column.is_none() {
927 return Err(KitError::Validation(
928 "approx sum/avg requires a column".into(),
929 ));
930 }
931 let cid = match column {
932 Some(name) => Some(
933 t.columns
934 .iter()
935 .find(|c| c.name == name)
936 .ok_or_else(|| KitError::Validation(format!("unknown column '{name}'")))?
937 .id as u16,
938 ),
939 None => None,
940 };
941 let core_agg = match agg {
942 ApproxAggKind::Count => ApproxAgg::Count,
943 ApproxAggKind::Sum => ApproxAgg::Sum,
944 ApproxAggKind::Avg => ApproxAgg::Avg,
945 };
946 let handle = self.inner.table(table).map_err(KitError::from)?;
947 let mut guard = handle.lock();
948 let res = guard
949 .approx_aggregate(&[], cid, core_agg, z)
950 .map_err(KitError::from)?;
951 Ok(res.map(|r| ApproxAggregate {
952 point: r.point,
953 ci_low: r.ci_low,
954 ci_high: r.ci_high,
955 n_population: r.n_population,
956 n_sample_live: r.n_sample_live,
957 n_passing: r.n_passing,
958 }))
959 }
960
961 pub fn scan_batched<F>(&self, table: &str, batch_size: usize, mut f: F) -> Result<()>
967 where
968 F: FnMut(&[serde_json::Map<String, Value>]) -> Result<()>,
969 {
970 let kit_t = self
971 .schema
972 .tables
973 .iter()
974 .find(|t| t.name == table)
975 .ok_or_else(|| KitError::Validation(format!("unknown table '{table}'")))?;
976 let batch_size = batch_size.max(1);
977 let (snapshot, _pin) = self.inner.snapshot();
980 let handle = self.inner.table(table).map_err(KitError::from)?;
981 let guard = handle.lock();
982
983 let mut projection: Vec<(u16, mongreldb_core::schema::TypeId)> = Vec::new();
985 let mut meta: Vec<(String, mongreldb_kit_core::schema::ColumnType)> = Vec::new();
986 for c in &guard.schema().columns {
987 if let Some(kc) = kit_t.columns.iter().find(|kc| kc.id as u16 == c.id) {
988 projection.push((c.id, c.ty.clone()));
989 meta.push((kc.name.clone(), kc.storage_type));
990 }
991 }
992
993 match guard
994 .scan_cursor(snapshot, projection, &[])
995 .map_err(KitError::from)?
996 {
997 Some(mut cursor) => {
998 let mut buf: Vec<serde_json::Map<String, Value>> = Vec::with_capacity(batch_size);
999 while let Some(batch) = cursor.next_batch().map_err(KitError::from)? {
1000 let nrows = batch.first().map(|c| c.len()).unwrap_or(0);
1001 for j in 0..nrows {
1002 let mut m = serde_json::Map::new();
1003 for (ci, (name, ty)) in meta.iter().enumerate() {
1004 let cv = batch
1005 .get(ci)
1006 .and_then(|col| col.value_at(j))
1007 .unwrap_or(CoreValue::Null);
1008 m.insert(name.clone(), crate::schema::core_to_json(&cv, *ty)?);
1009 }
1010 buf.push(m);
1011 if buf.len() >= batch_size {
1012 f(&buf)?;
1013 buf.clear();
1014 }
1015 }
1016 }
1017 if !buf.is_empty() {
1018 f(&buf)?;
1019 }
1020 Ok(())
1021 }
1022 None => {
1023 drop(guard);
1024 let rows = self.visible_core_rows_at(table, snapshot)?;
1025 let maps: Vec<serde_json::Map<String, Value>> = rows
1026 .iter()
1027 .map(|r| crate::schema::core_row_to_json(r, kit_t).map(|row| row.values))
1028 .collect::<Result<Vec<_>>>()?;
1029 for chunk in maps.chunks(batch_size) {
1030 f(chunk)?;
1031 }
1032 Ok(())
1033 }
1034 }
1035 }
1036
1037 pub fn set_similarity(
1046 &self,
1047 table: &str,
1048 column: &str,
1049 query: &[String],
1050 k: usize,
1051 ) -> Result<Vec<SimilarRow>> {
1052 let t = self
1053 .schema
1054 .tables
1055 .iter()
1056 .find(|t| t.name == table)
1057 .ok_or_else(|| KitError::Validation(format!("unknown table '{table}'")))?;
1058 let col = t.columns.iter().find(|c| c.name == column).ok_or_else(|| {
1059 KitError::Validation(format!("unknown column '{column}' on table '{table}'"))
1060 })?;
1061 let query_set: std::collections::HashSet<String> = query.iter().cloned().collect();
1062
1063 let has_minhash = t.indexes.iter().any(|idx| {
1064 idx.kind == KitIndexKind::MinHash && idx.columns.iter().any(|c| c == column)
1065 });
1066 let rows = if has_minhash {
1067 let query_hashes: Vec<u64> = query
1069 .iter()
1070 .map(|s| mongreldb_core::index::minhash_token_hash(s))
1071 .collect();
1072 let cand_k = k.saturating_mul(8).max(k + 64);
1074 let cond = mongreldb_core::query::Condition::MinHashSimilar {
1075 column_id: col.id as u16,
1076 query: query_hashes,
1077 k: cand_k,
1078 };
1079 let (snapshot, _pin) = self.inner.snapshot();
1080 let core_rows = self.query_core_rows_at(table, &[cond], snapshot)?;
1081 core_rows
1082 .iter()
1083 .map(|r| crate::schema::core_row_to_json(r, t))
1084 .collect::<Result<Vec<_>>>()?
1085 } else {
1086 let tx = self.begin()?;
1087 tx.all_rows(table)?
1088 };
1089
1090 let mut scored: Vec<SimilarRow> = Vec::new();
1091 for row in rows {
1092 let set = parse_string_set(row.values.get(column));
1093 let inter = set.iter().filter(|x| query_set.contains(*x)).count();
1094 let union = set.len() + query_set.len() - inter;
1095 let sim = if union == 0 {
1096 0.0
1097 } else {
1098 inter as f64 / union as f64
1099 };
1100 if sim > 0.0 {
1101 scored.push(SimilarRow {
1102 row,
1103 similarity: sim,
1104 });
1105 }
1106 }
1107 scored.sort_by(|a, b| {
1108 b.similarity
1109 .partial_cmp(&a.similarity)
1110 .unwrap_or(std::cmp::Ordering::Equal)
1111 });
1112 scored.truncate(k);
1113 Ok(scored)
1114 }
1115
1116 pub fn flush(&self) -> Result<()> {
1120 for name in self.inner.table_names() {
1121 let handle = self.inner.table(&name).map_err(KitError::from)?;
1122 let mut guard = handle.lock();
1123 guard.flush().map_err(KitError::from)?;
1124 }
1125 Ok(())
1126 }
1127
1128 pub fn incremental_aggregate(
1140 &self,
1141 table: &str,
1142 column: Option<&str>,
1143 agg: IncrementalAggKind,
1144 filter: Option<&mongreldb_kit_core::query::Expr>,
1145 ) -> Result<IncrementalAggregate> {
1146 let t = self
1147 .schema
1148 .tables
1149 .iter()
1150 .find(|t| t.name == table)
1151 .ok_or_else(|| KitError::Validation(format!("unknown table '{table}'")))?;
1152 if !matches!(agg, IncrementalAggKind::Count) && column.is_none() {
1153 return Err(KitError::Validation(
1154 "sum/min/max/avg incremental aggregate requires a column".into(),
1155 ));
1156 }
1157 let cid = match column {
1158 Some(name) => Some(
1159 t.columns
1160 .iter()
1161 .find(|c| c.name == name)
1162 .ok_or_else(|| KitError::Validation(format!("unknown column '{name}'")))?
1163 .id as u16,
1164 ),
1165 None => None,
1166 };
1167 let conditions = match filter {
1168 Some(expr) => {
1169 let plan = crate::pushdown::translate_predicate(t, expr).ok_or_else(|| {
1170 KitError::Validation(
1171 "filter is not index-translatable for an incremental aggregate".into(),
1172 )
1173 })?;
1174 if !plan.fully_translated {
1175 return Err(KitError::Validation(
1176 "filter has a residual that an incremental aggregate cannot apply exactly"
1177 .into(),
1178 ));
1179 }
1180 plan.conditions
1181 }
1182 None => Vec::new(),
1183 };
1184 let core_agg = match agg {
1185 IncrementalAggKind::Count => NativeAgg::Count,
1186 IncrementalAggKind::Sum => NativeAgg::Sum,
1187 IncrementalAggKind::Min => NativeAgg::Min,
1188 IncrementalAggKind::Max => NativeAgg::Max,
1189 IncrementalAggKind::Avg => NativeAgg::Avg,
1190 };
1191 let cache_key = incremental_cache_key(t.id, cid, agg, &conditions);
1192 let handle = self.inner.table(table).map_err(KitError::from)?;
1193 let mut guard = handle.lock();
1194 let res = guard
1195 .aggregate_incremental(cache_key, &conditions, cid, core_agg)
1196 .map_err(KitError::from)?;
1197 Ok(IncrementalAggregate {
1198 value: agg_state_value(&res.state),
1199 incremental: res.incremental,
1200 delta_rows: res.delta_rows,
1201 })
1202 }
1203
1204 pub fn applied_migrations(&self) -> Result<Vec<mongreldb_kit_core::migrations::Migration>> {
1206 crate::migrate::load_applied_migrations(&self.inner)
1207 }
1208
1209 pub(crate) fn core_db(&self) -> &CoreDatabase {
1210 &self.inner
1211 }
1212
1213 pub(crate) fn core_arc(&self) -> Arc<CoreDatabase> {
1216 Arc::clone(&self.inner)
1217 }
1218
1219 pub fn close(&self) -> Result<()> {
1224 self.inner.close().map_err(KitError::from)
1225 }
1226
1227 pub fn compact_all(&self) -> Result<(usize, usize)> {
1232 self.inner.compact().map_err(KitError::from)
1233 }
1234
1235 pub fn compact_table(&self, name: &str) -> Result<bool> {
1238 self.inner.compact_table(name).map_err(KitError::from)
1239 }
1240
1241 pub fn rename_table(&mut self, from: &str, to: &str) -> Result<()> {
1251 if from.starts_with("__kit_") || to.starts_with("__kit_") {
1252 return Err(KitError::Validation(
1253 "rename_table: names beginning with '__kit_' are reserved for internal tables"
1254 .into(),
1255 ));
1256 }
1257 self.inner.rename_table(from, to).map_err(KitError::from)?;
1258 if !self.schema.rename_table(from, to) {
1261 return Err(KitError::Integrity(format!(
1264 "rename_table: kit schema has no table '{from}' (or '{to}' already exists)"
1265 )));
1266 }
1267 for table in &mut self.schema.tables {
1268 for fk in &mut table.foreign_keys {
1269 if fk.references_table == from {
1270 fk.references_table = to.to_string();
1271 }
1272 }
1273 }
1274 store_schema(&self.root, &self.schema)?;
1275 Ok(())
1276 }
1277
1278 pub fn analyze(&self) -> Result<()> {
1283 for name in self.inner.table_names() {
1284 let handle = self.inner.table(&name).map_err(KitError::from)?;
1285 handle.lock().ensure_indexes_complete()?;
1286 }
1287 Ok(())
1288 }
1289
1290 pub fn vacuum(&self) -> Result<usize> {
1294 self.inner.compact().map_err(KitError::from)?;
1295 self.inner.gc().map_err(KitError::from)
1296 }
1297
1298 pub fn create_view(&self, spec: &ViewSpec) -> Result<()> {
1304 self.sql(&spec.create_sql())?;
1305 Ok(())
1306 }
1307
1308 pub fn drop_view(&self, name: &str) -> Result<()> {
1310 self.sql(&format!("DROP VIEW IF EXISTS {name}"))?;
1311 Ok(())
1312 }
1313
1314 pub fn reserve_auto_inc(&self, table: &str) -> Result<Option<i64>> {
1322 let handle = self.inner.table(table).map_err(KitError::from)?;
1323 let mut guard = handle.lock();
1324 guard.reserve_auto_inc().map_err(KitError::from)
1325 }
1326
1327 pub fn create_user(&self, username: &str, password: &str) -> Result<()> {
1331 self.inner
1332 .create_user(username, password)
1333 .map_err(KitError::from)?;
1334 Ok(())
1335 }
1336
1337 pub fn drop_user(&self, username: &str) -> Result<()> {
1339 self.inner.drop_user(username).map_err(KitError::from)
1340 }
1341
1342 pub fn alter_user_password(&self, username: &str, new_password: &str) -> Result<()> {
1344 self.inner
1345 .alter_user_password(username, new_password)
1346 .map_err(KitError::from)
1347 }
1348
1349 pub fn verify_user(
1351 &self,
1352 username: &str,
1353 password: &str,
1354 ) -> Result<Option<mongreldb_core::auth::UserEntry>> {
1355 self.inner
1356 .verify_user(username, password)
1357 .map_err(KitError::from)
1358 }
1359
1360 pub fn set_user_admin(&self, username: &str, is_admin: bool) -> Result<()> {
1362 self.inner
1363 .set_user_admin(username, is_admin)
1364 .map_err(KitError::from)
1365 }
1366
1367 pub fn users(&self) -> Vec<String> {
1369 self.inner.users().into_iter().map(|u| u.username).collect()
1370 }
1371
1372 pub fn create_role(&self, name: &str) -> Result<()> {
1374 self.inner.create_role(name).map_err(KitError::from)?;
1375 Ok(())
1376 }
1377
1378 pub fn drop_role(&self, name: &str) -> Result<()> {
1380 self.inner.drop_role(name).map_err(KitError::from)
1381 }
1382
1383 pub fn roles(&self) -> Vec<String> {
1385 self.inner.roles().into_iter().map(|r| r.name).collect()
1386 }
1387
1388 pub fn grant_role(&self, username: &str, role_name: &str) -> Result<()> {
1390 self.inner
1391 .grant_role(username, role_name)
1392 .map_err(KitError::from)
1393 }
1394
1395 pub fn revoke_role(&self, username: &str, role_name: &str) -> Result<()> {
1397 self.inner
1398 .revoke_role(username, role_name)
1399 .map_err(KitError::from)
1400 }
1401
1402 pub fn grant_permission(
1404 &self,
1405 role_name: &str,
1406 permission: mongreldb_core::auth::Permission,
1407 ) -> Result<()> {
1408 self.inner
1409 .grant_permission(role_name, permission)
1410 .map_err(KitError::from)
1411 }
1412
1413 pub fn revoke_permission(
1415 &self,
1416 role_name: &str,
1417 permission: mongreldb_core::auth::Permission,
1418 ) -> Result<()> {
1419 self.inner
1420 .revoke_permission(role_name, permission)
1421 .map_err(KitError::from)
1422 }
1423
1424 pub fn set_spill_threshold(&self, bytes: u64) {
1430 self.inner.set_spill_threshold(bytes);
1431 }
1432
1433 pub fn set_recursive_triggers(&self, enabled: bool) {
1435 self.inner.set_recursive_triggers(enabled);
1436 }
1437
1438 pub fn trigger_config(&self) -> mongreldb_core::TriggerConfig {
1440 self.inner.trigger_config()
1441 }
1442
1443 pub fn set_trigger_config(&self, config: mongreldb_core::TriggerConfig) -> Result<()> {
1445 self.inner
1446 .set_trigger_config(config)
1447 .map_err(KitError::from)
1448 }
1449
1450 pub fn set_table_compaction_zstd_level(&self, table: &str, level: i32) -> Result<()> {
1452 let handle = self.inner.table(table).map_err(KitError::from)?;
1453 handle.lock().set_compaction_zstd_level(level);
1454 Ok(())
1455 }
1456
1457 pub fn set_table_result_cache_max_bytes(&self, table: &str, max_bytes: u64) -> Result<()> {
1459 let handle = self.inner.table(table).map_err(KitError::from)?;
1460 handle.lock().set_result_cache_max_bytes(max_bytes);
1461 Ok(())
1462 }
1463
1464 pub fn set_table_mutable_run_spill_bytes(&self, table: &str, bytes: u64) -> Result<()> {
1466 let handle = self.inner.table(table).map_err(KitError::from)?;
1467 handle.lock().set_mutable_run_spill_bytes(bytes);
1468 Ok(())
1469 }
1470
1471 pub fn set_table_sync_byte_threshold(&self, table: &str, threshold: u64) -> Result<()> {
1473 let handle = self.inner.table(table).map_err(KitError::from)?;
1474 handle.lock().set_sync_byte_threshold(threshold);
1475 Ok(())
1476 }
1477
1478 pub fn set_table_index_build_policy(
1481 &self,
1482 table: &str,
1483 policy: mongreldb_core::IndexBuildPolicy,
1484 ) -> Result<()> {
1485 let handle = self.inner.table(table).map_err(KitError::from)?;
1486 handle.lock().set_index_build_policy(policy);
1487 Ok(())
1488 }
1489
1490 pub fn table_page_cache_stats(&self, table: &str) -> Result<mongreldb_core::cache::CacheStats> {
1492 let handle = self.inner.table(table).map_err(KitError::from)?;
1493 let stats = handle.lock().page_cache_stats();
1494 Ok(stats)
1495 }
1496
1497 pub fn table_run_count(&self, table: &str) -> Result<usize> {
1499 let handle = self.inner.table(table).map_err(KitError::from)?;
1500 let n = handle.lock().run_count();
1501 Ok(n)
1502 }
1503
1504 pub fn table_memtable_len(&self, table: &str) -> Result<usize> {
1506 let handle = self.inner.table(table).map_err(KitError::from)?;
1507 let n = handle.lock().memtable_len();
1508 Ok(n)
1509 }
1510
1511 pub fn table_mutable_run_len(&self, table: &str) -> Result<usize> {
1513 let handle = self.inner.table(table).map_err(KitError::from)?;
1514 let n = handle.lock().mutable_run_len();
1515 Ok(n)
1516 }
1517
1518 pub fn table_page_cache_len(&self, table: &str) -> Result<usize> {
1520 let handle = self.inner.table(table).map_err(KitError::from)?;
1521 let n = handle.lock().page_cache_len();
1522 Ok(n)
1523 }
1524
1525 pub fn table_decoded_cache_len(&self, table: &str) -> Result<usize> {
1527 let handle = self.inner.table(table).map_err(KitError::from)?;
1528 let n = handle.lock().decoded_cache_len();
1529 Ok(n)
1530 }
1531
1532 pub fn sql(&self, statement: &str) -> Result<Vec<arrow::record_batch::RecordBatch>> {
1550 let session = match self.session.lock().take() {
1551 Some(s) => s,
1552 None => {
1553 mongreldb_query::MongrelSession::open(self.core_arc()).map_err(KitError::from)?
1554 }
1555 };
1556 let runtime = sql_runtime();
1557 let result = runtime
1558 .block_on(session.run(statement))
1559 .map_err(KitError::from);
1560 *self.session.lock() = Some(session);
1562 result
1563 }
1564
1565 pub fn refresh_sql_session(&self) -> Result<()> {
1570 let session =
1571 mongreldb_query::MongrelSession::open(self.core_arc()).map_err(KitError::from)?;
1572 *self.session.lock() = Some(session);
1573 Ok(())
1574 }
1575
1576 pub fn sql_arrow(&self, statement: &str) -> Result<Vec<u8>> {
1582 let batches = self.sql(statement)?;
1583 crate::arrow_util::batches_to_ipc(&batches)
1584 }
1585
1586 pub fn sql_rows(&self, statement: &str) -> Result<Vec<serde_json::Map<String, Value>>> {
1590 let batches = self.sql(statement)?;
1591 crate::arrow_util::batches_to_rows(&batches)
1592 }
1593
1594 pub(crate) fn lookup_row_id(&self, table: &str, key: &[u8]) -> Result<Option<RowId>> {
1598 let handle = self.inner.table(table).map_err(KitError::from)?;
1599 let mut guard = handle.lock();
1600 guard.ensure_indexes_complete()?;
1601 Ok(guard.lookup_pk(key))
1602 }
1603
1604 pub(crate) fn root(&self) -> &Path {
1605 &self.root
1606 }
1607
1608 pub(crate) fn visible_core_rows_at(
1612 &self,
1613 table_name: &str,
1614 snapshot: Snapshot,
1615 ) -> Result<Vec<CoreRow>> {
1616 let handle = self.inner.table(table_name).map_err(KitError::from)?;
1617 let guard = handle.lock();
1618 guard.visible_rows(snapshot).map_err(KitError::from)
1619 }
1620
1621 pub(crate) fn query_core_rows_at(
1628 &self,
1629 table_name: &str,
1630 conditions: &[mongreldb_core::query::Condition],
1631 snapshot: Snapshot,
1632 ) -> Result<Vec<CoreRow>> {
1633 if conditions.is_empty() {
1634 return self.visible_core_rows_at(table_name, snapshot);
1635 }
1636 let handle = self.inner.table(table_name).map_err(KitError::from)?;
1637 let mut guard = handle.lock();
1638 let q = mongreldb_core::query::Query {
1639 conditions: conditions.to_vec(),
1640 };
1641 guard.query(&q).map_err(KitError::from)
1642 }
1643
1644 pub(crate) fn flush_table(&self, table_name: &str) -> Result<()> {
1652 let handle = self.inner.table(table_name).map_err(KitError::from)?;
1653 handle.lock().flush().map_err(KitError::from)?;
1654 Ok(())
1655 }
1656
1657 pub(crate) fn count_core_rows_at(
1667 &self,
1668 table_name: &str,
1669 conditions: &[mongreldb_core::query::Condition],
1670 snapshot: Snapshot,
1671 ) -> Result<Option<u64>> {
1672 let handle = self.inner.table(table_name).map_err(KitError::from)?;
1673 let mut guard = handle.lock();
1674 if guard.snapshot().epoch != snapshot.epoch {
1675 return Ok(None); }
1677 guard
1678 .count_conditions(conditions, snapshot)
1679 .map_err(KitError::from)
1680 }
1681
1682 pub(crate) fn aggregate_core_at(
1691 &self,
1692 table_name: &str,
1693 column: Option<u16>,
1694 conditions: &[mongreldb_core::query::Condition],
1695 agg: NativeAgg,
1696 snapshot: Snapshot,
1697 ) -> Result<Option<NativeAggResult>> {
1698 let handle = self.inner.table(table_name).map_err(KitError::from)?;
1699 let guard = handle.lock();
1700 if guard.snapshot().epoch != snapshot.epoch {
1701 return Ok(None); }
1703 guard
1704 .aggregate_native(snapshot, column, conditions, agg)
1705 .map_err(KitError::from)
1706 }
1707
1708 pub(crate) fn count_distinct_core_at(
1717 &self,
1718 table_name: &str,
1719 column_id: u16,
1720 snapshot: Snapshot,
1721 ) -> Result<Option<u64>> {
1722 let handle = self.inner.table(table_name).map_err(KitError::from)?;
1723 let mut guard = handle.lock();
1724 if guard.snapshot().epoch != snapshot.epoch {
1725 return Ok(None); }
1727 guard
1728 .count_distinct_from_bitmap(column_id)
1729 .map_err(KitError::from)
1730 }
1731
1732 #[allow(dead_code)]
1734 pub(crate) fn get_core_row(&self, table_name: &str, row_id: u64) -> Result<Option<CoreRow>> {
1735 let handle = self.inner.table(table_name).map_err(KitError::from)?;
1736 let guard = handle.lock();
1737 let snapshot = guard.snapshot();
1738 Ok(guard.get(mongreldb_core::RowId(row_id), snapshot))
1739 }
1740}
1741
1742pub(crate) fn create_core_table(db: &CoreDatabase, name: &str, schema: CoreSchema) -> Result<()> {
1743 if db.table_id(name).is_ok() {
1744 return Ok(());
1745 }
1746 db.create_table(name, schema).map_err(KitError::from)?;
1747 Ok(())
1748}
1749
1750fn sql_runtime() -> &'static tokio::runtime::Runtime {
1755 use std::sync::OnceLock;
1756 static RT: OnceLock<tokio::runtime::Runtime> = OnceLock::new();
1757 RT.get_or_init(|| {
1758 tokio::runtime::Builder::new_current_thread()
1759 .enable_all()
1760 .build()
1761 .expect("failed to build kit SQL tokio runtime")
1762 })
1763}
1764
1765fn core_procedure(spec: &ProcedureSpec) -> Result<mongreldb_core::StoredProcedure> {
1766 let parsed: mongreldb_core::StoredProcedure =
1767 serde_json::from_value(spec.json.clone()).map_err(KitError::from)?;
1768 mongreldb_core::StoredProcedure::new(parsed.name, parsed.mode, parsed.params, parsed.body, 0)
1769 .map_err(KitError::from)
1770}
1771
1772fn core_trigger(spec: &TriggerSpec) -> Result<mongreldb_core::StoredTrigger> {
1773 let parsed: mongreldb_core::StoredTrigger =
1774 serde_json::from_value(spec.json.clone()).map_err(KitError::from)?;
1775 mongreldb_core::StoredTrigger::new(
1776 parsed.name,
1777 mongreldb_core::TriggerDefinition {
1778 target: parsed.target,
1779 timing: parsed.timing,
1780 event: parsed.event,
1781 update_of: parsed.update_of,
1782 target_columns: parsed.target_columns,
1783 when: parsed.when,
1784 program: parsed.program,
1785 },
1786 0,
1787 )
1788 .map_err(KitError::from)
1789}
1790
1791fn json_to_core_value(value: &Value) -> Result<CoreValue> {
1792 match value {
1793 Value::Null => Ok(CoreValue::Null),
1794 Value::Bool(value) => Ok(CoreValue::Bool(*value)),
1795 Value::Number(value) => {
1796 if let Some(value) = value.as_i64() {
1797 Ok(CoreValue::Int64(value))
1798 } else if let Some(value) = value.as_f64() {
1799 Ok(CoreValue::Float64(value))
1800 } else {
1801 Err(KitError::Validation("unsupported JSON number".into()))
1802 }
1803 }
1804 Value::String(value) => Ok(CoreValue::Bytes(value.as_bytes().to_vec())),
1805 Value::Array(_) | Value::Object(_) => Err(KitError::Validation(
1806 "procedure args only support scalar JSON values".into(),
1807 )),
1808 }
1809}
1810
1811pub(crate) fn internal_bytes(row: &CoreRow, col_id: u16) -> Option<String> {
1813 match row.columns.get(&col_id) {
1814 Some(CoreValue::Bytes(b)) => String::from_utf8(b.clone()).ok(),
1815 _ => None,
1816 }
1817}
1818
1819fn reap_rotated_wal_segments(db: &CoreDatabase) {
1834 let _ = db.gc();
1835}
1836
1837pub(crate) fn load_schema(path: &Path) -> Result<KitSchema> {
1838 let file = path.join(SCHEMA_FILE);
1839 let json = std::fs::read_to_string(&file)
1840 .map_err(|e| KitError::Migration(format!("cannot read schema file: {e}")))?;
1841 let schema: KitSchema = serde_json::from_str(&json)?;
1842 Ok(schema)
1843}
1844
1845pub(crate) fn store_schema(path: &Path, schema: &KitSchema) -> Result<()> {
1846 let file = path.join(SCHEMA_FILE);
1847 let json = serde_json::to_string_pretty(schema)?;
1848 std::fs::write(&file, json)?;
1849 Ok(())
1850}
1851
1852pub(crate) fn persist_schema(db: &Database, schema: &KitSchema) -> Result<()> {
1854 store_schema(&db.root, schema)
1855}
1856
1857#[cfg(test)]
1858mod tests {
1859 use super::open_core_with_retry;
1860
1861 fn lock_error() -> mongreldb_core::MongrelError {
1862 mongreldb_core::MongrelError::Io(std::io::Error::other(
1863 "database at /tmp/db is locked by another process: would block",
1864 ))
1865 }
1866
1867 #[test]
1868 fn open_retry_waits_for_lock_contention_only() {
1869 let mut calls = 0;
1870 let value = open_core_with_retry(50, || {
1871 calls += 1;
1872 if calls < 3 {
1873 Err(lock_error())
1874 } else {
1875 Ok(7)
1876 }
1877 })
1878 .unwrap();
1879 assert_eq!(value, 7);
1880 assert_eq!(calls, 3);
1881
1882 let mut non_lock_calls = 0;
1883 let err: mongreldb_core::Result<()> = open_core_with_retry(50, || {
1884 non_lock_calls += 1;
1885 Err(mongreldb_core::MongrelError::Other("nope".into()))
1886 });
1887 let err = err.unwrap_err();
1888 assert_eq!(non_lock_calls, 1);
1889 assert!(matches!(err, mongreldb_core::MongrelError::Other(_)));
1890 }
1891}