diff --git a/Cargo.toml b/Cargo.toml
index ff231178a2b3..48e555bd5527 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -19,6 +19,7 @@
exclude = ["datafusion-cli"]
members = [
"datafusion/common",
+ "datafusion/common_runtime",
"datafusion/core",
"datafusion/expr",
"datafusion/execution",
@@ -65,13 +66,14 @@ arrow-ord = { version = "50.0.0", default-features = false }
arrow-schema = { version = "50.0.0", default-features = false }
arrow-string = { version = "50.0.0", default-features = false }
async-trait = "0.1.73"
-bigdecimal = "0.4.1"
+bigdecimal = "=0.4.1"
bytes = "1.4"
chrono = { version = "0.4.34", default-features = false }
ctor = "0.2.0"
dashmap = "5.4.0"
datafusion = { path = "datafusion/core", version = "36.0.0", default-features = false }
datafusion-common = { path = "datafusion/common", version = "36.0.0", default-features = false }
+datafusion-common-runtime = { path = "datafusion/common_runtime", version = "36.0.0" }
datafusion-execution = { path = "datafusion/execution", version = "36.0.0" }
datafusion-expr = { path = "datafusion/expr", version = "36.0.0" }
datafusion-functions = { path = "datafusion/functions", version = "36.0.0" }
@@ -97,7 +99,7 @@ parquet = { version = "50.0.0", default-features = false, features = ["arrow", "
rand = "0.8"
rstest = "0.18.0"
serde_json = "1"
-sqlparser = { version = "0.43.0", features = ["visitor"] }
+sqlparser = { version = "0.44.0", features = ["visitor"] }
tempfile = "3"
thiserror = "1.0.44"
tokio = { version = "1.36", features = ["macros", "rt", "sync"] }
diff --git a/README.md b/README.md
index 634aa426bdff..e5ac9503be44 100644
--- a/README.md
+++ b/README.md
@@ -38,7 +38,7 @@
[API Docs](https://docs.rs/datafusion/latest/datafusion/) |
[Chat](https://discord.com/channels/885562378132000778/885562378132000781)
-
+
DataFusion is a very fast, extensible query engine for building high-quality data-centric systems in
[Rust](http://rustlang.org), using the [Apache Arrow](https://arrow.apache.org)
diff --git a/clippy.toml b/clippy.toml
index 6eb9906c89cf..62d8263085df 100644
--- a/clippy.toml
+++ b/clippy.toml
@@ -1,6 +1,6 @@
disallowed-methods = [
{ path = "tokio::task::spawn", reason = "To provide cancel-safety, use `SpawnedTask::spawn` instead (https://github.com/apache/arrow-datafusion/issues/6513)" },
- { path = "tokio::task::spawn_blocking", reason = "To provide cancel-safety, use `SpawnedTask::spawn` instead (https://github.com/apache/arrow-datafusion/issues/6513)" },
+ { path = "tokio::task::spawn_blocking", reason = "To provide cancel-safety, use `SpawnedTask::spawn_blocking` instead (https://github.com/apache/arrow-datafusion/issues/6513)" },
]
disallowed-types = [
diff --git a/datafusion-cli/Cargo.lock b/datafusion-cli/Cargo.lock
index 2379a30ce10f..46484be0e195 100644
--- a/datafusion-cli/Cargo.lock
+++ b/datafusion-cli/Cargo.lock
@@ -25,9 +25,9 @@ checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
[[package]]
name = "ahash"
-version = "0.8.10"
+version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8b79b82693f705137f8fb9b37871d99e4f9a7df12b917eed79c3d3954830a60b"
+checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
dependencies = [
"cfg-if",
"const-random",
@@ -270,7 +270,7 @@ dependencies = [
"arrow-schema",
"chrono",
"half",
- "indexmap 2.2.3",
+ "indexmap 2.2.5",
"lexical-core",
"num",
"serde",
@@ -384,7 +384,7 @@ checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.51",
+ "syn 2.0.52",
]
[[package]]
@@ -874,7 +874,7 @@ dependencies = [
"iana-time-zone",
"num-traits",
"serde",
- "windows-targets 0.52.3",
+ "windows-targets 0.52.4",
]
[[package]]
@@ -962,9 +962,9 @@ dependencies = [
[[package]]
name = "const-random"
-version = "0.1.17"
+version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5aaf16c9c2c612020bcfd042e170f6e32de9b9d75adb5277cdbbd2e2c8c8299a"
+checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359"
dependencies = [
"const-random-macro",
]
@@ -1073,7 +1073,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad291aa74992b9b7a7e88c38acbbf6ad7e107f1d90ee8775b7bc1fc3394f485c"
dependencies = [
"quote",
- "syn 2.0.51",
+ "syn 2.0.52",
]
[[package]]
@@ -1112,6 +1112,7 @@ dependencies = [
"chrono",
"dashmap",
"datafusion-common",
+ "datafusion-common-runtime",
"datafusion-execution",
"datafusion-expr",
"datafusion-functions",
@@ -1125,7 +1126,7 @@ dependencies = [
"glob",
"half",
"hashbrown 0.14.3",
- "indexmap 2.2.3",
+ "indexmap 2.2.5",
"itertools",
"log",
"num-traits",
@@ -1193,6 +1194,13 @@ dependencies = [
"sqlparser",
]
+[[package]]
+name = "datafusion-common-runtime"
+version = "36.0.0"
+dependencies = [
+ "tokio",
+]
+
[[package]]
name = "datafusion-execution"
version = "36.0.0"
@@ -1291,7 +1299,7 @@ dependencies = [
"half",
"hashbrown 0.14.3",
"hex",
- "indexmap 2.2.3",
+ "indexmap 2.2.5",
"itertools",
"log",
"md-5",
@@ -1316,13 +1324,14 @@ dependencies = [
"async-trait",
"chrono",
"datafusion-common",
+ "datafusion-common-runtime",
"datafusion-execution",
"datafusion-expr",
"datafusion-physical-expr",
"futures",
"half",
"hashbrown 0.14.3",
- "indexmap 2.2.3",
+ "indexmap 2.2.5",
"itertools",
"log",
"once_cell",
@@ -1610,7 +1619,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.51",
+ "syn 2.0.52",
]
[[package]]
@@ -1694,7 +1703,7 @@ dependencies = [
"futures-sink",
"futures-util",
"http",
- "indexmap 2.2.3",
+ "indexmap 2.2.5",
"slab",
"tokio",
"tokio-util",
@@ -1754,9 +1763,9 @@ dependencies = [
[[package]]
name = "hermit-abi"
-version = "0.3.8"
+version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "379dada1584ad501b383485dd706b8afb7a70fcbc7f4da7d780638a5a6124a60"
+checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
[[package]]
name = "hex"
@@ -1911,9 +1920,9 @@ dependencies = [
[[package]]
name = "indexmap"
-version = "2.2.3"
+version = "2.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "233cf39063f058ea2caae4091bf4a3ef70a653afbc026f5c4a4135d114e3c177"
+checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4"
dependencies = [
"equivalent",
"hashbrown 0.14.3",
@@ -2112,9 +2121,9 @@ dependencies = [
[[package]]
name = "log"
-version = "0.4.20"
+version = "0.4.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
+checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
[[package]]
name = "lz4_flex"
@@ -2178,9 +2187,9 @@ dependencies = [
[[package]]
name = "mio"
-version = "0.8.10"
+version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09"
+checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
dependencies = [
"libc",
"wasi",
@@ -2301,7 +2310,7 @@ version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
dependencies = [
- "hermit-abi 0.3.8",
+ "hermit-abi 0.3.9",
"libc",
]
@@ -2334,7 +2343,7 @@ dependencies = [
"rand",
"reqwest",
"ring 0.17.8",
- "rustls-pemfile 2.1.0",
+ "rustls-pemfile 2.1.1",
"serde",
"serde_json",
"snafu",
@@ -2463,7 +2472,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9"
dependencies = [
"fixedbitset",
- "indexmap 2.2.3",
+ "indexmap 2.2.5",
]
[[package]]
@@ -2521,7 +2530,7 @@ checksum = "266c042b60c9c76b8d53061e52b2e0d1116abc57cefc8c5cd671619a56ac3690"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.51",
+ "syn 2.0.52",
]
[[package]]
@@ -2917,9 +2926,9 @@ dependencies = [
[[package]]
name = "rustls-pemfile"
-version = "2.1.0"
+version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3c333bb734fcdedcea57de1602543590f545f127dc8b533324318fd492c5c70b"
+checksum = "f48172685e6ff52a556baa527774f61fcaa884f59daf3375c62a3f1cd2549dab"
dependencies = [
"base64",
"rustls-pki-types",
@@ -3062,7 +3071,7 @@ checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.51",
+ "syn 2.0.52",
]
[[package]]
@@ -3181,9 +3190,9 @@ checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
[[package]]
name = "sqlparser"
-version = "0.43.1"
+version = "0.44.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f95c4bae5aba7cd30bd506f7140026ade63cff5afd778af8854026f9606bf5d4"
+checksum = "aaf9c7ff146298ffda83a200f8d5084f08dcee1edfc135fcc1d646a45d50ffd6"
dependencies = [
"log",
"sqlparser_derive",
@@ -3197,7 +3206,7 @@ checksum = "01b2e185515564f15375f593fb966b5718bc624ba77fe49fa4616ad619690554"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.51",
+ "syn 2.0.52",
]
[[package]]
@@ -3243,7 +3252,7 @@ dependencies = [
"proc-macro2",
"quote",
"rustversion",
- "syn 2.0.51",
+ "syn 2.0.52",
]
[[package]]
@@ -3256,7 +3265,7 @@ dependencies = [
"proc-macro2",
"quote",
"rustversion",
- "syn 2.0.51",
+ "syn 2.0.52",
]
[[package]]
@@ -3278,9 +3287,9 @@ dependencies = [
[[package]]
name = "syn"
-version = "2.0.51"
+version = "2.0.52"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6ab617d94515e94ae53b8406c628598680aa0c9587474ecbe58188f7b345d66c"
+checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07"
dependencies = [
"proc-macro2",
"quote",
@@ -3364,7 +3373,7 @@ checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.51",
+ "syn 2.0.52",
]
[[package]]
@@ -3459,7 +3468,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.51",
+ "syn 2.0.52",
]
[[package]]
@@ -3556,7 +3565,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.51",
+ "syn 2.0.52",
]
[[package]]
@@ -3601,7 +3610,7 @@ checksum = "f03ca4cb38206e2bef0700092660bb74d696f808514dae47fa1467cbfe26e96e"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.51",
+ "syn 2.0.52",
]
[[package]]
@@ -3711,9 +3720,9 @@ dependencies = [
[[package]]
name = "walkdir"
-version = "2.4.0"
+version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee"
+checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
dependencies = [
"same-file",
"winapi-util",
@@ -3755,7 +3764,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
- "syn 2.0.51",
+ "syn 2.0.52",
"wasm-bindgen-shared",
]
@@ -3789,7 +3798,7 @@ checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.51",
+ "syn 2.0.52",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@@ -3870,7 +3879,7 @@ version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
dependencies = [
- "windows-targets 0.52.3",
+ "windows-targets 0.52.4",
]
[[package]]
@@ -3888,7 +3897,7 @@ version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
- "windows-targets 0.52.3",
+ "windows-targets 0.52.4",
]
[[package]]
@@ -3908,17 +3917,17 @@ dependencies = [
[[package]]
name = "windows-targets"
-version = "0.52.3"
+version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d380ba1dc7187569a8a9e91ed34b8ccfc33123bbacb8c0aed2d1ad7f3ef2dc5f"
+checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b"
dependencies = [
- "windows_aarch64_gnullvm 0.52.3",
- "windows_aarch64_msvc 0.52.3",
- "windows_i686_gnu 0.52.3",
- "windows_i686_msvc 0.52.3",
- "windows_x86_64_gnu 0.52.3",
- "windows_x86_64_gnullvm 0.52.3",
- "windows_x86_64_msvc 0.52.3",
+ "windows_aarch64_gnullvm 0.52.4",
+ "windows_aarch64_msvc 0.52.4",
+ "windows_i686_gnu 0.52.4",
+ "windows_i686_msvc 0.52.4",
+ "windows_x86_64_gnu 0.52.4",
+ "windows_x86_64_gnullvm 0.52.4",
+ "windows_x86_64_msvc 0.52.4",
]
[[package]]
@@ -3929,9 +3938,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]]
name = "windows_aarch64_gnullvm"
-version = "0.52.3"
+version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "68e5dcfb9413f53afd9c8f86e56a7b4d86d9a2fa26090ea2dc9e40fba56c6ec6"
+checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9"
[[package]]
name = "windows_aarch64_msvc"
@@ -3941,9 +3950,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]]
name = "windows_aarch64_msvc"
-version = "0.52.3"
+version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8dab469ebbc45798319e69eebf92308e541ce46760b49b18c6b3fe5e8965b30f"
+checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675"
[[package]]
name = "windows_i686_gnu"
@@ -3953,9 +3962,9 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]]
name = "windows_i686_gnu"
-version = "0.52.3"
+version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2a4e9b6a7cac734a8b4138a4e1044eac3404d8326b6c0f939276560687a033fb"
+checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3"
[[package]]
name = "windows_i686_msvc"
@@ -3965,9 +3974,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]]
name = "windows_i686_msvc"
-version = "0.52.3"
+version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "28b0ec9c422ca95ff34a78755cfa6ad4a51371da2a5ace67500cf7ca5f232c58"
+checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02"
[[package]]
name = "windows_x86_64_gnu"
@@ -3977,9 +3986,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]]
name = "windows_x86_64_gnu"
-version = "0.52.3"
+version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "704131571ba93e89d7cd43482277d6632589b18ecf4468f591fbae0a8b101614"
+checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03"
[[package]]
name = "windows_x86_64_gnullvm"
@@ -3989,9 +3998,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]]
name = "windows_x86_64_gnullvm"
-version = "0.52.3"
+version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "42079295511643151e98d61c38c0acc444e52dd42ab456f7ccfd5152e8ecf21c"
+checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177"
[[package]]
name = "windows_x86_64_msvc"
@@ -4001,9 +4010,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]]
name = "windows_x86_64_msvc"
-version = "0.52.3"
+version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0770833d60a970638e989b3fa9fd2bb1aaadcf88963d1659fd7d9990196ed2d6"
+checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8"
[[package]]
name = "winreg"
@@ -4047,7 +4056,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.51",
+ "syn 2.0.52",
]
[[package]]
diff --git a/datafusion-examples/examples/advanced_udwf.rs b/datafusion-examples/examples/advanced_udwf.rs
index 826abc28e174..41c6381df5d4 100644
--- a/datafusion-examples/examples/advanced_udwf.rs
+++ b/datafusion-examples/examples/advanced_udwf.rs
@@ -169,7 +169,7 @@ async fn main() -> Result<()> {
// creating a new `PartitionEvaluator`)
//
// `ORDER BY time`: within each partition ('green' or 'red') the
- // rows will be be ordered by the value in the `time` column
+ // rows will be ordered by the value in the `time` column
//
// `evaluate_inside_range` is invoked with a window defined by the
// SQL. In this case:
diff --git a/datafusion-examples/examples/rewrite_expr.rs b/datafusion-examples/examples/rewrite_expr.rs
index 8d13d1201881..cc1396f770e4 100644
--- a/datafusion-examples/examples/rewrite_expr.rs
+++ b/datafusion-examples/examples/rewrite_expr.rs
@@ -17,7 +17,7 @@
use arrow::datatypes::{DataType, Field, Schema, SchemaRef};
use datafusion_common::config::ConfigOptions;
-use datafusion_common::tree_node::{Transformed, TreeNode};
+use datafusion_common::tree_node::{Transformed, TransformedResult, TreeNode};
use datafusion_common::{plan_err, Result, ScalarValue};
use datafusion_expr::{
AggregateUDF, Between, Expr, Filter, LogicalPlan, ScalarUDF, TableSource, WindowUDF,
@@ -95,14 +95,15 @@ impl MyAnalyzerRule {
Ok(match plan {
LogicalPlan::Filter(filter) => {
let predicate = Self::analyze_expr(filter.predicate.clone())?;
- Transformed::Yes(LogicalPlan::Filter(Filter::try_new(
+ Transformed::yes(LogicalPlan::Filter(Filter::try_new(
predicate,
filter.input,
)?))
}
- _ => Transformed::No(plan),
+ _ => Transformed::no(plan),
})
})
+ .data()
}
fn analyze_expr(expr: Expr) -> Result {
@@ -111,13 +112,14 @@ impl MyAnalyzerRule {
Ok(match expr {
Expr::Literal(ScalarValue::Int64(i)) => {
// transform to UInt64
- Transformed::Yes(Expr::Literal(ScalarValue::UInt64(
+ Transformed::yes(Expr::Literal(ScalarValue::UInt64(
i.map(|i| i as u64),
)))
}
- _ => Transformed::No(expr),
+ _ => Transformed::no(expr),
})
})
+ .data()
}
}
@@ -175,14 +177,15 @@ fn my_rewrite(expr: Expr) -> Result {
let low: Expr = *low;
let high: Expr = *high;
if negated {
- Transformed::Yes(expr.clone().lt(low).or(expr.gt(high)))
+ Transformed::yes(expr.clone().lt(low).or(expr.gt(high)))
} else {
- Transformed::Yes(expr.clone().gt_eq(low).and(expr.lt_eq(high)))
+ Transformed::yes(expr.clone().gt_eq(low).and(expr.lt_eq(high)))
}
}
- _ => Transformed::No(expr),
+ _ => Transformed::no(expr),
})
})
+ .data()
}
#[derive(Default)]
diff --git a/datafusion-examples/examples/simple_udwf.rs b/datafusion-examples/examples/simple_udwf.rs
index a6149d661e75..5555e873aeb7 100644
--- a/datafusion-examples/examples/simple_udwf.rs
+++ b/datafusion-examples/examples/simple_udwf.rs
@@ -72,7 +72,7 @@ async fn main() -> Result<()> {
// creating a new `PartitionEvaluator`)
//
// `ORDER BY time`: within each partition ('green' or 'red') the
- // rows will be be ordered by the value in the `time` column
+ // rows will be ordered by the value in the `time` column
//
// `evaluate_inside_range` is invoked with a window defined by the
// SQL. In this case:
diff --git a/datafusion/common/src/parsers.rs b/datafusion/common/src/parsers.rs
index ea2508f8c455..9583ecbdb733 100644
--- a/datafusion/common/src/parsers.rs
+++ b/datafusion/common/src/parsers.rs
@@ -17,6 +17,7 @@
//! Interval parsing logic
use sqlparser::parser::ParserError;
+use std::fmt::Display;
use std::result;
use std::str::FromStr;
@@ -54,16 +55,16 @@ impl FromStr for CompressionTypeVariant {
}
}
-impl ToString for CompressionTypeVariant {
- fn to_string(&self) -> String {
- match self {
+impl Display for CompressionTypeVariant {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ let str = match self {
Self::GZIP => "GZIP",
Self::BZIP2 => "BZIP2",
Self::XZ => "XZ",
Self::ZSTD => "ZSTD",
Self::UNCOMPRESSED => "",
- }
- .to_string()
+ };
+ write!(f, "{}", str)
}
}
diff --git a/datafusion/common/src/scalar/mod.rs b/datafusion/common/src/scalar/mod.rs
index 6ab4507f949c..f431e6264367 100644
--- a/datafusion/common/src/scalar/mod.rs
+++ b/datafusion/common/src/scalar/mod.rs
@@ -4451,7 +4451,7 @@ mod tests {
// per distinct value.
//
// The alignment requirements differ across architectures and
- // thus the size of the enum appears to as as well
+ // thus the size of the enum appears to as well
assert_eq!(std::mem::size_of::(), 48);
}
diff --git a/datafusion/common/src/tree_node.rs b/datafusion/common/src/tree_node.rs
index c5c4ee824d61..2d653a27c47b 100644
--- a/datafusion/common/src/tree_node.rs
+++ b/datafusion/common/src/tree_node.rs
@@ -22,29 +22,74 @@ use std::sync::Arc;
use crate::Result;
-/// If the function returns [`VisitRecursion::Continue`], the normal execution of the
-/// function continues. If it returns [`VisitRecursion::Skip`], the function returns
-/// with [`VisitRecursion::Continue`] to jump next recursion step, bypassing further
-/// exploration of the current step. In case of [`VisitRecursion::Stop`], the function
-/// return with [`VisitRecursion::Stop`] and recursion halts.
+/// This macro is used to control continuation behaviors during tree traversals
+/// based on the specified direction. Depending on `$DIRECTION` and the value of
+/// the given expression (`$EXPR`), which should be a variant of [`TreeNodeRecursion`],
+/// the macro results in the following behavior:
+///
+/// - If the expression returns [`TreeNodeRecursion::Continue`], normal execution
+/// continues.
+/// - If it returns [`TreeNodeRecursion::Stop`], recursion halts and propagates
+/// [`TreeNodeRecursion::Stop`].
+/// - If it returns [`TreeNodeRecursion::Jump`], the continuation behavior depends
+/// on the traversal direction:
+/// - For `UP` direction, the function returns with [`TreeNodeRecursion::Jump`],
+/// bypassing further bottom-up closures until the next top-down closure.
+/// - For `DOWN` direction, the function returns with [`TreeNodeRecursion::Continue`],
+/// skipping further exploration.
+/// - If no direction is specified, `Jump` is treated like `Continue`.
#[macro_export]
-macro_rules! handle_tree_recursion {
- ($EXPR:expr) => {
+macro_rules! handle_visit_recursion {
+ // Internal helper macro for handling the `Jump` case based on the direction:
+ (@handle_jump UP) => {
+ return Ok(TreeNodeRecursion::Jump)
+ };
+ (@handle_jump DOWN) => {
+ return Ok(TreeNodeRecursion::Continue)
+ };
+ (@handle_jump) => {
+ {} // Treat `Jump` like `Continue`, do nothing and continue execution.
+ };
+
+ // Main macro logic with variables to handle directionality.
+ ($EXPR:expr $(, $DIRECTION:ident)?) => {
match $EXPR {
- VisitRecursion::Continue => {}
- // If the recursion should skip, do not apply to its children, let
- // the recursion continue:
- VisitRecursion::Skip => return Ok(VisitRecursion::Continue),
- // If the recursion should stop, do not apply to its children:
- VisitRecursion::Stop => return Ok(VisitRecursion::Stop),
+ TreeNodeRecursion::Continue => {}
+ TreeNodeRecursion::Jump => handle_visit_recursion!(@handle_jump $($DIRECTION)?),
+ TreeNodeRecursion::Stop => return Ok(TreeNodeRecursion::Stop),
}
};
}
-/// Defines a visitable and rewriteable a tree node. This trait is
-/// implemented for plans ([`ExecutionPlan`] and [`LogicalPlan`]) as
-/// well as expression trees ([`PhysicalExpr`], [`Expr`]) in
-/// DataFusion
+/// This macro is used to determine continuation during combined transforming
+/// traversals.
+///
+/// Depending on the [`TreeNodeRecursion`] the bottom-up closure returns,
+/// [`Transformed::try_transform_node_with()`] decides recursion continuation
+/// and if state propagation is necessary. Then, the same procedure recursively
+/// applies to the children of the node in question.
+macro_rules! handle_transform_recursion {
+ ($F_DOWN:expr, $F_SELF:expr, $F_UP:expr) => {{
+ let pre_visited = $F_DOWN?;
+ match pre_visited.tnr {
+ TreeNodeRecursion::Continue => pre_visited
+ .data
+ .map_children($F_SELF)?
+ .try_transform_node_with($F_UP, TreeNodeRecursion::Jump),
+ #[allow(clippy::redundant_closure_call)]
+ TreeNodeRecursion::Jump => $F_UP(pre_visited.data),
+ TreeNodeRecursion::Stop => return Ok(pre_visited),
+ }
+ .map(|mut post_visited| {
+ post_visited.transformed |= pre_visited.transformed;
+ post_visited
+ })
+ }};
+}
+
+/// Defines a visitable and rewriteable tree node. This trait is implemented
+/// for plans ([`ExecutionPlan`] and [`LogicalPlan`]) as well as expression
+/// trees ([`PhysicalExpr`], [`Expr`]) in DataFusion.
///
///
/// [`ExecutionPlan`]: https://docs.rs/datafusion/latest/datafusion/physical_plan/trait.ExecutionPlan.html
@@ -52,283 +97,507 @@ macro_rules! handle_tree_recursion {
/// [`LogicalPlan`]: https://docs.rs/datafusion-expr/latest/datafusion_expr/logical_plan/enum.LogicalPlan.html
/// [`Expr`]: https://docs.rs/datafusion-expr/latest/datafusion_expr/expr/enum.Expr.html
pub trait TreeNode: Sized {
- /// Applies `op` to the node and its children. `op` is applied in a preoder way,
- /// and it is controlled by [`VisitRecursion`], which means result of the `op`
- /// on the self node can cause an early return.
+ /// Visit the tree node using the given [`TreeNodeVisitor`], performing a
+ /// depth-first walk of the node and its children.
+ ///
+ /// Consider the following tree structure:
+ /// ```text
+ /// ParentNode
+ /// left: ChildNode1
+ /// right: ChildNode2
+ /// ```
///
- /// The `op` closure can be used to collect some info from the
- /// tree node or do some checking for the tree node.
- fn apply Result>(
+ /// Here, the nodes would be visited using the following order:
+ /// ```text
+ /// TreeNodeVisitor::f_down(ParentNode)
+ /// TreeNodeVisitor::f_down(ChildNode1)
+ /// TreeNodeVisitor::f_up(ChildNode1)
+ /// TreeNodeVisitor::f_down(ChildNode2)
+ /// TreeNodeVisitor::f_up(ChildNode2)
+ /// TreeNodeVisitor::f_up(ParentNode)
+ /// ```
+ ///
+ /// See [`TreeNodeRecursion`] for more details on controlling the traversal.
+ ///
+ /// If [`TreeNodeVisitor::f_down()`] or [`TreeNodeVisitor::f_up()`] returns [`Err`],
+ /// the recursion stops immediately.
+ ///
+ /// If using the default [`TreeNodeVisitor::f_up`] that does nothing, consider using
+ /// [`Self::apply`].
+ fn visit>(
&self,
- op: &mut F,
- ) -> Result {
- handle_tree_recursion!(op(self)?);
- self.apply_children(&mut |node| node.apply(op))
+ visitor: &mut V,
+ ) -> Result {
+ match visitor.f_down(self)? {
+ TreeNodeRecursion::Continue => {
+ handle_visit_recursion!(
+ self.apply_children(&mut |n| n.visit(visitor))?,
+ UP
+ );
+ visitor.f_up(self)
+ }
+ TreeNodeRecursion::Jump => visitor.f_up(self),
+ TreeNodeRecursion::Stop => Ok(TreeNodeRecursion::Stop),
+ }
}
- /// Visit the tree node using the given [TreeNodeVisitor]
- /// It performs a depth first walk of an node and its children.
+ /// Implements the [visitor pattern](https://en.wikipedia.org/wiki/Visitor_pattern) for
+ /// recursively transforming [`TreeNode`]s.
///
- /// For an node tree such as
+ /// Consider the following tree structure:
/// ```text
/// ParentNode
/// left: ChildNode1
/// right: ChildNode2
/// ```
///
- /// The nodes are visited using the following order
+ /// Here, the nodes would be visited using the following order:
/// ```text
- /// pre_visit(ParentNode)
- /// pre_visit(ChildNode1)
- /// post_visit(ChildNode1)
- /// pre_visit(ChildNode2)
- /// post_visit(ChildNode2)
- /// post_visit(ParentNode)
+ /// TreeNodeRewriter::f_down(ParentNode)
+ /// TreeNodeRewriter::f_down(ChildNode1)
+ /// TreeNodeRewriter::f_up(ChildNode1)
+ /// TreeNodeRewriter::f_down(ChildNode2)
+ /// TreeNodeRewriter::f_up(ChildNode2)
+ /// TreeNodeRewriter::f_up(ParentNode)
/// ```
///
- /// If an Err result is returned, recursion is stopped immediately
+ /// See [`TreeNodeRecursion`] for more details on controlling the traversal.
///
- /// If [`VisitRecursion::Stop`] is returned on a call to pre_visit, no
- /// children of that node will be visited, nor is post_visit
- /// called on that node. Details see [`TreeNodeVisitor`]
+ /// If [`TreeNodeVisitor::f_down()`] or [`TreeNodeVisitor::f_up()`] returns [`Err`],
+ /// the recursion stops immediately.
+ fn rewrite>(
+ self,
+ rewriter: &mut R,
+ ) -> Result> {
+ handle_transform_recursion!(rewriter.f_down(self), |c| c.rewrite(rewriter), |n| {
+ rewriter.f_up(n)
+ })
+ }
+
+ /// Applies `f` to the node and its children. `f` is applied in a pre-order
+ /// way, and it is controlled by [`TreeNodeRecursion`], which means result
+ /// of the `f` on a node can cause an early return.
///
- /// If using the default [`TreeNodeVisitor::post_visit`] that does
- /// nothing, [`Self::apply`] should be preferred.
- fn visit>(
+ /// The `f` closure can be used to collect some information from tree nodes
+ /// or run a check on the tree.
+ fn apply Result>(
&self,
- visitor: &mut V,
- ) -> Result {
- handle_tree_recursion!(visitor.pre_visit(self)?);
- handle_tree_recursion!(self.apply_children(&mut |node| node.visit(visitor))?);
- visitor.post_visit(self)
- }
-
- /// Convenience utils for writing optimizers rule: recursively apply the given `op` to the node tree.
- /// When `op` does not apply to a given node, it is left unchanged.
- /// The default tree traversal direction is transform_up(Postorder Traversal).
- fn transform(self, op: &F) -> Result
- where
- F: Fn(Self) -> Result>,
- {
- self.transform_up(op)
- }
-
- /// Convenience utils for writing optimizers rule: recursively apply the given 'op' to the node and all of its
- /// children(Preorder Traversal).
- /// When the `op` does not apply to a given node, it is left unchanged.
- fn transform_down(self, op: &F) -> Result
- where
- F: Fn(Self) -> Result>,
- {
- let after_op = op(self)?.into();
- after_op.map_children(|node| node.transform_down(op))
- }
-
- /// Convenience utils for writing optimizers rule: recursively apply the given 'op' to the node and all of its
- /// children(Preorder Traversal) using a mutable function, `F`.
- /// When the `op` does not apply to a given node, it is left unchanged.
- fn transform_down_mut(self, op: &mut F) -> Result
- where
- F: FnMut(Self) -> Result>,
- {
- let after_op = op(self)?.into();
- after_op.map_children(|node| node.transform_down_mut(op))
- }
-
- /// Convenience utils for writing optimizers rule: recursively apply the given 'op' first to all of its
- /// children and then itself(Postorder Traversal).
- /// When the `op` does not apply to a given node, it is left unchanged.
- fn transform_up(self, op: &F) -> Result
- where
- F: Fn(Self) -> Result>,
- {
- let after_op_children = self.map_children(|node| node.transform_up(op))?;
- let new_node = op(after_op_children)?.into();
- Ok(new_node)
- }
-
- /// Convenience utils for writing optimizers rule: recursively apply the given 'op' first to all of its
- /// children and then itself(Postorder Traversal) using a mutable function, `F`.
- /// When the `op` does not apply to a given node, it is left unchanged.
- fn transform_up_mut(self, op: &mut F) -> Result
- where
- F: FnMut(Self) -> Result>,
- {
- let after_op_children = self.map_children(|node| node.transform_up_mut(op))?;
- let new_node = op(after_op_children)?.into();
- Ok(new_node)
- }
-
- /// Transform the tree node using the given [TreeNodeRewriter]
- /// It performs a depth first walk of an node and its children.
+ f: &mut F,
+ ) -> Result {
+ handle_visit_recursion!(f(self)?, DOWN);
+ self.apply_children(&mut |n| n.apply(f))
+ }
+
+ /// Convenience utility for writing optimizer rules: Recursively apply the
+ /// given function `f` to the tree in a bottom-up (post-order) fashion. When
+ /// `f` does not apply to a given node, it is left unchanged.
+ fn transform Result>>(
+ self,
+ f: &F,
+ ) -> Result> {
+ self.transform_up(f)
+ }
+
+ /// Convenience utility for writing optimizer rules: Recursively apply the
+ /// given function `f` to a node and then to its children (pre-order traversal).
+ /// When `f` does not apply to a given node, it is left unchanged.
+ fn transform_down Result>>(
+ self,
+ f: &F,
+ ) -> Result> {
+ f(self)?.try_transform_node_with(
+ |n| n.map_children(|c| c.transform_down(f)),
+ TreeNodeRecursion::Continue,
+ )
+ }
+
+ /// Convenience utility for writing optimizer rules: Recursively apply the
+ /// given mutable function `f` to a node and then to its children (pre-order
+ /// traversal). When `f` does not apply to a given node, it is left unchanged.
+ fn transform_down_mut Result>>(
+ self,
+ f: &mut F,
+ ) -> Result> {
+ f(self)?.try_transform_node_with(
+ |n| n.map_children(|c| c.transform_down_mut(f)),
+ TreeNodeRecursion::Continue,
+ )
+ }
+
+ /// Convenience utility for writing optimizer rules: Recursively apply the
+ /// given function `f` to all children of a node, and then to the node itself
+ /// (post-order traversal). When `f` does not apply to a given node, it is
+ /// left unchanged.
+ fn transform_up Result>>(
+ self,
+ f: &F,
+ ) -> Result> {
+ self.map_children(|c| c.transform_up(f))?
+ .try_transform_node_with(f, TreeNodeRecursion::Jump)
+ }
+
+ /// Convenience utility for writing optimizer rules: Recursively apply the
+ /// given mutable function `f` to all children of a node, and then to the
+ /// node itself (post-order traversal). When `f` does not apply to a given
+ /// node, it is left unchanged.
+ fn transform_up_mut Result>>(
+ self,
+ f: &mut F,
+ ) -> Result> {
+ self.map_children(|c| c.transform_up_mut(f))?
+ .try_transform_node_with(f, TreeNodeRecursion::Jump)
+ }
+
+ /// Transforms the tree using `f_down` while traversing the tree top-down
+ /// (pre-order), and using `f_up` while traversing the tree bottom-up
+ /// (post-order).
+ ///
+ /// Use this method if you want to start the `f_up` process right where `f_down` jumps.
+ /// This can make the whole process faster by reducing the number of `f_up` steps.
+ /// If you don't need this, it's just like using `transform_down_mut` followed by
+ /// `transform_up_mut` on the same tree.
///
- /// For an node tree such as
+ /// Consider the following tree structure:
/// ```text
/// ParentNode
/// left: ChildNode1
/// right: ChildNode2
/// ```
///
- /// The nodes are visited using the following order
+ /// The nodes are visited using the following order:
/// ```text
- /// pre_visit(ParentNode)
- /// pre_visit(ChildNode1)
- /// mutate(ChildNode1)
- /// pre_visit(ChildNode2)
- /// mutate(ChildNode2)
- /// mutate(ParentNode)
+ /// f_down(ParentNode)
+ /// f_down(ChildNode1)
+ /// f_up(ChildNode1)
+ /// f_down(ChildNode2)
+ /// f_up(ChildNode2)
+ /// f_up(ParentNode)
/// ```
///
- /// If an Err result is returned, recursion is stopped immediately
+ /// See [`TreeNodeRecursion`] for more details on controlling the traversal.
///
- /// If [`false`] is returned on a call to pre_visit, no
- /// children of that node will be visited, nor is mutate
- /// called on that node
+ /// If `f_down` or `f_up` returns [`Err`], the recursion stops immediately.
///
- /// If using the default [`TreeNodeRewriter::pre_visit`] which
- /// returns `true`, [`Self::transform`] should be preferred.
- fn rewrite>(self, rewriter: &mut R) -> Result {
- let need_mutate = match rewriter.pre_visit(&self)? {
- RewriteRecursion::Mutate => return rewriter.mutate(self),
- RewriteRecursion::Stop => return Ok(self),
- RewriteRecursion::Continue => true,
- RewriteRecursion::Skip => false,
- };
-
- let after_op_children = self.map_children(|node| node.rewrite(rewriter))?;
-
- // now rewrite this node itself
- if need_mutate {
- rewriter.mutate(after_op_children)
- } else {
- Ok(after_op_children)
- }
+ /// Example:
+ /// ```text
+ /// | +---+
+ /// | | J |
+ /// | +---+
+ /// | |
+ /// | +---+
+ /// TreeNodeRecursion::Continue | | I |
+ /// | +---+
+ /// | |
+ /// | +---+
+ /// \|/ | F |
+ /// ' +---+
+ /// / \ ___________________
+ /// When `f_down` is +---+ \ ---+
+ /// applied on node "E", | E | | G |
+ /// it returns with "Jump". +---+ +---+
+ /// | |
+ /// +---+ +---+
+ /// | C | | H |
+ /// +---+ +---+
+ /// / \
+ /// +---+ +---+
+ /// | B | | D |
+ /// +---+ +---+
+ /// |
+ /// +---+
+ /// | A |
+ /// +---+
+ ///
+ /// Instead of starting from leaf nodes, `f_up` starts from the node "E".
+ /// +---+
+ /// | | J |
+ /// | +---+
+ /// | |
+ /// | +---+
+ /// | | I |
+ /// | +---+
+ /// | |
+ /// / +---+
+ /// / | F |
+ /// / +---+
+ /// / / \ ______________________
+ /// | +---+ . \ ---+
+ /// | | E | /|\ After `f_down` jumps | G |
+ /// | +---+ | on node E, `f_up` +---+
+ /// \------| ---/ if applied on node E. |
+ /// +---+ +---+
+ /// | C | | H |
+ /// +---+ +---+
+ /// / \
+ /// +---+ +---+
+ /// | B | | D |
+ /// +---+ +---+
+ /// |
+ /// +---+
+ /// | A |
+ /// +---+
+ /// ```
+ fn transform_down_up<
+ FD: FnMut(Self) -> Result>,
+ FU: FnMut(Self) -> Result>,
+ >(
+ self,
+ f_down: &mut FD,
+ f_up: &mut FU,
+ ) -> Result> {
+ handle_transform_recursion!(
+ f_down(self),
+ |c| c.transform_down_up(f_down, f_up),
+ f_up
+ )
}
- /// Apply the closure `F` to the node's children
- fn apply_children(&self, op: &mut F) -> Result
- where
- F: FnMut(&Self) -> Result;
+ /// Apply the closure `F` to the node's children.
+ fn apply_children Result>(
+ &self,
+ f: &mut F,
+ ) -> Result;
- /// Apply transform `F` to the node's children, the transform `F` might have a direction(Preorder or Postorder)
- fn map_children(self, transform: F) -> Result
- where
- F: FnMut(Self) -> Result;
+ /// Apply transform `F` to the node's children. Note that the transform `F`
+ /// might have a direction (pre-order or post-order).
+ fn map_children Result>>(
+ self,
+ f: F,
+ ) -> Result>;
}
-/// Implements the [visitor
-/// pattern](https://en.wikipedia.org/wiki/Visitor_pattern) for recursively walking [`TreeNode`]s.
-///
-/// [`TreeNodeVisitor`] allows keeping the algorithms
-/// separate from the code to traverse the structure of the `TreeNode`
-/// tree and makes it easier to add new types of tree node and
-/// algorithms.
-///
-/// When passed to[`TreeNode::visit`], [`TreeNodeVisitor::pre_visit`]
-/// and [`TreeNodeVisitor::post_visit`] are invoked recursively
-/// on an node tree.
+/// Implements the [visitor pattern](https://en.wikipedia.org/wiki/Visitor_pattern)
+/// for recursively walking [`TreeNode`]s.
///
-/// If an [`Err`] result is returned, recursion is stopped
-/// immediately.
+/// A [`TreeNodeVisitor`] allows one to express algorithms separately from the
+/// code traversing the structure of the `TreeNode` tree, making it easier to
+/// add new types of tree nodes and algorithms.
///
-/// If [`VisitRecursion::Stop`] is returned on a call to pre_visit, no
-/// children of that tree node are visited, nor is post_visit
-/// called on that tree node
-///
-/// If [`VisitRecursion::Stop`] is returned on a call to post_visit, no
-/// siblings of that tree node are visited, nor is post_visit
-/// called on its parent tree node
-///
-/// If [`VisitRecursion::Skip`] is returned on a call to pre_visit, no
-/// children of that tree node are visited.
+/// When passed to [`TreeNode::visit`], [`TreeNodeVisitor::f_down`] and
+/// [`TreeNodeVisitor::f_up`] are invoked recursively on the tree.
+/// See [`TreeNodeRecursion`] for more details on controlling the traversal.
pub trait TreeNodeVisitor: Sized {
/// The node type which is visitable.
- type N: TreeNode;
+ type Node: TreeNode;
/// Invoked before any children of `node` are visited.
- fn pre_visit(&mut self, node: &Self::N) -> Result;
+ /// Default implementation simply continues the recursion.
+ fn f_down(&mut self, _node: &Self::Node) -> Result {
+ Ok(TreeNodeRecursion::Continue)
+ }
- /// Invoked after all children of `node` are visited. Default
- /// implementation does nothing.
- fn post_visit(&mut self, _node: &Self::N) -> Result {
- Ok(VisitRecursion::Continue)
+ /// Invoked after all children of `node` are visited.
+ /// Default implementation simply continues the recursion.
+ fn f_up(&mut self, _node: &Self::Node) -> Result {
+ Ok(TreeNodeRecursion::Continue)
}
}
-/// Trait for potentially recursively transform an [`TreeNode`] node
-/// tree. When passed to `TreeNode::rewrite`, `TreeNodeRewriter::mutate` is
-/// invoked recursively on all nodes of a tree.
+/// Trait for potentially recursively transforming a tree of [`TreeNode`]s.
pub trait TreeNodeRewriter: Sized {
/// The node type which is rewritable.
- type N: TreeNode;
+ type Node: TreeNode;
- /// Invoked before (Preorder) any children of `node` are rewritten /
- /// visited. Default implementation returns `Ok(Recursion::Continue)`
- fn pre_visit(&mut self, _node: &Self::N) -> Result {
- Ok(RewriteRecursion::Continue)
+ /// Invoked while traversing down the tree before any children are rewritten.
+ /// Default implementation returns the node as is and continues recursion.
+ fn f_down(&mut self, node: Self::Node) -> Result> {
+ Ok(Transformed::no(node))
}
- /// Invoked after (Postorder) all children of `node` have been mutated and
- /// returns a potentially modified node.
- fn mutate(&mut self, node: Self::N) -> Result;
-}
-
-/// Controls how the [`TreeNode`] recursion should proceed for [`TreeNode::rewrite`].
-#[derive(Debug)]
-pub enum RewriteRecursion {
- /// Continue rewrite this node tree.
- Continue,
- /// Call 'op' immediately and return.
- Mutate,
- /// Do not rewrite the children of this node.
- Stop,
- /// Keep recursive but skip apply op on this node
- Skip,
+ /// Invoked while traversing up the tree after all children have been rewritten.
+ /// Default implementation returns the node as is and continues recursion.
+ fn f_up(&mut self, node: Self::Node) -> Result> {
+ Ok(Transformed::no(node))
+ }
}
-/// Controls how the [`TreeNode`] recursion should proceed for [`TreeNode::visit`].
-#[derive(Debug)]
-pub enum VisitRecursion {
- /// Continue the visit to this node tree.
+/// Controls how [`TreeNode`] recursions should proceed.
+#[derive(Debug, PartialEq, Clone, Copy)]
+pub enum TreeNodeRecursion {
+ /// Continue recursion with the next node.
Continue,
- /// Keep recursive but skip applying op on the children
- Skip,
- /// Stop the visit to this node tree.
+ /// In top-down traversals, skip recursing into children but continue with
+ /// the next node, which actually means pruning of the subtree.
+ ///
+ /// In bottom-up traversals, bypass calling bottom-up closures till the next
+ /// leaf node.
+ ///
+ /// In combined traversals, if it is the `f_down` (pre-order) phase, execution
+ /// "jumps" to the next `f_up` (post-order) phase by shortcutting its children.
+ /// If it is the `f_up` (post-order) phase, execution "jumps" to the next `f_down`
+ /// (pre-order) phase by shortcutting its parent nodes until the first parent node
+ /// having unvisited children path.
+ Jump,
+ /// Stop recursion.
Stop,
}
-pub enum Transformed {
- /// The item was transformed / rewritten somehow
- Yes(T),
- /// The item was not transformed
- No(T),
+/// This struct is used by tree transformation APIs such as
+/// - [`TreeNode::rewrite`],
+/// - [`TreeNode::transform_down`],
+/// - [`TreeNode::transform_down_mut`],
+/// - [`TreeNode::transform_up`],
+/// - [`TreeNode::transform_up_mut`],
+/// - [`TreeNode::transform_down_up`]
+///
+/// to control the transformation and return the transformed result.
+///
+/// Specifically, API users can provide transformation closures or [`TreeNodeRewriter`]
+/// implementations to control the transformation by returning:
+/// - The resulting (possibly transformed) node,
+/// - A flag indicating whether any change was made to the node, and
+/// - A flag specifying how to proceed with the recursion.
+///
+/// At the end of the transformation, the return value will contain:
+/// - The final (possibly transformed) tree,
+/// - A flag indicating whether any change was made to the tree, and
+/// - A flag specifying how the recursion ended.
+#[derive(PartialEq, Debug)]
+pub struct Transformed {
+ pub data: T,
+ pub transformed: bool,
+ pub tnr: TreeNodeRecursion,
}
impl Transformed {
- pub fn into(self) -> T {
- match self {
- Transformed::Yes(t) => t,
- Transformed::No(t) => t,
+ /// Create a new `Transformed` object with the given information.
+ pub fn new(data: T, transformed: bool, tnr: TreeNodeRecursion) -> Self {
+ Self {
+ data,
+ transformed,
+ tnr,
}
}
- pub fn into_pair(self) -> (T, bool) {
- match self {
- Transformed::Yes(t) => (t, true),
- Transformed::No(t) => (t, false),
+ /// Wrapper for transformed data with [`TreeNodeRecursion::Continue`] statement.
+ pub fn yes(data: T) -> Self {
+ Self::new(data, true, TreeNodeRecursion::Continue)
+ }
+
+ /// Wrapper for unchanged data with [`TreeNodeRecursion::Continue`] statement.
+ pub fn no(data: T) -> Self {
+ Self::new(data, false, TreeNodeRecursion::Continue)
+ }
+
+ /// Applies the given `f` to the data of this [`Transformed`] object.
+ pub fn update_data U>(self, f: F) -> Transformed {
+ Transformed::new(f(self.data), self.transformed, self.tnr)
+ }
+
+ /// Maps the data of [`Transformed`] object to the result of the given `f`.
+ pub fn map_data Result>(self, f: F) -> Result> {
+ f(self.data).map(|data| Transformed::new(data, self.transformed, self.tnr))
+ }
+
+ /// Handling [`TreeNodeRecursion::Continue`] and [`TreeNodeRecursion::Stop`]
+ /// is straightforward, but [`TreeNodeRecursion::Jump`] can behave differently
+ /// when we are traversing down or up on a tree. If [`TreeNodeRecursion`] of
+ /// the node is [`TreeNodeRecursion::Jump`], recursion stops with the given
+ /// `return_if_jump` value.
+ fn try_transform_node_with Result>>(
+ mut self,
+ f: F,
+ return_if_jump: TreeNodeRecursion,
+ ) -> Result> {
+ match self.tnr {
+ TreeNodeRecursion::Continue => {
+ return f(self.data).map(|mut t| {
+ t.transformed |= self.transformed;
+ t
+ });
+ }
+ TreeNodeRecursion::Jump => {
+ self.tnr = return_if_jump;
+ }
+ TreeNodeRecursion::Stop => {}
+ }
+ Ok(self)
+ }
+
+ /// If [`TreeNodeRecursion`] of the node is [`TreeNodeRecursion::Continue`] or
+ /// [`TreeNodeRecursion::Jump`], transformation is applied to the node.
+ /// Otherwise, it remains as it is.
+ pub fn try_transform_node Result>>(
+ self,
+ f: F,
+ ) -> Result> {
+ if self.tnr == TreeNodeRecursion::Stop {
+ Ok(self)
+ } else {
+ f(self.data).map(|mut t| {
+ t.transformed |= self.transformed;
+ t
+ })
}
}
}
-/// Helper trait for implementing [`TreeNode`] that have children stored as Arc's
-///
-/// If some trait object, such as `dyn T`, implements this trait,
-/// its related `Arc` will automatically implement [`TreeNode`]
+/// Transformation helper to process tree nodes that are siblings.
+pub trait TransformedIterator: Iterator {
+ fn map_until_stop_and_collect<
+ F: FnMut(Self::Item) -> Result>,
+ >(
+ self,
+ f: F,
+ ) -> Result>>;
+}
+
+impl TransformedIterator for I {
+ fn map_until_stop_and_collect<
+ F: FnMut(Self::Item) -> Result>,
+ >(
+ self,
+ mut f: F,
+ ) -> Result>> {
+ let mut tnr = TreeNodeRecursion::Continue;
+ let mut transformed = false;
+ let data = self
+ .map(|item| match tnr {
+ TreeNodeRecursion::Continue | TreeNodeRecursion::Jump => {
+ f(item).map(|result| {
+ tnr = result.tnr;
+ transformed |= result.transformed;
+ result.data
+ })
+ }
+ TreeNodeRecursion::Stop => Ok(item),
+ })
+ .collect::>>()?;
+ Ok(Transformed::new(data, transformed, tnr))
+ }
+}
+
+/// Transformation helper to access [`Transformed`] fields in a [`Result`] easily.
+pub trait TransformedResult {
+ fn data(self) -> Result;
+
+ fn transformed(self) -> Result;
+
+ fn tnr(self) -> Result;
+}
+
+impl TransformedResult for Result> {
+ fn data(self) -> Result {
+ self.map(|t| t.data)
+ }
+
+ fn transformed(self) -> Result {
+ self.map(|t| t.transformed)
+ }
+
+ fn tnr(self) -> Result {
+ self.map(|t| t.tnr)
+ }
+}
+
+/// Helper trait for implementing [`TreeNode`] that have children stored as
+/// `Arc`s. If some trait object, such as `dyn T`, implements this trait,
+/// its related `Arc` will automatically implement [`TreeNode`].
pub trait DynTreeNode {
- /// Returns all children of the specified TreeNode
+ /// Returns all children of the specified `TreeNode`.
fn arc_children(&self) -> Vec>;
- /// construct a new self with the specified children
+ /// Constructs a new node with the specified children.
fn with_new_arc_children(
&self,
arc_self: Arc,
@@ -336,32 +605,40 @@ pub trait DynTreeNode {
) -> Result>;
}
-/// Blanket implementation for Arc for any tye that implements
-/// [`DynTreeNode`] (such as [`Arc`])
+/// Blanket implementation for any `Arc` where `T` implements [`DynTreeNode`]
+/// (such as [`Arc`]).
impl TreeNode for Arc {
- /// Apply the closure `F` to the node's children
- fn apply_children(&self, op: &mut F) -> Result
- where
- F: FnMut(&Self) -> Result,
- {
+ fn apply_children Result>(
+ &self,
+ f: &mut F,
+ ) -> Result {
+ let mut tnr = TreeNodeRecursion::Continue;
for child in self.arc_children() {
- handle_tree_recursion!(op(&child)?)
+ tnr = f(&child)?;
+ handle_visit_recursion!(tnr)
}
- Ok(VisitRecursion::Continue)
+ Ok(tnr)
}
- fn map_children(self, transform: F) -> Result
- where
- F: FnMut(Self) -> Result,
- {
+ fn map_children Result>>(
+ self,
+ f: F,
+ ) -> Result> {
let children = self.arc_children();
if !children.is_empty() {
- let new_children =
- children.into_iter().map(transform).collect::>()?;
- let arc_self = Arc::clone(&self);
- self.with_new_arc_children(arc_self, new_children)
+ let new_children = children.into_iter().map_until_stop_and_collect(f)?;
+ // Propagate up `new_children.transformed` and `new_children.tnr`
+ // along with the node containing transformed children.
+ if new_children.transformed {
+ let arc_self = Arc::clone(&self);
+ new_children.map_data(|new_children| {
+ self.with_new_arc_children(arc_self, new_children)
+ })
+ } else {
+ Ok(Transformed::new(self, false, new_children.tnr))
+ }
} else {
- Ok(self)
+ Ok(Transformed::no(self))
}
}
}
@@ -381,28 +658,1016 @@ pub trait ConcreteTreeNode: Sized {
}
impl TreeNode for T {
- /// Apply the closure `F` to the node's children
- fn apply_children(&self, op: &mut F) -> Result
- where
- F: FnMut(&Self) -> Result,
- {
+ fn apply_children Result>(
+ &self,
+ f: &mut F,
+ ) -> Result {
+ let mut tnr = TreeNodeRecursion::Continue;
for child in self.children() {
- handle_tree_recursion!(op(child)?)
+ tnr = f(child)?;
+ handle_visit_recursion!(tnr)
}
- Ok(VisitRecursion::Continue)
+ Ok(tnr)
}
- fn map_children(self, transform: F) -> Result
- where
- F: FnMut(Self) -> Result,
- {
+ fn map_children Result>>(
+ self,
+ f: F,
+ ) -> Result> {
let (new_self, children) = self.take_children();
if !children.is_empty() {
- let new_children =
- children.into_iter().map(transform).collect::>()?;
- new_self.with_new_children(new_children)
+ let new_children = children.into_iter().map_until_stop_and_collect(f)?;
+ // Propagate up `new_children.transformed` and `new_children.tnr` along with
+ // the node containing transformed children.
+ new_children.map_data(|new_children| new_self.with_new_children(new_children))
} else {
- Ok(new_self)
+ Ok(Transformed::no(new_self))
}
}
}
+
+#[cfg(test)]
+mod tests {
+ use std::fmt::Display;
+
+ use crate::tree_node::{
+ Transformed, TransformedIterator, TreeNode, TreeNodeRecursion, TreeNodeRewriter,
+ TreeNodeVisitor,
+ };
+ use crate::Result;
+
+ #[derive(PartialEq, Debug)]
+ struct TestTreeNode {
+ children: Vec>,
+ data: T,
+ }
+
+ impl TestTreeNode {
+ fn new(children: Vec>, data: T) -> Self {
+ Self { children, data }
+ }
+ }
+
+ impl TreeNode for TestTreeNode {
+ fn apply_children(&self, f: &mut F) -> Result
+ where
+ F: FnMut(&Self) -> Result,
+ {
+ let mut tnr = TreeNodeRecursion::Continue;
+ for child in &self.children {
+ tnr = f(child)?;
+ handle_visit_recursion!(tnr);
+ }
+ Ok(tnr)
+ }
+
+ fn map_children(self, f: F) -> Result>
+ where
+ F: FnMut(Self) -> Result>,
+ {
+ Ok(self
+ .children
+ .into_iter()
+ .map_until_stop_and_collect(f)?
+ .update_data(|new_children| Self {
+ children: new_children,
+ ..self
+ }))
+ }
+ }
+
+ // J
+ // |
+ // I
+ // |
+ // F
+ // / \
+ // E G
+ // | |
+ // C H
+ // / \
+ // B D
+ // |
+ // A
+ fn test_tree() -> TestTreeNode {
+ let node_a = TestTreeNode::new(vec![], "a".to_string());
+ let node_b = TestTreeNode::new(vec![], "b".to_string());
+ let node_d = TestTreeNode::new(vec![node_a], "d".to_string());
+ let node_c = TestTreeNode::new(vec![node_b, node_d], "c".to_string());
+ let node_e = TestTreeNode::new(vec![node_c], "e".to_string());
+ let node_h = TestTreeNode::new(vec![], "h".to_string());
+ let node_g = TestTreeNode::new(vec![node_h], "g".to_string());
+ let node_f = TestTreeNode::new(vec![node_e, node_g], "f".to_string());
+ let node_i = TestTreeNode::new(vec![node_f], "i".to_string());
+ TestTreeNode::new(vec![node_i], "j".to_string())
+ }
+
+ // Continue on all nodes
+ // Expected visits in a combined traversal
+ fn all_visits() -> Vec {
+ vec![
+ "f_down(j)",
+ "f_down(i)",
+ "f_down(f)",
+ "f_down(e)",
+ "f_down(c)",
+ "f_down(b)",
+ "f_up(b)",
+ "f_down(d)",
+ "f_down(a)",
+ "f_up(a)",
+ "f_up(d)",
+ "f_up(c)",
+ "f_up(e)",
+ "f_down(g)",
+ "f_down(h)",
+ "f_up(h)",
+ "f_up(g)",
+ "f_up(f)",
+ "f_up(i)",
+ "f_up(j)",
+ ]
+ .into_iter()
+ .map(|s| s.to_string())
+ .collect()
+ }
+
+ // Expected transformed tree after a combined traversal
+ fn transformed_tree() -> TestTreeNode {
+ let node_a = TestTreeNode::new(vec![], "f_up(f_down(a))".to_string());
+ let node_b = TestTreeNode::new(vec![], "f_up(f_down(b))".to_string());
+ let node_d = TestTreeNode::new(vec![node_a], "f_up(f_down(d))".to_string());
+ let node_c =
+ TestTreeNode::new(vec![node_b, node_d], "f_up(f_down(c))".to_string());
+ let node_e = TestTreeNode::new(vec![node_c], "f_up(f_down(e))".to_string());
+ let node_h = TestTreeNode::new(vec![], "f_up(f_down(h))".to_string());
+ let node_g = TestTreeNode::new(vec![node_h], "f_up(f_down(g))".to_string());
+ let node_f =
+ TestTreeNode::new(vec![node_e, node_g], "f_up(f_down(f))".to_string());
+ let node_i = TestTreeNode::new(vec![node_f], "f_up(f_down(i))".to_string());
+ TestTreeNode::new(vec![node_i], "f_up(f_down(j))".to_string())
+ }
+
+ // Expected transformed tree after a top-down traversal
+ fn transformed_down_tree() -> TestTreeNode {
+ let node_a = TestTreeNode::new(vec![], "f_down(a)".to_string());
+ let node_b = TestTreeNode::new(vec![], "f_down(b)".to_string());
+ let node_d = TestTreeNode::new(vec![node_a], "f_down(d)".to_string());
+ let node_c = TestTreeNode::new(vec![node_b, node_d], "f_down(c)".to_string());
+ let node_e = TestTreeNode::new(vec![node_c], "f_down(e)".to_string());
+ let node_h = TestTreeNode::new(vec![], "f_down(h)".to_string());
+ let node_g = TestTreeNode::new(vec![node_h], "f_down(g)".to_string());
+ let node_f = TestTreeNode::new(vec![node_e, node_g], "f_down(f)".to_string());
+ let node_i = TestTreeNode::new(vec![node_f], "f_down(i)".to_string());
+ TestTreeNode::new(vec![node_i], "f_down(j)".to_string())
+ }
+
+ // Expected transformed tree after a bottom-up traversal
+ fn transformed_up_tree() -> TestTreeNode {
+ let node_a = TestTreeNode::new(vec![], "f_up(a)".to_string());
+ let node_b = TestTreeNode::new(vec![], "f_up(b)".to_string());
+ let node_d = TestTreeNode::new(vec![node_a], "f_up(d)".to_string());
+ let node_c = TestTreeNode::new(vec![node_b, node_d], "f_up(c)".to_string());
+ let node_e = TestTreeNode::new(vec![node_c], "f_up(e)".to_string());
+ let node_h = TestTreeNode::new(vec![], "f_up(h)".to_string());
+ let node_g = TestTreeNode::new(vec![node_h], "f_up(g)".to_string());
+ let node_f = TestTreeNode::new(vec![node_e, node_g], "f_up(f)".to_string());
+ let node_i = TestTreeNode::new(vec![node_f], "f_up(i)".to_string());
+ TestTreeNode::new(vec![node_i], "f_up(j)".to_string())
+ }
+
+ // f_down Jump on A node
+ fn f_down_jump_on_a_visits() -> Vec {
+ vec![
+ "f_down(j)",
+ "f_down(i)",
+ "f_down(f)",
+ "f_down(e)",
+ "f_down(c)",
+ "f_down(b)",
+ "f_up(b)",
+ "f_down(d)",
+ "f_down(a)",
+ "f_up(a)",
+ "f_up(d)",
+ "f_up(c)",
+ "f_up(e)",
+ "f_down(g)",
+ "f_down(h)",
+ "f_up(h)",
+ "f_up(g)",
+ "f_up(f)",
+ "f_up(i)",
+ "f_up(j)",
+ ]
+ .into_iter()
+ .map(|s| s.to_string())
+ .collect()
+ }
+
+ fn f_down_jump_on_a_transformed_down_tree() -> TestTreeNode {
+ let node_a = TestTreeNode::new(vec![], "f_down(a)".to_string());
+ let node_b = TestTreeNode::new(vec![], "f_down(b)".to_string());
+ let node_d = TestTreeNode::new(vec![node_a], "f_down(d)".to_string());
+ let node_c = TestTreeNode::new(vec![node_b, node_d], "f_down(c)".to_string());
+ let node_e = TestTreeNode::new(vec![node_c], "f_down(e)".to_string());
+ let node_h = TestTreeNode::new(vec![], "f_down(h)".to_string());
+ let node_g = TestTreeNode::new(vec![node_h], "f_down(g)".to_string());
+ let node_f = TestTreeNode::new(vec![node_e, node_g], "f_down(f)".to_string());
+ let node_i = TestTreeNode::new(vec![node_f], "f_down(i)".to_string());
+ TestTreeNode::new(vec![node_i], "f_down(j)".to_string())
+ }
+
+ // f_down Jump on E node
+ fn f_down_jump_on_e_visits() -> Vec {
+ vec![
+ "f_down(j)",
+ "f_down(i)",
+ "f_down(f)",
+ "f_down(e)",
+ "f_up(e)",
+ "f_down(g)",
+ "f_down(h)",
+ "f_up(h)",
+ "f_up(g)",
+ "f_up(f)",
+ "f_up(i)",
+ "f_up(j)",
+ ]
+ .into_iter()
+ .map(|s| s.to_string())
+ .collect()
+ }
+
+ fn f_down_jump_on_e_transformed_tree() -> TestTreeNode {
+ let node_a = TestTreeNode::new(vec![], "a".to_string());
+ let node_b = TestTreeNode::new(vec![], "b".to_string());
+ let node_d = TestTreeNode::new(vec![node_a], "d".to_string());
+ let node_c = TestTreeNode::new(vec![node_b, node_d], "c".to_string());
+ let node_e = TestTreeNode::new(vec![node_c], "f_up(f_down(e))".to_string());
+ let node_h = TestTreeNode::new(vec![], "f_up(f_down(h))".to_string());
+ let node_g = TestTreeNode::new(vec![node_h], "f_up(f_down(g))".to_string());
+ let node_f =
+ TestTreeNode::new(vec![node_e, node_g], "f_up(f_down(f))".to_string());
+ let node_i = TestTreeNode::new(vec![node_f], "f_up(f_down(i))".to_string());
+ TestTreeNode::new(vec![node_i], "f_up(f_down(j))".to_string())
+ }
+
+ fn f_down_jump_on_e_transformed_down_tree() -> TestTreeNode {
+ let node_a = TestTreeNode::new(vec![], "a".to_string());
+ let node_b = TestTreeNode::new(vec![], "b".to_string());
+ let node_d = TestTreeNode::new(vec![node_a], "d".to_string());
+ let node_c = TestTreeNode::new(vec![node_b, node_d], "c".to_string());
+ let node_e = TestTreeNode::new(vec![node_c], "f_down(e)".to_string());
+ let node_h = TestTreeNode::new(vec![], "f_down(h)".to_string());
+ let node_g = TestTreeNode::new(vec![node_h], "f_down(g)".to_string());
+ let node_f = TestTreeNode::new(vec![node_e, node_g], "f_down(f)".to_string());
+ let node_i = TestTreeNode::new(vec![node_f], "f_down(i)".to_string());
+ TestTreeNode::new(vec![node_i], "f_down(j)".to_string())
+ }
+
+ // f_up Jump on A node
+ fn f_up_jump_on_a_visits() -> Vec {
+ vec![
+ "f_down(j)",
+ "f_down(i)",
+ "f_down(f)",
+ "f_down(e)",
+ "f_down(c)",
+ "f_down(b)",
+ "f_up(b)",
+ "f_down(d)",
+ "f_down(a)",
+ "f_up(a)",
+ "f_down(g)",
+ "f_down(h)",
+ "f_up(h)",
+ "f_up(g)",
+ "f_up(f)",
+ "f_up(i)",
+ "f_up(j)",
+ ]
+ .into_iter()
+ .map(|s| s.to_string())
+ .collect()
+ }
+
+ fn f_up_jump_on_a_transformed_tree() -> TestTreeNode {
+ let node_a = TestTreeNode::new(vec![], "f_up(f_down(a))".to_string());
+ let node_b = TestTreeNode::new(vec![], "f_up(f_down(b))".to_string());
+ let node_d = TestTreeNode::new(vec![node_a], "f_down(d)".to_string());
+ let node_c = TestTreeNode::new(vec![node_b, node_d], "f_down(c)".to_string());
+ let node_e = TestTreeNode::new(vec![node_c], "f_down(e)".to_string());
+ let node_h = TestTreeNode::new(vec![], "f_up(f_down(h))".to_string());
+ let node_g = TestTreeNode::new(vec![node_h], "f_up(f_down(g))".to_string());
+ let node_f =
+ TestTreeNode::new(vec![node_e, node_g], "f_up(f_down(f))".to_string());
+ let node_i = TestTreeNode::new(vec![node_f], "f_up(f_down(i))".to_string());
+ TestTreeNode::new(vec![node_i], "f_up(f_down(j))".to_string())
+ }
+
+ fn f_up_jump_on_a_transformed_up_tree() -> TestTreeNode {
+ let node_a = TestTreeNode::new(vec![], "f_up(a)".to_string());
+ let node_b = TestTreeNode::new(vec![], "f_up(b)".to_string());
+ let node_d = TestTreeNode::new(vec![node_a], "d".to_string());
+ let node_c = TestTreeNode::new(vec![node_b, node_d], "c".to_string());
+ let node_e = TestTreeNode::new(vec![node_c], "e".to_string());
+ let node_h = TestTreeNode::new(vec![], "f_up(h)".to_string());
+ let node_g = TestTreeNode::new(vec![node_h], "f_up(g)".to_string());
+ let node_f = TestTreeNode::new(vec![node_e, node_g], "f_up(f)".to_string());
+ let node_i = TestTreeNode::new(vec![node_f], "f_up(i)".to_string());
+ TestTreeNode::new(vec![node_i], "f_up(j)".to_string())
+ }
+
+ // f_up Jump on E node
+ fn f_up_jump_on_e_visits() -> Vec {
+ vec![
+ "f_down(j)",
+ "f_down(i)",
+ "f_down(f)",
+ "f_down(e)",
+ "f_down(c)",
+ "f_down(b)",
+ "f_up(b)",
+ "f_down(d)",
+ "f_down(a)",
+ "f_up(a)",
+ "f_up(d)",
+ "f_up(c)",
+ "f_up(e)",
+ "f_down(g)",
+ "f_down(h)",
+ "f_up(h)",
+ "f_up(g)",
+ "f_up(f)",
+ "f_up(i)",
+ "f_up(j)",
+ ]
+ .into_iter()
+ .map(|s| s.to_string())
+ .collect()
+ }
+
+ fn f_up_jump_on_e_transformed_tree() -> TestTreeNode {
+ transformed_tree()
+ }
+
+ fn f_up_jump_on_e_transformed_up_tree() -> TestTreeNode {
+ transformed_up_tree()
+ }
+
+ // f_down Stop on A node
+
+ fn f_down_stop_on_a_visits() -> Vec {
+ vec![
+ "f_down(j)",
+ "f_down(i)",
+ "f_down(f)",
+ "f_down(e)",
+ "f_down(c)",
+ "f_down(b)",
+ "f_up(b)",
+ "f_down(d)",
+ "f_down(a)",
+ ]
+ .into_iter()
+ .map(|s| s.to_string())
+ .collect()
+ }
+
+ fn f_down_stop_on_a_transformed_tree() -> TestTreeNode {
+ let node_a = TestTreeNode::new(vec![], "f_down(a)".to_string());
+ let node_b = TestTreeNode::new(vec![], "f_up(f_down(b))".to_string());
+ let node_d = TestTreeNode::new(vec![node_a], "f_down(d)".to_string());
+ let node_c = TestTreeNode::new(vec![node_b, node_d], "f_down(c)".to_string());
+ let node_e = TestTreeNode::new(vec![node_c], "f_down(e)".to_string());
+ let node_h = TestTreeNode::new(vec![], "h".to_string());
+ let node_g = TestTreeNode::new(vec![node_h], "g".to_string());
+ let node_f = TestTreeNode::new(vec![node_e, node_g], "f_down(f)".to_string());
+ let node_i = TestTreeNode::new(vec![node_f], "f_down(i)".to_string());
+ TestTreeNode::new(vec![node_i], "f_down(j)".to_string())
+ }
+
+ fn f_down_stop_on_a_transformed_down_tree() -> TestTreeNode {
+ let node_a = TestTreeNode::new(vec![], "f_down(a)".to_string());
+ let node_b = TestTreeNode::new(vec![], "f_down(b)".to_string());
+ let node_d = TestTreeNode::new(vec![node_a], "f_down(d)".to_string());
+ let node_c = TestTreeNode::new(vec![node_b, node_d], "f_down(c)".to_string());
+ let node_e = TestTreeNode::new(vec![node_c], "f_down(e)".to_string());
+ let node_h = TestTreeNode::new(vec![], "h".to_string());
+ let node_g = TestTreeNode::new(vec![node_h], "g".to_string());
+ let node_f = TestTreeNode::new(vec![node_e, node_g], "f_down(f)".to_string());
+ let node_i = TestTreeNode::new(vec![node_f], "f_down(i)".to_string());
+ TestTreeNode::new(vec![node_i], "f_down(j)".to_string())
+ }
+
+ // f_down Stop on E node
+ fn f_down_stop_on_e_visits() -> Vec {
+ vec!["f_down(j)", "f_down(i)", "f_down(f)", "f_down(e)"]
+ .into_iter()
+ .map(|s| s.to_string())
+ .collect()
+ }
+
+ fn f_down_stop_on_e_transformed_tree() -> TestTreeNode {
+ let node_a = TestTreeNode::new(vec![], "a".to_string());
+ let node_b = TestTreeNode::new(vec![], "b".to_string());
+ let node_d = TestTreeNode::new(vec![node_a], "d".to_string());
+ let node_c = TestTreeNode::new(vec![node_b, node_d], "c".to_string());
+ let node_e = TestTreeNode::new(vec![node_c], "f_down(e)".to_string());
+ let node_h = TestTreeNode::new(vec![], "h".to_string());
+ let node_g = TestTreeNode::new(vec![node_h], "g".to_string());
+ let node_f = TestTreeNode::new(vec![node_e, node_g], "f_down(f)".to_string());
+ let node_i = TestTreeNode::new(vec![node_f], "f_down(i)".to_string());
+ TestTreeNode::new(vec![node_i], "f_down(j)".to_string())
+ }
+
+ fn f_down_stop_on_e_transformed_down_tree() -> TestTreeNode {
+ let node_a = TestTreeNode::new(vec![], "a".to_string());
+ let node_b = TestTreeNode::new(vec![], "b".to_string());
+ let node_d = TestTreeNode::new(vec![node_a], "d".to_string());
+ let node_c = TestTreeNode::new(vec![node_b, node_d], "c".to_string());
+ let node_e = TestTreeNode::new(vec![node_c], "f_down(e)".to_string());
+ let node_h = TestTreeNode::new(vec![], "h".to_string());
+ let node_g = TestTreeNode::new(vec![node_h], "g".to_string());
+ let node_f = TestTreeNode::new(vec![node_e, node_g], "f_down(f)".to_string());
+ let node_i = TestTreeNode::new(vec![node_f], "f_down(i)".to_string());
+ TestTreeNode::new(vec![node_i], "f_down(j)".to_string())
+ }
+
+ // f_up Stop on A node
+ fn f_up_stop_on_a_visits() -> Vec {
+ vec![
+ "f_down(j)",
+ "f_down(i)",
+ "f_down(f)",
+ "f_down(e)",
+ "f_down(c)",
+ "f_down(b)",
+ "f_up(b)",
+ "f_down(d)",
+ "f_down(a)",
+ "f_up(a)",
+ ]
+ .into_iter()
+ .map(|s| s.to_string())
+ .collect()
+ }
+
+ fn f_up_stop_on_a_transformed_tree() -> TestTreeNode {
+ let node_a = TestTreeNode::new(vec![], "f_up(f_down(a))".to_string());
+ let node_b = TestTreeNode::new(vec![], "f_up(f_down(b))".to_string());
+ let node_d = TestTreeNode::new(vec![node_a], "f_down(d)".to_string());
+ let node_c = TestTreeNode::new(vec![node_b, node_d], "f_down(c)".to_string());
+ let node_e = TestTreeNode::new(vec![node_c], "f_down(e)".to_string());
+ let node_h = TestTreeNode::new(vec![], "h".to_string());
+ let node_g = TestTreeNode::new(vec![node_h], "g".to_string());
+ let node_f = TestTreeNode::new(vec![node_e, node_g], "f_down(f)".to_string());
+ let node_i = TestTreeNode::new(vec![node_f], "f_down(i)".to_string());
+ TestTreeNode::new(vec![node_i], "f_down(j)".to_string())
+ }
+
+ fn f_up_stop_on_a_transformed_up_tree() -> TestTreeNode {
+ let node_a = TestTreeNode::new(vec![], "f_up(a)".to_string());
+ let node_b = TestTreeNode::new(vec![], "f_up(b)".to_string());
+ let node_d = TestTreeNode::new(vec![node_a], "d".to_string());
+ let node_c = TestTreeNode::new(vec![node_b, node_d], "c".to_string());
+ let node_e = TestTreeNode::new(vec![node_c], "e".to_string());
+ let node_h = TestTreeNode::new(vec![], "h".to_string());
+ let node_g = TestTreeNode::new(vec![node_h], "g".to_string());
+ let node_f = TestTreeNode::new(vec![node_e, node_g], "f".to_string());
+ let node_i = TestTreeNode::new(vec![node_f], "i".to_string());
+ TestTreeNode::new(vec![node_i], "j".to_string())
+ }
+
+ // f_up Stop on E node
+ fn f_up_stop_on_e_visits() -> Vec {
+ vec![
+ "f_down(j)",
+ "f_down(i)",
+ "f_down(f)",
+ "f_down(e)",
+ "f_down(c)",
+ "f_down(b)",
+ "f_up(b)",
+ "f_down(d)",
+ "f_down(a)",
+ "f_up(a)",
+ "f_up(d)",
+ "f_up(c)",
+ "f_up(e)",
+ ]
+ .into_iter()
+ .map(|s| s.to_string())
+ .collect()
+ }
+
+ fn f_up_stop_on_e_transformed_tree() -> TestTreeNode {
+ let node_a = TestTreeNode::new(vec![], "f_up(f_down(a))".to_string());
+ let node_b = TestTreeNode::new(vec![], "f_up(f_down(b))".to_string());
+ let node_d = TestTreeNode::new(vec![node_a], "f_up(f_down(d))".to_string());
+ let node_c =
+ TestTreeNode::new(vec![node_b, node_d], "f_up(f_down(c))".to_string());
+ let node_e = TestTreeNode::new(vec![node_c], "f_up(f_down(e))".to_string());
+ let node_h = TestTreeNode::new(vec![], "h".to_string());
+ let node_g = TestTreeNode::new(vec![node_h], "g".to_string());
+ let node_f = TestTreeNode::new(vec![node_e, node_g], "f_down(f)".to_string());
+ let node_i = TestTreeNode::new(vec![node_f], "f_down(i)".to_string());
+ TestTreeNode::new(vec![node_i], "f_down(j)".to_string())
+ }
+
+ fn f_up_stop_on_e_transformed_up_tree() -> TestTreeNode {
+ let node_a = TestTreeNode::new(vec![], "f_up(a)".to_string());
+ let node_b = TestTreeNode::new(vec![], "f_up(b)".to_string());
+ let node_d = TestTreeNode::new(vec![node_a], "f_up(d)".to_string());
+ let node_c = TestTreeNode::new(vec![node_b, node_d], "f_up(c)".to_string());
+ let node_e = TestTreeNode::new(vec![node_c], "f_up(e)".to_string());
+ let node_h = TestTreeNode::new(vec![], "h".to_string());
+ let node_g = TestTreeNode::new(vec![node_h], "g".to_string());
+ let node_f = TestTreeNode::new(vec![node_e, node_g], "f".to_string());
+ let node_i = TestTreeNode::new(vec![node_f], "i".to_string());
+ TestTreeNode::new(vec![node_i], "j".to_string())
+ }
+
+ fn down_visits(visits: Vec) -> Vec {
+ visits
+ .into_iter()
+ .filter(|v| v.starts_with("f_down"))
+ .collect()
+ }
+
+ type TestVisitorF = Box) -> Result>;
+
+ struct TestVisitor {
+ visits: Vec,
+ f_down: TestVisitorF,
+ f_up: TestVisitorF,
+ }
+
+ impl TestVisitor {
+ fn new(f_down: TestVisitorF, f_up: TestVisitorF) -> Self {
+ Self {
+ visits: vec![],
+ f_down,
+ f_up,
+ }
+ }
+ }
+
+ impl TreeNodeVisitor for TestVisitor {
+ type Node = TestTreeNode;
+
+ fn f_down(&mut self, node: &Self::Node) -> Result {
+ self.visits.push(format!("f_down({})", node.data));
+ (*self.f_down)(node)
+ }
+
+ fn f_up(&mut self, node: &Self::Node) -> Result {
+ self.visits.push(format!("f_up({})", node.data));
+ (*self.f_up)(node)
+ }
+ }
+
+ fn visit_continue(_: &TestTreeNode) -> Result {
+ Ok(TreeNodeRecursion::Continue)
+ }
+
+ fn visit_event_on>(
+ data: D,
+ event: TreeNodeRecursion,
+ ) -> impl FnMut(&TestTreeNode) -> Result {
+ let d = data.into();
+ move |node| {
+ Ok(if node.data == d {
+ event
+ } else {
+ TreeNodeRecursion::Continue
+ })
+ }
+ }
+
+ macro_rules! visit_test {
+ ($NAME:ident, $F_DOWN:expr, $F_UP:expr, $EXPECTED_VISITS:expr) => {
+ #[test]
+ fn $NAME() -> Result<()> {
+ let tree = test_tree();
+ let mut visitor = TestVisitor::new(Box::new($F_DOWN), Box::new($F_UP));
+ tree.visit(&mut visitor)?;
+ assert_eq!(visitor.visits, $EXPECTED_VISITS);
+
+ Ok(())
+ }
+ };
+ }
+
+ macro_rules! test_apply {
+ ($NAME:ident, $F:expr, $EXPECTED_VISITS:expr) => {
+ #[test]
+ fn $NAME() -> Result<()> {
+ let tree = test_tree();
+ let mut visits = vec![];
+ tree.apply(&mut |node| {
+ visits.push(format!("f_down({})", node.data));
+ $F(node)
+ })?;
+ assert_eq!(visits, $EXPECTED_VISITS);
+
+ Ok(())
+ }
+ };
+ }
+
+ type TestRewriterF =
+ Box) -> Result>>>;
+
+ struct TestRewriter {
+ f_down: TestRewriterF,
+ f_up: TestRewriterF,
+ }
+
+ impl TestRewriter {
+ fn new(f_down: TestRewriterF, f_up: TestRewriterF) -> Self {
+ Self { f_down, f_up }
+ }
+ }
+
+ impl TreeNodeRewriter for TestRewriter {
+ type Node = TestTreeNode;
+
+ fn f_down(&mut self, node: Self::Node) -> Result> {
+ (*self.f_down)(node)
+ }
+
+ fn f_up(&mut self, node: Self::Node) -> Result> {
+ (*self.f_up)(node)
+ }
+ }
+
+ fn transform_yes>(
+ transformation_name: N,
+ ) -> impl FnMut(TestTreeNode) -> Result>> {
+ move |node| {
+ Ok(Transformed::yes(TestTreeNode::new(
+ node.children,
+ format!("{}({})", transformation_name, node.data).into(),
+ )))
+ }
+ }
+
+ fn transform_and_event_on<
+ N: Display,
+ T: PartialEq + Display + From,
+ D: Into,
+ >(
+ transformation_name: N,
+ data: D,
+ event: TreeNodeRecursion,
+ ) -> impl FnMut(TestTreeNode) -> Result>> {
+ let d = data.into();
+ move |node| {
+ let new_node = TestTreeNode::new(
+ node.children,
+ format!("{}({})", transformation_name, node.data).into(),
+ );
+ Ok(if node.data == d {
+ Transformed::new(new_node, true, event)
+ } else {
+ Transformed::yes(new_node)
+ })
+ }
+ }
+
+ macro_rules! rewrite_test {
+ ($NAME:ident, $F_DOWN:expr, $F_UP:expr, $EXPECTED_TREE:expr) => {
+ #[test]
+ fn $NAME() -> Result<()> {
+ let tree = test_tree();
+ let mut rewriter = TestRewriter::new(Box::new($F_DOWN), Box::new($F_UP));
+ assert_eq!(tree.rewrite(&mut rewriter)?, $EXPECTED_TREE);
+
+ Ok(())
+ }
+ };
+ }
+
+ macro_rules! transform_test {
+ ($NAME:ident, $F_DOWN:expr, $F_UP:expr, $EXPECTED_TREE:expr) => {
+ #[test]
+ fn $NAME() -> Result<()> {
+ let tree = test_tree();
+ assert_eq!(
+ tree.transform_down_up(&mut $F_DOWN, &mut $F_UP,)?,
+ $EXPECTED_TREE
+ );
+
+ Ok(())
+ }
+ };
+ }
+
+ macro_rules! transform_down_test {
+ ($NAME:ident, $F:expr, $EXPECTED_TREE:expr) => {
+ #[test]
+ fn $NAME() -> Result<()> {
+ let tree = test_tree();
+ assert_eq!(tree.transform_down_mut(&mut $F)?, $EXPECTED_TREE);
+
+ Ok(())
+ }
+ };
+ }
+
+ macro_rules! transform_up_test {
+ ($NAME:ident, $F:expr, $EXPECTED_TREE:expr) => {
+ #[test]
+ fn $NAME() -> Result<()> {
+ let tree = test_tree();
+ assert_eq!(tree.transform_up_mut(&mut $F)?, $EXPECTED_TREE);
+
+ Ok(())
+ }
+ };
+ }
+
+ visit_test!(test_visit, visit_continue, visit_continue, all_visits());
+ visit_test!(
+ test_visit_f_down_jump_on_a,
+ visit_event_on("a", TreeNodeRecursion::Jump),
+ visit_continue,
+ f_down_jump_on_a_visits()
+ );
+ visit_test!(
+ test_visit_f_down_jump_on_e,
+ visit_event_on("e", TreeNodeRecursion::Jump),
+ visit_continue,
+ f_down_jump_on_e_visits()
+ );
+ visit_test!(
+ test_visit_f_up_jump_on_a,
+ visit_continue,
+ visit_event_on("a", TreeNodeRecursion::Jump),
+ f_up_jump_on_a_visits()
+ );
+ visit_test!(
+ test_visit_f_up_jump_on_e,
+ visit_continue,
+ visit_event_on("e", TreeNodeRecursion::Jump),
+ f_up_jump_on_e_visits()
+ );
+ visit_test!(
+ test_visit_f_down_stop_on_a,
+ visit_event_on("a", TreeNodeRecursion::Stop),
+ visit_continue,
+ f_down_stop_on_a_visits()
+ );
+ visit_test!(
+ test_visit_f_down_stop_on_e,
+ visit_event_on("e", TreeNodeRecursion::Stop),
+ visit_continue,
+ f_down_stop_on_e_visits()
+ );
+ visit_test!(
+ test_visit_f_up_stop_on_a,
+ visit_continue,
+ visit_event_on("a", TreeNodeRecursion::Stop),
+ f_up_stop_on_a_visits()
+ );
+ visit_test!(
+ test_visit_f_up_stop_on_e,
+ visit_continue,
+ visit_event_on("e", TreeNodeRecursion::Stop),
+ f_up_stop_on_e_visits()
+ );
+
+ test_apply!(test_apply, visit_continue, down_visits(all_visits()));
+ test_apply!(
+ test_apply_f_down_jump_on_a,
+ visit_event_on("a", TreeNodeRecursion::Jump),
+ down_visits(f_down_jump_on_a_visits())
+ );
+ test_apply!(
+ test_apply_f_down_jump_on_e,
+ visit_event_on("e", TreeNodeRecursion::Jump),
+ down_visits(f_down_jump_on_e_visits())
+ );
+ test_apply!(
+ test_apply_f_down_stop_on_a,
+ visit_event_on("a", TreeNodeRecursion::Stop),
+ down_visits(f_down_stop_on_a_visits())
+ );
+ test_apply!(
+ test_apply_f_down_stop_on_e,
+ visit_event_on("e", TreeNodeRecursion::Stop),
+ down_visits(f_down_stop_on_e_visits())
+ );
+
+ rewrite_test!(
+ test_rewrite,
+ transform_yes("f_down"),
+ transform_yes("f_up"),
+ Transformed::yes(transformed_tree())
+ );
+ rewrite_test!(
+ test_rewrite_f_down_jump_on_a,
+ transform_and_event_on("f_down", "a", TreeNodeRecursion::Jump),
+ transform_yes("f_up"),
+ Transformed::yes(transformed_tree())
+ );
+ rewrite_test!(
+ test_rewrite_f_down_jump_on_e,
+ transform_and_event_on("f_down", "e", TreeNodeRecursion::Jump),
+ transform_yes("f_up"),
+ Transformed::yes(f_down_jump_on_e_transformed_tree())
+ );
+ rewrite_test!(
+ test_rewrite_f_up_jump_on_a,
+ transform_yes("f_down"),
+ transform_and_event_on("f_up", "f_down(a)", TreeNodeRecursion::Jump),
+ Transformed::yes(f_up_jump_on_a_transformed_tree())
+ );
+ rewrite_test!(
+ test_rewrite_f_up_jump_on_e,
+ transform_yes("f_down"),
+ transform_and_event_on("f_up", "f_down(e)", TreeNodeRecursion::Jump),
+ Transformed::yes(f_up_jump_on_e_transformed_tree())
+ );
+ rewrite_test!(
+ test_rewrite_f_down_stop_on_a,
+ transform_and_event_on("f_down", "a", TreeNodeRecursion::Stop),
+ transform_yes("f_up"),
+ Transformed::new(
+ f_down_stop_on_a_transformed_tree(),
+ true,
+ TreeNodeRecursion::Stop
+ )
+ );
+ rewrite_test!(
+ test_rewrite_f_down_stop_on_e,
+ transform_and_event_on("f_down", "e", TreeNodeRecursion::Stop),
+ transform_yes("f_up"),
+ Transformed::new(
+ f_down_stop_on_e_transformed_tree(),
+ true,
+ TreeNodeRecursion::Stop
+ )
+ );
+ rewrite_test!(
+ test_rewrite_f_up_stop_on_a,
+ transform_yes("f_down"),
+ transform_and_event_on("f_up", "f_down(a)", TreeNodeRecursion::Stop),
+ Transformed::new(
+ f_up_stop_on_a_transformed_tree(),
+ true,
+ TreeNodeRecursion::Stop
+ )
+ );
+ rewrite_test!(
+ test_rewrite_f_up_stop_on_e,
+ transform_yes("f_down"),
+ transform_and_event_on("f_up", "f_down(e)", TreeNodeRecursion::Stop),
+ Transformed::new(
+ f_up_stop_on_e_transformed_tree(),
+ true,
+ TreeNodeRecursion::Stop
+ )
+ );
+
+ transform_test!(
+ test_transform,
+ transform_yes("f_down"),
+ transform_yes("f_up"),
+ Transformed::yes(transformed_tree())
+ );
+ transform_test!(
+ test_transform_f_down_jump_on_a,
+ transform_and_event_on("f_down", "a", TreeNodeRecursion::Jump),
+ transform_yes("f_up"),
+ Transformed::yes(transformed_tree())
+ );
+ transform_test!(
+ test_transform_f_down_jump_on_e,
+ transform_and_event_on("f_down", "e", TreeNodeRecursion::Jump),
+ transform_yes("f_up"),
+ Transformed::yes(f_down_jump_on_e_transformed_tree())
+ );
+ transform_test!(
+ test_transform_f_up_jump_on_a,
+ transform_yes("f_down"),
+ transform_and_event_on("f_up", "f_down(a)", TreeNodeRecursion::Jump),
+ Transformed::yes(f_up_jump_on_a_transformed_tree())
+ );
+ transform_test!(
+ test_transform_f_up_jump_on_e,
+ transform_yes("f_down"),
+ transform_and_event_on("f_up", "f_down(e)", TreeNodeRecursion::Jump),
+ Transformed::yes(f_up_jump_on_e_transformed_tree())
+ );
+ transform_test!(
+ test_transform_f_down_stop_on_a,
+ transform_and_event_on("f_down", "a", TreeNodeRecursion::Stop),
+ transform_yes("f_up"),
+ Transformed::new(
+ f_down_stop_on_a_transformed_tree(),
+ true,
+ TreeNodeRecursion::Stop
+ )
+ );
+ transform_test!(
+ test_transform_f_down_stop_on_e,
+ transform_and_event_on("f_down", "e", TreeNodeRecursion::Stop),
+ transform_yes("f_up"),
+ Transformed::new(
+ f_down_stop_on_e_transformed_tree(),
+ true,
+ TreeNodeRecursion::Stop
+ )
+ );
+ transform_test!(
+ test_transform_f_up_stop_on_a,
+ transform_yes("f_down"),
+ transform_and_event_on("f_up", "f_down(a)", TreeNodeRecursion::Stop),
+ Transformed::new(
+ f_up_stop_on_a_transformed_tree(),
+ true,
+ TreeNodeRecursion::Stop
+ )
+ );
+ transform_test!(
+ test_transform_f_up_stop_on_e,
+ transform_yes("f_down"),
+ transform_and_event_on("f_up", "f_down(e)", TreeNodeRecursion::Stop),
+ Transformed::new(
+ f_up_stop_on_e_transformed_tree(),
+ true,
+ TreeNodeRecursion::Stop
+ )
+ );
+
+ transform_down_test!(
+ test_transform_down,
+ transform_yes("f_down"),
+ Transformed::yes(transformed_down_tree())
+ );
+ transform_down_test!(
+ test_transform_down_f_down_jump_on_a,
+ transform_and_event_on("f_down", "a", TreeNodeRecursion::Jump),
+ Transformed::yes(f_down_jump_on_a_transformed_down_tree())
+ );
+ transform_down_test!(
+ test_transform_down_f_down_jump_on_e,
+ transform_and_event_on("f_down", "e", TreeNodeRecursion::Jump),
+ Transformed::yes(f_down_jump_on_e_transformed_down_tree())
+ );
+ transform_down_test!(
+ test_transform_down_f_down_stop_on_a,
+ transform_and_event_on("f_down", "a", TreeNodeRecursion::Stop),
+ Transformed::new(
+ f_down_stop_on_a_transformed_down_tree(),
+ true,
+ TreeNodeRecursion::Stop
+ )
+ );
+ transform_down_test!(
+ test_transform_down_f_down_stop_on_e,
+ transform_and_event_on("f_down", "e", TreeNodeRecursion::Stop),
+ Transformed::new(
+ f_down_stop_on_e_transformed_down_tree(),
+ true,
+ TreeNodeRecursion::Stop
+ )
+ );
+
+ transform_up_test!(
+ test_transform_up,
+ transform_yes("f_up"),
+ Transformed::yes(transformed_up_tree())
+ );
+ transform_up_test!(
+ test_transform_up_f_up_jump_on_a,
+ transform_and_event_on("f_up", "a", TreeNodeRecursion::Jump),
+ Transformed::yes(f_up_jump_on_a_transformed_up_tree())
+ );
+ transform_up_test!(
+ test_transform_up_f_up_jump_on_e,
+ transform_and_event_on("f_up", "e", TreeNodeRecursion::Jump),
+ Transformed::yes(f_up_jump_on_e_transformed_up_tree())
+ );
+ transform_up_test!(
+ test_transform_up_f_up_stop_on_a,
+ transform_and_event_on("f_up", "a", TreeNodeRecursion::Stop),
+ Transformed::new(
+ f_up_stop_on_a_transformed_up_tree(),
+ true,
+ TreeNodeRecursion::Stop
+ )
+ );
+ transform_up_test!(
+ test_transform_up_f_up_stop_on_e,
+ transform_and_event_on("f_up", "e", TreeNodeRecursion::Stop),
+ Transformed::new(
+ f_up_stop_on_e_transformed_up_tree(),
+ true,
+ TreeNodeRecursion::Stop
+ )
+ );
+}
diff --git a/datafusion/common_runtime/Cargo.toml b/datafusion/common_runtime/Cargo.toml
new file mode 100644
index 000000000000..7ed8b2cf2975
--- /dev/null
+++ b/datafusion/common_runtime/Cargo.toml
@@ -0,0 +1,36 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+[package]
+name = "datafusion-common-runtime"
+description = "Common Runtime functionality for DataFusion query engine"
+keywords = ["arrow", "query", "sql"]
+readme = "README.md"
+version = { workspace = true }
+edition = { workspace = true }
+homepage = { workspace = true }
+repository = { workspace = true }
+license = { workspace = true }
+authors = { workspace = true }
+rust-version = { workspace = true }
+
+[lib]
+name = "datafusion_common_runtime"
+path = "src/lib.rs"
+
+[dependencies]
+tokio = { workspace = true }
diff --git a/datafusion/common_runtime/README.md b/datafusion/common_runtime/README.md
new file mode 100644
index 000000000000..77100e52603c
--- /dev/null
+++ b/datafusion/common_runtime/README.md
@@ -0,0 +1,26 @@
+
+
+# DataFusion Common Runtime
+
+[DataFusion][df] is an extensible query execution framework, written in Rust, that uses Apache Arrow as its in-memory format.
+
+This crate is a submodule of DataFusion that provides common utilities.
+
+[df]: https://crates.io/crates/datafusion
diff --git a/datafusion/common_runtime/src/common.rs b/datafusion/common_runtime/src/common.rs
new file mode 100644
index 000000000000..2f7ddb972f42
--- /dev/null
+++ b/datafusion/common_runtime/src/common.rs
@@ -0,0 +1,77 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+use std::future::Future;
+
+use tokio::task::{JoinError, JoinSet};
+
+/// Helper that provides a simple API to spawn a single task and join it.
+/// Provides guarantees of aborting on `Drop` to keep it cancel-safe.
+///
+/// Technically, it's just a wrapper of `JoinSet` (with size=1).
+#[derive(Debug)]
+pub struct SpawnedTask {
+ inner: JoinSet,
+}
+
+impl SpawnedTask {
+ pub fn spawn(task: T) -> Self
+ where
+ T: Future