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 set_history_retention_epochs(&self, epochs: u64) -> Result<()> {
819 self.inner
820 .set_history_retention_epochs(epochs)
821 .map_err(KitError::from)
822 }
823
824 pub fn history_retention_epochs(&self) -> u64 {
825 self.inner.history_retention_epochs()
826 }
827
828 pub fn earliest_retained_epoch(&self) -> u64 {
829 self.inner.earliest_retained_epoch().0
830 }
831
832 pub fn export_tsv(&self, table: &str) -> Result<String> {
836 let t = self
837 .schema
838 .tables
839 .iter()
840 .find(|t| t.name == table)
841 .ok_or_else(|| KitError::Validation(format!("unknown table '{table}'")))?
842 .clone();
843 let tx = self.begin()?;
844 let rows = tx.all_rows(table)?;
845 Ok(crate::tsv::rows_to_tsv(&t, &rows))
846 }
847
848 pub fn import_tsv(&self, table: &str, text: &str) -> Result<usize> {
852 let t = self
853 .schema
854 .tables
855 .iter()
856 .find(|t| t.name == table)
857 .ok_or_else(|| KitError::Validation(format!("unknown table '{table}'")))?
858 .clone();
859 let rows = crate::tsv::tsv_to_rows(&t, text)?;
860 let n = rows.len();
861 self.transaction(1, |tx| {
862 tx.insert_many(table, rows.clone())?;
863 Ok(())
864 })?;
865 Ok(n)
866 }
867
868 pub fn explain(
873 &self,
874 table: &str,
875 predicate: &mongreldb_kit_core::query::Expr,
876 ) -> Result<ExplainPlan> {
877 let t = self
878 .schema
879 .tables
880 .iter()
881 .find(|t| t.name == table)
882 .ok_or_else(|| KitError::Validation(format!("unknown table '{table}'")))?;
883 Ok(match crate::pushdown::translate_predicate(t, predicate) {
884 Some(p) => ExplainPlan {
885 index_accelerated: p.can_push(),
886 exact: p.fully_translated,
887 pushed_conditions: p.conditions.iter().map(condition_label).collect(),
888 },
889 None => ExplainPlan {
890 index_accelerated: false,
891 exact: false,
892 pushed_conditions: Vec::new(),
893 },
894 })
895 }
896
897 pub fn rows_at_epoch(&self, table: &str, epoch: u64) -> Result<Vec<crate::schema::Row>> {
903 let t = self
904 .schema
905 .tables
906 .iter()
907 .find(|t| t.name == table)
908 .ok_or_else(|| KitError::Validation(format!("unknown table '{table}'")))?;
909 let current = self.snapshot_epoch();
910 if epoch > current {
911 return Err(KitError::Validation(format!(
912 "epoch {epoch} is in the future (current committed epoch is {current})"
913 )));
914 }
915 let snap = Snapshot::at(mongreldb_core::epoch::Epoch(epoch));
916 let rows = self.visible_core_rows_at(table, snap)?;
917 rows.iter()
918 .map(|r| crate::schema::core_row_to_json(r, t))
919 .collect()
920 }
921
922 pub fn approx_aggregate(
928 &self,
929 table: &str,
930 column: Option<&str>,
931 agg: ApproxAggKind,
932 z: f64,
933 ) -> Result<Option<ApproxAggregate>> {
934 let t = self
935 .schema
936 .tables
937 .iter()
938 .find(|t| t.name == table)
939 .ok_or_else(|| KitError::Validation(format!("unknown table '{table}'")))?;
940 if matches!(agg, ApproxAggKind::Sum | ApproxAggKind::Avg) && column.is_none() {
941 return Err(KitError::Validation(
942 "approx sum/avg requires a column".into(),
943 ));
944 }
945 let cid = match column {
946 Some(name) => Some(
947 t.columns
948 .iter()
949 .find(|c| c.name == name)
950 .ok_or_else(|| KitError::Validation(format!("unknown column '{name}'")))?
951 .id as u16,
952 ),
953 None => None,
954 };
955 let core_agg = match agg {
956 ApproxAggKind::Count => ApproxAgg::Count,
957 ApproxAggKind::Sum => ApproxAgg::Sum,
958 ApproxAggKind::Avg => ApproxAgg::Avg,
959 };
960 let handle = self.inner.table(table).map_err(KitError::from)?;
961 let mut guard = handle.lock();
962 let res = guard
963 .approx_aggregate(&[], cid, core_agg, z)
964 .map_err(KitError::from)?;
965 Ok(res.map(|r| ApproxAggregate {
966 point: r.point,
967 ci_low: r.ci_low,
968 ci_high: r.ci_high,
969 n_population: r.n_population,
970 n_sample_live: r.n_sample_live,
971 n_passing: r.n_passing,
972 }))
973 }
974
975 pub fn scan_batched<F>(&self, table: &str, batch_size: usize, mut f: F) -> Result<()>
981 where
982 F: FnMut(&[serde_json::Map<String, Value>]) -> Result<()>,
983 {
984 let kit_t = self
985 .schema
986 .tables
987 .iter()
988 .find(|t| t.name == table)
989 .ok_or_else(|| KitError::Validation(format!("unknown table '{table}'")))?;
990 let batch_size = batch_size.max(1);
991 let (snapshot, _pin) = self.inner.snapshot();
994 let handle = self.inner.table(table).map_err(KitError::from)?;
995 let guard = handle.lock();
996
997 let mut projection: Vec<(u16, mongreldb_core::schema::TypeId)> = Vec::new();
999 let mut meta: Vec<(String, mongreldb_kit_core::schema::ColumnType)> = Vec::new();
1000 for c in &guard.schema().columns {
1001 if let Some(kc) = kit_t.columns.iter().find(|kc| kc.id as u16 == c.id) {
1002 projection.push((c.id, c.ty.clone()));
1003 meta.push((kc.name.clone(), kc.storage_type));
1004 }
1005 }
1006
1007 match guard
1008 .scan_cursor(snapshot, projection, &[])
1009 .map_err(KitError::from)?
1010 {
1011 Some(mut cursor) => {
1012 let mut buf: Vec<serde_json::Map<String, Value>> = Vec::with_capacity(batch_size);
1013 while let Some(batch) = cursor.next_batch().map_err(KitError::from)? {
1014 let nrows = batch.first().map(|c| c.len()).unwrap_or(0);
1015 for j in 0..nrows {
1016 let mut m = serde_json::Map::new();
1017 for (ci, (name, ty)) in meta.iter().enumerate() {
1018 let cv = batch
1019 .get(ci)
1020 .and_then(|col| col.value_at(j))
1021 .unwrap_or(CoreValue::Null);
1022 m.insert(name.clone(), crate::schema::core_to_json(&cv, *ty)?);
1023 }
1024 buf.push(m);
1025 if buf.len() >= batch_size {
1026 f(&buf)?;
1027 buf.clear();
1028 }
1029 }
1030 }
1031 if !buf.is_empty() {
1032 f(&buf)?;
1033 }
1034 Ok(())
1035 }
1036 None => {
1037 drop(guard);
1038 let rows = self.visible_core_rows_at(table, snapshot)?;
1039 let maps: Vec<serde_json::Map<String, Value>> = rows
1040 .iter()
1041 .map(|r| crate::schema::core_row_to_json(r, kit_t).map(|row| row.values))
1042 .collect::<Result<Vec<_>>>()?;
1043 for chunk in maps.chunks(batch_size) {
1044 f(chunk)?;
1045 }
1046 Ok(())
1047 }
1048 }
1049 }
1050
1051 pub fn set_similarity(
1060 &self,
1061 table: &str,
1062 column: &str,
1063 query: &[String],
1064 k: usize,
1065 ) -> Result<Vec<SimilarRow>> {
1066 let t = self
1067 .schema
1068 .tables
1069 .iter()
1070 .find(|t| t.name == table)
1071 .ok_or_else(|| KitError::Validation(format!("unknown table '{table}'")))?;
1072 let col = t.columns.iter().find(|c| c.name == column).ok_or_else(|| {
1073 KitError::Validation(format!("unknown column '{column}' on table '{table}'"))
1074 })?;
1075 let query_set: std::collections::HashSet<String> = query.iter().cloned().collect();
1076
1077 let has_minhash = t.indexes.iter().any(|idx| {
1078 idx.kind == KitIndexKind::MinHash && idx.columns.iter().any(|c| c == column)
1079 });
1080 let rows = if has_minhash {
1081 let query_hashes: Vec<u64> = query
1083 .iter()
1084 .map(|s| mongreldb_core::index::minhash_token_hash(s))
1085 .collect();
1086 let cand_k = k.saturating_mul(8).max(k + 64);
1088 let cond = mongreldb_core::query::Condition::MinHashSimilar {
1089 column_id: col.id as u16,
1090 query: query_hashes,
1091 k: cand_k,
1092 };
1093 let (snapshot, _pin) = self.inner.snapshot();
1094 let core_rows = self.query_core_rows_at(table, &[cond], snapshot)?;
1095 core_rows
1096 .iter()
1097 .map(|r| crate::schema::core_row_to_json(r, t))
1098 .collect::<Result<Vec<_>>>()?
1099 } else {
1100 let tx = self.begin()?;
1101 tx.all_rows(table)?
1102 };
1103
1104 let mut scored: Vec<SimilarRow> = Vec::new();
1105 for row in rows {
1106 let set = parse_string_set(row.values.get(column));
1107 let inter = set.iter().filter(|x| query_set.contains(*x)).count();
1108 let union = set.len() + query_set.len() - inter;
1109 let sim = if union == 0 {
1110 0.0
1111 } else {
1112 inter as f64 / union as f64
1113 };
1114 if sim > 0.0 {
1115 scored.push(SimilarRow {
1116 row,
1117 similarity: sim,
1118 });
1119 }
1120 }
1121 scored.sort_by(|a, b| {
1122 b.similarity
1123 .partial_cmp(&a.similarity)
1124 .unwrap_or(std::cmp::Ordering::Equal)
1125 });
1126 scored.truncate(k);
1127 Ok(scored)
1128 }
1129
1130 pub fn flush(&self) -> Result<()> {
1134 for name in self.inner.table_names() {
1135 let handle = self.inner.table(&name).map_err(KitError::from)?;
1136 let mut guard = handle.lock();
1137 guard.flush().map_err(KitError::from)?;
1138 }
1139 Ok(())
1140 }
1141
1142 pub fn incremental_aggregate(
1154 &self,
1155 table: &str,
1156 column: Option<&str>,
1157 agg: IncrementalAggKind,
1158 filter: Option<&mongreldb_kit_core::query::Expr>,
1159 ) -> Result<IncrementalAggregate> {
1160 let t = self
1161 .schema
1162 .tables
1163 .iter()
1164 .find(|t| t.name == table)
1165 .ok_or_else(|| KitError::Validation(format!("unknown table '{table}'")))?;
1166 if !matches!(agg, IncrementalAggKind::Count) && column.is_none() {
1167 return Err(KitError::Validation(
1168 "sum/min/max/avg incremental aggregate requires a column".into(),
1169 ));
1170 }
1171 let cid = match column {
1172 Some(name) => Some(
1173 t.columns
1174 .iter()
1175 .find(|c| c.name == name)
1176 .ok_or_else(|| KitError::Validation(format!("unknown column '{name}'")))?
1177 .id as u16,
1178 ),
1179 None => None,
1180 };
1181 let conditions = match filter {
1182 Some(expr) => {
1183 let plan = crate::pushdown::translate_predicate(t, expr).ok_or_else(|| {
1184 KitError::Validation(
1185 "filter is not index-translatable for an incremental aggregate".into(),
1186 )
1187 })?;
1188 if !plan.fully_translated {
1189 return Err(KitError::Validation(
1190 "filter has a residual that an incremental aggregate cannot apply exactly"
1191 .into(),
1192 ));
1193 }
1194 plan.conditions
1195 }
1196 None => Vec::new(),
1197 };
1198 let core_agg = match agg {
1199 IncrementalAggKind::Count => NativeAgg::Count,
1200 IncrementalAggKind::Sum => NativeAgg::Sum,
1201 IncrementalAggKind::Min => NativeAgg::Min,
1202 IncrementalAggKind::Max => NativeAgg::Max,
1203 IncrementalAggKind::Avg => NativeAgg::Avg,
1204 };
1205 let cache_key = incremental_cache_key(t.id, cid, agg, &conditions);
1206 let handle = self.inner.table(table).map_err(KitError::from)?;
1207 let mut guard = handle.lock();
1208 let res = guard
1209 .aggregate_incremental(cache_key, &conditions, cid, core_agg)
1210 .map_err(KitError::from)?;
1211 Ok(IncrementalAggregate {
1212 value: agg_state_value(&res.state),
1213 incremental: res.incremental,
1214 delta_rows: res.delta_rows,
1215 })
1216 }
1217
1218 pub fn applied_migrations(&self) -> Result<Vec<mongreldb_kit_core::migrations::Migration>> {
1220 crate::migrate::load_applied_migrations(&self.inner)
1221 }
1222
1223 pub(crate) fn core_db(&self) -> &CoreDatabase {
1224 &self.inner
1225 }
1226
1227 pub(crate) fn core_arc(&self) -> Arc<CoreDatabase> {
1230 Arc::clone(&self.inner)
1231 }
1232
1233 pub fn close(&self) -> Result<()> {
1238 self.inner.close().map_err(KitError::from)
1239 }
1240
1241 pub fn compact_all(&self) -> Result<(usize, usize)> {
1246 self.inner.compact().map_err(KitError::from)
1247 }
1248
1249 pub fn compact_table(&self, name: &str) -> Result<bool> {
1252 self.inner.compact_table(name).map_err(KitError::from)
1253 }
1254
1255 pub fn rename_table(&mut self, from: &str, to: &str) -> Result<()> {
1265 if from.starts_with("__kit_") || to.starts_with("__kit_") {
1266 return Err(KitError::Validation(
1267 "rename_table: names beginning with '__kit_' are reserved for internal tables"
1268 .into(),
1269 ));
1270 }
1271 self.inner.rename_table(from, to).map_err(KitError::from)?;
1272 if !self.schema.rename_table(from, to) {
1275 return Err(KitError::Integrity(format!(
1278 "rename_table: kit schema has no table '{from}' (or '{to}' already exists)"
1279 )));
1280 }
1281 for table in &mut self.schema.tables {
1282 for fk in &mut table.foreign_keys {
1283 if fk.references_table == from {
1284 fk.references_table = to.to_string();
1285 }
1286 }
1287 }
1288 store_schema(&self.root, &self.schema)?;
1289 Ok(())
1290 }
1291
1292 pub fn analyze(&self) -> Result<()> {
1297 for name in self.inner.table_names() {
1298 let handle = self.inner.table(&name).map_err(KitError::from)?;
1299 handle.lock().ensure_indexes_complete()?;
1300 }
1301 Ok(())
1302 }
1303
1304 pub fn vacuum(&self) -> Result<usize> {
1308 self.inner.compact().map_err(KitError::from)?;
1309 self.inner.gc().map_err(KitError::from)
1310 }
1311
1312 pub fn create_view(&self, spec: &ViewSpec) -> Result<()> {
1318 self.sql(&spec.create_sql())?;
1319 Ok(())
1320 }
1321
1322 pub fn drop_view(&self, name: &str) -> Result<()> {
1324 self.sql(&format!("DROP VIEW IF EXISTS {name}"))?;
1325 Ok(())
1326 }
1327
1328 pub fn reserve_auto_inc(&self, table: &str) -> Result<Option<i64>> {
1336 let handle = self.inner.table(table).map_err(KitError::from)?;
1337 let mut guard = handle.lock();
1338 guard.reserve_auto_inc().map_err(KitError::from)
1339 }
1340
1341 pub fn create_user(&self, username: &str, password: &str) -> Result<()> {
1345 self.inner
1346 .create_user(username, password)
1347 .map_err(KitError::from)?;
1348 Ok(())
1349 }
1350
1351 pub fn drop_user(&self, username: &str) -> Result<()> {
1353 self.inner.drop_user(username).map_err(KitError::from)
1354 }
1355
1356 pub fn alter_user_password(&self, username: &str, new_password: &str) -> Result<()> {
1358 self.inner
1359 .alter_user_password(username, new_password)
1360 .map_err(KitError::from)
1361 }
1362
1363 pub fn verify_user(
1365 &self,
1366 username: &str,
1367 password: &str,
1368 ) -> Result<Option<mongreldb_core::auth::UserEntry>> {
1369 self.inner
1370 .verify_user(username, password)
1371 .map_err(KitError::from)
1372 }
1373
1374 pub fn set_user_admin(&self, username: &str, is_admin: bool) -> Result<()> {
1376 self.inner
1377 .set_user_admin(username, is_admin)
1378 .map_err(KitError::from)
1379 }
1380
1381 pub fn users(&self) -> Vec<String> {
1383 self.inner.users().into_iter().map(|u| u.username).collect()
1384 }
1385
1386 pub fn create_role(&self, name: &str) -> Result<()> {
1388 self.inner.create_role(name).map_err(KitError::from)?;
1389 Ok(())
1390 }
1391
1392 pub fn drop_role(&self, name: &str) -> Result<()> {
1394 self.inner.drop_role(name).map_err(KitError::from)
1395 }
1396
1397 pub fn roles(&self) -> Vec<String> {
1399 self.inner.roles().into_iter().map(|r| r.name).collect()
1400 }
1401
1402 pub fn grant_role(&self, username: &str, role_name: &str) -> Result<()> {
1404 self.inner
1405 .grant_role(username, role_name)
1406 .map_err(KitError::from)
1407 }
1408
1409 pub fn revoke_role(&self, username: &str, role_name: &str) -> Result<()> {
1411 self.inner
1412 .revoke_role(username, role_name)
1413 .map_err(KitError::from)
1414 }
1415
1416 pub fn grant_permission(
1418 &self,
1419 role_name: &str,
1420 permission: mongreldb_core::auth::Permission,
1421 ) -> Result<()> {
1422 self.inner
1423 .grant_permission(role_name, permission)
1424 .map_err(KitError::from)
1425 }
1426
1427 pub fn revoke_permission(
1429 &self,
1430 role_name: &str,
1431 permission: mongreldb_core::auth::Permission,
1432 ) -> Result<()> {
1433 self.inner
1434 .revoke_permission(role_name, permission)
1435 .map_err(KitError::from)
1436 }
1437
1438 pub fn set_spill_threshold(&self, bytes: u64) {
1444 self.inner.set_spill_threshold(bytes);
1445 }
1446
1447 pub fn set_recursive_triggers(&self, enabled: bool) {
1449 self.inner.set_recursive_triggers(enabled);
1450 }
1451
1452 pub fn trigger_config(&self) -> mongreldb_core::TriggerConfig {
1454 self.inner.trigger_config()
1455 }
1456
1457 pub fn set_trigger_config(&self, config: mongreldb_core::TriggerConfig) -> Result<()> {
1459 self.inner
1460 .set_trigger_config(config)
1461 .map_err(KitError::from)
1462 }
1463
1464 pub fn set_table_compaction_zstd_level(&self, table: &str, level: i32) -> Result<()> {
1466 let handle = self.inner.table(table).map_err(KitError::from)?;
1467 handle.lock().set_compaction_zstd_level(level);
1468 Ok(())
1469 }
1470
1471 pub fn set_table_result_cache_max_bytes(&self, table: &str, max_bytes: u64) -> Result<()> {
1473 let handle = self.inner.table(table).map_err(KitError::from)?;
1474 handle.lock().set_result_cache_max_bytes(max_bytes);
1475 Ok(())
1476 }
1477
1478 pub fn set_table_mutable_run_spill_bytes(&self, table: &str, bytes: u64) -> Result<()> {
1480 let handle = self.inner.table(table).map_err(KitError::from)?;
1481 handle.lock().set_mutable_run_spill_bytes(bytes);
1482 Ok(())
1483 }
1484
1485 pub fn set_table_sync_byte_threshold(&self, table: &str, threshold: u64) -> Result<()> {
1487 let handle = self.inner.table(table).map_err(KitError::from)?;
1488 handle.lock().set_sync_byte_threshold(threshold);
1489 Ok(())
1490 }
1491
1492 pub fn set_table_index_build_policy(
1495 &self,
1496 table: &str,
1497 policy: mongreldb_core::IndexBuildPolicy,
1498 ) -> Result<()> {
1499 let handle = self.inner.table(table).map_err(KitError::from)?;
1500 handle.lock().set_index_build_policy(policy);
1501 Ok(())
1502 }
1503
1504 pub fn table_page_cache_stats(&self, table: &str) -> Result<mongreldb_core::cache::CacheStats> {
1506 let handle = self.inner.table(table).map_err(KitError::from)?;
1507 let stats = handle.lock().page_cache_stats();
1508 Ok(stats)
1509 }
1510
1511 pub fn table_run_count(&self, table: &str) -> Result<usize> {
1513 let handle = self.inner.table(table).map_err(KitError::from)?;
1514 let n = handle.lock().run_count();
1515 Ok(n)
1516 }
1517
1518 pub fn table_memtable_len(&self, table: &str) -> Result<usize> {
1520 let handle = self.inner.table(table).map_err(KitError::from)?;
1521 let n = handle.lock().memtable_len();
1522 Ok(n)
1523 }
1524
1525 pub fn table_mutable_run_len(&self, table: &str) -> Result<usize> {
1527 let handle = self.inner.table(table).map_err(KitError::from)?;
1528 let n = handle.lock().mutable_run_len();
1529 Ok(n)
1530 }
1531
1532 pub fn table_page_cache_len(&self, table: &str) -> Result<usize> {
1534 let handle = self.inner.table(table).map_err(KitError::from)?;
1535 let n = handle.lock().page_cache_len();
1536 Ok(n)
1537 }
1538
1539 pub fn table_decoded_cache_len(&self, table: &str) -> Result<usize> {
1541 let handle = self.inner.table(table).map_err(KitError::from)?;
1542 let n = handle.lock().decoded_cache_len();
1543 Ok(n)
1544 }
1545
1546 pub fn sql(&self, statement: &str) -> Result<Vec<arrow::record_batch::RecordBatch>> {
1564 let session = match self.session.lock().take() {
1565 Some(s) => s,
1566 None => {
1567 mongreldb_query::MongrelSession::open(self.core_arc()).map_err(KitError::from)?
1568 }
1569 };
1570 let runtime = sql_runtime();
1571 let result = runtime
1572 .block_on(session.run(statement))
1573 .map_err(KitError::from);
1574 *self.session.lock() = Some(session);
1576 result
1577 }
1578
1579 pub fn refresh_sql_session(&self) -> Result<()> {
1584 let session =
1585 mongreldb_query::MongrelSession::open(self.core_arc()).map_err(KitError::from)?;
1586 *self.session.lock() = Some(session);
1587 Ok(())
1588 }
1589
1590 pub fn sql_arrow(&self, statement: &str) -> Result<Vec<u8>> {
1596 let batches = self.sql(statement)?;
1597 crate::arrow_util::batches_to_ipc(&batches)
1598 }
1599
1600 pub fn sql_rows(&self, statement: &str) -> Result<Vec<serde_json::Map<String, Value>>> {
1604 let batches = self.sql(statement)?;
1605 crate::arrow_util::batches_to_rows(&batches)
1606 }
1607
1608 pub(crate) fn lookup_row_id(&self, table: &str, key: &[u8]) -> Result<Option<RowId>> {
1612 let handle = self.inner.table(table).map_err(KitError::from)?;
1613 let mut guard = handle.lock();
1614 guard.ensure_indexes_complete()?;
1615 Ok(guard.lookup_pk(key))
1616 }
1617
1618 pub(crate) fn root(&self) -> &Path {
1619 &self.root
1620 }
1621
1622 pub(crate) fn visible_core_rows_at(
1626 &self,
1627 table_name: &str,
1628 snapshot: Snapshot,
1629 ) -> Result<Vec<CoreRow>> {
1630 let handle = self.inner.table(table_name).map_err(KitError::from)?;
1631 let guard = handle.lock();
1632 guard.visible_rows(snapshot).map_err(KitError::from)
1633 }
1634
1635 pub(crate) fn query_core_rows_at(
1642 &self,
1643 table_name: &str,
1644 conditions: &[mongreldb_core::query::Condition],
1645 snapshot: Snapshot,
1646 ) -> Result<Vec<CoreRow>> {
1647 if conditions.is_empty() {
1648 return self.visible_core_rows_at(table_name, snapshot);
1649 }
1650 let handle = self.inner.table(table_name).map_err(KitError::from)?;
1651 let mut guard = handle.lock();
1652 let q = mongreldb_core::query::Query {
1653 conditions: conditions.to_vec(),
1654 };
1655 guard.query(&q).map_err(KitError::from)
1656 }
1657
1658 pub(crate) fn flush_table(&self, table_name: &str) -> Result<()> {
1666 let handle = self.inner.table(table_name).map_err(KitError::from)?;
1667 handle.lock().flush().map_err(KitError::from)?;
1668 Ok(())
1669 }
1670
1671 pub(crate) fn count_core_rows_at(
1681 &self,
1682 table_name: &str,
1683 conditions: &[mongreldb_core::query::Condition],
1684 snapshot: Snapshot,
1685 ) -> Result<Option<u64>> {
1686 let handle = self.inner.table(table_name).map_err(KitError::from)?;
1687 let mut guard = handle.lock();
1688 if guard.snapshot().epoch != snapshot.epoch {
1689 return Ok(None); }
1691 guard
1692 .count_conditions(conditions, snapshot)
1693 .map_err(KitError::from)
1694 }
1695
1696 pub(crate) fn aggregate_core_at(
1705 &self,
1706 table_name: &str,
1707 column: Option<u16>,
1708 conditions: &[mongreldb_core::query::Condition],
1709 agg: NativeAgg,
1710 snapshot: Snapshot,
1711 ) -> Result<Option<NativeAggResult>> {
1712 let handle = self.inner.table(table_name).map_err(KitError::from)?;
1713 let guard = handle.lock();
1714 if guard.snapshot().epoch != snapshot.epoch {
1715 return Ok(None); }
1717 guard
1718 .aggregate_native(snapshot, column, conditions, agg)
1719 .map_err(KitError::from)
1720 }
1721
1722 pub(crate) fn count_distinct_core_at(
1731 &self,
1732 table_name: &str,
1733 column_id: u16,
1734 snapshot: Snapshot,
1735 ) -> Result<Option<u64>> {
1736 let handle = self.inner.table(table_name).map_err(KitError::from)?;
1737 let mut guard = handle.lock();
1738 if guard.snapshot().epoch != snapshot.epoch {
1739 return Ok(None); }
1741 guard
1742 .count_distinct_from_bitmap(column_id)
1743 .map_err(KitError::from)
1744 }
1745
1746 #[allow(dead_code)]
1748 pub(crate) fn get_core_row(&self, table_name: &str, row_id: u64) -> Result<Option<CoreRow>> {
1749 let handle = self.inner.table(table_name).map_err(KitError::from)?;
1750 let guard = handle.lock();
1751 let snapshot = guard.snapshot();
1752 Ok(guard.get(mongreldb_core::RowId(row_id), snapshot))
1753 }
1754}
1755
1756pub(crate) fn create_core_table(db: &CoreDatabase, name: &str, schema: CoreSchema) -> Result<()> {
1757 if db.table_id(name).is_ok() {
1758 return Ok(());
1759 }
1760 db.create_table(name, schema).map_err(KitError::from)?;
1761 Ok(())
1762}
1763
1764fn sql_runtime() -> &'static tokio::runtime::Runtime {
1769 use std::sync::OnceLock;
1770 static RT: OnceLock<tokio::runtime::Runtime> = OnceLock::new();
1771 RT.get_or_init(|| {
1772 tokio::runtime::Builder::new_current_thread()
1773 .enable_all()
1774 .build()
1775 .expect("failed to build kit SQL tokio runtime")
1776 })
1777}
1778
1779fn core_procedure(spec: &ProcedureSpec) -> Result<mongreldb_core::StoredProcedure> {
1780 let parsed: mongreldb_core::StoredProcedure =
1781 serde_json::from_value(spec.json.clone()).map_err(KitError::from)?;
1782 mongreldb_core::StoredProcedure::new(parsed.name, parsed.mode, parsed.params, parsed.body, 0)
1783 .map_err(KitError::from)
1784}
1785
1786fn core_trigger(spec: &TriggerSpec) -> Result<mongreldb_core::StoredTrigger> {
1787 let parsed: mongreldb_core::StoredTrigger =
1788 serde_json::from_value(spec.json.clone()).map_err(KitError::from)?;
1789 mongreldb_core::StoredTrigger::new(
1790 parsed.name,
1791 mongreldb_core::TriggerDefinition {
1792 target: parsed.target,
1793 timing: parsed.timing,
1794 event: parsed.event,
1795 update_of: parsed.update_of,
1796 target_columns: parsed.target_columns,
1797 when: parsed.when,
1798 program: parsed.program,
1799 },
1800 0,
1801 )
1802 .map_err(KitError::from)
1803}
1804
1805fn json_to_core_value(value: &Value) -> Result<CoreValue> {
1806 match value {
1807 Value::Null => Ok(CoreValue::Null),
1808 Value::Bool(value) => Ok(CoreValue::Bool(*value)),
1809 Value::Number(value) => {
1810 if let Some(value) = value.as_i64() {
1811 Ok(CoreValue::Int64(value))
1812 } else if let Some(value) = value.as_f64() {
1813 Ok(CoreValue::Float64(value))
1814 } else {
1815 Err(KitError::Validation("unsupported JSON number".into()))
1816 }
1817 }
1818 Value::String(value) => Ok(CoreValue::Bytes(value.as_bytes().to_vec())),
1819 Value::Array(_) | Value::Object(_) => Err(KitError::Validation(
1820 "procedure args only support scalar JSON values".into(),
1821 )),
1822 }
1823}
1824
1825pub(crate) fn internal_bytes(row: &CoreRow, col_id: u16) -> Option<String> {
1827 match row.columns.get(&col_id) {
1828 Some(CoreValue::Bytes(b)) => String::from_utf8(b.clone()).ok(),
1829 _ => None,
1830 }
1831}
1832
1833fn reap_rotated_wal_segments(db: &CoreDatabase) {
1848 let _ = db.gc();
1849}
1850
1851pub(crate) fn load_schema(path: &Path) -> Result<KitSchema> {
1852 let file = path.join(SCHEMA_FILE);
1853 let json = std::fs::read_to_string(&file)
1854 .map_err(|e| KitError::Migration(format!("cannot read schema file: {e}")))?;
1855 let schema: KitSchema = serde_json::from_str(&json)?;
1856 Ok(schema)
1857}
1858
1859pub(crate) fn store_schema(path: &Path, schema: &KitSchema) -> Result<()> {
1860 let file = path.join(SCHEMA_FILE);
1861 let json = serde_json::to_string_pretty(schema)?;
1862 std::fs::write(&file, json)?;
1863 Ok(())
1864}
1865
1866pub(crate) fn persist_schema(db: &Database, schema: &KitSchema) -> Result<()> {
1868 store_schema(&db.root, schema)
1869}
1870
1871#[cfg(test)]
1872mod tests {
1873 use super::open_core_with_retry;
1874
1875 fn lock_error() -> mongreldb_core::MongrelError {
1876 mongreldb_core::MongrelError::Io(std::io::Error::other(
1877 "database at /tmp/db is locked by another process: would block",
1878 ))
1879 }
1880
1881 #[test]
1882 fn open_retry_waits_for_lock_contention_only() {
1883 let mut calls = 0;
1884 let value = open_core_with_retry(50, || {
1885 calls += 1;
1886 if calls < 3 {
1887 Err(lock_error())
1888 } else {
1889 Ok(7)
1890 }
1891 })
1892 .unwrap();
1893 assert_eq!(value, 7);
1894 assert_eq!(calls, 3);
1895
1896 let mut non_lock_calls = 0;
1897 let err: mongreldb_core::Result<()> = open_core_with_retry(50, || {
1898 non_lock_calls += 1;
1899 Err(mongreldb_core::MongrelError::Other("nope".into()))
1900 });
1901 let err = err.unwrap_err();
1902 assert_eq!(non_lock_calls, 1);
1903 assert!(matches!(err, mongreldb_core::MongrelError::Other(_)));
1904 }
1905}