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