Skip to main content

icydb_core/db/session/
write.rs

1#[cfg(test)]
2use crate::db::{DataStore, IndexStore};
3use crate::{
4    db::{DbSession, PersistedRow, WriteBatchResponse, data::UpdatePatch, executor::MutationMode},
5    error::InternalError,
6    traits::{CanisterKind, EntityValue},
7};
8
9impl<C: CanisterKind> DbSession<C> {
10    /// Insert one entity row.
11    pub fn insert<E>(&self, entity: E) -> Result<E, InternalError>
12    where
13        E: PersistedRow<Canister = C> + EntityValue,
14    {
15        self.execute_save_entity(|save| save.insert(entity))
16    }
17
18    /// Insert a single-entity-type batch atomically in one commit window.
19    ///
20    /// If any item fails pre-commit validation, no row in the batch is persisted.
21    ///
22    /// This API is not a multi-entity transaction surface.
23    pub fn insert_many_atomic<E>(
24        &self,
25        entities: impl IntoIterator<Item = E>,
26    ) -> Result<WriteBatchResponse<E>, InternalError>
27    where
28        E: PersistedRow<Canister = C> + EntityValue,
29    {
30        self.execute_save_batch(|save| save.insert_many_atomic(entities))
31    }
32
33    /// Insert a batch with explicitly non-atomic semantics.
34    ///
35    /// WARNING: fail-fast and non-atomic. Earlier inserts may commit before an error.
36    pub fn insert_many_non_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_non_atomic(entities))
44    }
45
46    /// Replace one existing entity row.
47    pub fn replace<E>(&self, entity: E) -> Result<E, InternalError>
48    where
49        E: PersistedRow<Canister = C> + EntityValue,
50    {
51        self.execute_save_entity(|save| save.replace(entity))
52    }
53
54    /// Apply one structural mutation under one explicit write-mode contract.
55    ///
56    /// This is the public core session boundary for structural writes:
57    /// callers provide the key, field patch, and intended mutation mode, and
58    /// the session routes that through the shared structural mutation pipeline.
59    pub fn mutate_structural<E>(
60        &self,
61        key: E::Key,
62        patch: UpdatePatch,
63        mode: MutationMode,
64    ) -> Result<E, InternalError>
65    where
66        E: PersistedRow<Canister = C> + EntityValue,
67    {
68        self.execute_save_entity(|save| save.apply_structural_mutation(mode, key, patch))
69    }
70
71    /// Apply one structural full-row replacement, inserting if missing.
72    ///
73    /// Replace semantics rebuild the after-image from an empty row layout, so
74    /// omitted fields do not inherit old-row values implicitly.
75    #[allow(dead_code)]
76    pub(in crate::db) fn replace_structural<E>(
77        &self,
78        key: E::Key,
79        patch: UpdatePatch,
80    ) -> Result<E, InternalError>
81    where
82        E: PersistedRow<Canister = C> + EntityValue,
83    {
84        self.mutate_structural(key, patch, MutationMode::Replace)
85    }
86
87    /// Replace a single-entity-type batch atomically in one commit window.
88    ///
89    /// If any item fails pre-commit validation, no row in the batch is persisted.
90    ///
91    /// This API is not a multi-entity transaction surface.
92    pub fn replace_many_atomic<E>(
93        &self,
94        entities: impl IntoIterator<Item = E>,
95    ) -> Result<WriteBatchResponse<E>, InternalError>
96    where
97        E: PersistedRow<Canister = C> + EntityValue,
98    {
99        self.execute_save_batch(|save| save.replace_many_atomic(entities))
100    }
101
102    /// Replace a batch with explicitly non-atomic semantics.
103    ///
104    /// WARNING: fail-fast and non-atomic. Earlier replaces may commit before an error.
105    pub fn replace_many_non_atomic<E>(
106        &self,
107        entities: impl IntoIterator<Item = E>,
108    ) -> Result<WriteBatchResponse<E>, InternalError>
109    where
110        E: PersistedRow<Canister = C> + EntityValue,
111    {
112        self.execute_save_batch(|save| save.replace_many_non_atomic(entities))
113    }
114
115    /// Update one existing entity row.
116    pub fn update<E>(&self, entity: E) -> Result<E, InternalError>
117    where
118        E: PersistedRow<Canister = C> + EntityValue,
119    {
120        self.execute_save_entity(|save| save.update(entity))
121    }
122
123    /// Apply one structural insert from a patch-defined full after-image.
124    ///
125    /// Insert semantics require the patch to describe the full row payload
126    /// because no old-row baseline exists to fill missing fields.
127    #[allow(dead_code)]
128    pub(in crate::db) fn insert_structural<E>(
129        &self,
130        key: E::Key,
131        patch: UpdatePatch,
132    ) -> Result<E, InternalError>
133    where
134        E: PersistedRow<Canister = C> + EntityValue,
135    {
136        self.mutate_structural(key, patch, MutationMode::Insert)
137    }
138
139    /// Apply one structural field patch to an existing entity row.
140    ///
141    /// This session-owned boundary keeps structural mutation out of the raw
142    /// executor surface while still routing through the same typed save
143    /// preflight before commit staging.
144    #[allow(dead_code)]
145    pub(in crate::db) fn update_structural<E>(
146        &self,
147        key: E::Key,
148        patch: UpdatePatch,
149    ) -> Result<E, InternalError>
150    where
151        E: PersistedRow<Canister = C> + EntityValue,
152    {
153        self.mutate_structural(key, patch, MutationMode::Update)
154    }
155
156    /// Update a single-entity-type batch atomically in one commit window.
157    ///
158    /// If any item fails pre-commit validation, no row in the batch is persisted.
159    ///
160    /// This API is not a multi-entity transaction surface.
161    pub fn update_many_atomic<E>(
162        &self,
163        entities: impl IntoIterator<Item = E>,
164    ) -> Result<WriteBatchResponse<E>, InternalError>
165    where
166        E: PersistedRow<Canister = C> + EntityValue,
167    {
168        self.execute_save_batch(|save| save.update_many_atomic(entities))
169    }
170
171    /// Update a batch with explicitly non-atomic semantics.
172    ///
173    /// WARNING: fail-fast and non-atomic. Earlier updates may commit before an error.
174    pub fn update_many_non_atomic<E>(
175        &self,
176        entities: impl IntoIterator<Item = E>,
177    ) -> Result<WriteBatchResponse<E>, InternalError>
178    where
179        E: PersistedRow<Canister = C> + EntityValue,
180    {
181        self.execute_save_batch(|save| save.update_many_non_atomic(entities))
182    }
183
184    /// TEST ONLY: clear all registered data and index stores for this database.
185    #[cfg(test)]
186    #[doc(hidden)]
187    pub fn clear_stores_for_tests(&self) {
188        self.db.with_store_registry(|reg| {
189            // Test cleanup only: clearing all stores is set-like and does not
190            // depend on registry iteration order.
191            for (_, store) in reg.iter() {
192                store.with_data_mut(DataStore::clear);
193                store.with_index_mut(IndexStore::clear);
194            }
195        });
196    }
197}