1use crate::{
7 error::InternalError,
8 value::{InputValue, OutputValue},
9};
10use candid::CandidType;
11use icydb_schema::ScalarType;
12use serde::Deserialize;
13use std::collections::BTreeSet;
14
15#[doc(hidden)]
24#[derive(Clone, Debug, Eq, PartialEq)]
25pub enum DynamicWriteCell {
26 Omitted,
28 Default,
30 Null,
32 Value(InputValue),
34}
35
36#[doc(hidden)]
45#[derive(Clone, Debug, Default, Eq, PartialEq)]
46pub struct DynamicStructuralPatch {
47 fields: Vec<(String, DynamicWriteCell)>,
48}
49
50impl DynamicStructuralPatch {
51 #[must_use]
53 pub const fn new(fields: Vec<(String, DynamicWriteCell)>) -> Self {
54 Self { fields }
55 }
56
57 #[must_use]
59 pub const fn fields(&self) -> &[(String, DynamicWriteCell)] {
60 self.fields.as_slice()
61 }
62}
63
64#[doc(hidden)]
73#[derive(Clone, Debug, Eq, PartialEq)]
74pub enum DynamicMutation {
75 Insert {
77 entity: String,
79 patch: DynamicStructuralPatch,
81 },
82 Update {
84 entity: String,
86 key: InputValue,
88 patch: DynamicStructuralPatch,
90 },
91 Replace {
93 entity: String,
95 key: InputValue,
97 patch: DynamicStructuralPatch,
99 },
100 Delete {
102 entity: String,
104 key: InputValue,
106 },
107}
108
109impl DynamicMutation {
110 #[must_use]
112 pub const fn entity(&self) -> &str {
113 match self {
114 Self::Insert { entity, .. }
115 | Self::Update { entity, .. }
116 | Self::Replace { entity, .. }
117 | Self::Delete { entity, .. } => entity.as_str(),
118 }
119 }
120}
121
122#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq)]
129pub struct DynamicMutationResult {
130 pub entity: String,
132 pub columns: Vec<String>,
134 pub rows: Vec<Vec<OutputValue>>,
136 pub affected_rows: u32,
138}
139
140#[doc(hidden)]
148#[derive(Clone, Debug, Eq, PartialEq)]
149pub struct DynamicTypedFieldBindingRequest {
150 pub(crate) field_type: DynamicTypedFieldType,
151 pub(crate) nullable: bool,
152 pub(crate) source_key: String,
153}
154
155impl DynamicTypedFieldBindingRequest {
156 #[must_use]
158 pub const fn new(
159 source_key: String,
160 field_type: DynamicTypedFieldType,
161 nullable: bool,
162 ) -> Self {
163 Self {
164 field_type,
165 nullable,
166 source_key,
167 }
168 }
169}
170
171#[doc(hidden)]
173#[derive(Clone, Debug, Eq, PartialEq)]
174pub enum DynamicTypedFieldType {
175 Scalar(ScalarType),
177 List(Box<Self>),
179 Named(String),
181}
182
183#[doc(hidden)]
185#[derive(Debug)]
186pub enum DynamicTypedBindingError {
187 FieldUnavailable,
189 IncompatibleField,
191 Internal(InternalError),
193}
194
195impl From<InternalError> for DynamicTypedBindingError {
196 fn from(error: InternalError) -> Self {
197 Self::Internal(error)
198 }
199}
200
201#[derive(Clone, Debug, Eq, PartialEq)]
202struct DynamicTypedFieldBinding {
203 source_key: String,
204 field_id: u32,
205 slot: u16,
206 label: String,
207}
208
209#[doc(hidden)]
216#[derive(Clone, Debug, Default, Eq, PartialEq)]
217pub struct DynamicTypedStructuralPatch {
218 entity_source: String,
219 entity_tag: u64,
220 accepted_fingerprint: [u8; 16],
221 fields: Vec<(u32, u16, DynamicWriteCell)>,
222}
223
224impl DynamicTypedStructuralPatch {
225 #[must_use]
227 pub(crate) const fn fields(&self) -> &[(u32, u16, DynamicWriteCell)] {
228 self.fields.as_slice()
229 }
230
231 pub(crate) fn is_bound_to(&self, binding: &DynamicTypedEntityBinding) -> bool {
232 self.entity_source == binding.entity_source
233 && self.entity_tag == binding.entity_tag
234 && self.accepted_fingerprint == binding.accepted_fingerprint
235 }
236}
237
238#[doc(hidden)]
246#[derive(Clone, Debug, Eq, PartialEq)]
247pub enum DynamicTypedMutation {
248 Insert {
250 patch: DynamicTypedStructuralPatch,
252 },
253 Update {
255 key: InputValue,
257 patch: DynamicTypedStructuralPatch,
259 },
260 Replace {
262 key: InputValue,
264 patch: DynamicTypedStructuralPatch,
266 },
267}
268
269#[doc(hidden)]
274#[derive(Clone, Debug, Eq, PartialEq)]
275pub struct DynamicTypedEntityBinding {
276 pub(crate) database_incarnation: [u8; 16],
277 pub(crate) entity_source: String,
278 pub(crate) entity_label: String,
279 pub(crate) entity_tag: u64,
280 pub(crate) accepted_revision: u64,
281 pub(crate) accepted_fingerprint: [u8; 16],
282 pub(crate) entity_generation: u32,
283 fields: Vec<DynamicTypedFieldBinding>,
284 pub(crate) named_types: Vec<(String, String)>,
285 pub(crate) enum_variants: Vec<(String, String, String)>,
286 pub(crate) composite_fields: Vec<(String, String, String)>,
287}
288
289impl DynamicTypedEntityBinding {
290 #[expect(
291 clippy::too_many_arguments,
292 reason = "the opaque binding keeps every accepted authority component explicit"
293 )]
294 pub(crate) fn new(
295 database_incarnation: [u8; 16],
296 entity_source: String,
297 entity_label: String,
298 entity_tag: u64,
299 accepted_revision: u64,
300 accepted_fingerprint: [u8; 16],
301 entity_generation: u32,
302 fields: Vec<(String, u32, u16, String)>,
303 named_types: Vec<(String, String)>,
304 enum_variants: Vec<(String, String, String)>,
305 composite_fields: Vec<(String, String, String)>,
306 ) -> Result<Self, InternalError> {
307 let mut sources = BTreeSet::new();
308 let mut ids = BTreeSet::new();
309 let mut slots = BTreeSet::new();
310 let fields = fields
311 .into_iter()
312 .map(|(source_key, field_id, slot, label)| {
313 if !sources.insert(source_key.clone())
314 || !ids.insert(field_id)
315 || !slots.insert(slot)
316 {
317 return Err(InternalError::store_invariant());
318 }
319 Ok(DynamicTypedFieldBinding {
320 source_key,
321 field_id,
322 slot,
323 label,
324 })
325 })
326 .collect::<Result<Vec<_>, _>>()?;
327
328 Ok(Self {
329 database_incarnation,
330 entity_source,
331 entity_label,
332 entity_tag,
333 accepted_revision,
334 accepted_fingerprint,
335 entity_generation,
336 fields,
337 named_types,
338 enum_variants,
339 composite_fields,
340 })
341 }
342
343 #[must_use]
345 pub const fn entity(&self) -> &str {
346 self.entity_label.as_str()
347 }
348
349 #[must_use]
351 pub const fn entity_source(&self) -> &str {
352 self.entity_source.as_str()
353 }
354
355 #[must_use]
357 pub fn field_slot(&self, source_key: &str) -> Option<u16> {
358 self.fields
359 .iter()
360 .find_map(|field| (field.source_key == source_key).then_some(field.slot))
361 }
362
363 #[must_use]
365 pub fn output_field_slot(&self, label: &str) -> Option<u16> {
366 self.fields
367 .iter()
368 .find_map(|field| (field.label == label).then_some(field.slot))
369 }
370
371 pub(crate) fn field_identity_bindings(&self) -> impl Iterator<Item = (&str, u32, u16)> {
372 self.fields
373 .iter()
374 .map(|field| (field.source_key.as_str(), field.field_id, field.slot))
375 }
376
377 #[must_use]
379 pub fn bind_write_fields(
380 &self,
381 fields: Vec<(String, DynamicWriteCell)>,
382 ) -> Option<DynamicTypedStructuralPatch> {
383 let mut seen_ids = BTreeSet::new();
384 let mut seen_slots = BTreeSet::new();
385 let mut bound = Vec::with_capacity(fields.len());
386 for (source_key, cell) in fields {
387 let field = self
388 .fields
389 .iter()
390 .find(|field| field.source_key == source_key)?;
391 if !seen_ids.insert(field.field_id) || !seen_slots.insert(field.slot) {
392 return None;
393 }
394 bound.push((field.field_id, field.slot, cell));
395 }
396 Some(DynamicTypedStructuralPatch {
397 entity_source: self.entity_source.clone(),
398 entity_tag: self.entity_tag,
399 accepted_fingerprint: self.accepted_fingerprint,
400 fields: bound,
401 })
402 }
403
404 #[must_use]
406 pub fn named_type_name(&self, source_key: &str) -> Option<&str> {
407 self.named_types
408 .iter()
409 .find_map(|(source, name)| (source == source_key).then_some(name.as_str()))
410 }
411
412 #[must_use]
414 pub fn enum_variant_name(&self, type_source_key: &str, source_key: &str) -> Option<&str> {
415 self.enum_variants
416 .iter()
417 .find_map(|(bound_type, source, name)| {
418 (bound_type == type_source_key && source == source_key).then_some(name.as_str())
419 })
420 }
421
422 #[must_use]
424 pub fn composite_field_name(&self, type_source_key: &str, source_key: &str) -> Option<&str> {
425 self.composite_fields
426 .iter()
427 .find_map(|(bound_type, source, name)| {
428 (bound_type == type_source_key && source == source_key).then_some(name.as_str())
429 })
430 }
431}