Skip to main content

icydb_core/db/session/
write.rs

1//! Module: db::session::write
2//! Responsibility: session-owned typed write APIs for insert, replace, update,
3//! and structural mutation entrypoints over the shared save pipeline.
4//! Does not own: commit staging, mutation execution, or persistence encoding.
5//! Boundary: keeps public session write semantics above the executor save surface.
6
7use crate::{
8    db::{DbSession, PersistedRow, WriteBatchResponse, data::UpdatePatch, executor::MutationMode},
9    error::InternalError,
10    traits::{CanisterKind, EntityCreateInput, EntityValue},
11};
12
13impl<C: CanisterKind> DbSession<C> {
14    /// Insert one entity row.
15    pub fn insert<E>(&self, entity: E) -> Result<E, InternalError>
16    where
17        E: PersistedRow<Canister = C> + EntityValue,
18    {
19        self.execute_save_entity(|save| save.insert(entity))
20    }
21
22    /// Insert one authored typed input.
23    pub fn create<I>(&self, input: I) -> Result<I::Entity, InternalError>
24    where
25        I: EntityCreateInput,
26        I::Entity: PersistedRow<Canister = C> + EntityValue,
27    {
28        self.execute_save_entity(|save| save.create(input))
29    }
30
31    /// Insert a single-entity-type batch atomically in one commit window.
32    ///
33    /// If any item fails pre-commit validation, no row in the batch is persisted.
34    ///
35    /// This API is not a multi-entity transaction surface.
36    pub fn insert_many_atomic<E>(
37        &self,
38        entities: impl IntoIterator<Item = E>,
39    ) -> Result<WriteBatchResponse<E>, InternalError>
40    where
41        E: PersistedRow<Canister = C> + EntityValue,
42    {
43        self.execute_save_batch(|save| save.insert_many_atomic(entities))
44    }
45
46    /// Insert a batch with explicitly non-atomic semantics.
47    ///
48    /// WARNING: fail-fast and non-atomic. Earlier inserts may commit before an error.
49    pub fn insert_many_non_atomic<E>(
50        &self,
51        entities: impl IntoIterator<Item = E>,
52    ) -> Result<WriteBatchResponse<E>, InternalError>
53    where
54        E: PersistedRow<Canister = C> + EntityValue,
55    {
56        self.execute_save_batch(|save| save.insert_many_non_atomic(entities))
57    }
58
59    /// Replace one existing entity row.
60    pub fn replace<E>(&self, entity: E) -> Result<E, InternalError>
61    where
62        E: PersistedRow<Canister = C> + EntityValue,
63    {
64        self.execute_save_entity(|save| save.replace(entity))
65    }
66
67    /// Apply one structural mutation under one explicit write-mode contract.
68    ///
69    /// This is the public core session boundary for structural writes:
70    /// callers provide the key, field patch, and intended mutation mode, and
71    /// the session routes that through the shared structural mutation pipeline.
72    pub fn mutate_structural<E>(
73        &self,
74        key: E::Key,
75        patch: UpdatePatch,
76        mode: MutationMode,
77    ) -> Result<E, InternalError>
78    where
79        E: PersistedRow<Canister = C> + EntityValue,
80    {
81        self.execute_save_entity(|save| save.apply_structural_mutation(mode, key, patch))
82    }
83
84    /// Apply one structural replacement, inserting if missing.
85    ///
86    /// Replace semantics still do not inherit omitted fields from the old row.
87    /// Missing fields must materialize through explicit defaults or managed
88    /// field preflight, or the write fails closed.
89    #[cfg(test)]
90    pub(in crate::db) fn replace_structural<E>(
91        &self,
92        key: E::Key,
93        patch: UpdatePatch,
94    ) -> Result<E, InternalError>
95    where
96        E: PersistedRow<Canister = C> + EntityValue,
97    {
98        self.mutate_structural(key, patch, MutationMode::Replace)
99    }
100
101    /// Replace a single-entity-type batch atomically in one commit window.
102    ///
103    /// If any item fails pre-commit validation, no row in the batch is persisted.
104    ///
105    /// This API is not a multi-entity transaction surface.
106    pub fn replace_many_atomic<E>(
107        &self,
108        entities: impl IntoIterator<Item = E>,
109    ) -> Result<WriteBatchResponse<E>, InternalError>
110    where
111        E: PersistedRow<Canister = C> + EntityValue,
112    {
113        self.execute_save_batch(|save| save.replace_many_atomic(entities))
114    }
115
116    /// Replace a batch with explicitly non-atomic semantics.
117    ///
118    /// WARNING: fail-fast and non-atomic. Earlier replaces may commit before an error.
119    pub fn replace_many_non_atomic<E>(
120        &self,
121        entities: impl IntoIterator<Item = E>,
122    ) -> Result<WriteBatchResponse<E>, InternalError>
123    where
124        E: PersistedRow<Canister = C> + EntityValue,
125    {
126        self.execute_save_batch(|save| save.replace_many_non_atomic(entities))
127    }
128
129    /// Update one existing entity row.
130    pub fn update<E>(&self, entity: E) -> Result<E, InternalError>
131    where
132        E: PersistedRow<Canister = C> + EntityValue,
133    {
134        self.execute_save_entity(|save| save.update(entity))
135    }
136
137    /// Apply one structural insert from a patch-defined after-image.
138    ///
139    /// Insert semantics no longer require a pre-built full row image.
140    /// Missing fields still fail closed unless derive-owned materialization can
141    /// supply them through explicit defaults or managed-field preflight.
142    #[cfg(test)]
143    pub(in crate::db) fn insert_structural<E>(
144        &self,
145        key: E::Key,
146        patch: UpdatePatch,
147    ) -> Result<E, InternalError>
148    where
149        E: PersistedRow<Canister = C> + EntityValue,
150    {
151        self.mutate_structural(key, patch, MutationMode::Insert)
152    }
153
154    /// Apply one structural field patch to an existing entity row.
155    ///
156    /// This session-owned boundary keeps structural mutation out of the raw
157    /// executor surface while still routing through the same typed save
158    /// preflight before commit staging.
159    #[cfg(test)]
160    pub(in crate::db) fn update_structural<E>(
161        &self,
162        key: E::Key,
163        patch: UpdatePatch,
164    ) -> Result<E, InternalError>
165    where
166        E: PersistedRow<Canister = C> + EntityValue,
167    {
168        self.mutate_structural(key, patch, MutationMode::Update)
169    }
170
171    /// Update a single-entity-type batch atomically in one commit window.
172    ///
173    /// If any item fails pre-commit validation, no row in the batch is persisted.
174    ///
175    /// This API is not a multi-entity transaction surface.
176    pub fn update_many_atomic<E>(
177        &self,
178        entities: impl IntoIterator<Item = E>,
179    ) -> Result<WriteBatchResponse<E>, InternalError>
180    where
181        E: PersistedRow<Canister = C> + EntityValue,
182    {
183        self.execute_save_batch(|save| save.update_many_atomic(entities))
184    }
185
186    /// Update a batch with explicitly non-atomic semantics.
187    ///
188    /// WARNING: fail-fast and non-atomic. Earlier updates may commit before an error.
189    pub fn update_many_non_atomic<E>(
190        &self,
191        entities: impl IntoIterator<Item = E>,
192    ) -> Result<WriteBatchResponse<E>, InternalError>
193    where
194        E: PersistedRow<Canister = C> + EntityValue,
195    {
196        self.execute_save_batch(|save| save.update_many_non_atomic(entities))
197    }
198}