1use crate::{
8 db::{
9 DbSession, PersistedRow, WriteBatchResponse,
10 data::{FieldSlot, StructuralPatch},
11 executor::MutationMode,
12 schema::{
13 AcceptedFieldAbsencePolicy, AcceptedRowLayoutRuntimeDescriptor, SchemaInfo,
14 accepted_commit_schema_fingerprint_for_model,
15 },
16 },
17 error::InternalError,
18 traits::{CanisterKind, EntityCreateInput, EntityValue},
19 value::Value,
20};
21
22fn append_accepted_structural_patch_field(
27 entity_path: &'static str,
28 descriptor: &AcceptedRowLayoutRuntimeDescriptor<'_>,
29 patch: StructuralPatch,
30 field_name: &str,
31 value: Value,
32) -> Result<StructuralPatch, InternalError> {
33 let slot = descriptor
34 .field_slot_index_by_name(field_name)
35 .ok_or_else(|| InternalError::mutation_structural_field_unknown(entity_path, field_name))?;
36
37 Ok(patch.set(FieldSlot::from_validated_index(slot), value))
38}
39
40fn validate_structural_patch_schema_policy<E>(
46 descriptor: &AcceptedRowLayoutRuntimeDescriptor<'_>,
47 patch: &StructuralPatch,
48 mode: MutationMode,
49) -> Result<(), InternalError>
50where
51 E: PersistedRow + EntityValue,
52{
53 reject_explicit_generated_fields_from_accepted_patch::<E>(descriptor, patch)?;
54
55 if matches!(mode, MutationMode::Update) {
56 return Ok(());
57 }
58
59 let mut provided_slots = vec![false; descriptor.required_slot_count()];
60 for entry in patch.entries() {
61 let slot = entry.slot().index();
62 if slot < provided_slots.len() {
63 provided_slots[slot] = true;
64 }
65 }
66
67 for field in descriptor.fields() {
71 let slot = usize::from(field.slot().get());
72 if provided_slots.get(slot).copied().unwrap_or(false) {
73 continue;
74 }
75
76 if matches!(field.absence_policy(), AcceptedFieldAbsencePolicy::Required) {
77 return Err(
78 InternalError::mutation_structural_patch_required_field_missing(
79 E::PATH,
80 field.name(),
81 ),
82 );
83 }
84 }
85
86 Ok(())
87}
88
89fn reject_explicit_generated_fields_from_accepted_patch<E>(
95 descriptor: &AcceptedRowLayoutRuntimeDescriptor<'_>,
96 patch: &StructuralPatch,
97) -> Result<(), InternalError>
98where
99 E: PersistedRow + EntityValue,
100{
101 for entry in patch.entries() {
102 let slot = entry.slot().index();
103 let Some(accepted_field) = descriptor.field_for_slot_index(slot) else {
104 continue;
105 };
106 let write_policy = accepted_field.write_policy();
107
108 if write_policy.insert_generation().is_some()
109 && accepted_field.name() != descriptor.primary_key_name()
110 {
111 return Err(InternalError::mutation_generated_field_explicit(
112 E::PATH,
113 accepted_field.name(),
114 ));
115 }
116 }
117
118 Ok(())
119}
120
121impl<C: CanisterKind> DbSession<C> {
122 pub fn insert<E>(&self, entity: E) -> Result<E, InternalError>
124 where
125 E: PersistedRow<Canister = C> + EntityValue,
126 {
127 self.execute_save_entity(|save| save.insert(entity))
128 }
129
130 pub fn create<I>(&self, input: I) -> Result<I::Entity, InternalError>
132 where
133 I: EntityCreateInput,
134 I::Entity: PersistedRow<Canister = C> + EntityValue,
135 {
136 self.execute_save_entity(|save| save.create(input))
137 }
138
139 pub fn insert_many_atomic<E>(
145 &self,
146 entities: impl IntoIterator<Item = E>,
147 ) -> Result<WriteBatchResponse<E>, InternalError>
148 where
149 E: PersistedRow<Canister = C> + EntityValue,
150 {
151 self.execute_save_batch(|save| save.insert_many_atomic(entities))
152 }
153
154 pub fn insert_many_non_atomic<E>(
158 &self,
159 entities: impl IntoIterator<Item = E>,
160 ) -> Result<WriteBatchResponse<E>, InternalError>
161 where
162 E: PersistedRow<Canister = C> + EntityValue,
163 {
164 self.execute_save_batch(|save| save.insert_many_non_atomic(entities))
165 }
166
167 pub fn replace<E>(&self, entity: E) -> Result<E, InternalError>
169 where
170 E: PersistedRow<Canister = C> + EntityValue,
171 {
172 self.execute_save_entity(|save| save.replace(entity))
173 }
174
175 pub fn mutate_structural<E>(
181 &self,
182 key: E::Key,
183 patch: StructuralPatch,
184 mode: MutationMode,
185 ) -> Result<E, InternalError>
186 where
187 E: PersistedRow<Canister = C> + EntityValue,
188 {
189 let accepted_schema = self.ensure_accepted_schema_snapshot::<E>()?;
190 let (descriptor, _) = AcceptedRowLayoutRuntimeDescriptor::from_generated_compatible_schema(
191 &accepted_schema,
192 E::MODEL,
193 )?;
194 validate_structural_patch_schema_policy::<E>(&descriptor, &patch, mode)?;
195 let accepted_schema_info =
196 SchemaInfo::from_accepted_snapshot_for_model(E::MODEL, &accepted_schema);
197 let accepted_schema_fingerprint =
198 accepted_commit_schema_fingerprint_for_model(E::MODEL, &accepted_schema)?;
199
200 let row_decode_contract = descriptor.row_decode_contract();
201 let mutation_row_decode_contract = row_decode_contract.clone();
202
203 self.execute_save_with_checked_accepted_row_contract(
204 row_decode_contract,
205 accepted_schema_info,
206 accepted_schema_fingerprint,
207 |save| save.apply_structural_mutation(mode, key, patch, mutation_row_decode_contract),
208 std::convert::identity,
209 )
210 }
211
212 pub fn structural_patch<E, I, S>(&self, fields: I) -> Result<StructuralPatch, InternalError>
219 where
220 E: PersistedRow<Canister = C> + EntityValue,
221 I: IntoIterator<Item = (S, Value)>,
222 S: AsRef<str>,
223 {
224 let accepted_schema = self.ensure_accepted_schema_snapshot::<E>()?;
225 let (descriptor, _) = AcceptedRowLayoutRuntimeDescriptor::from_generated_compatible_schema(
226 &accepted_schema,
227 E::MODEL,
228 )?;
229 let mut patch = StructuralPatch::new();
230
231 for (field_name, value) in fields {
235 let field_name = field_name.as_ref();
236 patch = append_accepted_structural_patch_field(
237 E::PATH,
238 &descriptor,
239 patch,
240 field_name,
241 value,
242 )?;
243 }
244
245 Ok(patch)
246 }
247
248 #[cfg(test)]
254 pub(in crate::db) fn replace_structural<E>(
255 &self,
256 key: E::Key,
257 patch: StructuralPatch,
258 ) -> Result<E, InternalError>
259 where
260 E: PersistedRow<Canister = C> + EntityValue,
261 {
262 self.mutate_structural(key, patch, MutationMode::Replace)
263 }
264
265 pub fn replace_many_atomic<E>(
271 &self,
272 entities: impl IntoIterator<Item = E>,
273 ) -> Result<WriteBatchResponse<E>, InternalError>
274 where
275 E: PersistedRow<Canister = C> + EntityValue,
276 {
277 self.execute_save_batch(|save| save.replace_many_atomic(entities))
278 }
279
280 pub fn replace_many_non_atomic<E>(
284 &self,
285 entities: impl IntoIterator<Item = E>,
286 ) -> Result<WriteBatchResponse<E>, InternalError>
287 where
288 E: PersistedRow<Canister = C> + EntityValue,
289 {
290 self.execute_save_batch(|save| save.replace_many_non_atomic(entities))
291 }
292
293 pub fn update<E>(&self, entity: E) -> Result<E, InternalError>
295 where
296 E: PersistedRow<Canister = C> + EntityValue,
297 {
298 self.execute_save_entity(|save| save.update(entity))
299 }
300
301 #[cfg(test)]
307 pub(in crate::db) fn insert_structural<E>(
308 &self,
309 key: E::Key,
310 patch: StructuralPatch,
311 ) -> Result<E, InternalError>
312 where
313 E: PersistedRow<Canister = C> + EntityValue,
314 {
315 self.mutate_structural(key, patch, MutationMode::Insert)
316 }
317
318 #[cfg(test)]
324 pub(in crate::db) fn update_structural<E>(
325 &self,
326 key: E::Key,
327 patch: StructuralPatch,
328 ) -> Result<E, InternalError>
329 where
330 E: PersistedRow<Canister = C> + EntityValue,
331 {
332 self.mutate_structural(key, patch, MutationMode::Update)
333 }
334
335 pub fn update_many_atomic<E>(
341 &self,
342 entities: impl IntoIterator<Item = E>,
343 ) -> Result<WriteBatchResponse<E>, InternalError>
344 where
345 E: PersistedRow<Canister = C> + EntityValue,
346 {
347 self.execute_save_batch(|save| save.update_many_atomic(entities))
348 }
349
350 pub fn update_many_non_atomic<E>(
354 &self,
355 entities: impl IntoIterator<Item = E>,
356 ) -> Result<WriteBatchResponse<E>, InternalError>
357 where
358 E: PersistedRow<Canister = C> + EntityValue,
359 {
360 self.execute_save_batch(|save| save.update_many_non_atomic(entities))
361 }
362}