icydb_core/db/executor/
save.rs

1use crate::{
2    Error,
3    db::{
4        Db,
5        executor::ExecutorError,
6        query::{SaveMode, SaveQuery},
7        store::DataKey,
8    },
9    deserialize,
10    obs::metrics,
11    serialize,
12    traits::EntityKind,
13    visitor::{sanitize, validate},
14};
15use std::marker::PhantomData;
16
17///
18/// SaveExecutor
19///
20
21#[derive(Clone, Copy)]
22pub struct SaveExecutor<E: EntityKind> {
23    db: Db<E::Canister>,
24    debug: bool,
25    _marker: PhantomData<E>,
26}
27
28impl<E: EntityKind> SaveExecutor<E> {
29    #[must_use]
30    pub const fn new(db: Db<E::Canister>, debug: bool) -> Self {
31        Self {
32            db,
33            debug,
34            _marker: PhantomData,
35        }
36    }
37
38    // debug
39    #[must_use]
40    pub const fn debug(mut self) -> Self {
41        self.debug = true;
42        self
43    }
44
45    ///
46    /// EXECUTION METHODS
47    ///
48
49    /// Insert a brand-new entity (errors if the key already exists).
50    pub fn insert(&self, entity: E) -> Result<E, Error> {
51        let entity = self.save_entity(SaveMode::Insert, entity)?;
52
53        Ok(entity)
54    }
55
56    /// Insert a new view, returning the stored view.
57    pub fn insert_view<V>(&self, view: E::ViewType) -> Result<E::ViewType, Error> {
58        let entity = E::from_view(view);
59        let saved_view = self.insert(entity)?.to_view();
60
61        Ok(saved_view)
62    }
63
64    /// Update an existing entity (errors if it does not exist).
65    pub fn update(&self, entity: E) -> Result<E, Error> {
66        let entity = self.save_entity(SaveMode::Update, entity)?;
67
68        Ok(entity)
69    }
70
71    /// Update an existing view (errors if it does not exist).
72    pub fn update_view<V>(&self, view: E::ViewType) -> Result<E::ViewType, Error> {
73        let entity = E::from_view(view);
74        let saved_view = self.update(entity)?.to_view();
75
76        Ok(saved_view)
77    }
78
79    /// Replace an entity, inserting if missing.
80    pub fn replace(&self, entity: E) -> Result<E, Error> {
81        let entity = self.save_entity(SaveMode::Replace, entity)?;
82
83        Ok(entity)
84    }
85
86    /// Replace a view, inserting if missing.
87    pub fn replace_view<V>(&self, view: E::ViewType) -> Result<E::ViewType, Error> {
88        let entity = E::from_view(view);
89        let saved_view = self.replace(entity)?.to_view();
90
91        Ok(saved_view)
92    }
93
94    // execute
95    // serializes the save query to pass to save_entity
96    /// Execute a serialized save query.
97    pub fn execute(&self, query: SaveQuery) -> Result<E, Error> {
98        let e: E = deserialize(&query.bytes)?;
99        let entity = self.save_entity(query.mode, e)?;
100
101        Ok(entity)
102    }
103
104    // save_entity
105    fn save_entity(&self, mode: SaveMode, mut entity: E) -> Result<E, Error> {
106        let mut span = metrics::Span::<E>::new(metrics::ExecKind::Save);
107        let key = entity.key();
108        let ctx = self.db.context::<E>();
109
110        // sanitize & validate
111        sanitize(&mut entity);
112        validate(&entity)?;
113
114        // debug
115        //   debug!(self.debug, "query.{mode}: {} ({key:?}) ", E::PATH);
116
117        //
118        // match save mode
119        // on Update and Replace compare old and new data
120        //
121
122        let data_key = DataKey::new::<E>(key);
123        let old_result = ctx.with_store(|store| store.get(&data_key))?;
124
125        // did anything change?
126        let old = match (mode, old_result) {
127            (SaveMode::Insert | SaveMode::Replace, None) => None,
128
129            (SaveMode::Update | SaveMode::Replace, Some(old_bytes)) => {
130                let old = deserialize::<E>(&old_bytes)?;
131                Some(old)
132            }
133
134            // invalid
135            (SaveMode::Insert, Some(_)) => return Err(ExecutorError::KeyExists(data_key))?,
136            (SaveMode::Update, None) => return Err(ExecutorError::KeyNotFound(data_key))?,
137        };
138
139        // now we can serialize
140        let bytes = serialize(&entity)?;
141
142        // replace indexes, fail if there are any unique violations
143        self.replace_indexes(old.as_ref(), &entity)?;
144
145        // insert data row
146        ctx.with_store_mut(|store| store.insert(data_key.clone(), bytes))?;
147        span.set_rows(1);
148
149        Ok(entity)
150    }
151
152    // replace_indexes: two-phase (validate, then mutate) to avoid partial updates
153    fn replace_indexes(&self, old: Option<&E>, new: &E) -> Result<(), Error> {
154        use crate::db::store::IndexKey;
155
156        // Phase 1: validate uniqueness for all indexes without mutating
157        for index in E::INDEXES {
158            // Only check when we can compute the new key and the index is unique
159            if index.unique
160                && let Some(new_idx_key) = IndexKey::new(new, index)
161            {
162                let store = self.db.with_index(|reg| reg.try_get_store(index.store))?;
163                let violates = store.with_borrow(|s| {
164                    if let Some(existing) = s.get(&new_idx_key) {
165                        let new_entity_key = new.key();
166                        !existing.contains(&new_entity_key) && !existing.is_empty()
167                    } else {
168                        false
169                    }
170                });
171                if violates {
172                    // Count the unique violation just like the store-level check would have
173                    metrics::with_state_mut(|m| {
174                        metrics::record_unique_violation_for::<E>(m);
175                    });
176
177                    return Err(ExecutorError::index_violation(E::PATH, index.fields).into());
178                }
179            }
180        }
181
182        // Phase 2: apply changes (remove old, insert new) for each index
183        for index in E::INDEXES {
184            let store = self.db.with_index(|reg| reg.try_get_store(index.store))?;
185            store.with_borrow_mut(|s| {
186                if let Some(old) = old {
187                    s.remove_index_entry(old, index);
188                }
189                s.insert_index_entry(new, index)?;
190
191                Ok::<(), Error>(())
192            })?;
193        }
194
195        Ok(())
196    }
197}