Skip to main content

icydb_core/db/
mod.rs

1//! Module: db
2//!
3//! Responsibility: root subsystem wiring, façade re-exports, and runtime hook contracts.
4//! Does not own: feature semantics delegated to child modules (`query`, `executor`, etc.).
5//! Boundary: top-level db API and internal orchestration entrypoints.
6
7pub(crate) mod access;
8pub(crate) mod catalog;
9pub(crate) mod cursor;
10pub(crate) mod diagnostics;
11pub(crate) mod identity;
12#[cfg(feature = "diagnostics")]
13pub(crate) mod physical_access;
14pub(crate) mod predicate;
15pub(crate) mod query;
16pub(crate) mod registry;
17pub(crate) mod response;
18pub(crate) mod runtime_hooks;
19pub(crate) mod scalar_expr;
20pub(crate) mod schema;
21pub(crate) mod session;
22#[cfg(feature = "sql")]
23pub(crate) mod sql;
24
25pub(in crate::db) mod codec;
26pub(in crate::db) mod commit;
27pub(in crate::db) mod data;
28pub(in crate::db) mod direction;
29pub(in crate::db) mod executor;
30pub(in crate::db) mod index;
31pub(in crate::db) mod journal;
32pub(in crate::db) mod key_taxonomy;
33pub(in crate::db) mod numeric;
34pub(in crate::db) mod ordered_overlay;
35pub(in crate::db) mod relation;
36pub(in crate::db) mod sql_shared;
37#[cfg(test)]
38mod tests;
39
40use crate::{
41    db::{
42        commit::{CommitRowOp, PreparedRowCommitOp, ensure_recovered},
43        data::RawDataStoreKey,
44        executor::Context,
45        registry::StoreHandle,
46        schema::{PersistedFieldKind, ensure_accepted_schema_snapshot},
47    },
48    error::InternalError,
49    traits::{CanisterKind, EntityKind, EntityValue},
50    types::EntityTag,
51};
52use std::{collections::BTreeSet, marker::PhantomData, thread::LocalKey};
53
54pub use catalog::{
55    EntityCatalogCounts, EntityCatalogDescription, MemoryCatalogDescription,
56    StoreCatalogDescription,
57};
58#[doc(hidden)]
59pub use codec::hex::encode_hex_lower;
60pub use cursor::{decode_cursor, encode_cursor};
61pub use runtime_hooks::EntityRuntimeHooks;
62// These hidden helper re-exports remain public so the crate-root `__macro`
63// boundary can route generated code through one stable path without widening
64// the normal `db` facade contract.
65pub use data::{DataStore, PersistedRow, SlotReader, SlotWriter, StructuralPatch};
66#[doc(hidden)]
67pub use data::{
68    PersistedScalar, ScalarSlotValueRef, ScalarValueRef,
69    decode_persisted_many_slot_payload_by_meta, decode_persisted_option_scalar_slot_payload,
70    decode_persisted_option_slot_payload_by_kind, decode_persisted_option_slot_payload_by_meta,
71    decode_persisted_scalar_slot_payload, decode_persisted_slot_payload_by_kind,
72    decode_persisted_slot_payload_by_meta, decode_persisted_structured_many_slot_payload,
73    decode_persisted_structured_slot_payload, decode_slot_into_runtime_value,
74    encode_persisted_many_slot_payload_by_meta, encode_persisted_option_scalar_slot_payload,
75    encode_persisted_option_slot_payload_by_meta, encode_persisted_scalar_slot_payload,
76    encode_persisted_slot_payload_by_kind, encode_persisted_slot_payload_by_meta,
77    encode_persisted_structured_many_slot_payload, encode_persisted_structured_slot_payload,
78    encode_runtime_value_into_slot,
79};
80#[cfg(feature = "diagnostics")]
81#[doc(hidden)]
82pub use data::{StructuralReadMetrics, with_structural_read_metrics};
83#[cfg(all(test, not(feature = "diagnostics")))]
84#[expect(unused_imports)]
85pub(crate) use data::{StructuralReadMetrics, with_structural_read_metrics};
86pub use diagnostics::execution_trace::{
87    ExecutionAccessPathVariant, ExecutionMetrics, ExecutionOptimization, ExecutionStats,
88    ExecutionTrace,
89};
90pub use diagnostics::model::{
91    DataStoreSnapshot, EntitySnapshot, IndexStoreSnapshot, IntegrityReport, IntegrityStoreSnapshot,
92    IntegrityTotals, SchemaStoreSnapshot, StorageReport, StoreSnapshotStorageMode,
93};
94#[doc(hidden)]
95pub use executor::EntityAuthority;
96pub use executor::MutationMode;
97pub use executor::{ExecutionFamily, RouteExecutionMode};
98#[cfg(feature = "diagnostics")]
99#[doc(hidden)]
100pub use executor::{RowCheckMetrics, with_row_check_metrics};
101#[cfg(all(test, not(feature = "diagnostics")))]
102#[expect(unused_imports)]
103pub(crate) use executor::{RowCheckMetrics, with_row_check_metrics};
104#[cfg(feature = "diagnostics")]
105#[doc(hidden)]
106pub use executor::{ScalarMaterializationLaneMetrics, with_scalar_materialization_lane_metrics};
107#[cfg(all(test, not(feature = "diagnostics")))]
108#[expect(unused_imports)]
109pub(crate) use executor::{
110    ScalarMaterializationLaneMetrics, with_scalar_materialization_lane_metrics,
111};
112pub use identity::{EntityName, IndexName};
113pub use index::{IndexState, IndexStore};
114#[doc(hidden)]
115pub use journal::JournalTailStore;
116#[doc(hidden)]
117pub use key_taxonomy::{
118    CompositePrimaryKeyValue, CompositePrimaryKeyValueError, PrimaryKeyComponent, PrimaryKeyValue,
119};
120pub use predicate::{
121    CoercionId, CompareFieldsPredicate, CompareOp, ComparePredicate, MissingRowPolicy, Predicate,
122    UnsupportedQueryFeature,
123};
124#[doc(hidden)]
125pub use predicate::{
126    parse_generated_index_predicate_sql, validate_generated_index_predicate_fields,
127};
128pub use query::builder::numeric_projection::{
129    NumericProjectionExpr, RoundProjectionExpr, add, div, mul, round, round_expr, sub,
130};
131pub use query::plan::validate::PlanError;
132pub use query::{
133    api::ResponseCardinalityExt,
134    builder::{
135        AggregateExpr, FieldRef, TextProjectionExpr, ValueProjectionExpr, avg, contains, count,
136        count_by, ends_with, exists, first, last, left, length, lower, ltrim, max, max_by, min,
137        min_by, position, replace, right, rtrim, starts_with, substring, substring_with_length,
138        sum, trim, upper,
139    },
140    explain::{
141        ExplainAccessCandidateV1, ExplainAccessDecisionKind, ExplainAccessDecisionV1,
142        ExplainAggregateTerminalPlan, ExplainEligibleAlternativeV1, ExplainExecutionDescriptor,
143        ExplainExecutionMode, ExplainExecutionNodeDescriptor, ExplainExecutionNodeType,
144        ExplainExecutionOrderingSource, ExplainPlan, ExplainRejectedIndexV1,
145        ExplainResidualSummaryV1, ExplainSelectedAccessV1,
146    },
147    expr::{FilterExpr, FilterValue, OrderExpr, OrderTerm, asc, desc, field},
148    fluent::{
149        delete::FluentDeleteQuery,
150        load::{FluentLoadQuery, LoadQueryResult, PagedLoadQuery},
151    },
152    intent::{
153        AccessRequirementError, AccessRequirementViolation, CompiledQuery, IntentError,
154        PlannedQuery, Query, QueryError, QueryExecutionError, RequiredAccessPath,
155    },
156    plan::{DeleteSpec, LoadSpec, OrderDirection, QueryMode},
157    trace::{QueryTracePlan, TraceExecutionFamily, TraceReuseArtifactClass, TraceReuseEvent},
158};
159pub use registry::{
160    StoreAllocationIdentities, StoreAllocationIdentity, StoreAllocationIdentityCapability,
161    StoreCommitParticipation, StoreDurability, StoreLiveValidationCapability,
162    StoreRecoveryCapability, StoreRegistry, StoreRelationSourceCapability,
163    StoreRelationTargetCapability, StoreRuntimeStorageCapabilities, StoreRuntimeStorageMode,
164    StoreSchemaMetadataCapability,
165};
166pub use response::{
167    EntityResponse, GroupedRow, PagedGroupedExecution, PagedGroupedExecutionWithTrace,
168    PagedLoadExecution, PagedLoadExecutionWithTrace, ProjectedRow, ProjectionResponse,
169    Response as RowResponse, ResponseError, ResponseRow, Row, WriteBatchResponse,
170};
171pub use schema::{
172    EntityFieldDescription, EntityIndexDescription, EntityRelationCardinality,
173    EntityRelationDescription, EntityRelationStrength, EntitySchemaCheckDescription,
174    EntitySchemaDescription, SchemaStore, ValidateError,
175};
176#[cfg(not(feature = "sql"))]
177pub use session::DbSession;
178#[cfg(feature = "sql")]
179pub use session::{
180    DbSession, SqlDdlExecutionStatus, SqlDdlMutationKind, SqlDdlPreparationReport,
181    SqlStatementResult, SqlStatementSurface, sql_statement_entity_name, sql_statement_surface,
182};
183#[cfg(feature = "diagnostics")]
184pub use session::{
185    DirectDataRowAttribution, GroupedCountAttribution, GroupedExecutionAttribution,
186    QueryExecutionAttribution,
187};
188#[cfg(all(feature = "sql", feature = "diagnostics"))]
189pub use session::{
190    SqlCompileAttribution, SqlExecutionAttribution, SqlPureCoveringAttribution,
191    SqlQueryCacheAttribution, SqlQueryExecutionAttribution, SqlScalarAggregateAttribution,
192};
193#[cfg(all(feature = "sql", feature = "diagnostics"))]
194#[doc(hidden)]
195pub use session::{
196    SqlProjectionMaterializationMetrics, with_sql_projection_materialization_metrics,
197};
198#[cfg(feature = "sql")]
199pub use sql::identifier::{
200    identifier_last_segment, identifiers_tail_match, normalize_identifier_to_scope,
201    split_qualified_identifier,
202};
203#[cfg(feature = "sql")]
204pub use sql::lowering::LoweredSqlCommand;
205
206/// Hidden generated-code alias for borrowed structural map entry payload slices.
207#[doc(hidden)]
208pub type GeneratedStructuralMapPayloadSlices<'a> = Vec<(&'a [u8], &'a [u8])>;
209
210/// Hidden generated-code alias for one decoded enum payload frame.
211#[doc(hidden)]
212pub type GeneratedStructuralEnumPayload<'a> = (String, Option<String>, Option<&'a [u8]>);
213
214/// Hidden generated-code helper for canonical structural text payload framing.
215#[doc(hidden)]
216#[must_use]
217pub(crate) fn encode_generated_structural_text_payload_bytes(value: &str) -> Vec<u8> {
218    data::encode_value_storage_text(value)
219}
220
221/// Hidden generated-code helper for canonical structural list payload framing.
222#[doc(hidden)]
223#[must_use]
224pub(crate) fn encode_generated_structural_list_payload_bytes(items: &[&[u8]]) -> Vec<u8> {
225    data::encode_value_storage_list_item_slices(items)
226}
227
228/// Hidden generated-code helper for canonical structural map payload framing.
229#[doc(hidden)]
230#[must_use]
231pub(crate) fn encode_generated_structural_map_payload_bytes(entries: &[(&[u8], &[u8])]) -> Vec<u8> {
232    data::encode_value_storage_map_entry_slices(entries)
233}
234
235/// Hidden generated-code helper for canonical structural enum payload framing.
236#[doc(hidden)]
237#[must_use]
238pub(crate) fn encode_generated_structural_enum_payload_bytes(
239    variant: &str,
240    path: Option<&str>,
241    payload: Option<&[u8]>,
242) -> Vec<u8> {
243    data::encode_enum(variant, path, payload)
244}
245
246/// Hidden generated-code helper for structural text payload decoding.
247#[doc(hidden)]
248pub(crate) fn decode_generated_structural_text_payload_bytes(
249    raw_bytes: &[u8],
250) -> Result<String, InternalError> {
251    data::decode_value_storage_text(raw_bytes).map_err(InternalError::persisted_row_decode_failed)
252}
253
254/// Hidden generated-code helper for structural list payload decoding.
255#[doc(hidden)]
256pub(crate) fn decode_generated_structural_list_payload_bytes(
257    raw_bytes: &[u8],
258) -> Result<Vec<&[u8]>, InternalError> {
259    data::decode_value_storage_list_item_slices(raw_bytes)
260        .map_err(InternalError::persisted_row_decode_failed)
261}
262
263/// Hidden generated-code helper for structural map payload decoding.
264#[doc(hidden)]
265pub(crate) fn decode_generated_structural_map_payload_bytes(
266    raw_bytes: &[u8],
267) -> Result<GeneratedStructuralMapPayloadSlices<'_>, InternalError> {
268    data::decode_value_storage_map_entry_slices(raw_bytes)
269        .map_err(InternalError::persisted_row_decode_failed)
270}
271
272/// Hidden generated-code helper for structural enum payload decoding.
273#[doc(hidden)]
274pub(crate) fn decode_generated_structural_enum_payload_bytes(
275    raw_bytes: &[u8],
276) -> Result<GeneratedStructuralEnumPayload<'_>, InternalError> {
277    data::decode_enum(raw_bytes).map_err(InternalError::persisted_row_decode_failed)
278}
279
280/// Hidden generated-code helper for persisted structured payload decode errors.
281#[doc(hidden)]
282pub(crate) fn generated_persisted_structured_payload_decode_failed(
283    detail: impl std::fmt::Display,
284) -> InternalError {
285    InternalError::persisted_row_decode_failed(detail)
286}
287
288///
289/// Db
290/// A handle to the set of stores registered for a specific canister domain.
291///
292
293pub(crate) struct Db<C: CanisterKind> {
294    store: &'static LocalKey<StoreRegistry>,
295    entity_runtime_hooks: &'static [EntityRuntimeHooks<C>],
296    _marker: PhantomData<C>,
297}
298
299impl<C: CanisterKind> Db<C> {
300    /// Construct a db handle without per-entity runtime hooks.
301    #[must_use]
302    #[cfg(test)]
303    pub(crate) const fn new(store: &'static LocalKey<StoreRegistry>) -> Self {
304        Self::new_with_hooks(store, &[])
305    }
306
307    /// Construct a db handle with explicit per-entity runtime hook wiring.
308    #[must_use]
309    pub(crate) const fn new_with_hooks(
310        store: &'static LocalKey<StoreRegistry>,
311        entity_runtime_hooks: &'static [EntityRuntimeHooks<C>],
312    ) -> Self {
313        #[cfg(debug_assertions)]
314        {
315            let _ = crate::db::runtime_hooks::debug_assert_unique_runtime_hook_tags(
316                entity_runtime_hooks,
317            );
318        }
319
320        Self {
321            store,
322            entity_runtime_hooks,
323            _marker: PhantomData,
324        }
325    }
326
327    #[must_use]
328    pub(in crate::db) const fn context<E>(&self) -> Context<'_, E>
329    where
330        E: EntityKind<Canister = C> + EntityValue,
331    {
332        Context::new(self)
333    }
334
335    /// Resolve one named store after enforcing startup recovery.
336    pub(in crate::db) fn recovered_store(&self, path: &str) -> Result<StoreHandle, InternalError> {
337        ensure_recovered(self)?;
338
339        self.store_handle(path)
340    }
341
342    // Resolve one named store without re-entering recovery.
343    //
344    // Internal commit/recovery paths already own recovery authority and must
345    // not bounce back through `ensure_recovered`, or they can recurse through
346    // replay/rebuild preparation.
347    pub(in crate::db) fn store_handle(&self, path: &str) -> Result<StoreHandle, InternalError> {
348        self.with_store_registry(|registry| registry.try_get_store(path))
349    }
350
351    /// Ensure startup/in-progress commit recovery has been applied.
352    pub(crate) fn ensure_recovered_state(&self) -> Result<(), InternalError> {
353        ensure_recovered(self)
354    }
355
356    /// Execute one closure against the registered store set.
357    pub(crate) fn with_store_registry<R>(&self, f: impl FnOnce(&StoreRegistry) -> R) -> R {
358        self.store.with(|reg| f(reg))
359    }
360
361    /// Resolve one stable in-process cache scope identifier for this store registry.
362    ///
363    /// Session-level SQL and structural query caches use this scope to share
364    /// reusable artifacts across fresh `DbSession` values that point at the
365    /// same generated canister store wiring without leaking entries across
366    /// unrelated registries in tests or multi-canister host processes.
367    #[must_use]
368    pub(in crate::db) fn cache_scope_id(&self) -> usize {
369        std::ptr::from_ref::<LocalKey<StoreRegistry>>(self.store) as usize
370    }
371
372    /// Build one named-store resolver for executor/runtime helpers.
373    #[must_use]
374    pub(in crate::db) fn store_resolver(&self) -> executor::StoreResolver<'_> {
375        executor::StoreResolver::new(self)
376    }
377
378    /// Mark every registered index store as fully rebuilt and query-visible.
379    ///
380    /// Recovery restores visibility only after rebuild and post-recovery
381    /// integrity validation complete successfully.
382    pub(in crate::db) fn mark_all_registered_index_stores_ready(&self) {
383        self.with_store_registry(|registry| {
384            for (_, handle) in registry.iter() {
385                handle.mark_index_ready();
386            }
387        });
388    }
389
390    /// Build one storage diagnostics report for registered stores/entities.
391    pub(crate) fn storage_report(
392        &self,
393        name_to_path: &[(&'static str, &'static str)],
394    ) -> Result<StorageReport, InternalError> {
395        diagnostics::storage_report(self, name_to_path)
396    }
397
398    /// Build one storage diagnostics report using default entity-path labels.
399    pub(crate) fn storage_report_default(&self) -> Result<StorageReport, InternalError> {
400        diagnostics::storage_report_default(self)
401    }
402
403    /// Build one integrity scan report for registered stores/entities.
404    pub(crate) fn integrity_report(&self) -> Result<IntegrityReport, InternalError> {
405        diagnostics::integrity_report(self)
406    }
407
408    pub(in crate::db) fn prepare_row_commit_op(
409        &self,
410        op: &CommitRowOp,
411    ) -> Result<PreparedRowCommitOp, InternalError> {
412        runtime_hooks::prepare_row_commit_with_hook(self, self.entity_runtime_hooks, op)
413    }
414
415    // Validate strong relation constraints for delete-selected target keys.
416    pub(crate) fn validate_delete_strong_relations(
417        &self,
418        target_path: &str,
419        deleted_target_keys: &BTreeSet<RawDataStoreKey>,
420    ) -> Result<(), InternalError> {
421        runtime_hooks::validate_delete_strong_relations_with_hooks(
422            self,
423            self.entity_runtime_hooks,
424            target_path,
425            deleted_target_keys,
426        )
427    }
428}
429
430impl<C: CanisterKind> Db<C> {
431    /// Return whether this db has any registered runtime hook callbacks.
432    #[must_use]
433    pub(crate) const fn has_runtime_hooks(&self) -> bool {
434        runtime_hooks::has_runtime_hooks(self.entity_runtime_hooks)
435    }
436
437    /// Return one deterministic list of registered runtime entity catalog rows.
438    pub(crate) fn runtime_entity_catalog(
439        &self,
440    ) -> Result<Vec<EntityCatalogDescription>, InternalError> {
441        let mut entities = Vec::with_capacity(self.entity_runtime_hooks.len());
442
443        for hooks in self.entity_runtime_hooks {
444            let store = self.recovered_store(hooks.store_path)?;
445            let storage = store
446                .storage_capabilities()
447                .storage_mode()
448                .as_str()
449                .to_string();
450            let accepted = store.with_schema_mut(|schema_store| {
451                ensure_accepted_schema_snapshot(
452                    schema_store,
453                    hooks.entity_tag,
454                    hooks.entity_path,
455                    hooks.model,
456                )
457            })?;
458            let snapshot = accepted.persisted_snapshot();
459
460            entities.push(EntityCatalogDescription::new(
461                snapshot.entity_name().to_string(),
462                snapshot.entity_path().to_string(),
463                hooks.store_path.to_string(),
464                storage,
465                EntityCatalogCounts::new(
466                    u32::try_from(snapshot.fields().len()).unwrap_or(u32::MAX),
467                    u32::try_from(snapshot.indexes().len()).unwrap_or(u32::MAX),
468                    u32::try_from(relation_field_count(snapshot.fields())).unwrap_or(u32::MAX),
469                    snapshot.version().get(),
470                ),
471            ));
472        }
473
474        Ok(entities)
475    }
476
477    /// Return one deterministic list of registered runtime stores.
478    #[must_use]
479    pub(crate) fn runtime_store_catalog(&self) -> Vec<StoreCatalogDescription> {
480        let mut stores = self.with_store_registry(|registry| {
481            registry
482                .iter()
483                .map(|(store_path, handle)| {
484                    StoreCatalogDescription::new(
485                        store_path.to_string(),
486                        handle
487                            .storage_capabilities()
488                            .storage_mode()
489                            .as_str()
490                            .to_string(),
491                    )
492                })
493                .collect::<Vec<_>>()
494        });
495        stores.sort_by(|left, right| left.store_path().cmp(right.store_path()));
496        stores
497    }
498
499    /// Return one deterministic list of registered stable-memory allocations.
500    #[must_use]
501    pub(crate) fn runtime_memory_catalog(&self) -> Vec<MemoryCatalogDescription> {
502        let mut memory = self.with_store_registry(|registry| {
503            registry
504                .iter()
505                .flat_map(|(store_path, handle)| {
506                    [
507                        handle.data_allocation(),
508                        handle.index_allocation(),
509                        handle.schema_allocation(),
510                        handle.journal_allocation(),
511                    ]
512                    .into_iter()
513                    .flatten()
514                    .map(move |allocation| {
515                        MemoryCatalogDescription::new(
516                            allocation.stable_key().to_string(),
517                            allocation.memory_id(),
518                            store_path.to_string(),
519                        )
520                    })
521                })
522                .collect::<Vec<_>>()
523        });
524        memory.sort_by(|left, right| {
525            left.memory_id()
526                .cmp(&right.memory_id())
527                .then_with(|| left.tag().cmp(right.tag()))
528                .then_with(|| left.store_path().cmp(right.store_path()))
529        });
530        memory
531    }
532
533    // Resolve exactly one runtime hook for a persisted entity tag.
534    // Duplicate matches are treated as store invariants.
535    pub(crate) fn runtime_hook_for_entity_tag(
536        &self,
537        entity_tag: EntityTag,
538    ) -> Result<&EntityRuntimeHooks<C>, InternalError> {
539        runtime_hooks::resolve_runtime_hook_by_tag(self.entity_runtime_hooks, entity_tag)
540    }
541
542    // Resolve exactly one runtime hook for a persisted entity path.
543    // Duplicate matches are treated as store invariants.
544    pub(crate) fn runtime_hook_for_entity_path(
545        &self,
546        entity_path: &str,
547    ) -> Result<&EntityRuntimeHooks<C>, InternalError> {
548        runtime_hooks::resolve_runtime_hook_by_path(self.entity_runtime_hooks, entity_path)
549    }
550}
551
552fn relation_field_count(fields: &[crate::db::schema::PersistedFieldSnapshot]) -> usize {
553    fields
554        .iter()
555        .filter(|field| persisted_kind_is_relation_field(field.kind()))
556        .count()
557}
558
559fn persisted_kind_is_relation_field(kind: &PersistedFieldKind) -> bool {
560    match kind {
561        PersistedFieldKind::Relation { .. } => true,
562        PersistedFieldKind::List(inner) | PersistedFieldKind::Set(inner) => {
563            matches!(inner.as_ref(), PersistedFieldKind::Relation { .. })
564        }
565        _ => false,
566    }
567}
568
569impl<C: CanisterKind> Copy for Db<C> {}
570
571impl<C: CanisterKind> Clone for Db<C> {
572    fn clone(&self) -> Self {
573        *self
574    }
575}