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::{
8        Db,
9        commit::{
10            CommitRowOp, CommitSchemaFingerprint, PreparedRowCommitOp,
11            commit_schema_fingerprint_for_entity, prepare_row_commit_for_entity,
12        },
13        relation::StrongRelationDeleteValidateFn,
14    },
15    error::InternalError,
16    traits::{CanisterKind, EntityIdentity, EntityKind, EntityValue},
17};
18
19///
20/// EntityRuntimeHooks
21///
22/// Per-entity runtime callbacks used for commit preparation and delete-side
23/// strong relation validation.
24///
25
26pub struct EntityRuntimeHooks<C: CanisterKind> {
27    pub(crate) entity_name: &'static str,
28    pub(crate) entity_path: &'static str,
29    pub(in crate::db) commit_schema_fingerprint: fn() -> CommitSchemaFingerprint,
30    pub(in crate::db) prepare_row_commit:
31        fn(&Db<C>, &CommitRowOp) -> Result<PreparedRowCommitOp, InternalError>,
32    pub(crate) validate_delete_strong_relations: StrongRelationDeleteValidateFn<C>,
33}
34
35impl<C: CanisterKind> EntityRuntimeHooks<C> {
36    #[must_use]
37    /// Build one runtime hook contract for a concrete runtime entity.
38    pub(in crate::db) const fn new(
39        entity_name: &'static str,
40        entity_path: &'static str,
41        commit_schema_fingerprint: fn() -> CommitSchemaFingerprint,
42        prepare_row_commit: fn(&Db<C>, &CommitRowOp) -> Result<PreparedRowCommitOp, InternalError>,
43        validate_delete_strong_relations: StrongRelationDeleteValidateFn<C>,
44    ) -> Self {
45        Self {
46            entity_name,
47            entity_path,
48            commit_schema_fingerprint,
49            prepare_row_commit,
50            validate_delete_strong_relations,
51        }
52    }
53
54    #[must_use]
55    /// Build runtime hooks from one entity type.
56    pub const fn for_entity<E>() -> Self
57    where
58        E: EntityKind<Canister = C> + EntityValue,
59    {
60        Self::new(
61            <E as EntityIdentity>::ENTITY_NAME,
62            E::PATH,
63            commit_schema_fingerprint_for_runtime_entity::<E>,
64            prepare_row_commit_for_entity::<E>,
65            crate::db::relation::validate_delete_strong_relations_for_source::<E>,
66        )
67    }
68}
69
70fn commit_schema_fingerprint_for_runtime_entity<E>() -> CommitSchemaFingerprint
71where
72    E: EntityKind,
73{
74    commit_schema_fingerprint_for_entity::<E>()
75}
76
77/// Return whether this db has any registered runtime hook callbacks.
78#[must_use]
79pub(in crate::db) const fn has_runtime_hooks<C: CanisterKind>(
80    entity_runtime_hooks: &[EntityRuntimeHooks<C>],
81) -> bool {
82    !entity_runtime_hooks.is_empty()
83}
84
85// Resolve exactly one runtime hook for a persisted entity name.
86// Duplicate matches are treated as store invariants.
87pub(in crate::db) fn resolve_runtime_hook_by_name<'a, C: CanisterKind>(
88    entity_runtime_hooks: &'a [EntityRuntimeHooks<C>],
89    entity_name: &str,
90) -> Result<&'a EntityRuntimeHooks<C>, InternalError> {
91    let mut matched = None;
92    for hooks in entity_runtime_hooks {
93        if hooks.entity_name != entity_name {
94            continue;
95        }
96
97        if matched.is_some() {
98            return Err(InternalError::store_invariant(format!(
99                "duplicate runtime hooks for entity name '{entity_name}'"
100            )));
101        }
102
103        matched = Some(hooks);
104    }
105
106    matched.ok_or_else(|| {
107        InternalError::store_unsupported(format!(
108            "unsupported entity name in data store: '{entity_name}'"
109        ))
110    })
111}
112
113// Resolve exactly one runtime hook for a persisted entity path.
114// Duplicate matches are treated as store invariants.
115pub(in crate::db) fn resolve_runtime_hook_by_path<'a, C: CanisterKind>(
116    entity_runtime_hooks: &'a [EntityRuntimeHooks<C>],
117    entity_path: &str,
118) -> Result<&'a EntityRuntimeHooks<C>, InternalError> {
119    let mut matched = None;
120    for hooks in entity_runtime_hooks {
121        if hooks.entity_path != entity_path {
122            continue;
123        }
124
125        if matched.is_some() {
126            return Err(InternalError::store_invariant(format!(
127                "duplicate runtime hooks for entity path '{entity_path}'"
128            )));
129        }
130
131        matched = Some(hooks);
132    }
133
134    matched.ok_or_else(|| InternalError::unsupported_entity_path(entity_path))
135}