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_row_constraints,
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 accepted_row_constraints,
211 |save| save.apply_structural_mutation(mode, key, patch, mutation_row_decode_contract),
212 std::convert::identity,
213 )
214 }
215
216 pub fn structural_patch<E, I, S, V>(
223 &self,
224 fields: I,
225 ) -> Result<AuthoredStructuralPatch, InternalError>
226 where
227 E: PersistedRow<Canister = C>,
228 I: IntoIterator<Item = (S, V)>,
229 S: AsRef<str>,
230 V: Into<InputValue>,
231 {
232 let context = self.accepted_schema_catalog_context_for_query::<E>()?;
233 let (descriptor, _) = AcceptedRowLayoutRuntimeContract::from_generated_compatible_schema(
234 context.snapshot(),
235 E::MODEL,
236 context.enum_catalog(),
237 context.composite_catalog(),
238 )?;
239 let mut patch = AuthoredStructuralPatch::new();
240
241 for (field_name, value) in fields {
245 let field_name = field_name.as_ref();
246 patch = append_accepted_structural_patch_field(
247 E::PATH,
248 &descriptor,
249 patch,
250 field_name,
251 value.into(),
252 )?;
253 }
254
255 Ok(patch)
256 }
257
258 #[cfg(test)]
264 pub(in crate::db) fn replace_structural<E>(
265 &self,
266 key: E::Key,
267 patch: AuthoredStructuralPatch,
268 ) -> Result<E, InternalError>
269 where
270 E: PersistedRow<Canister = C>,
271 {
272 self.mutate_structural(key, patch, MutationMode::Replace)
273 }
274
275 pub fn replace_many_atomic<E>(
283 &self,
284 entities: impl IntoIterator<Item = E>,
285 ) -> Result<WriteBatchResponse<E>, InternalError>
286 where
287 E: PersistedRow<Canister = C>,
288 {
289 self.execute_save_batch(|save| save.replace_many_atomic(entities))
290 }
291
292 pub fn replace_many_non_atomic<E>(
299 &self,
300 entities: impl IntoIterator<Item = E>,
301 ) -> Result<WriteBatchResponse<E>, InternalError>
302 where
303 E: PersistedRow<Canister = C>,
304 {
305 self.execute_save_batch(|save| save.replace_many_non_atomic(entities))
306 }
307
308 pub fn update<E>(&self, entity: E) -> Result<E, InternalError>
310 where
311 E: PersistedRow<Canister = C>,
312 {
313 self.execute_save_entity(|save| save.update(entity))
314 }
315
316 #[cfg(test)]
322 pub(in crate::db) fn insert_structural<E>(
323 &self,
324 key: E::Key,
325 patch: AuthoredStructuralPatch,
326 ) -> Result<E, InternalError>
327 where
328 E: PersistedRow<Canister = C>,
329 {
330 self.mutate_structural(key, patch, MutationMode::Insert)
331 }
332
333 #[cfg(test)]
339 pub(in crate::db) fn update_structural<E>(
340 &self,
341 key: E::Key,
342 patch: AuthoredStructuralPatch,
343 ) -> Result<E, InternalError>
344 where
345 E: PersistedRow<Canister = C>,
346 {
347 self.mutate_structural(key, patch, MutationMode::Update)
348 }
349
350 pub fn update_many_atomic<E>(
358 &self,
359 entities: impl IntoIterator<Item = E>,
360 ) -> Result<WriteBatchResponse<E>, InternalError>
361 where
362 E: PersistedRow<Canister = C>,
363 {
364 self.execute_save_batch(|save| save.update_many_atomic(entities))
365 }
366
367 pub fn update_many_non_atomic<E>(
374 &self,
375 entities: impl IntoIterator<Item = E>,
376 ) -> Result<WriteBatchResponse<E>, InternalError>
377 where
378 E: PersistedRow<Canister = C>,
379 {
380 self.execute_save_batch(|save| save.update_many_non_atomic(entities))
381 }
382}