1use super::accepted_schema::accepted_save_contract_for_catalog_context;
8use crate::{
9 db::{
10 DbSession, PersistedRow, WriteBatchResponse,
11 data::{AuthoredStructuralPatch, FieldSlot},
12 executor::MutationMode,
13 schema::{AcceptedFieldAbsencePolicy, AcceptedRowLayoutRuntimeContract},
14 },
15 error::InternalError,
16 traits::{AuthoredFieldProjection, CanisterKind, EntityCreateInput, EntityValue},
17 value::InputValue,
18};
19
20fn append_accepted_structural_patch_field(
25 entity_path: &'static str,
26 descriptor: &AcceptedRowLayoutRuntimeContract<'_>,
27 patch: AuthoredStructuralPatch,
28 field_name: &str,
29 value: InputValue,
30) -> Result<AuthoredStructuralPatch, InternalError> {
31 let slot = descriptor
32 .field_slot_index_by_name(field_name)
33 .ok_or_else(|| InternalError::mutation_structural_field_unknown(entity_path, field_name))?;
34
35 Ok(patch.set(FieldSlot::from_validated_index(slot), value))
36}
37
38fn validate_structural_patch_schema_policy<E>(
44 descriptor: &AcceptedRowLayoutRuntimeContract<'_>,
45 patch: &AuthoredStructuralPatch,
46 mode: MutationMode,
47) -> Result<(), InternalError>
48where
49 E: PersistedRow + EntityValue,
50{
51 reject_explicit_generated_fields_from_accepted_patch::<E>(descriptor, patch)?;
52
53 if matches!(mode, MutationMode::Update) {
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 reject_explicit_generated_fields_from_accepted_patch<E>(
93 descriptor: &AcceptedRowLayoutRuntimeContract<'_>,
94 patch: &AuthoredStructuralPatch,
95) -> Result<(), InternalError>
96where
97 E: PersistedRow + EntityValue,
98{
99 for entry in patch.entries() {
100 let slot = entry.slot().index();
101 let Some(accepted_field) = descriptor.field_for_slot_index(slot) else {
102 continue;
103 };
104 let write_policy = accepted_field.write_policy();
105
106 if write_policy.insert_generation().is_some()
107 && !descriptor.is_primary_key_field_name(accepted_field.name())
108 {
109 return Err(InternalError::mutation_generated_field_explicit(
110 E::PATH,
111 accepted_field.name(),
112 ));
113 }
114 }
115
116 Ok(())
117}
118
119impl<C: CanisterKind> DbSession<C> {
120 pub fn insert<E>(&self, entity: E) -> Result<E, InternalError>
122 where
123 E: PersistedRow<Canister = C> + EntityValue + AuthoredFieldProjection,
124 {
125 self.execute_save_entity(|save| save.insert(entity))
126 }
127
128 pub fn create<I>(&self, input: I) -> Result<I::Entity, InternalError>
130 where
131 I: EntityCreateInput,
132 I::Entity: PersistedRow<Canister = C> + EntityValue + AuthoredFieldProjection,
133 {
134 self.execute_save_entity(|save| save.create(input))
135 }
136
137 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 + AuthoredFieldProjection,
150 {
151 self.execute_save_batch(|save| save.insert_many_atomic(entities))
152 }
153
154 pub fn insert_many_non_atomic<E>(
161 &self,
162 entities: impl IntoIterator<Item = E>,
163 ) -> Result<WriteBatchResponse<E>, InternalError>
164 where
165 E: PersistedRow<Canister = C> + EntityValue + AuthoredFieldProjection,
166 {
167 self.execute_save_batch(|save| save.insert_many_non_atomic(entities))
168 }
169
170 pub fn replace<E>(&self, entity: E) -> Result<E, InternalError>
172 where
173 E: PersistedRow<Canister = C> + EntityValue + AuthoredFieldProjection,
174 {
175 self.execute_save_entity(|save| save.replace(entity))
176 }
177
178 pub fn mutate_structural<E>(
184 &self,
185 key: E::Key,
186 patch: AuthoredStructuralPatch,
187 mode: MutationMode,
188 ) -> Result<E, InternalError>
189 where
190 E: PersistedRow<Canister = C> + EntityValue + AuthoredFieldProjection,
191 {
192 let context = self.accepted_schema_catalog_context_for_query::<E>()?;
193 let (descriptor, _) = AcceptedRowLayoutRuntimeContract::from_generated_compatible_schema(
194 context.snapshot(),
195 E::MODEL,
196 )?;
197 validate_structural_patch_schema_policy::<E>(&descriptor, &patch, mode)?;
198 let (
199 row_decode_contract,
200 mutation_row_decode_contract,
201 accepted_schema_info,
202 accepted_schema_fingerprint,
203 ) = accepted_save_contract_for_catalog_context::<E>(&context, &descriptor);
204
205 self.execute_save_with_checked_accepted_row_contract(
206 row_decode_contract,
207 accepted_schema_info,
208 accepted_schema_fingerprint,
209 |save| save.apply_structural_mutation(mode, key, patch, mutation_row_decode_contract),
210 std::convert::identity,
211 )
212 }
213
214 pub fn structural_patch<E, I, S, V>(
221 &self,
222 fields: I,
223 ) -> Result<AuthoredStructuralPatch, InternalError>
224 where
225 E: PersistedRow<Canister = C> + EntityValue,
226 I: IntoIterator<Item = (S, V)>,
227 S: AsRef<str>,
228 V: Into<InputValue>,
229 {
230 let accepted_schema = self.ensure_accepted_schema_snapshot::<E>()?;
231 let (descriptor, _) = AcceptedRowLayoutRuntimeContract::from_generated_compatible_schema(
232 &accepted_schema,
233 E::MODEL,
234 )?;
235 let mut patch = AuthoredStructuralPatch::new();
236
237 for (field_name, value) in fields {
241 let field_name = field_name.as_ref();
242 patch = append_accepted_structural_patch_field(
243 E::PATH,
244 &descriptor,
245 patch,
246 field_name,
247 value.into(),
248 )?;
249 }
250
251 Ok(patch)
252 }
253
254 #[cfg(test)]
260 pub(in crate::db) fn replace_structural<E>(
261 &self,
262 key: E::Key,
263 patch: AuthoredStructuralPatch,
264 ) -> Result<E, InternalError>
265 where
266 E: PersistedRow<Canister = C> + EntityValue + AuthoredFieldProjection,
267 {
268 self.mutate_structural(key, patch, MutationMode::Replace)
269 }
270
271 pub fn replace_many_atomic<E>(
279 &self,
280 entities: impl IntoIterator<Item = E>,
281 ) -> Result<WriteBatchResponse<E>, InternalError>
282 where
283 E: PersistedRow<Canister = C> + EntityValue + AuthoredFieldProjection,
284 {
285 self.execute_save_batch(|save| save.replace_many_atomic(entities))
286 }
287
288 pub fn replace_many_non_atomic<E>(
295 &self,
296 entities: impl IntoIterator<Item = E>,
297 ) -> Result<WriteBatchResponse<E>, InternalError>
298 where
299 E: PersistedRow<Canister = C> + EntityValue + AuthoredFieldProjection,
300 {
301 self.execute_save_batch(|save| save.replace_many_non_atomic(entities))
302 }
303
304 pub fn update<E>(&self, entity: E) -> Result<E, InternalError>
306 where
307 E: PersistedRow<Canister = C> + EntityValue + AuthoredFieldProjection,
308 {
309 self.execute_save_entity(|save| save.update(entity))
310 }
311
312 #[cfg(test)]
318 pub(in crate::db) fn insert_structural<E>(
319 &self,
320 key: E::Key,
321 patch: AuthoredStructuralPatch,
322 ) -> Result<E, InternalError>
323 where
324 E: PersistedRow<Canister = C> + EntityValue + AuthoredFieldProjection,
325 {
326 self.mutate_structural(key, patch, MutationMode::Insert)
327 }
328
329 #[cfg(test)]
335 pub(in crate::db) fn update_structural<E>(
336 &self,
337 key: E::Key,
338 patch: AuthoredStructuralPatch,
339 ) -> Result<E, InternalError>
340 where
341 E: PersistedRow<Canister = C> + EntityValue + AuthoredFieldProjection,
342 {
343 self.mutate_structural(key, patch, MutationMode::Update)
344 }
345
346 pub fn update_many_atomic<E>(
354 &self,
355 entities: impl IntoIterator<Item = E>,
356 ) -> Result<WriteBatchResponse<E>, InternalError>
357 where
358 E: PersistedRow<Canister = C> + EntityValue + AuthoredFieldProjection,
359 {
360 self.execute_save_batch(|save| save.update_many_atomic(entities))
361 }
362
363 pub fn update_many_non_atomic<E>(
370 &self,
371 entities: impl IntoIterator<Item = E>,
372 ) -> Result<WriteBatchResponse<E>, InternalError>
373 where
374 E: PersistedRow<Canister = C> + EntityValue + AuthoredFieldProjection,
375 {
376 self.execute_save_batch(|save| save.update_many_non_atomic(entities))
377 }
378}