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