Skip to main content

icydb_core/db/commit/
hooks.rs

1//! Module: commit::hooks
2//! Responsibility: runtime hook contracts and resolution for commit/recovery orchestration.
3//! Does not own: planner semantics, executor branching, or relation invariants.
4//! Boundary: db root delegates hook discovery and hook contract shape to commit.
5
6use crate::{
7    db::index::{StructuralIndexEntryReader, StructuralPrimaryRowReader},
8    db::{
9        Db,
10        commit::{
11            CommitRowOp, PreparedRowCommitOp, prepare_row_commit_for_entity,
12            prepare_row_commit_for_entity_with_structural_readers,
13        },
14        relation::StrongRelationDeleteValidateFn,
15    },
16    error::InternalError,
17    model::entity::EntityModel,
18    traits::{CanisterKind, EntityKind, EntityValue, Path},
19    types::EntityTag,
20};
21
22// Runtime hook callback used when commit preparation must read existing primary
23// rows and index entries through structural reader facades.
24type PrepareRowCommitWithReadersFn<C> = fn(
25    &Db<C>,
26    &CommitRowOp,
27    &dyn StructuralPrimaryRowReader,
28    &dyn StructuralIndexEntryReader,
29) -> Result<PreparedRowCommitOp, InternalError>;
30
31// Runtime hook callback used for the normal row-commit preparation path.
32type PrepareRowCommitFn<C> = fn(&Db<C>, &CommitRowOp) -> Result<PreparedRowCommitOp, InternalError>;
33
34///
35/// EntityRuntimeHooks
36///
37/// Per-entity runtime callbacks used for commit preparation and delete-side
38/// strong relation validation.
39/// Keeps entity and store routing metadata alongside callback roots so runtime
40/// recovery and structural preflight can resolve the right store without
41/// reintroducing typed entity parameters.
42///
43
44pub struct EntityRuntimeHooks<C: CanisterKind> {
45    pub(crate) entity_tag: EntityTag,
46    pub(crate) model: &'static EntityModel,
47    pub(crate) entity_path: &'static str,
48    pub(crate) store_path: &'static str,
49    pub(in crate::db) prepare_row_commit: PrepareRowCommitFn<C>,
50    pub(in crate::db) prepare_row_commit_with_readers: PrepareRowCommitWithReadersFn<C>,
51    pub(crate) validate_delete_strong_relations: StrongRelationDeleteValidateFn<C>,
52}
53
54impl<C: CanisterKind> EntityRuntimeHooks<C> {
55    /// Build one runtime hook contract for a concrete runtime entity.
56    #[must_use]
57    pub(in crate::db) const fn new(
58        entity_tag: EntityTag,
59        model: &'static EntityModel,
60        entity_path: &'static str,
61        store_path: &'static str,
62        prepare_row_commit: PrepareRowCommitFn<C>,
63        prepare_row_commit_with_readers: PrepareRowCommitWithReadersFn<C>,
64        validate_delete_strong_relations: StrongRelationDeleteValidateFn<C>,
65    ) -> Self {
66        Self {
67            entity_tag,
68            model,
69            entity_path,
70            store_path,
71            prepare_row_commit,
72            prepare_row_commit_with_readers,
73            validate_delete_strong_relations,
74        }
75    }
76
77    /// Build runtime hooks from one entity type.
78    #[must_use]
79    pub const fn for_entity<E>() -> Self
80    where
81        E: EntityKind<Canister = C> + EntityValue,
82    {
83        Self::new(
84            E::ENTITY_TAG,
85            E::MODEL,
86            E::PATH,
87            E::Store::PATH,
88            prepare_row_commit_for_entity::<E>,
89            prepare_row_commit_for_entity_with_structural_readers::<E>,
90            crate::db::relation::validate_delete_strong_relations_for_source::<E>,
91        )
92    }
93}
94
95/// Return whether this db has any registered runtime hook callbacks.
96#[must_use]
97pub(in crate::db) const fn has_runtime_hooks<C: CanisterKind>(
98    entity_runtime_hooks: &[EntityRuntimeHooks<C>],
99) -> bool {
100    !entity_runtime_hooks.is_empty()
101}
102
103/// Validate that each runtime hook owns one unique entity tag.
104///
105/// This runs only in debug builds at hook table construction time so duplicate
106/// registrations fail before runtime dispatch begins.
107#[must_use]
108#[cfg(debug_assertions)]
109pub(in crate::db) const fn debug_assert_unique_runtime_hook_tags<C: CanisterKind>(
110    entity_runtime_hooks: &[EntityRuntimeHooks<C>],
111) -> bool {
112    let mut i = 0usize;
113    while i < entity_runtime_hooks.len() {
114        let mut j = i + 1;
115        while j < entity_runtime_hooks.len() {
116            if entity_runtime_hooks[i].entity_tag.value()
117                == entity_runtime_hooks[j].entity_tag.value()
118            {
119                panic!("duplicate EntityTag detected in runtime hooks");
120            }
121            j += 1;
122        }
123        i += 1;
124    }
125
126    true
127}
128
129/// Resolve exactly one runtime hook for a persisted `EntityTag`.
130/// Duplicate matches are treated as store invariants.
131pub(in crate::db) fn resolve_runtime_hook_by_tag<C: CanisterKind>(
132    entity_runtime_hooks: &[EntityRuntimeHooks<C>],
133    entity_tag: EntityTag,
134) -> Result<&EntityRuntimeHooks<C>, InternalError> {
135    let mut matched = None;
136    for hooks in entity_runtime_hooks {
137        if hooks.entity_tag != entity_tag {
138            continue;
139        }
140
141        if matched.is_some() {
142            return Err(InternalError::duplicate_runtime_hooks_for_entity_tag(
143                entity_tag,
144            ));
145        }
146
147        matched = Some(hooks);
148    }
149
150    matched.ok_or_else(|| InternalError::unsupported_entity_tag_in_data_store(entity_tag))
151}
152
153/// Resolve exactly one runtime hook for a persisted entity path.
154/// Duplicate matches are treated as store invariants.
155pub(in crate::db) fn resolve_runtime_hook_by_path<'a, C: CanisterKind>(
156    entity_runtime_hooks: &'a [EntityRuntimeHooks<C>],
157    entity_path: &str,
158) -> Result<&'a EntityRuntimeHooks<C>, InternalError> {
159    let mut matched = None;
160    for hooks in entity_runtime_hooks {
161        if hooks.entity_path != entity_path {
162            continue;
163        }
164
165        if matched.is_some() {
166            return Err(InternalError::duplicate_runtime_hooks_for_entity_path(
167                entity_path,
168            ));
169        }
170
171        matched = Some(hooks);
172    }
173
174    matched.ok_or_else(|| InternalError::unsupported_entity_path(entity_path))
175}