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