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 entity::EntityCreateInput,
16 error::InternalError,
17 traits::CanisterKind,
18 value::InputValue,
19};
20
21fn append_accepted_structural_patch_field(
26 entity_path: &'static str,
27 descriptor: &AcceptedRowLayoutRuntimeContract<'_>,
28 patch: AuthoredStructuralPatch,
29 field_name: &str,
30 value: InputValue,
31) -> Result<AuthoredStructuralPatch, InternalError> {
32 let slot = descriptor
33 .field_slot_index_by_name(field_name)
34 .ok_or_else(|| InternalError::mutation_structural_field_unknown(entity_path, field_name))?;
35
36 Ok(patch.set(FieldSlot::from_validated_index(slot), value))
37}
38
39fn validate_structural_patch_schema_policy<E>(
45 descriptor: &AcceptedRowLayoutRuntimeContract<'_>,
46 patch: &AuthoredStructuralPatch,
47 mode: MutationMode,
48) -> Result<(), InternalError>
49where
50 E: PersistedRow,
51{
52 reject_explicit_generated_fields_from_accepted_patch::<E>(descriptor, patch)?;
53
54 if matches!(mode, MutationMode::Update) {
55 return Ok(());
56 }
57
58 let mut provided_slots = vec![false; descriptor.required_slot_count()];
59 for entry in patch.entries() {
60 let slot = entry.slot().index();
61 if slot < provided_slots.len() {
62 provided_slots[slot] = true;
63 }
64 }
65
66 for field in descriptor.fields() {
70 let slot = usize::from(field.slot().get());
71 if provided_slots.get(slot).copied().unwrap_or(false) {
72 continue;
73 }
74
75 if matches!(field.absence_policy(), AcceptedFieldAbsencePolicy::Required) {
76 return Err(
77 InternalError::mutation_structural_patch_required_field_missing(
78 E::PATH,
79 field.name(),
80 ),
81 );
82 }
83 }
84
85 Ok(())
86}
87
88fn reject_explicit_generated_fields_from_accepted_patch<E>(
94 descriptor: &AcceptedRowLayoutRuntimeContract<'_>,
95 patch: &AuthoredStructuralPatch,
96) -> Result<(), InternalError>
97where
98 E: PersistedRow,
99{
100 for entry in patch.entries() {
101 let slot = entry.slot().index();
102 let Some(accepted_field) = descriptor.field_for_slot_index(slot) else {
103 continue;
104 };
105 let write_policy = accepted_field.write_policy();
106
107 if write_policy.insert_generation().is_some()
108 && !descriptor.is_primary_key_field_name(accepted_field.name())
109 {
110 return Err(InternalError::mutation_generated_field_explicit(
111 E::PATH,
112 accepted_field.name(),
113 ));
114 }
115 }
116
117 Ok(())
118}
119
120impl<C: CanisterKind> DbSession<C> {
121 pub fn insert<E>(&self, entity: E) -> Result<E, InternalError>
123 where
124 E: PersistedRow<Canister = C>,
125 {
126 self.execute_save_entity(|save| save.insert(entity))
127 }
128
129 pub fn create<I>(&self, input: I) -> Result<I::Entity, InternalError>
131 where
132 I: EntityCreateInput,
133 I::Entity: PersistedRow<Canister = C>,
134 {
135 self.execute_save_entity(|save| save.create(input))
136 }
137
138 pub fn insert_many_atomic<E>(
146 &self,
147 entities: impl IntoIterator<Item = E>,
148 ) -> Result<WriteBatchResponse<E>, InternalError>
149 where
150 E: PersistedRow<Canister = C>,
151 {
152 self.execute_save_batch(|save| save.insert_many_atomic(entities))
153 }
154
155 pub fn insert_many_non_atomic<E>(
162 &self,
163 entities: impl IntoIterator<Item = E>,
164 ) -> Result<WriteBatchResponse<E>, InternalError>
165 where
166 E: PersistedRow<Canister = C>,
167 {
168 self.execute_save_batch(|save| save.insert_many_non_atomic(entities))
169 }
170
171 pub fn replace<E>(&self, entity: E) -> Result<E, InternalError>
173 where
174 E: PersistedRow<Canister = C>,
175 {
176 self.execute_save_entity(|save| save.replace(entity))
177 }
178
179 pub fn mutate_structural<E>(
185 &self,
186 key: E::Key,
187 patch: AuthoredStructuralPatch,
188 mode: MutationMode,
189 ) -> Result<E, InternalError>
190 where
191 E: PersistedRow<Canister = C>,
192 {
193 let context = self.accepted_schema_catalog_context_for_query::<E>()?;
194 let (descriptor, _) = AcceptedRowLayoutRuntimeContract::from_generated_compatible_schema(
195 context.snapshot(),
196 E::MODEL,
197 )?;
198 validate_structural_patch_schema_policy::<E>(&descriptor, &patch, mode)?;
199 let (
200 row_decode_contract,
201 mutation_row_decode_contract,
202 accepted_schema_info,
203 accepted_schema_fingerprint,
204 ) = accepted_save_contract_for_catalog_context::<E>(&context, &descriptor);
205
206 self.execute_save_with_checked_accepted_row_contract(
207 row_decode_contract,
208 accepted_schema_info,
209 accepted_schema_fingerprint,
210 |save| save.apply_structural_mutation(mode, key, patch, mutation_row_decode_contract),
211 std::convert::identity,
212 )
213 }
214
215 pub fn structural_patch<E, I, S, V>(
222 &self,
223 fields: I,
224 ) -> Result<AuthoredStructuralPatch, InternalError>
225 where
226 E: PersistedRow<Canister = C>,
227 I: IntoIterator<Item = (S, V)>,
228 S: AsRef<str>,
229 V: Into<InputValue>,
230 {
231 let accepted_schema = self.ensure_accepted_schema_snapshot::<E>()?;
232 let (descriptor, _) = AcceptedRowLayoutRuntimeContract::from_generated_compatible_schema(
233 &accepted_schema,
234 E::MODEL,
235 )?;
236 let mut patch = AuthoredStructuralPatch::new();
237
238 for (field_name, value) in fields {
242 let field_name = field_name.as_ref();
243 patch = append_accepted_structural_patch_field(
244 E::PATH,
245 &descriptor,
246 patch,
247 field_name,
248 value.into(),
249 )?;
250 }
251
252 Ok(patch)
253 }
254
255 #[cfg(test)]
261 pub(in crate::db) fn replace_structural<E>(
262 &self,
263 key: E::Key,
264 patch: AuthoredStructuralPatch,
265 ) -> Result<E, InternalError>
266 where
267 E: PersistedRow<Canister = C>,
268 {
269 self.mutate_structural(key, patch, MutationMode::Replace)
270 }
271
272 pub fn replace_many_atomic<E>(
280 &self,
281 entities: impl IntoIterator<Item = E>,
282 ) -> Result<WriteBatchResponse<E>, InternalError>
283 where
284 E: PersistedRow<Canister = C>,
285 {
286 self.execute_save_batch(|save| save.replace_many_atomic(entities))
287 }
288
289 pub fn replace_many_non_atomic<E>(
296 &self,
297 entities: impl IntoIterator<Item = E>,
298 ) -> Result<WriteBatchResponse<E>, InternalError>
299 where
300 E: PersistedRow<Canister = C>,
301 {
302 self.execute_save_batch(|save| save.replace_many_non_atomic(entities))
303 }
304
305 pub fn update<E>(&self, entity: E) -> Result<E, InternalError>
307 where
308 E: PersistedRow<Canister = C>,
309 {
310 self.execute_save_entity(|save| save.update(entity))
311 }
312
313 #[cfg(test)]
319 pub(in crate::db) fn insert_structural<E>(
320 &self,
321 key: E::Key,
322 patch: AuthoredStructuralPatch,
323 ) -> Result<E, InternalError>
324 where
325 E: PersistedRow<Canister = C>,
326 {
327 self.mutate_structural(key, patch, MutationMode::Insert)
328 }
329
330 #[cfg(test)]
336 pub(in crate::db) fn update_structural<E>(
337 &self,
338 key: E::Key,
339 patch: AuthoredStructuralPatch,
340 ) -> Result<E, InternalError>
341 where
342 E: PersistedRow<Canister = C>,
343 {
344 self.mutate_structural(key, patch, MutationMode::Update)
345 }
346
347 pub fn update_many_atomic<E>(
355 &self,
356 entities: impl IntoIterator<Item = E>,
357 ) -> Result<WriteBatchResponse<E>, InternalError>
358 where
359 E: PersistedRow<Canister = C>,
360 {
361 self.execute_save_batch(|save| save.update_many_atomic(entities))
362 }
363
364 pub fn update_many_non_atomic<E>(
371 &self,
372 entities: impl IntoIterator<Item = E>,
373 ) -> Result<WriteBatchResponse<E>, InternalError>
374 where
375 E: PersistedRow<Canister = C>,
376 {
377 self.execute_save_batch(|save| save.update_many_non_atomic(entities))
378 }
379}