1use crate::{
10 db::{DynamicMutationResult, TypedBindingContext, session::DbSession},
11 error::Error,
12 traits::CanisterKind,
13 value::{InputValue, OutputValue, PublicEnumValue, PublicValue},
14};
15
16use icydb_core as core;
17use serde::Deserialize;
18use std::{
19 collections::BTreeSet, error::Error as StdError, fmt, marker::PhantomData, sync::Arc,
20 vec::IntoIter,
21};
22
23#[doc(hidden)]
24pub use icydb_core::db::{TypedEntityDescriptor, TypedFieldDescriptor, TypedFieldType};
25
26#[derive(Clone, Debug, Deserialize, Eq, PartialEq, serde::Serialize)]
34pub enum WriteCell<T> {
35 Omitted,
37
38 Default,
40
41 Null,
43
44 Value(T),
46}
47
48impl<T> WriteCell<T> {
49 #[must_use]
51 pub fn map<U>(self, map: impl FnOnce(T) -> U) -> WriteCell<U> {
52 match self {
53 Self::Omitted => WriteCell::Omitted,
54 Self::Default => WriteCell::Default,
55 Self::Null => WriteCell::Null,
56 Self::Value(value) => WriteCell::Value(map(value)),
57 }
58 }
59}
60
61#[derive(Clone, Debug, Eq, PartialEq)]
63pub struct OutputRow {
64 projection: OutputRowProjection,
65 values: Vec<OutputValue>,
66}
67
68#[derive(Clone, Debug, Eq, PartialEq)]
69pub(crate) struct OutputRowProjection(Arc<OutputRowProjectionInner>);
70
71#[derive(Debug, Eq, PartialEq)]
72struct OutputRowProjectionInner {
73 binding: TypedEntityBinding,
74 entity: String,
75 accepted_slots: Vec<(u16, usize)>,
76 value_count: usize,
77}
78
79impl OutputRowProjection {
80 pub(crate) fn new(
81 binding: &TypedEntityBinding,
82 entity: impl Into<String>,
83 columns: &[String],
84 ) -> Result<Self, TypedAdapterError> {
85 let entity = entity.into();
86 if entity != binding.entity() {
87 return Err(TypedAdapterError::EntityMismatch);
88 }
89 let mut seen_slots = BTreeSet::new();
90 let mut accepted_slots = Vec::with_capacity(columns.len());
91 for (value_index, column) in columns.iter().enumerate() {
92 let Some(slot) = binding.inner.output_field_slot(column) else {
93 continue;
94 };
95 if !seen_slots.insert(slot) {
96 return Err(TypedAdapterError::RowShapeMismatch);
97 }
98 accepted_slots.push((slot, value_index));
99 }
100 Ok(Self(Arc::new(OutputRowProjectionInner {
101 binding: binding.clone(),
102 entity,
103 accepted_slots,
104 value_count: columns.len(),
105 })))
106 }
107
108 pub(crate) fn project(&self, values: Vec<OutputValue>) -> Result<OutputRow, TypedAdapterError> {
109 if values.len() != self.0.value_count {
110 return Err(TypedAdapterError::RowShapeMismatch);
111 }
112 Ok(self.project_validated(values))
113 }
114
115 fn project_validated(&self, values: Vec<OutputValue>) -> OutputRow {
116 OutputRow {
117 projection: self.clone(),
118 values,
119 }
120 }
121
122 fn entity(&self) -> &str {
123 self.0.entity.as_str()
124 }
125
126 fn binding(&self) -> &TypedEntityBinding {
127 &self.0.binding
128 }
129}
130
131#[doc(hidden)]
133pub struct PreparedOutputRows {
134 projection: OutputRowProjection,
135 rows: IntoIter<Vec<OutputValue>>,
136}
137
138impl PreparedOutputRows {
139 fn new(
140 binding: &TypedEntityBinding,
141 entity: String,
142 columns: Vec<String>,
143 rows: Vec<Vec<OutputValue>>,
144 ) -> Result<Self, TypedAdapterError> {
145 let projection = OutputRowProjection::new(binding, entity, columns.as_slice())?;
146 for values in &rows {
147 if values.len() != projection.0.value_count {
148 return Err(TypedAdapterError::RowShapeMismatch);
149 }
150 }
151 Ok(Self {
152 projection,
153 rows: rows.into_iter(),
154 })
155 }
156}
157
158impl Iterator for PreparedOutputRows {
159 type Item = OutputRow;
160
161 fn next(&mut self) -> Option<Self::Item> {
162 self.rows
163 .next()
164 .map(|values| self.projection.project_validated(values))
165 }
166
167 fn size_hint(&self) -> (usize, Option<usize>) {
168 self.rows.size_hint()
169 }
170}
171
172impl ExactSizeIterator for PreparedOutputRows {}
173
174impl OutputRow {
175 fn new(
176 binding: &TypedEntityBinding,
177 entity: impl Into<String>,
178 columns: &[String],
179 values: Vec<OutputValue>,
180 ) -> Result<Self, TypedAdapterError> {
181 OutputRowProjection::new(binding, entity, columns)?.project(values)
182 }
183
184 #[must_use]
186 pub fn entity(&self) -> &str {
187 self.projection.entity()
188 }
189}
190
191struct MutationResultRowParts {
192 affected_rows: u32,
193 columns: Vec<String>,
194 entity: String,
195 values: Vec<OutputValue>,
196}
197
198fn mutation_result_row_parts(
199 result: DynamicMutationResult,
200) -> Result<MutationResultRowParts, TypedAdapterError> {
201 let DynamicMutationResult {
202 entity,
203 columns,
204 mut rows,
205 affected_rows,
206 } = result;
207 if rows.len() != 1 {
208 return Err(TypedAdapterError::RowShapeMismatch);
209 }
210 let values = rows.pop().ok_or(TypedAdapterError::RowShapeMismatch)?;
211 Ok(MutationResultRowParts {
212 affected_rows,
213 columns,
214 entity,
215 values,
216 })
217}
218
219#[inline(never)]
220fn project_single_mutation_result(
221 binding: &TypedEntityBinding,
222 result: DynamicMutationResult,
223) -> Result<OutputRow, TypedAdapterError> {
224 let parts = mutation_result_row_parts(result)?;
225 OutputRow::new(
226 binding,
227 parts.entity,
228 parts.columns.as_slice(),
229 parts.values,
230 )
231}
232
233#[inline(never)]
234fn project_mutation_result_batch(
235 binding: &TypedEntityBinding,
236 results: Vec<DynamicMutationResult>,
237 expected_results: usize,
238) -> Result<Vec<OutputRow>, TypedAdapterError> {
239 if results.len() != expected_results {
240 return Err(TypedAdapterError::RowShapeMismatch);
241 }
242 let mut results = results.into_iter();
243 let Some(first) = results.next() else {
244 return Ok(Vec::new());
245 };
246 let first = mutation_result_row_parts(first)?;
247 let projection = OutputRowProjection::new(binding, first.entity, first.columns.as_slice())?;
248 let mut projected = Vec::with_capacity(expected_results);
249 projected.push(projection.project(first.values)?);
250
251 for result in results {
252 let result = mutation_result_row_parts(result)?;
253 if result.entity != projection.entity() {
254 return Err(TypedAdapterError::EntityMismatch);
255 }
256 if result.columns != first.columns {
257 return Err(TypedAdapterError::RowShapeMismatch);
258 }
259 projected.push(projection.project(result.values)?);
260 }
261
262 Ok(projected)
263}
264
265#[derive(Clone, Copy, Debug, Eq, PartialEq)]
267pub enum TypedAdapterError {
268 BatchHandleMismatch,
270
271 BatchRowConsumed,
273
274 BindingUnavailable(TypedBindingContext),
276
277 EntityMismatch,
279
280 FieldUnavailable,
282
283 IncompatibleField,
285
286 RowFieldUnavailable,
288
289 RowShapeMismatch,
291
292 StaleBinding,
294
295 ValueShapeMismatch,
297}
298
299impl fmt::Display for TypedAdapterError {
300 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
301 formatter.write_str(match self {
302 Self::BatchHandleMismatch => "typed batch handle mismatch",
303 Self::BatchRowConsumed => "typed batch row already consumed",
304 Self::BindingUnavailable(context) => {
305 return write!(
306 formatter,
307 "typed binding source unavailable (entity {:?}, field {:?})",
308 context.entity_source(),
309 context.field_source(),
310 );
311 }
312 Self::EntityMismatch => "typed binding entity mismatch",
313 Self::FieldUnavailable => "typed binding field unavailable",
314 Self::IncompatibleField => "typed binding field contract is incompatible",
315 Self::RowFieldUnavailable => "typed row field unavailable",
316 Self::RowShapeMismatch => "typed row shape mismatch",
317 Self::StaleBinding => "typed binding is stale",
318 Self::ValueShapeMismatch => "typed row value shape mismatch",
319 })
320 }
321}
322
323impl StdError for TypedAdapterError {}
324
325impl From<icydb_model::TypedValueError> for TypedAdapterError {
326 fn from(error: icydb_model::TypedValueError) -> Self {
327 match error {
328 icydb_model::TypedValueError::SourceUnavailable => Self::FieldUnavailable,
329 icydb_model::TypedValueError::ShapeMismatch => Self::ValueShapeMismatch,
330 }
331 }
332}
333
334#[derive(Debug)]
336pub enum TypedOperationError {
337 Adapter(TypedAdapterError),
339 Database(Error),
341}
342
343impl fmt::Display for TypedOperationError {
344 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
345 match self {
346 Self::Adapter(error) => error.fmt(formatter),
347 Self::Database(error) => error.fmt(formatter),
348 }
349 }
350}
351
352impl StdError for TypedOperationError {}
353
354impl From<Error> for TypedOperationError {
355 fn from(error: Error) -> Self {
356 Self::Database(error)
357 }
358}
359
360impl From<TypedAdapterError> for TypedOperationError {
361 fn from(error: TypedAdapterError) -> Self {
362 Self::Adapter(error)
363 }
364}
365
366#[derive(Clone, Debug, Eq, PartialEq)]
368pub struct TypedEntityBinding {
369 inner: Arc<core::db::DynamicTypedEntityBinding>,
373}
374
375impl TypedEntityBinding {
376 fn new(inner: core::db::DynamicTypedEntityBinding) -> Self {
377 Self {
378 inner: Arc::new(inner),
379 }
380 }
381
382 pub(crate) fn inner(&self) -> &core::db::DynamicTypedEntityBinding {
383 &self.inner
384 }
385
386 pub(crate) fn entity(&self) -> &str {
387 self.inner.entity()
388 }
389
390 pub fn take_row_value(
407 &self,
408 field_source_key: &str,
409 row: &mut OutputRow,
410 ) -> Result<OutputValue, TypedAdapterError> {
411 if row.projection.0.binding != *self {
412 return Err(TypedAdapterError::StaleBinding);
413 }
414 if row.projection.0.entity != self.inner.entity() {
415 return Err(TypedAdapterError::EntityMismatch);
416 }
417 let slot = self
418 .inner
419 .field_slot(field_source_key)
420 .ok_or(TypedAdapterError::FieldUnavailable)?;
421 let index = row
422 .projection
423 .0
424 .accepted_slots
425 .iter()
426 .find_map(|(bound_slot, index)| (*bound_slot == slot).then_some(*index))
427 .ok_or(TypedAdapterError::RowFieldUnavailable)?;
428 let value = row
429 .values
430 .get_mut(index)
431 .ok_or(TypedAdapterError::RowShapeMismatch)?;
432 Ok(std::mem::replace(value, OutputValue::null()))
433 }
434
435 #[doc(hidden)]
437 #[must_use]
438 pub fn named_type_name(&self, source_key: &str) -> Option<&str> {
439 self.inner.named_type_name(source_key)
440 }
441
442 #[doc(hidden)]
444 #[must_use]
445 pub fn enum_variant_name(&self, type_source_key: &str, source_key: &str) -> Option<&str> {
446 self.inner.enum_variant_name(type_source_key, source_key)
447 }
448
449 #[doc(hidden)]
451 #[must_use]
452 pub fn composite_field_name(&self, type_source_key: &str, source_key: &str) -> Option<&str> {
453 self.inner.composite_field_name(type_source_key, source_key)
454 }
455
456 #[doc(hidden)]
458 pub fn record_output_values(
459 &self,
460 type_source_key: &str,
461 member_source_keys: &[&str],
462 value: PublicValue,
463 ) -> Result<Vec<PublicValue>, TypedAdapterError> {
464 let mut accepted_names = Vec::with_capacity(member_source_keys.len());
465 for source_key in member_source_keys {
466 let name = self
467 .composite_field_name(type_source_key, source_key)
468 .ok_or(TypedAdapterError::FieldUnavailable)?;
469 accepted_names.push(name);
470 }
471
472 exact_record_output_values(accepted_names.as_slice(), value)
473 }
474}
475
476impl icydb_model::TypedAdapterContext for TypedEntityBinding {
477 type PublicValue = PublicValue;
478
479 fn input_scalar(&self, value: icydb_model::TypedScalarValue) -> Self::PublicValue {
480 match value {
481 icydb_model::TypedScalarValue::Account(value) => PublicValue::Account(value),
482 icydb_model::TypedScalarValue::Blob(value) => PublicValue::Blob(value.into_bytes()),
483 icydb_model::TypedScalarValue::Bool(value) => PublicValue::Bool(value),
484 icydb_model::TypedScalarValue::Date(value) => PublicValue::Date(value),
485 icydb_model::TypedScalarValue::Decimal(value) => PublicValue::Decimal(value),
486 icydb_model::TypedScalarValue::Duration(value) => PublicValue::Duration(value),
487 icydb_model::TypedScalarValue::Float32(value) => PublicValue::Float32(value),
488 icydb_model::TypedScalarValue::Float64(value) => PublicValue::Float64(value),
489 icydb_model::TypedScalarValue::Int64(value) => PublicValue::Int64(value),
490 icydb_model::TypedScalarValue::Int128(value) => PublicValue::Int128(value),
491 icydb_model::TypedScalarValue::Nat64(value) => PublicValue::Nat64(value),
492 icydb_model::TypedScalarValue::Nat128(value) => PublicValue::Nat128(value),
493 icydb_model::TypedScalarValue::Principal(value) => PublicValue::Principal(value),
494 icydb_model::TypedScalarValue::Subaccount(value) => PublicValue::Subaccount(value),
495 icydb_model::TypedScalarValue::Text(value) => PublicValue::Text(value),
496 icydb_model::TypedScalarValue::Timestamp(value) => PublicValue::Timestamp(value),
497 icydb_model::TypedScalarValue::Ulid(value) => PublicValue::Ulid(value),
498 icydb_model::TypedScalarValue::Unit => PublicValue::Unit,
499 icydb_model::TypedScalarValue::U256(value) => PublicValue::U256(value),
500 }
501 }
502
503 fn input_list(&self, values: Vec<Self::PublicValue>) -> Self::PublicValue {
504 PublicValue::List(values)
505 }
506
507 fn input_map(&self, entries: Vec<(Self::PublicValue, Self::PublicValue)>) -> Self::PublicValue {
508 PublicValue::Map(entries)
509 }
510
511 fn input_null(&self) -> Self::PublicValue {
512 PublicValue::Null
513 }
514
515 fn input_enum(
516 &self,
517 type_source_key: &'static str,
518 variant_source_key: &'static str,
519 payload: Option<Self::PublicValue>,
520 ) -> Result<Self::PublicValue, icydb_model::TypedValueError> {
521 let type_name = self
522 .named_type_name(type_source_key)
523 .ok_or(icydb_model::TypedValueError::SourceUnavailable)?;
524 let variant_name = self
525 .enum_variant_name(type_source_key, variant_source_key)
526 .ok_or(icydb_model::TypedValueError::SourceUnavailable)?;
527 let value = PublicEnumValue::new(variant_name, Some(type_name));
528 Ok(PublicValue::Enum(match payload {
529 Some(payload) => value.with_payload(payload),
530 None => value,
531 }))
532 }
533
534 fn input_record(
535 &self,
536 type_source_key: &'static str,
537 fields: Vec<(&'static str, Self::PublicValue)>,
538 ) -> Result<Self::PublicValue, icydb_model::TypedValueError> {
539 fields
540 .into_iter()
541 .map(|(source_key, value)| {
542 let name = self
543 .composite_field_name(type_source_key, source_key)
544 .ok_or(icydb_model::TypedValueError::SourceUnavailable)?;
545 Ok((PublicValue::Text(name.to_string()), value))
546 })
547 .collect::<Result<Vec<_>, _>>()
548 .map(PublicValue::Map)
549 }
550
551 fn output_scalar(&self, value: Self::PublicValue) -> Option<icydb_model::TypedScalarValue> {
552 Some(match value {
553 PublicValue::Account(value) => icydb_model::TypedScalarValue::Account(value),
554 PublicValue::Blob(value) => {
555 icydb_model::TypedScalarValue::Blob(icydb_schema::Blob::from(value))
556 }
557 PublicValue::Bool(value) => icydb_model::TypedScalarValue::Bool(value),
558 PublicValue::Date(value) => icydb_model::TypedScalarValue::Date(value),
559 PublicValue::Decimal(value) => icydb_model::TypedScalarValue::Decimal(value),
560 PublicValue::Duration(value) => icydb_model::TypedScalarValue::Duration(value),
561 PublicValue::Float32(value) => icydb_model::TypedScalarValue::Float32(value),
562 PublicValue::Float64(value) => icydb_model::TypedScalarValue::Float64(value),
563 PublicValue::Int64(value) => icydb_model::TypedScalarValue::Int64(value),
564 PublicValue::Int128(value) => icydb_model::TypedScalarValue::Int128(value),
565 PublicValue::Nat64(value) => icydb_model::TypedScalarValue::Nat64(value),
566 PublicValue::Nat128(value) => icydb_model::TypedScalarValue::Nat128(value),
567 PublicValue::Principal(value) => icydb_model::TypedScalarValue::Principal(value),
568 PublicValue::Subaccount(value) => icydb_model::TypedScalarValue::Subaccount(value),
569 PublicValue::Text(value) => icydb_model::TypedScalarValue::Text(value),
570 PublicValue::Timestamp(value) => icydb_model::TypedScalarValue::Timestamp(value),
571 PublicValue::Ulid(value) => icydb_model::TypedScalarValue::Ulid(value),
572 PublicValue::Unit => icydb_model::TypedScalarValue::Unit,
573 PublicValue::U256(value) => icydb_model::TypedScalarValue::U256(value),
574 PublicValue::Enum(_)
575 | PublicValue::List(_)
576 | PublicValue::Map(_)
577 | PublicValue::Null => {
578 return None;
579 }
580 })
581 }
582
583 fn output_list(&self, value: Self::PublicValue) -> Option<Vec<Self::PublicValue>> {
584 match value {
585 PublicValue::List(values) => Some(values),
586 _ => None,
587 }
588 }
589
590 fn output_map(
591 &self,
592 value: Self::PublicValue,
593 ) -> Option<Vec<(Self::PublicValue, Self::PublicValue)>> {
594 match value {
595 PublicValue::Map(entries) => Some(entries),
596 _ => None,
597 }
598 }
599
600 fn output_is_null(&self, value: &Self::PublicValue) -> bool {
601 matches!(value, PublicValue::Null)
602 }
603
604 fn output_enum(
605 &self,
606 descriptor: &'static icydb_model::TypedEnumDescriptor,
607 value: Self::PublicValue,
608 ) -> Result<icydb_model::TypedEnumSelection<Self::PublicValue>, icydb_model::TypedValueError>
609 {
610 let PublicValue::Enum(value) = value else {
611 return Err(icydb_model::TypedValueError::ShapeMismatch);
612 };
613 let type_name = self
614 .named_type_name(descriptor.type_source_key)
615 .ok_or(icydb_model::TypedValueError::SourceUnavailable)?;
616 if value.path() != Some(type_name) {
617 return Err(icydb_model::TypedValueError::ShapeMismatch);
618 }
619 let variant_source_key = self
620 .inner
621 .enum_variant_source_key(descriptor.type_source_key, value.variant())
622 .ok_or(icydb_model::TypedValueError::ShapeMismatch)?;
623 let ordinal = descriptor
624 .variants
625 .iter()
626 .position(|source_key| *source_key == variant_source_key)
627 .ok_or(icydb_model::TypedValueError::ShapeMismatch)?;
628 Ok(icydb_model::TypedEnumSelection {
629 ordinal,
630 payload: value.into_parts().2,
631 })
632 }
633
634 fn output_record(
635 &self,
636 type_source_key: &'static str,
637 member_source_keys: &[&'static str],
638 value: Self::PublicValue,
639 ) -> Result<Vec<Self::PublicValue>, icydb_model::TypedValueError> {
640 self.record_output_values(type_source_key, member_source_keys, value)
641 .map_err(|error| match error {
642 TypedAdapterError::FieldUnavailable => {
643 icydb_model::TypedValueError::SourceUnavailable
644 }
645 _ => icydb_model::TypedValueError::ShapeMismatch,
646 })
647 }
648}
649
650fn exact_record_output_values(
651 accepted_names: &[&str],
652 value: PublicValue,
653) -> Result<Vec<PublicValue>, TypedAdapterError> {
654 let PublicValue::Map(mut entries) = value else {
655 return Err(TypedAdapterError::ValueShapeMismatch);
656 };
657 if entries.len() != accepted_names.len() {
658 return Err(TypedAdapterError::ValueShapeMismatch);
659 }
660
661 for (index, accepted) in accepted_names.iter().enumerate() {
665 if accepted_names[..index].contains(accepted) {
666 return Err(TypedAdapterError::ValueShapeMismatch);
667 }
668 let offset = entries[index..]
669 .iter()
670 .position(|(key, _)| matches!(key, PublicValue::Text(name) if name == accepted))
671 .ok_or(TypedAdapterError::ValueShapeMismatch)?;
672 entries.swap(index, index + offset);
673 }
674
675 Ok(entries.into_iter().map(|(_, value)| value).collect())
676}
677
678#[cfg(test)]
679mod typed_record_output_tests {
680 use super::{TypedAdapterError, exact_record_output_values};
681 use crate::value::PublicValue;
682
683 #[test]
684 fn typed_bindings_and_output_rows_remain_send_and_sync() {
685 fn assert_send_sync<T: Send + Sync>() {}
686 assert_send_sync::<super::TypedEntityBinding>();
687 assert_send_sync::<super::OutputRow>();
688 }
689
690 fn text(value: &str) -> PublicValue {
691 PublicValue::Text(value.to_string())
692 }
693
694 #[test]
695 fn exact_record_output_reorders_accepted_members() {
696 let value = PublicValue::Map(vec![
697 (text("count"), PublicValue::Nat64(7)),
698 (text("label"), text("Ada")),
699 ]);
700
701 let values = exact_record_output_values(&["label", "count"], value)
702 .expect("exact accepted record output should decode");
703
704 assert!(matches!(&values[0], PublicValue::Text(value) if value == "Ada"));
705 assert_eq!(values[1], PublicValue::Nat64(7));
706 }
707
708 #[test]
709 fn exact_record_output_rejects_duplicate_missing_and_unknown_members() {
710 let malformed = [
711 PublicValue::Map(vec![
712 (text("label"), text("Ada")),
713 (text("label"), text("Grace")),
714 ]),
715 PublicValue::Map(vec![(text("label"), text("Ada"))]),
716 PublicValue::Map(vec![
717 (text("label"), text("Ada")),
718 (text("other"), PublicValue::Nat64(7)),
719 ]),
720 PublicValue::Map(vec![
721 (PublicValue::Nat64(1), text("Ada")),
722 (text("count"), PublicValue::Nat64(7)),
723 ]),
724 PublicValue::List(Vec::new()),
725 ];
726
727 for value in malformed.clone() {
728 assert_eq!(
729 exact_record_output_values(&["label", "count"], value),
730 Err(TypedAdapterError::ValueShapeMismatch),
731 );
732 }
733 assert_eq!(
734 exact_record_output_values(&["label", "label"], malformed[0].clone()),
735 Err(TypedAdapterError::ValueShapeMismatch),
736 );
737 }
738
739 #[test]
740 fn exact_record_output_preserves_every_member_permutation_and_null() {
741 let members = [
742 (text("label"), text("Ada")),
743 (text("count"), PublicValue::Nat64(7)),
744 (text("note"), PublicValue::Null),
745 ];
746 for order in [
747 [0, 1, 2],
748 [0, 2, 1],
749 [1, 0, 2],
750 [1, 2, 0],
751 [2, 0, 1],
752 [2, 1, 0],
753 ] {
754 let record = PublicValue::Map(
755 order
756 .into_iter()
757 .map(|index| members[index].clone())
758 .collect(),
759 );
760 assert_eq!(
761 exact_record_output_values(&["label", "count", "note"], record),
762 Ok(vec![text("Ada"), PublicValue::Nat64(7), PublicValue::Null]),
763 );
764 }
765 assert_eq!(
766 exact_record_output_values(&[], PublicValue::Map(Vec::new())),
767 Ok(Vec::new())
768 );
769 }
770
771 #[test]
772 fn typed_input_failures_retain_source_and_shape_classes() {
773 assert_eq!(
774 TypedAdapterError::from(icydb_model::TypedValueError::SourceUnavailable),
775 TypedAdapterError::FieldUnavailable,
776 );
777 assert_eq!(
778 TypedAdapterError::from(icydb_model::TypedValueError::ShapeMismatch),
779 TypedAdapterError::ValueShapeMismatch,
780 );
781 }
782}
783
784pub trait TypedRowAdapter {
786 type Row;
788
789 fn decode_row(
791 binding: &TypedEntityBinding,
792 row: OutputRow,
793 ) -> Result<Self::Row, TypedAdapterError>;
794}
795
796pub trait TypedEntityAdapter: TypedRowAdapter {
798 #[doc(hidden)]
800 const DESCRIPTOR: &'static TypedEntityDescriptor;
801
802 fn typed_binding<C>(session: &DbSession<C>) -> Result<TypedEntityBinding, TypedOperationError>
804 where
805 C: CanisterKind,
806 {
807 session.bind_typed_entity(Self::DESCRIPTOR)
808 }
809}
810
811pub trait TypedWriteAdapter {
813 type Entity: TypedEntityAdapter;
815
816 fn encode_write(self, binding: &TypedEntityBinding) -> Result<TypedWrite, TypedAdapterError>;
818}
819
820#[doc(hidden)]
822pub struct BoundWriteEncoder {
823 binding: TypedEntityBinding,
824 fields: Vec<(usize, core::db::DynamicWriteCell)>,
825}
826
827impl BoundWriteEncoder {
828 #[must_use]
830 pub fn new(binding: &TypedEntityBinding, field_count: usize) -> Self {
831 Self {
832 binding: binding.clone(),
833 fields: Vec::with_capacity(field_count),
834 }
835 }
836
837 pub fn push(&mut self, descriptor_ordinal: usize, cell: WriteCell<InputValue>) {
839 self.fields.push((descriptor_ordinal, cell.into_core()));
840 }
841
842 pub fn insert(self) -> Result<TypedWrite, TypedAdapterError> {
844 let (binding, patch) = self.into_bound_patch()?;
845 Ok(TypedWrite {
846 binding,
847 mutation: core::db::DynamicTypedMutation::Insert { patch },
848 })
849 }
850
851 pub fn update(self, key: InputValue) -> Result<TypedWrite, TypedAdapterError> {
853 let (binding, patch) = self.into_bound_patch()?;
854 Ok(TypedWrite {
855 binding,
856 mutation: core::db::DynamicTypedMutation::Update { key, patch },
857 })
858 }
859
860 pub fn replace(self, key: InputValue) -> Result<TypedWrite, TypedAdapterError> {
862 let (binding, patch) = self.into_bound_patch()?;
863 Ok(TypedWrite {
864 binding,
865 mutation: core::db::DynamicTypedMutation::Replace { key, patch },
866 })
867 }
868
869 fn into_bound_patch(
870 self,
871 ) -> Result<(TypedEntityBinding, core::db::DynamicTypedStructuralPatch), TypedAdapterError>
872 {
873 let Self { binding, fields } = self;
874 let patch = binding
875 .inner
876 .bind_write_ordinals(fields)
877 .ok_or(TypedAdapterError::FieldUnavailable)?;
878 Ok((binding, patch))
879 }
880}
881
882#[derive(Clone, Debug)]
884pub struct TypedWrite {
885 binding: TypedEntityBinding,
886 mutation: core::db::DynamicTypedMutation,
887}
888
889impl TypedWrite {
890 #[must_use]
892 pub fn delete(binding: &TypedEntityBinding, key: InputValue) -> Self {
893 Self {
894 binding: binding.clone(),
895 mutation: core::db::DynamicTypedMutation::Delete { key },
896 }
897 }
898}
899
900pub struct TypedWriteHandle<E> {
905 owner: Arc<()>,
906 position: usize,
907 entity: PhantomData<fn() -> E>,
908}
909
910impl<E> Clone for TypedWriteHandle<E> {
911 fn clone(&self) -> Self {
912 Self {
913 owner: self.owner.clone(),
914 position: self.position,
915 entity: PhantomData,
916 }
917 }
918}
919
920pub struct TrustedTypedWriteBatch<'session, C: CanisterKind> {
922 session: &'session DbSession<C>,
923 owner: Arc<()>,
924 writes: Vec<TypedWrite>,
925 bindings: Vec<TypedEntityBinding>,
926}
927
928impl<'session, C: CanisterKind> TrustedTypedWriteBatch<'session, C> {
929 fn new(session: &'session DbSession<C>) -> Self {
930 Self {
931 session,
932 owner: Arc::new(()),
933 writes: Vec::new(),
934 bindings: Vec::new(),
935 }
936 }
937
938 pub fn push<W>(&mut self, input: W) -> Result<TypedWriteHandle<W::Entity>, TypedOperationError>
940 where
941 W: TypedWriteAdapter,
942 {
943 let binding = W::Entity::typed_binding(self.session)?;
944 let write = input.encode_write(&binding)?;
945 if write.binding != binding {
946 return Err(TypedAdapterError::EntityMismatch.into());
947 }
948 let position = self.writes.len();
949 self.writes.push(write);
950 self.bindings.push(binding);
951 Ok(TypedWriteHandle {
952 owner: self.owner.clone(),
953 position,
954 entity: PhantomData,
955 })
956 }
957
958 pub fn execute(self) -> Result<TypedWriteBatchResults, TypedOperationError> {
960 let results = self
961 .session
962 .execute_trusted_typed_write_batch(self.writes)?;
963 let results = project_typed_write_batch_results(self.bindings, results)?;
964 Ok(TypedWriteBatchResults {
965 owner: self.owner,
966 results,
967 })
968 }
969}
970
971pub struct TypedWriteBatchResult {
973 affected_rows: u32,
974 projection: OutputRowProjection,
975 row: Option<OutputRow>,
976}
977
978impl TypedWriteBatchResult {
979 #[must_use]
981 pub fn entity(&self) -> &str {
982 self.projection.entity()
983 }
984
985 #[must_use]
987 pub const fn affected_rows(&self) -> u32 {
988 self.affected_rows
989 }
990
991 fn take_row(&mut self) -> Result<OutputRow, TypedAdapterError> {
992 self.row.take().ok_or(TypedAdapterError::BatchRowConsumed)
993 }
994}
995
996pub struct TypedWriteBatchResults {
998 owner: Arc<()>,
999 results: Vec<TypedWriteBatchResult>,
1000}
1001
1002impl TypedWriteBatchResults {
1003 fn handle_position<E>(&self, handle: &TypedWriteHandle<E>) -> Result<usize, TypedAdapterError> {
1004 if !Arc::ptr_eq(&self.owner, &handle.owner) || handle.position >= self.results.len() {
1005 return Err(TypedAdapterError::BatchHandleMismatch);
1006 }
1007 Ok(handle.position)
1008 }
1009
1010 pub fn result<E>(
1012 &self,
1013 handle: &TypedWriteHandle<E>,
1014 ) -> Result<&TypedWriteBatchResult, TypedAdapterError> {
1015 let position = self.handle_position(handle)?;
1016 self.results
1017 .get(position)
1018 .ok_or(TypedAdapterError::BatchHandleMismatch)
1019 }
1020
1021 pub fn row<E>(&mut self, handle: &TypedWriteHandle<E>) -> Result<E::Row, TypedOperationError>
1026 where
1027 E: TypedEntityAdapter,
1028 {
1029 let position = self.handle_position(handle)?;
1030 let result = self
1031 .results
1032 .get_mut(position)
1033 .ok_or(TypedAdapterError::BatchHandleMismatch)?;
1034 let row = result.take_row()?;
1035 Ok(E::decode_row(result.projection.binding(), row)?)
1036 }
1037}
1038
1039fn project_typed_write_batch_results(
1040 bindings: Vec<TypedEntityBinding>,
1041 results: Vec<DynamicMutationResult>,
1042) -> Result<Vec<TypedWriteBatchResult>, TypedAdapterError> {
1043 if bindings.len() != results.len() {
1044 return Err(TypedAdapterError::RowShapeMismatch);
1045 }
1046 let mut projected = Vec::with_capacity(results.len());
1047 for (binding, result) in bindings.into_iter().zip(results) {
1048 let result = mutation_result_row_parts(result)?;
1049 let projection =
1050 OutputRowProjection::new(&binding, result.entity, result.columns.as_slice())?;
1051 let row = projection.project(result.values)?;
1052 projected.push(TypedWriteBatchResult {
1053 affected_rows: result.affected_rows,
1054 projection,
1055 row: Some(row),
1056 });
1057 }
1058 Ok(projected)
1059}
1060
1061impl WriteCell<InputValue> {
1062 fn into_core(self) -> core::db::DynamicWriteCell {
1063 match self {
1064 Self::Omitted => core::db::DynamicWriteCell::Omitted,
1065 Self::Default => core::db::DynamicWriteCell::Default,
1066 Self::Null => core::db::DynamicWriteCell::Null,
1067 Self::Value(value) => core::db::DynamicWriteCell::Value(value),
1068 }
1069 }
1070}
1071
1072#[derive(Clone, Debug, Default, Eq, PartialEq)]
1081pub struct StructuralPatch {
1082 fields: Vec<(String, WriteCell<InputValue>)>,
1083}
1084
1085impl StructuralPatch {
1086 #[must_use]
1088 pub const fn new() -> Self {
1089 Self { fields: Vec::new() }
1090 }
1091
1092 #[must_use]
1094 pub fn field(mut self, name: impl Into<String>, value: WriteCell<InputValue>) -> Self {
1095 self.fields.push((name.into(), value));
1096 self
1097 }
1098
1099 fn into_core(self) -> core::db::DynamicStructuralPatch {
1100 core::db::DynamicStructuralPatch::new(
1101 self.fields
1102 .into_iter()
1103 .map(|(name, value)| (name, value.into_core()))
1104 .collect(),
1105 )
1106 }
1107}
1108
1109#[derive(Clone, Debug, Eq, PartialEq)]
1117pub enum StructuralMutation {
1118 Insert {
1120 entity: String,
1122 patch: StructuralPatch,
1124 },
1125 Update {
1127 entity: String,
1129 key: InputValue,
1131 patch: StructuralPatch,
1133 },
1134 Replace {
1138 entity: String,
1140 key: InputValue,
1142 patch: StructuralPatch,
1144 },
1145 Delete {
1147 entity: String,
1149 key: InputValue,
1151 },
1152}
1153
1154impl StructuralMutation {
1155 fn entity(&self) -> &str {
1156 match self {
1157 Self::Insert { entity, .. }
1158 | Self::Update { entity, .. }
1159 | Self::Replace { entity, .. }
1160 | Self::Delete { entity, .. } => entity,
1161 }
1162 }
1163
1164 fn into_core(self) -> core::db::DynamicMutation {
1165 match self {
1166 Self::Insert { entity, patch } => core::db::DynamicMutation::Insert {
1167 entity,
1168 patch: patch.into_core(),
1169 },
1170 Self::Update { entity, key, patch } => core::db::DynamicMutation::Update {
1171 entity,
1172 key,
1173 patch: patch.into_core(),
1174 },
1175 Self::Replace { entity, key, patch } => core::db::DynamicMutation::Replace {
1176 entity,
1177 key,
1178 patch: patch.into_core(),
1179 },
1180 Self::Delete { entity, key } => core::db::DynamicMutation::Delete { entity, key },
1181 }
1182 }
1183}
1184
1185impl<C: CanisterKind> DbSession<C> {
1186 #[inline(never)]
1187 fn ensure_typed_binding_is_current(
1188 &self,
1189 binding: &TypedEntityBinding,
1190 ) -> Result<(), TypedOperationError> {
1191 let current = self
1192 .inner
1193 .typed_entity_binding_is_current(&binding.inner)
1194 .map_err(|error| TypedOperationError::Database(Error::from(error)))?;
1195 if !current {
1196 return Err(TypedAdapterError::StaleBinding.into());
1197 }
1198 Ok(())
1199 }
1200
1201 pub fn execute_trusted_structural_mutation(
1207 &self,
1208 request: StructuralMutation,
1209 ) -> Result<DynamicMutationResult, Error> {
1210 Ok(self
1211 .inner
1212 .execute_trusted_dynamic_mutation(&request.into_core())?)
1213 }
1214
1215 pub fn execute_trusted_structural_mutation_batch(
1225 &self,
1226 mutations: Vec<StructuralMutation>,
1227 ) -> Result<Vec<DynamicMutationResult>, Error> {
1228 let mutations = mutations
1229 .into_iter()
1230 .map(StructuralMutation::into_core)
1231 .collect();
1232 Ok(self
1233 .inner
1234 .execute_trusted_dynamic_mutation_batch(mutations)?)
1235 }
1236
1237 #[inline(never)]
1245 pub fn execute_trusted_structural_mutation_batch_rows(
1246 &self,
1247 binding: &TypedEntityBinding,
1248 mutations: Vec<StructuralMutation>,
1249 ) -> Result<Vec<OutputRow>, TypedOperationError> {
1250 self.ensure_typed_binding_is_current(binding)?;
1251 if mutations
1252 .iter()
1253 .any(|mutation| mutation.entity() != binding.entity())
1254 {
1255 return Err(TypedAdapterError::EntityMismatch.into());
1256 }
1257
1258 let expected_results = mutations.len();
1259 let mutations = mutations
1260 .into_iter()
1261 .map(StructuralMutation::into_core)
1262 .collect();
1263 let results = self
1264 .inner
1265 .execute_trusted_dynamic_mutation_batch(mutations)
1266 .map_err(|error| TypedOperationError::Database(Error::from(error)))?;
1267 Ok(project_mutation_result_batch(
1268 binding,
1269 results,
1270 expected_results,
1271 )?)
1272 }
1273
1274 pub fn execute_trusted_structural_insert_batch(
1280 &self,
1281 entity: &str,
1282 patches: Vec<StructuralPatch>,
1283 ) -> Result<DynamicMutationResult, Error> {
1284 let patches = patches
1285 .into_iter()
1286 .map(StructuralPatch::into_core)
1287 .collect();
1288 Ok(self
1289 .inner
1290 .execute_trusted_dynamic_insert_batch(entity, patches)?)
1291 }
1292
1293 #[doc(hidden)]
1295 pub fn prepare_typed_output_rows(
1296 &self,
1297 binding: &TypedEntityBinding,
1298 entity: String,
1299 columns: Vec<String>,
1300 rows: Vec<Vec<OutputValue>>,
1301 ) -> Result<PreparedOutputRows, TypedOperationError> {
1302 self.ensure_typed_binding_is_current(binding)?;
1303 PreparedOutputRows::new(binding, entity, columns, rows)
1304 .map_err(TypedOperationError::Adapter)
1305 }
1306
1307 #[doc(hidden)]
1309 pub fn bind_typed_entity(
1310 &self,
1311 descriptor: &TypedEntityDescriptor,
1312 ) -> Result<TypedEntityBinding, TypedOperationError> {
1313 self.inner
1314 .issue_typed_entity_binding(descriptor)
1315 .map(TypedEntityBinding::new)
1316 .map_err(|error| match error {
1317 core::db::DynamicTypedBindingError::SourceUnavailable(context) => {
1318 TypedOperationError::Adapter(TypedAdapterError::BindingUnavailable(context))
1319 }
1320 core::db::DynamicTypedBindingError::IncompatibleField => {
1321 TypedOperationError::Adapter(TypedAdapterError::IncompatibleField)
1322 }
1323 core::db::DynamicTypedBindingError::Internal(error) => {
1324 TypedOperationError::Database(Error::from(error))
1325 }
1326 })
1327 }
1328
1329 pub fn bind_typed_input<T>(
1338 &self,
1339 binding: &TypedEntityBinding,
1340 value: T,
1341 ) -> Result<InputValue, TypedOperationError>
1342 where
1343 T: icydb_model::TypedInputValue,
1344 {
1345 self.ensure_typed_binding_is_current(binding)?;
1346 let value = value
1347 .encode_typed_input(binding)
1348 .map_err(TypedAdapterError::from)?;
1349 Ok(InputValue::from_public(value))
1350 }
1351
1352 pub fn execute_trusted_typed_write(
1354 &self,
1355 write: TypedWrite,
1356 ) -> Result<DynamicMutationResult, TypedOperationError> {
1357 self.inner
1358 .execute_trusted_typed_mutation(&write.binding.inner, write.mutation)
1359 .map_err(|error| TypedOperationError::Database(Error::from(error)))?
1360 .ok_or(TypedOperationError::Adapter(
1361 TypedAdapterError::StaleBinding,
1362 ))
1363 }
1364
1365 #[inline(never)]
1372 pub fn execute_trusted_typed_write_row(
1373 &self,
1374 write: TypedWrite,
1375 ) -> Result<OutputRow, TypedOperationError> {
1376 let TypedWrite { binding, mutation } = write;
1377 let result = self
1378 .inner
1379 .execute_trusted_typed_mutation(&binding.inner, mutation)
1380 .map_err(|error| TypedOperationError::Database(Error::from(error)))?
1381 .ok_or(TypedOperationError::Adapter(
1382 TypedAdapterError::StaleBinding,
1383 ))?;
1384 Ok(project_single_mutation_result(&binding, result)?)
1385 }
1386
1387 pub fn execute_trusted_typed_write_batch(
1394 &self,
1395 writes: Vec<TypedWrite>,
1396 ) -> Result<Vec<DynamicMutationResult>, TypedOperationError> {
1397 let requests = writes
1398 .into_iter()
1399 .map(|write| (write.binding.inner, write.mutation))
1400 .collect();
1401 self.inner
1402 .execute_trusted_typed_mutation_batch_shared(requests)
1403 .map_err(|error| TypedOperationError::Database(Error::from(error)))?
1404 .ok_or(TypedOperationError::Adapter(
1405 TypedAdapterError::StaleBinding,
1406 ))
1407 }
1408
1409 #[inline(never)]
1416 pub fn execute_trusted_typed_write_batch_rows(
1417 &self,
1418 binding: &TypedEntityBinding,
1419 writes: Vec<TypedWrite>,
1420 ) -> Result<PreparedOutputRows, TypedOperationError> {
1421 if writes.iter().any(|write| write.binding != *binding) {
1422 return Err(TypedAdapterError::EntityMismatch.into());
1423 }
1424 let expected_results = writes.len();
1425 let requests = writes.into_iter().map(|write| write.mutation).collect();
1426 let result = self
1427 .inner
1428 .execute_trusted_same_entity_typed_mutation_batch(binding.inner(), requests)
1429 .map_err(|error| TypedOperationError::Database(Error::from(error)))?
1430 .ok_or(TypedOperationError::Adapter(
1431 TypedAdapterError::StaleBinding,
1432 ))?;
1433 let DynamicMutationResult {
1434 entity,
1435 columns,
1436 rows,
1437 affected_rows: _,
1438 } = result;
1439 if rows.len() != expected_results {
1440 return Err(TypedAdapterError::RowShapeMismatch.into());
1441 }
1442 Ok(PreparedOutputRows::new(binding, entity, columns, rows)?)
1443 }
1444
1445 #[must_use]
1450 pub fn trusted_typed_write_batch(&self) -> TrustedTypedWriteBatch<'_, C> {
1451 TrustedTypedWriteBatch::new(self)
1452 }
1453
1454 #[must_use]
1456 pub fn structural_patch<I, S>(&self, fields: I) -> StructuralPatch
1457 where
1458 I: IntoIterator<Item = (S, WriteCell<InputValue>)>,
1459 S: Into<String>,
1460 {
1461 StructuralPatch {
1462 fields: fields
1463 .into_iter()
1464 .map(|(name, value)| (name.into(), value))
1465 .collect(),
1466 }
1467 }
1468}