buoyant_kernel/transforms/mod.rs
1//! Shared transform infrastructure.
2use std::borrow::{Cow, ToOwned};
3
4mod carrier;
5mod expression;
6mod schema;
7
8pub use carrier::Carrier;
9pub(crate) use carrier::{carrier_into_inner_opt, carrier_try_none};
10
11pub use self::expression::{ExpressionDepthChecker, ExpressionTransform};
12pub use self::schema::{SchemaDepthChecker, SchemaTransform};
13
14/// Defines a transform's `Output` and `Residual` associated types.
15///
16/// Example: fallible schema visitor
17/// ```rust,no_run
18/// # use buoyant_kernel as delta_kernel;
19/// # use delta_kernel::transform_output_type;
20/// # use delta_kernel::schema::StructField;
21/// # use delta_kernel::transforms::SchemaTransform;
22/// # use delta_kernel::DeltaResult;
23/// struct Validate;
24///
25/// impl<'a> SchemaTransform<'a> for Validate {
26/// transform_output_type!(|'a, T| DeltaResult<()>);
27///
28/// fn transform_struct_field(&mut self, _field: &'a StructField) -> DeltaResult<()> {
29/// todo!()
30/// }
31/// }
32/// ```
33///
34/// Example: infallible filtering expression transform
35/// ```rust,no_run
36/// # use std::borrow::Cow;
37/// # use buoyant_kernel as delta_kernel;
38/// # use delta_kernel::transform_output_type;
39/// # use delta_kernel::expressions::ColumnName;
40/// # use delta_kernel::transforms::ExpressionTransform;
41/// struct KeepSomeColumns;
42///
43/// impl<'a> ExpressionTransform<'a> for KeepSomeColumns {
44/// transform_output_type!(|'a, T| Option<Cow<'a, T>>);
45///
46/// fn transform_expr_column(&mut self, _name: &'a ColumnName) -> Option<Cow<'a, ColumnName>> {
47/// todo!()
48/// }
49/// }
50/// ```
51#[macro_export]
52macro_rules! transform_output_type {
53 (|$lt:lifetime, $T:ident| $out:ty) => {
54 type Output<$T: ::std::borrow::ToOwned + ?Sized + $lt> = $out;
55 type Residual = <Self::Output<()> as $crate::transforms::Carrier<$lt, ()>>::Residual;
56 };
57}
58#[doc(inline)]
59pub use transform_output_type;
60
61/// Rebuilds a parent from transformed children only when needed.
62///
63/// This helper consumes transformed child carriers. When a filtering carrier is used and all
64/// children were filtered out, filter out the parent as well. If all children were unchanged, it
65/// returns a carrier for the original parent. Otherwise returns a carrier for a new owned parent,
66/// produced by the provided `map_owned`.
67pub(crate) fn map_owned_children_or_else<'a, Parent, Child, ChildCarrier, ParentCarrier, R>(
68 parent: &'a Parent,
69 children: impl ExactSizeIterator<Item = ChildCarrier>,
70 map_owned: impl FnOnce(Vec<<Child as ToOwned>::Owned>) -> <Parent as ToOwned>::Owned,
71) -> ParentCarrier
72where
73 Parent: ToOwned + ?Sized,
74 Child: ToOwned + ?Sized + 'a,
75 ChildCarrier: Carrier<'a, Child, Residual = R>,
76 ParentCarrier: Carrier<'a, Parent, Residual = R>,
77{
78 let num_children = children.len();
79 let mut num_borrowed = 0;
80
81 // NOTE: A transform in visitor mode uses the ZST () as Carrier (= vec allocates no memory),
82 let mut new_children = Vec::with_capacity(num_children);
83 for child in children {
84 if let Some(transformed) = carrier_into_inner_opt!(child) {
85 if let Cow::Borrowed(_) = transformed {
86 num_borrowed += 1;
87 }
88 new_children.push(transformed);
89 }
90 }
91
92 // If all children were filtered out, try to return the "None" carrier. If not supported, then
93 // the parent must have already been empty and it's safe to continue (return borrowed parent).
94 if new_children.is_empty() {
95 carrier_try_none!();
96 }
97
98 if num_borrowed < num_children {
99 let owned = new_children.into_iter().map(Cow::into_owned).collect();
100 Carrier::from_inner(Cow::Owned(map_owned(owned)))
101 } else {
102 Carrier::from_inner(Cow::Borrowed(parent))
103 }
104}
105
106/// Rebuilds a two-child parent from transformed children only when needed.
107///
108/// If either child is filtered out (`None`), filter out the parent by returning `None`. If both
109/// children survive as borrowed values, this returns a borrowed parent. Otherwise, it uses the
110/// provided `map_owned` function to rebuild and return an owned parent.
111pub(crate) fn map_owned_pair_or_else<'a, Parent, Child, ChildCarrier, ParentCarrier, R>(
112 parent: &'a Parent,
113 left: ChildCarrier,
114 right: ChildCarrier,
115 map_owned: impl FnOnce((Child::Owned, Child::Owned)) -> Parent,
116) -> ParentCarrier
117where
118 Parent: Clone,
119 Child: ToOwned + ?Sized + 'a,
120 ChildCarrier: Carrier<'a, Child, Residual = R>,
121 ParentCarrier: Carrier<'a, Parent, Residual = R>,
122{
123 let left = carrier_into_inner_opt!(left);
124 let right = carrier_into_inner_opt!(right);
125 let (Some(left), Some(right)) = (left, right) else {
126 // SAFETY: Only a filtering carrier could produce None => try_none must succeed.
127 carrier_try_none!();
128 unreachable!();
129 };
130 Carrier::from_inner(match (left, right) {
131 (Cow::Borrowed(_), Cow::Borrowed(_)) => Cow::Borrowed(parent),
132 (left, right) => Cow::Owned(map_owned((left.into_owned(), right.into_owned()))),
133 })
134}
135
136/// Rebuilds a single-child parent from a transformed child only when needed.
137///
138/// If the child is filtered out (`None`), filter out the parent by returning `None`. If the child
139/// survives as a borrowed value, this returns a borrowed parent. Otherwise, it uses the provided
140/// `map_owned` function to rebuild and return an owned parent.
141pub(crate) fn map_owned_or_else<'a, Parent, Child, ChildCarrier, ParentCarrier, R>(
142 parent: &'a Parent,
143 child: ChildCarrier,
144 map_owned: impl FnOnce(Child::Owned) -> Parent,
145) -> ParentCarrier
146where
147 Parent: Clone,
148 Child: ToOwned + ?Sized + 'a,
149 ChildCarrier: Carrier<'a, Child, Residual = R>,
150 ParentCarrier: Carrier<'a, Parent, Residual = R>,
151{
152 let child = carrier_into_inner_opt!(child);
153 let Some(child) = child else {
154 // SAFETY: Only a filtering carrier could produce None => try_none must succeed.
155 carrier_try_none!();
156 unreachable!();
157 };
158 Carrier::from_inner(match child {
159 Cow::Owned(v) => Cow::Owned(map_owned(v)),
160 Cow::Borrowed(_) => Cow::Borrowed(parent),
161 })
162}