Skip to content

Commit 233d8e9

Browse files
committed
Sketch out TreeNodeMutator API
1 parent 127fe5e commit 233d8e9

File tree

5 files changed

+466
-55
lines changed

5 files changed

+466
-55
lines changed

datafusion/common/src/tree_node.rs

Lines changed: 137 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
2121
use std::sync::Arc;
2222

23-
use crate::Result;
23+
use crate::{error::_not_impl_err, Result};
2424

2525
/// This macro is used to control continuation behaviors during tree traversals
2626
/// based on the specified direction. Depending on `$DIRECTION` and the value of
@@ -174,6 +174,66 @@ pub trait TreeNode: Sized {
174174
})
175175
}
176176

177+
/// Implements the [visitor pattern](https://en.wikipedia.org/wiki/Visitor_pattern) for
178+
/// recursively mutating / rewriting [`TreeNode`]s in place
179+
///
180+
/// Consider the following tree structure:
181+
/// ```text
182+
/// ParentNode
183+
/// left: ChildNode1
184+
/// right: ChildNode2
185+
/// ```
186+
///
187+
/// Here, the nodes would be mutated using the following order:
188+
/// ```text
189+
/// TreeNodeMutator::f_down(ParentNode)
190+
/// TreeNodeMutator::f_down(ChildNode1)
191+
/// TreeNodeMutator::f_up(ChildNode1)
192+
/// TreeNodeMutator::f_down(ChildNode2)
193+
/// TreeNodeMutator::f_up(ChildNode2)
194+
/// TreeNodeMutator::f_up(ParentNode)
195+
/// ```
196+
///
197+
/// See [`TreeNodeRecursion`] for more details on controlling the traversal.
198+
///
199+
/// # Error Handling
200+
///
201+
/// If [`TreeNodeVisitor::f_down()`] or [`TreeNodeVisitor::f_up()`] returns [`Err`],
202+
/// the recursion stops immediately and the tree may be left partially changed
203+
///
204+
/// # Changing Children During Traversal
205+
///
206+
/// If `f_down` changes the nodes children, the new children are visited
207+
/// (not the old children prior to rewrite)
208+
fn mutate<M: TreeNodeMutator<Node = Self>>(
209+
&mut self,
210+
mutator: &mut M,
211+
) -> Result<Transformed<()>> {
212+
// Note this is an inlined version of handle_transform_recursion!
213+
let pre_visited = mutator.f_down(self)?;
214+
215+
// Traverse children and then call f_up on self if necessary
216+
match pre_visited.tnr {
217+
TreeNodeRecursion::Continue => {
218+
// rewrite children recursively with mutator
219+
self.mutate_children(|c| c.mutate(mutator))?
220+
.try_transform_node_with(
221+
|_: ()| mutator.f_up(self),
222+
TreeNodeRecursion::Jump,
223+
)
224+
}
225+
TreeNodeRecursion::Jump => {
226+
// skip other children and start back up
227+
mutator.f_up(self)
228+
}
229+
TreeNodeRecursion::Stop => return Ok(pre_visited),
230+
}
231+
.map(|mut post_visited| {
232+
post_visited.transformed |= pre_visited.transformed;
233+
post_visited
234+
})
235+
}
236+
177237
/// Applies `f` to the node and its children. `f` is applied in a pre-order
178238
/// way, and it is controlled by [`TreeNodeRecursion`], which means result
179239
/// of the `f` on a node can cause an early return.
@@ -353,13 +413,34 @@ pub trait TreeNode: Sized {
353413
}
354414

355415
/// Apply the closure `F` to the node's children.
416+
///
417+
/// See `mutate_children` for rewriting in place
356418
fn apply_children<F: FnMut(&Self) -> Result<TreeNodeRecursion>>(
357419
&self,
358420
f: &mut F,
359421
) -> Result<TreeNodeRecursion>;
360422

361-
/// Apply transform `F` to the node's children. Note that the transform `F`
362-
/// might have a direction (pre-order or post-order).
423+
/// Rewrite the node's children in place using `F`.
424+
///
425+
/// Using [`Self::map_children`], the owned API, is more ideomatic and
426+
/// has clearer semantics on error (the node is consumed). However, it requires
427+
/// copying the interior fields of the tree node during rewrite
428+
///
429+
/// This API writes the nodes in place, which can be faster as it avoids
430+
/// copying. However, one downside is that the tree node can be left in an
431+
/// partially rewritten state when an error occurs.
432+
fn mutate_children<F: FnMut(&mut Self) -> Result<Transformed<()>>>(
433+
&mut self,
434+
_f: F,
435+
) -> Result<Transformed<()>> {
436+
_not_impl_err!(
437+
"mutate_children not implemented for {} yet",
438+
std::any::type_name::<Self>()
439+
)
440+
}
441+
442+
/// Apply transform `F` to potentially rewrite the node's children. Note
443+
/// that the transform `F` might have a direction (pre-order or post-order).
363444
fn map_children<F: FnMut(Self) -> Result<Transformed<Self>>>(
364445
self,
365446
f: F,
@@ -411,6 +492,36 @@ pub trait TreeNodeRewriter: Sized {
411492
}
412493
}
413494

495+
/// Trait for potentially rewriting tree of [`TreeNode`]s in place
496+
///
497+
/// See [`TreeNodeRewriter`] for rewriting owned tree ndoes
498+
/// See [`TreeNodeVisitor`] for visiting, but not changing, tree nodes
499+
pub trait TreeNodeMutator: Sized {
500+
/// The node type to rewrite.
501+
type Node: TreeNode;
502+
503+
/// Invoked while traversing down the tree before any children are rewritten.
504+
/// Default implementation returns the node as is and continues recursion.
505+
///
506+
/// Since this mutates the nodes in place, the returned Transformed object
507+
/// returns `()` (no data).
508+
///
509+
/// If the node's children are changed by `f_down`, the *new* children are
510+
/// visited, not the original.
511+
fn f_down(&mut self, _node: &mut Self::Node) -> Result<Transformed<()>> {
512+
Ok(Transformed::no(()))
513+
}
514+
515+
/// Invoked while traversing up the tree after all children have been rewritten.
516+
/// Default implementation returns the node as is and continues recursion.
517+
///
518+
/// Since this mutates the nodes in place, the returned Transformed object
519+
/// returns `()` (no data).
520+
fn f_up(&mut self, _node: &mut Self::Node) -> Result<Transformed<()>> {
521+
Ok(Transformed::no(()))
522+
}
523+
}
524+
414525
/// Controls how [`TreeNode`] recursions should proceed.
415526
#[derive(Debug, PartialEq, Clone, Copy)]
416527
pub enum TreeNodeRecursion {
@@ -489,6 +600,11 @@ impl<T> Transformed<T> {
489600
f(self.data).map(|data| Transformed::new(data, self.transformed, self.tnr))
490601
}
491602

603+
/// Invokes f(), depending on the value of self.tnr.
604+
///
605+
/// This is used to conditionally apply a function during a f_up tree
606+
/// traversal, if the result of children traversal was `Continue`.
607+
///
492608
/// Handling [`TreeNodeRecursion::Continue`] and [`TreeNodeRecursion::Stop`]
493609
/// is straightforward, but [`TreeNodeRecursion::Jump`] can behave differently
494610
/// when we are traversing down or up on a tree. If [`TreeNodeRecursion`] of
@@ -532,6 +648,24 @@ impl<T> Transformed<T> {
532648
}
533649
}
534650

651+
impl Transformed<()> {
652+
/// Invoke the given function `f` and combine the transformed state with
653+
/// the current state,
654+
///
655+
/// if f() returns an Err, returns that err
656+
/// If f() returns Ok, returns a true transformed flag if either self or
657+
/// the result of f() was transformed
658+
pub fn and_then<F>(self, f: F) -> Result<Transformed<()>>
659+
where
660+
F: FnOnce() -> Result<Transformed<()>>,
661+
{
662+
f().map(|mut t| {
663+
t.transformed |= self.transformed;
664+
t
665+
})
666+
}
667+
}
668+
535669
/// Transformation helper to process tree nodes that are siblings.
536670
pub trait TransformedIterator: Iterator {
537671
fn map_until_stop_and_collect<

datafusion/expr/src/logical_plan/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ mod ddl;
2020
pub mod display;
2121
pub mod dml;
2222
mod extension;
23+
mod mutate;
2324
mod plan;
2425
mod statement;
2526

0 commit comments

Comments
 (0)