use crate::{
error::InternalError,
value::{InputValue, OutputValue},
};
use candid::CandidType;
use icydb_schema::ScalarType;
use serde::Deserialize;
use std::collections::BTreeSet;
#[doc(hidden)]
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum DynamicWriteCell {
Omitted,
Default,
Null,
Value(InputValue),
}
#[doc(hidden)]
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct DynamicStructuralPatch {
fields: Vec<(String, DynamicWriteCell)>,
}
impl DynamicStructuralPatch {
#[must_use]
pub const fn new(fields: Vec<(String, DynamicWriteCell)>) -> Self {
Self { fields }
}
#[must_use]
pub const fn fields(&self) -> &[(String, DynamicWriteCell)] {
self.fields.as_slice()
}
}
#[doc(hidden)]
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum DynamicMutation {
Insert {
entity: String,
patch: DynamicStructuralPatch,
},
Update {
entity: String,
key: InputValue,
patch: DynamicStructuralPatch,
},
Replace {
entity: String,
key: InputValue,
patch: DynamicStructuralPatch,
},
Delete {
entity: String,
key: InputValue,
},
}
impl DynamicMutation {
#[must_use]
pub const fn entity(&self) -> &str {
match self {
Self::Insert { entity, .. }
| Self::Update { entity, .. }
| Self::Replace { entity, .. }
| Self::Delete { entity, .. } => entity.as_str(),
}
}
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq)]
pub struct DynamicMutationResult {
pub entity: String,
pub columns: Vec<String>,
pub rows: Vec<Vec<OutputValue>>,
pub affected_rows: u32,
}
#[doc(hidden)]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct DynamicTypedFieldBindingRequest {
pub(crate) field_type: DynamicTypedFieldType,
pub(crate) nullable: bool,
pub(crate) source_key: String,
}
impl DynamicTypedFieldBindingRequest {
#[must_use]
pub const fn new(
source_key: String,
field_type: DynamicTypedFieldType,
nullable: bool,
) -> Self {
Self {
field_type,
nullable,
source_key,
}
}
}
#[doc(hidden)]
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum DynamicTypedFieldType {
Scalar(ScalarType),
List(Box<Self>),
Named(String),
}
#[doc(hidden)]
#[derive(Debug)]
pub enum DynamicTypedBindingError {
FieldUnavailable,
IncompatibleField,
Internal(InternalError),
}
impl From<InternalError> for DynamicTypedBindingError {
fn from(error: InternalError) -> Self {
Self::Internal(error)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
struct DynamicTypedFieldBinding {
source_key: String,
field_id: u32,
slot: u16,
label: String,
}
#[doc(hidden)]
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct DynamicTypedStructuralPatch {
entity_source: String,
entity_tag: u64,
accepted_fingerprint: [u8; 16],
fields: Vec<(u32, u16, DynamicWriteCell)>,
}
impl DynamicTypedStructuralPatch {
#[must_use]
pub(crate) const fn fields(&self) -> &[(u32, u16, DynamicWriteCell)] {
self.fields.as_slice()
}
pub(crate) fn is_bound_to(&self, binding: &DynamicTypedEntityBinding) -> bool {
self.entity_source == binding.entity_source
&& self.entity_tag == binding.entity_tag
&& self.accepted_fingerprint == binding.accepted_fingerprint
}
}
#[doc(hidden)]
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum DynamicTypedMutation {
Insert {
patch: DynamicTypedStructuralPatch,
},
Update {
key: InputValue,
patch: DynamicTypedStructuralPatch,
},
Replace {
key: InputValue,
patch: DynamicTypedStructuralPatch,
},
}
#[doc(hidden)]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct DynamicTypedEntityBinding {
pub(crate) database_incarnation: [u8; 16],
pub(crate) entity_source: String,
pub(crate) entity_label: String,
pub(crate) entity_tag: u64,
pub(crate) accepted_revision: u64,
pub(crate) accepted_fingerprint: [u8; 16],
pub(crate) entity_generation: u32,
fields: Vec<DynamicTypedFieldBinding>,
pub(crate) named_types: Vec<(String, String)>,
pub(crate) enum_variants: Vec<(String, String, String)>,
pub(crate) composite_fields: Vec<(String, String, String)>,
}
impl DynamicTypedEntityBinding {
#[expect(
clippy::too_many_arguments,
reason = "the opaque binding keeps every accepted authority component explicit"
)]
pub(crate) fn new(
database_incarnation: [u8; 16],
entity_source: String,
entity_label: String,
entity_tag: u64,
accepted_revision: u64,
accepted_fingerprint: [u8; 16],
entity_generation: u32,
fields: Vec<(String, u32, u16, String)>,
named_types: Vec<(String, String)>,
enum_variants: Vec<(String, String, String)>,
composite_fields: Vec<(String, String, String)>,
) -> Result<Self, InternalError> {
let mut sources = BTreeSet::new();
let mut ids = BTreeSet::new();
let mut slots = BTreeSet::new();
let fields = fields
.into_iter()
.map(|(source_key, field_id, slot, label)| {
if !sources.insert(source_key.clone())
|| !ids.insert(field_id)
|| !slots.insert(slot)
{
return Err(InternalError::store_invariant());
}
Ok(DynamicTypedFieldBinding {
source_key,
field_id,
slot,
label,
})
})
.collect::<Result<Vec<_>, _>>()?;
Ok(Self {
database_incarnation,
entity_source,
entity_label,
entity_tag,
accepted_revision,
accepted_fingerprint,
entity_generation,
fields,
named_types,
enum_variants,
composite_fields,
})
}
#[must_use]
pub const fn entity(&self) -> &str {
self.entity_label.as_str()
}
#[must_use]
pub const fn entity_source(&self) -> &str {
self.entity_source.as_str()
}
#[must_use]
pub fn field_slot(&self, source_key: &str) -> Option<u16> {
self.fields
.iter()
.find_map(|field| (field.source_key == source_key).then_some(field.slot))
}
#[must_use]
pub fn output_field_slot(&self, label: &str) -> Option<u16> {
self.fields
.iter()
.find_map(|field| (field.label == label).then_some(field.slot))
}
pub(crate) fn field_identity_bindings(&self) -> impl Iterator<Item = (&str, u32, u16)> {
self.fields
.iter()
.map(|field| (field.source_key.as_str(), field.field_id, field.slot))
}
#[must_use]
pub fn bind_write_fields(
&self,
fields: Vec<(String, DynamicWriteCell)>,
) -> Option<DynamicTypedStructuralPatch> {
let mut seen_ids = BTreeSet::new();
let mut seen_slots = BTreeSet::new();
let mut bound = Vec::with_capacity(fields.len());
for (source_key, cell) in fields {
let field = self
.fields
.iter()
.find(|field| field.source_key == source_key)?;
if !seen_ids.insert(field.field_id) || !seen_slots.insert(field.slot) {
return None;
}
bound.push((field.field_id, field.slot, cell));
}
Some(DynamicTypedStructuralPatch {
entity_source: self.entity_source.clone(),
entity_tag: self.entity_tag,
accepted_fingerprint: self.accepted_fingerprint,
fields: bound,
})
}
#[must_use]
pub fn named_type_name(&self, source_key: &str) -> Option<&str> {
self.named_types
.iter()
.find_map(|(source, name)| (source == source_key).then_some(name.as_str()))
}
#[must_use]
pub fn enum_variant_name(&self, type_source_key: &str, source_key: &str) -> Option<&str> {
self.enum_variants
.iter()
.find_map(|(bound_type, source, name)| {
(bound_type == type_source_key && source == source_key).then_some(name.as_str())
})
}
#[must_use]
pub fn composite_field_name(&self, type_source_key: &str, source_key: &str) -> Option<&str> {
self.composite_fields
.iter()
.find_map(|(bound_type, source, name)| {
(bound_type == type_source_key && source == source_key).then_some(name.as_str())
})
}
}