1use crate::{
8 db::{
9 DbSession, PersistedRow, WriteBatchResponse,
10 data::{FieldSlot, StructuralPatch},
11 executor::MutationMode,
12 schema::{AcceptedFieldAbsencePolicy, AcceptedRowLayoutRuntimeDescriptor},
13 },
14 error::InternalError,
15 traits::{CanisterKind, EntityCreateInput, EntityValue},
16 value::Value,
17};
18
19fn append_accepted_structural_patch_field(
24 entity_path: &'static str,
25 descriptor: &AcceptedRowLayoutRuntimeDescriptor<'_>,
26 patch: StructuralPatch,
27 field_name: &str,
28 value: Value,
29) -> Result<StructuralPatch, InternalError> {
30 let slot = descriptor
31 .field_slot_index_by_name(field_name)
32 .ok_or_else(|| InternalError::mutation_structural_field_unknown(entity_path, field_name))?;
33
34 Ok(patch.set(FieldSlot::from_validated_index(slot), value))
35}
36
37fn validate_structural_patch_absence_policy<E>(
43 descriptor: &AcceptedRowLayoutRuntimeDescriptor<'_>,
44 patch: &StructuralPatch,
45 mode: MutationMode,
46) -> Result<(), InternalError>
47where
48 E: PersistedRow + EntityValue,
49{
50 if matches!(mode, MutationMode::Update) {
51 return Ok(());
52 }
53 if patch_explicitly_writes_generated_field::<E>(descriptor, patch) {
54 return Ok(());
55 }
56
57 let mut provided_slots = vec![false; descriptor.required_slot_count()];
58 for entry in patch.entries() {
59 let slot = entry.slot().index();
60 if slot < provided_slots.len() {
61 provided_slots[slot] = true;
62 }
63 }
64
65 for field in descriptor.fields() {
69 let slot = usize::from(field.slot().get());
70 if provided_slots.get(slot).copied().unwrap_or(false) {
71 continue;
72 }
73
74 if matches!(field.absence_policy(), AcceptedFieldAbsencePolicy::Required) {
75 return Err(
76 InternalError::mutation_structural_patch_required_field_missing(
77 E::PATH,
78 field.name(),
79 ),
80 );
81 }
82 }
83
84 Ok(())
85}
86
87fn patch_explicitly_writes_generated_field<E>(
93 descriptor: &AcceptedRowLayoutRuntimeDescriptor<'_>,
94 patch: &StructuralPatch,
95) -> bool
96where
97 E: PersistedRow + EntityValue,
98{
99 patch.entries().iter().any(|entry| {
100 let slot = entry.slot().index();
101 let Some(accepted_field) = descriptor.field_for_slot_index(slot) else {
102 return false;
103 };
104 let Some(write_policy) =
105 descriptor.generated_write_policy_for_accepted_slot_index(E::MODEL, slot)
106 else {
107 return false;
108 };
109
110 write_policy.insert_generation().is_some()
111 && accepted_field.name() != descriptor.primary_key_name()
112 })
113}
114
115impl<C: CanisterKind> DbSession<C> {
116 pub fn insert<E>(&self, entity: E) -> Result<E, InternalError>
118 where
119 E: PersistedRow<Canister = C> + EntityValue,
120 {
121 self.execute_save_entity(|save| save.insert(entity))
122 }
123
124 pub fn create<I>(&self, input: I) -> Result<I::Entity, InternalError>
126 where
127 I: EntityCreateInput,
128 I::Entity: PersistedRow<Canister = C> + EntityValue,
129 {
130 self.execute_save_entity(|save| save.create(input))
131 }
132
133 pub fn insert_many_atomic<E>(
139 &self,
140 entities: impl IntoIterator<Item = E>,
141 ) -> Result<WriteBatchResponse<E>, InternalError>
142 where
143 E: PersistedRow<Canister = C> + EntityValue,
144 {
145 self.execute_save_batch(|save| save.insert_many_atomic(entities))
146 }
147
148 pub fn insert_many_non_atomic<E>(
152 &self,
153 entities: impl IntoIterator<Item = E>,
154 ) -> Result<WriteBatchResponse<E>, InternalError>
155 where
156 E: PersistedRow<Canister = C> + EntityValue,
157 {
158 self.execute_save_batch(|save| save.insert_many_non_atomic(entities))
159 }
160
161 pub fn replace<E>(&self, entity: E) -> Result<E, InternalError>
163 where
164 E: PersistedRow<Canister = C> + EntityValue,
165 {
166 self.execute_save_entity(|save| save.replace(entity))
167 }
168
169 pub fn mutate_structural<E>(
175 &self,
176 key: E::Key,
177 patch: StructuralPatch,
178 mode: MutationMode,
179 ) -> Result<E, InternalError>
180 where
181 E: PersistedRow<Canister = C> + EntityValue,
182 {
183 let accepted_schema = self.ensure_accepted_schema_snapshot::<E>()?;
184 let descriptor =
185 AcceptedRowLayoutRuntimeDescriptor::from_accepted_schema(&accepted_schema)?;
186 descriptor.generated_compatible_row_shape_for_model(E::MODEL)?;
187 validate_structural_patch_absence_policy::<E>(&descriptor, &patch, mode)?;
188
189 self.execute_save_with_checked_accepted_schema(
190 |save| save.apply_structural_mutation(mode, key, patch),
191 std::convert::identity,
192 )
193 }
194
195 pub fn structural_patch<E, I, S>(&self, fields: I) -> Result<StructuralPatch, InternalError>
202 where
203 E: PersistedRow<Canister = C> + EntityValue,
204 I: IntoIterator<Item = (S, Value)>,
205 S: AsRef<str>,
206 {
207 let accepted_schema = self.ensure_accepted_schema_snapshot::<E>()?;
208 let descriptor =
209 AcceptedRowLayoutRuntimeDescriptor::from_accepted_schema(&accepted_schema)?;
210 descriptor.generated_compatible_row_shape_for_model(E::MODEL)?;
211 let mut patch = StructuralPatch::new();
212
213 for (field_name, value) in fields {
217 let field_name = field_name.as_ref();
218 patch = append_accepted_structural_patch_field(
219 E::PATH,
220 &descriptor,
221 patch,
222 field_name,
223 value,
224 )?;
225 }
226
227 Ok(patch)
228 }
229
230 #[cfg(test)]
236 pub(in crate::db) fn replace_structural<E>(
237 &self,
238 key: E::Key,
239 patch: StructuralPatch,
240 ) -> Result<E, InternalError>
241 where
242 E: PersistedRow<Canister = C> + EntityValue,
243 {
244 self.mutate_structural(key, patch, MutationMode::Replace)
245 }
246
247 pub fn replace_many_atomic<E>(
253 &self,
254 entities: impl IntoIterator<Item = E>,
255 ) -> Result<WriteBatchResponse<E>, InternalError>
256 where
257 E: PersistedRow<Canister = C> + EntityValue,
258 {
259 self.execute_save_batch(|save| save.replace_many_atomic(entities))
260 }
261
262 pub fn replace_many_non_atomic<E>(
266 &self,
267 entities: impl IntoIterator<Item = E>,
268 ) -> Result<WriteBatchResponse<E>, InternalError>
269 where
270 E: PersistedRow<Canister = C> + EntityValue,
271 {
272 self.execute_save_batch(|save| save.replace_many_non_atomic(entities))
273 }
274
275 pub fn update<E>(&self, entity: E) -> Result<E, InternalError>
277 where
278 E: PersistedRow<Canister = C> + EntityValue,
279 {
280 self.execute_save_entity(|save| save.update(entity))
281 }
282
283 #[cfg(test)]
289 pub(in crate::db) fn insert_structural<E>(
290 &self,
291 key: E::Key,
292 patch: StructuralPatch,
293 ) -> Result<E, InternalError>
294 where
295 E: PersistedRow<Canister = C> + EntityValue,
296 {
297 self.mutate_structural(key, patch, MutationMode::Insert)
298 }
299
300 #[cfg(test)]
306 pub(in crate::db) fn update_structural<E>(
307 &self,
308 key: E::Key,
309 patch: StructuralPatch,
310 ) -> Result<E, InternalError>
311 where
312 E: PersistedRow<Canister = C> + EntityValue,
313 {
314 self.mutate_structural(key, patch, MutationMode::Update)
315 }
316
317 pub fn update_many_atomic<E>(
323 &self,
324 entities: impl IntoIterator<Item = E>,
325 ) -> Result<WriteBatchResponse<E>, InternalError>
326 where
327 E: PersistedRow<Canister = C> + EntityValue,
328 {
329 self.execute_save_batch(|save| save.update_many_atomic(entities))
330 }
331
332 pub fn update_many_non_atomic<E>(
336 &self,
337 entities: impl IntoIterator<Item = E>,
338 ) -> Result<WriteBatchResponse<E>, InternalError>
339 where
340 E: PersistedRow<Canister = C> + EntityValue,
341 {
342 self.execute_save_batch(|save| save.update_many_non_atomic(entities))
343 }
344}