Skip to main content

icydb_core/db/session/
write.rs

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