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::{AcceptedRowLayoutRuntimeContract, accepted_insert_field_is_omittable},
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_database_owned_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 !accepted_insert_field_is_omittable(field.insert_omission_policy(), field.write_policy())
76 {
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_database_owned_fields_from_accepted_patch<E>(
93 descriptor: &AcceptedRowLayoutRuntimeContract<'_>,
94 patch: &AuthoredStructuralPatch,
95) -> Result<(), InternalError>
96where
97 E: PersistedRow,
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() || write_policy.write_management().is_some() {
107 return Err(InternalError::mutation_database_owned_field_explicit(
108 E::PATH,
109 accepted_field.name(),
110 ));
111 }
112 }
113
114 Ok(())
115}
116
117impl<C: CanisterKind> DbSession<C> {
118 pub fn insert<E>(&self, entity: E) -> Result<E, InternalError>
120 where
121 E: PersistedRow<Canister = C>,
122 {
123 self.execute_save_entity(|save| save.insert(entity))
124 }
125
126 pub fn create<I>(&self, input: I) -> Result<I::Entity, InternalError>
128 where
129 I: EntityCreateInput,
130 I::Entity: PersistedRow<Canister = C>,
131 {
132 self.execute_save_entity(|save| save.create(input))
133 }
134
135 pub fn insert_many_atomic<E>(
143 &self,
144 entities: impl IntoIterator<Item = E>,
145 ) -> Result<WriteBatchResponse<E>, InternalError>
146 where
147 E: PersistedRow<Canister = C>,
148 {
149 self.execute_save_batch(|save| save.insert_many_atomic(entities))
150 }
151
152 pub fn insert_many_non_atomic<E>(
159 &self,
160 entities: impl IntoIterator<Item = E>,
161 ) -> Result<WriteBatchResponse<E>, InternalError>
162 where
163 E: PersistedRow<Canister = C>,
164 {
165 self.execute_save_batch(|save| save.insert_many_non_atomic(entities))
166 }
167
168 pub fn replace<E>(&self, entity: E) -> Result<E, InternalError>
170 where
171 E: PersistedRow<Canister = C>,
172 {
173 self.execute_save_entity(|save| save.replace(entity))
174 }
175
176 pub fn mutate_structural<E>(
182 &self,
183 key: E::Key,
184 patch: AuthoredStructuralPatch,
185 mode: MutationMode,
186 ) -> Result<E, InternalError>
187 where
188 E: PersistedRow<Canister = C>,
189 {
190 let context = self.accepted_schema_catalog_context_for_query::<E>()?;
191 let (descriptor, _) = AcceptedRowLayoutRuntimeContract::from_generated_compatible_schema(
192 context.snapshot(),
193 E::MODEL,
194 context.enum_catalog(),
195 context.composite_catalog(),
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>,
226 I: IntoIterator<Item = (S, V)>,
227 S: AsRef<str>,
228 V: Into<InputValue>,
229 {
230 let context = self.accepted_schema_catalog_context_for_query::<E>()?;
231 let (descriptor, _) = AcceptedRowLayoutRuntimeContract::from_generated_compatible_schema(
232 context.snapshot(),
233 E::MODEL,
234 context.enum_catalog(),
235 context.composite_catalog(),
236 )?;
237 let mut patch = AuthoredStructuralPatch::new();
238
239 for (field_name, value) in fields {
243 let field_name = field_name.as_ref();
244 patch = append_accepted_structural_patch_field(
245 E::PATH,
246 &descriptor,
247 patch,
248 field_name,
249 value.into(),
250 )?;
251 }
252
253 Ok(patch)
254 }
255
256 #[cfg(test)]
262 pub(in crate::db) fn replace_structural<E>(
263 &self,
264 key: E::Key,
265 patch: AuthoredStructuralPatch,
266 ) -> Result<E, InternalError>
267 where
268 E: PersistedRow<Canister = C>,
269 {
270 self.mutate_structural(key, patch, MutationMode::Replace)
271 }
272
273 pub fn replace_many_atomic<E>(
281 &self,
282 entities: impl IntoIterator<Item = E>,
283 ) -> Result<WriteBatchResponse<E>, InternalError>
284 where
285 E: PersistedRow<Canister = C>,
286 {
287 self.execute_save_batch(|save| save.replace_many_atomic(entities))
288 }
289
290 pub fn replace_many_non_atomic<E>(
297 &self,
298 entities: impl IntoIterator<Item = E>,
299 ) -> Result<WriteBatchResponse<E>, InternalError>
300 where
301 E: PersistedRow<Canister = C>,
302 {
303 self.execute_save_batch(|save| save.replace_many_non_atomic(entities))
304 }
305
306 pub fn update<E>(&self, entity: E) -> Result<E, InternalError>
308 where
309 E: PersistedRow<Canister = C>,
310 {
311 self.execute_save_entity(|save| save.update(entity))
312 }
313
314 #[cfg(test)]
320 pub(in crate::db) fn insert_structural<E>(
321 &self,
322 key: E::Key,
323 patch: AuthoredStructuralPatch,
324 ) -> Result<E, InternalError>
325 where
326 E: PersistedRow<Canister = C>,
327 {
328 self.mutate_structural(key, patch, MutationMode::Insert)
329 }
330
331 #[cfg(test)]
337 pub(in crate::db) fn update_structural<E>(
338 &self,
339 key: E::Key,
340 patch: AuthoredStructuralPatch,
341 ) -> Result<E, InternalError>
342 where
343 E: PersistedRow<Canister = C>,
344 {
345 self.mutate_structural(key, patch, MutationMode::Update)
346 }
347
348 pub fn update_many_atomic<E>(
356 &self,
357 entities: impl IntoIterator<Item = E>,
358 ) -> Result<WriteBatchResponse<E>, InternalError>
359 where
360 E: PersistedRow<Canister = C>,
361 {
362 self.execute_save_batch(|save| save.update_many_atomic(entities))
363 }
364
365 pub fn update_many_non_atomic<E>(
372 &self,
373 entities: impl IntoIterator<Item = E>,
374 ) -> Result<WriteBatchResponse<E>, InternalError>
375 where
376 E: PersistedRow<Canister = C>,
377 {
378 self.execute_save_batch(|save| save.update_many_non_atomic(entities))
379 }
380}