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