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 contracts;
9pub(crate) mod cursor;
10pub(crate) mod diagnostics;
11pub(crate) mod identity;
12pub(crate) mod predicate;
13pub(crate) mod query;
14pub(crate) mod registry;
15pub(crate) mod response;
16pub(crate) mod scalar_expr;
17pub(crate) mod schema;
18pub(crate) mod session;
19#[cfg(feature = "sql")]
20pub(crate) mod sql;
21
22pub(in crate::db) mod codec;
23pub(in crate::db) mod commit;
24pub(in crate::db) mod data;
25pub(in crate::db) mod direction;
26pub(in crate::db) mod executor;
27pub(in crate::db) mod index;
28pub(in crate::db) mod migration;
29pub(in crate::db) mod numeric;
30pub(in crate::db) mod reduced_sql;
31pub(in crate::db) mod relation;
32
33use crate::{
34    db::{
35        commit::{CommitRowOp, PreparedRowCommitOp, ensure_recovered},
36        data::RawDataKey,
37        executor::Context,
38        registry::StoreHandle,
39        relation::model_has_strong_relations_to_target,
40    },
41    error::InternalError,
42    traits::{CanisterKind, EntityKind, EntityValue},
43    types::EntityTag,
44};
45use std::{collections::BTreeSet, marker::PhantomData, thread::LocalKey};
46
47pub use codec::cursor::{decode_cursor, encode_cursor};
48pub use commit::EntityRuntimeHooks;
49pub use data::{
50    DataStore, PersistedRow, PersistedScalar, ScalarSlotValueRef, ScalarValueRef, SlotReader,
51    SlotWriter, UpdatePatch, decode_persisted_custom_many_slot_payload,
52    decode_persisted_custom_slot_payload, decode_persisted_non_null_slot_payload,
53    decode_persisted_option_scalar_slot_payload, decode_persisted_option_slot_payload,
54    decode_persisted_scalar_slot_payload, decode_persisted_slot_payload,
55    encode_persisted_custom_many_slot_payload, encode_persisted_custom_slot_payload,
56    encode_persisted_option_scalar_slot_payload, encode_persisted_scalar_slot_payload,
57    encode_persisted_slot_payload,
58};
59#[cfg(feature = "structural-read-metrics")]
60#[doc(hidden)]
61pub use data::{StructuralReadMetrics, with_structural_read_metrics};
62#[cfg(all(test, not(feature = "structural-read-metrics")))]
63#[expect(unused_imports)]
64pub(crate) use data::{StructuralReadMetrics, with_structural_read_metrics};
65pub use diagnostics::{
66    ExecutionAccessPathVariant, ExecutionMetrics, ExecutionOptimization, ExecutionTrace,
67    IntegrityReport, IntegrityStoreSnapshot, IntegrityTotals, StorageReport,
68};
69#[doc(hidden)]
70pub use executor::EntityAuthority;
71pub use executor::MutationMode;
72pub use executor::{ExecutionFamily, RouteExecutionMode};
73#[cfg(feature = "structural-read-metrics")]
74#[doc(hidden)]
75pub use executor::{GroupedCountFoldMetrics, with_grouped_count_fold_metrics};
76#[cfg(feature = "structural-read-metrics")]
77#[doc(hidden)]
78pub use executor::{RowCheckMetrics, with_row_check_metrics};
79#[cfg(all(test, not(feature = "structural-read-metrics")))]
80#[expect(unused_imports)]
81pub(crate) use executor::{RowCheckMetrics, with_row_check_metrics};
82pub use identity::{EntityName, IndexName};
83pub use index::{IndexState, IndexStore};
84pub use migration::{
85    MigrationCursor, MigrationPlan, MigrationRowOp, MigrationRunOutcome, MigrationRunState,
86    MigrationStep,
87};
88pub use predicate::{
89    CoercionId, CompareOp, ComparePredicate, MissingRowPolicy, Predicate, UnsupportedQueryFeature,
90};
91#[doc(hidden)]
92pub use predicate::{
93    parse_generated_index_predicate_sql, validate_generated_index_predicate_fields,
94};
95pub use query::{
96    api::ResponseCardinalityExt,
97    builder::{
98        AggregateExpr, FieldRef, avg, count, count_by, exists, first, last, max, max_by, min,
99        min_by, sum,
100    },
101    explain::{
102        ExplainAggregateTerminalPlan, ExplainExecutionDescriptor, ExplainExecutionMode,
103        ExplainExecutionNodeDescriptor, ExplainExecutionNodeType, ExplainExecutionOrderingSource,
104        ExplainPlan,
105    },
106    expr::{FilterExpr, SortExpr},
107    fluent::{
108        delete::FluentDeleteQuery,
109        load::{FluentLoadQuery, PagedLoadQuery},
110    },
111    intent::{CompiledQuery, IntentError, PlannedQuery, Query, QueryError, QueryExecutionError},
112    plan::{DeleteSpec, LoadSpec, OrderDirection, PlanError, QueryMode},
113    trace::{QueryTracePlan, TraceExecutionFamily},
114};
115pub use registry::StoreRegistry;
116pub(in crate::db) use response::GroupedTextCursorPageWithTrace;
117pub use response::{
118    EntityResponse, GroupedRow, PagedGroupedExecution, PagedGroupedExecutionWithTrace,
119    PagedLoadExecution, PagedLoadExecutionWithTrace, ProjectedRow, ProjectionResponse,
120    Response as RowResponse, ResponseError, ResponseRow, Row, WriteBatchResponse,
121};
122pub use schema::{
123    EntityFieldDescription, EntityIndexDescription, EntityRelationCardinality,
124    EntityRelationDescription, EntityRelationStrength, EntitySchemaDescription, ValidateError,
125};
126#[cfg(not(feature = "sql"))]
127pub use session::DbSession;
128#[cfg(feature = "sql")]
129pub use session::{
130    DbSession, SqlDispatchResult, SqlParsedStatement, SqlStatementRoute,
131    debug_mark_store_index_state, debug_remove_entity_row_data_only,
132};
133#[cfg(all(feature = "sql", feature = "perf-attribution"))]
134pub use session::{LoweredSqlDispatchExecutorAttribution, SqlProjectionTextExecutorAttribution};
135#[cfg(all(feature = "sql", feature = "structural-read-metrics"))]
136#[doc(hidden)]
137pub use session::{
138    SqlProjectionMaterializationMetrics, with_sql_projection_materialization_metrics,
139};
140#[cfg(feature = "sql")]
141pub use sql::identifier::{
142    identifier_last_segment, identifiers_tail_match, normalize_identifier_to_scope,
143    split_qualified_identifier,
144};
145#[cfg(feature = "sql")]
146pub use sql::lowering::LoweredSqlCommand;
147
148///
149/// Db
150/// A handle to the set of stores registered for a specific canister domain.
151///
152
153pub(crate) struct Db<C: CanisterKind> {
154    store: &'static LocalKey<StoreRegistry>,
155    entity_runtime_hooks: &'static [EntityRuntimeHooks<C>],
156    _marker: PhantomData<C>,
157}
158
159impl<C: CanisterKind> Db<C> {
160    /// Construct a db handle without per-entity runtime hooks.
161    #[must_use]
162    #[cfg(test)]
163    pub(crate) const fn new(store: &'static LocalKey<StoreRegistry>) -> Self {
164        Self::new_with_hooks(store, &[])
165    }
166
167    /// Construct a db handle with explicit per-entity runtime hook wiring.
168    #[must_use]
169    pub(crate) const fn new_with_hooks(
170        store: &'static LocalKey<StoreRegistry>,
171        entity_runtime_hooks: &'static [EntityRuntimeHooks<C>],
172    ) -> Self {
173        #[cfg(debug_assertions)]
174        {
175            let _ = crate::db::commit::debug_assert_unique_runtime_hook_tags(entity_runtime_hooks);
176        }
177
178        Self {
179            store,
180            entity_runtime_hooks,
181            _marker: PhantomData,
182        }
183    }
184
185    #[must_use]
186    pub(in crate::db) const fn context<E>(&self) -> Context<'_, E>
187    where
188        E: EntityKind<Canister = C> + EntityValue,
189    {
190        Context::new(self)
191    }
192
193    /// Resolve one named store after enforcing startup recovery.
194    pub(in crate::db) fn recovered_store(&self, path: &str) -> Result<StoreHandle, InternalError> {
195        ensure_recovered(self)?;
196
197        self.store_handle(path)
198    }
199
200    // Resolve one named store without re-entering recovery.
201    //
202    // Internal commit/recovery paths already own recovery authority and must
203    // not bounce back through `ensure_recovered`, or they can recurse through
204    // replay/rebuild preparation.
205    fn store_handle(&self, path: &str) -> Result<StoreHandle, InternalError> {
206        self.with_store_registry(|registry| registry.try_get_store(path))
207    }
208
209    /// Ensure startup/in-progress commit recovery has been applied.
210    pub(crate) fn ensure_recovered_state(&self) -> Result<(), InternalError> {
211        ensure_recovered(self)
212    }
213
214    /// Execute one closure against the registered store set.
215    pub(crate) fn with_store_registry<R>(&self, f: impl FnOnce(&StoreRegistry) -> R) -> R {
216        self.store.with(|reg| f(reg))
217    }
218
219    /// Build one named-store resolver for executor/runtime helpers.
220    #[must_use]
221    pub(in crate::db) fn store_resolver(&self) -> executor::StoreResolver<'_> {
222        executor::StoreResolver::new(self)
223    }
224
225    /// Mark every registered index store as fully rebuilt and query-visible.
226    ///
227    /// Recovery restores visibility only after rebuild and post-recovery
228    /// integrity validation complete successfully.
229    pub(in crate::db) fn mark_all_registered_index_stores_ready(&self) {
230        self.with_store_registry(|registry| {
231            for (_, handle) in registry.iter() {
232                handle.mark_index_ready();
233            }
234        });
235    }
236
237    /// Build one storage diagnostics report for registered stores/entities.
238    pub(crate) fn storage_report(
239        &self,
240        name_to_path: &[(&'static str, &'static str)],
241    ) -> Result<StorageReport, InternalError> {
242        diagnostics::storage_report(self, name_to_path)
243    }
244
245    /// Build one storage diagnostics report using default entity-path labels.
246    pub(crate) fn storage_report_default(&self) -> Result<StorageReport, InternalError> {
247        diagnostics::storage_report_default(self)
248    }
249
250    /// Build one integrity scan report for registered stores/entities.
251    pub(crate) fn integrity_report(&self) -> Result<IntegrityReport, InternalError> {
252        diagnostics::integrity_report(self)
253    }
254
255    pub(in crate::db) fn prepare_row_commit_op(
256        &self,
257        op: &CommitRowOp,
258    ) -> Result<PreparedRowCommitOp, InternalError> {
259        let hooks = self.runtime_hook_for_entity_path(op.entity_path.as_ref())?;
260        let store = self.store_handle(hooks.store_path)?;
261
262        (hooks.prepare_row_commit_with_readers)(self, op, &store, &store)
263    }
264
265    /// Execute one bounded migration run using explicit row-op plan contracts.
266    pub(crate) fn execute_migration_plan(
267        &self,
268        plan: &migration::MigrationPlan,
269        max_steps: usize,
270    ) -> Result<migration::MigrationRunOutcome, InternalError> {
271        migration::execute_migration_plan(self, plan, max_steps)
272    }
273
274    // Validate strong relation constraints for delete-selected target keys.
275    pub(crate) fn validate_delete_strong_relations(
276        &self,
277        target_path: &str,
278        deleted_target_keys: &BTreeSet<RawDataKey>,
279    ) -> Result<(), InternalError> {
280        // Skip hook traversal when no target keys were deleted.
281        if deleted_target_keys.is_empty() {
282            return Ok(());
283        }
284
285        // Delegate delete-side relation validation to each entity runtime hook.
286        for hooks in self.entity_runtime_hooks {
287            if !model_has_strong_relations_to_target(hooks.model, target_path) {
288                continue;
289            }
290
291            (hooks.validate_delete_strong_relations)(self, target_path, deleted_target_keys)?;
292        }
293
294        Ok(())
295    }
296}
297
298impl<C: CanisterKind> Db<C> {
299    /// Return whether this db has any registered runtime hook callbacks.
300    #[must_use]
301    pub(crate) const fn has_runtime_hooks(&self) -> bool {
302        commit::has_runtime_hooks(self.entity_runtime_hooks)
303    }
304
305    /// Return one deterministic list of registered runtime entity names.
306    #[must_use]
307    pub(crate) fn runtime_entity_names(&self) -> Vec<String> {
308        self.entity_runtime_hooks
309            .iter()
310            .map(|hooks| hooks.model.name().to_string())
311            .collect()
312    }
313
314    // Resolve exactly one runtime hook for a persisted entity tag.
315    // Duplicate matches are treated as store invariants.
316    pub(crate) fn runtime_hook_for_entity_tag(
317        &self,
318        entity_tag: EntityTag,
319    ) -> Result<&EntityRuntimeHooks<C>, InternalError> {
320        commit::resolve_runtime_hook_by_tag(self.entity_runtime_hooks, entity_tag)
321    }
322
323    // Resolve exactly one runtime hook for a persisted entity path.
324    // Duplicate matches are treated as store invariants.
325    pub(crate) fn runtime_hook_for_entity_path(
326        &self,
327        entity_path: &str,
328    ) -> Result<&EntityRuntimeHooks<C>, InternalError> {
329        commit::resolve_runtime_hook_by_path(self.entity_runtime_hooks, entity_path)
330    }
331}
332
333impl<C: CanisterKind> Copy for Db<C> {}
334
335impl<C: CanisterKind> Clone for Db<C> {
336    fn clone(&self) -> Self {
337        *self
338    }
339}