Skip to main content

icydb_core/db/session/
write.rs

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