From f40d99ac8b10e03a41374706e9fa07194a922ca9 Mon Sep 17 00:00:00 2001
From: zhengpeng <847850277@qq.com>
Date: Fri, 24 Jul 2026 10:33:44 +0800
Subject: [PATCH 001/109] feat: migrate EmptyExec and PlaceholderRowExec to
ExecutionPlan proto hooks (#23784)
## Which issue does this PR close?
- Closes #23501 .
## Rationale for this change
Migrates the `EmptyExec` and `PlaceholderRowExec` leaf plans while
preserving the existing protobuf wire format.
## What changes are included in this PR?
- Implement `ExecutionPlan::try_to_proto` for `EmptyExec` and
`PlaceholderRowExec`.
- Add plan-specific `try_from_proto` implementations.
- Route protobuf encoding and decoding through the new hooks.
- Keep the deprecated helper methods as compatibility shims.
## Are these changes tested?
yes
## Are there any user-facing changes?
Hi @andygrove , would you be willing to review this PR when you have
time? Thanks!
---
datafusion/physical-plan/src/empty.rs | 48 ++++++++
.../physical-plan/src/placeholder_row.rs | 50 ++++++++
datafusion/proto/src/physical_plan/mod.rs | 111 ++++++++++--------
3 files changed, 161 insertions(+), 48 deletions(-)
diff --git a/datafusion/physical-plan/src/empty.rs b/datafusion/physical-plan/src/empty.rs
index 44a6f444dc4b5..3bd38bf238dc1 100644
--- a/datafusion/physical-plan/src/empty.rs
+++ b/datafusion/physical-plan/src/empty.rs
@@ -185,6 +185,54 @@ impl ExecutionPlan for EmptyExec {
Ok(Arc::new(stats))
}
+
+ #[cfg(feature = "proto")]
+ fn try_to_proto(
+ &self,
+ _ctx: &crate::proto::ExecutionPlanEncodeCtx<'_>,
+ ) -> Result> {
+ use datafusion_proto_models::protobuf;
+ let schema = self.schema().as_ref().try_into()?;
+ Ok(Some(protobuf::PhysicalPlanNode {
+ physical_plan_type: Some(
+ protobuf::physical_plan_node::PhysicalPlanType::Empty(
+ protobuf::EmptyExecNode {
+ schema: Some(schema),
+ partitions: self
+ .properties()
+ .output_partitioning()
+ .partition_count() as u32,
+ },
+ ),
+ ),
+ }))
+ }
+}
+
+#[cfg(feature = "proto")]
+impl EmptyExec {
+ /// Reconstruct an [`EmptyExec`] from its protobuf representation.
+ pub fn try_from_proto(
+ node: &datafusion_proto_models::protobuf::PhysicalPlanNode,
+ _ctx: &crate::proto::ExecutionPlanDecodeCtx<'_>,
+ ) -> Result> {
+ use datafusion_proto_models::protobuf;
+ let empty = crate::expect_plan_variant!(
+ node,
+ protobuf::physical_plan_node::PhysicalPlanType::Empty,
+ "EmptyExec",
+ );
+ let schema = empty.schema.as_ref().ok_or_else(|| {
+ datafusion_common::internal_datafusion_err!(
+ "EmptyExec is missing required field 'schema'"
+ )
+ })?;
+ let schema = Arc::new(arrow::datatypes::Schema::try_from(schema)?);
+ // A zero (absent) partition count comes from a plan encoded before the
+ // field existed, which always meant a single partition.
+ let partitions = empty.partitions.max(1) as usize;
+ Ok(Arc::new(EmptyExec::new(schema).with_partitions(partitions)))
+ }
}
#[cfg(test)]
diff --git a/datafusion/physical-plan/src/placeholder_row.rs b/datafusion/physical-plan/src/placeholder_row.rs
index 20d267331b2aa..5d71058269f49 100644
--- a/datafusion/physical-plan/src/placeholder_row.rs
+++ b/datafusion/physical-plan/src/placeholder_row.rs
@@ -186,6 +186,56 @@ impl ExecutionPlan for PlaceholderRowExec {
None,
)))
}
+
+ #[cfg(feature = "proto")]
+ fn try_to_proto(
+ &self,
+ _ctx: &crate::proto::ExecutionPlanEncodeCtx<'_>,
+ ) -> Result> {
+ use datafusion_proto_models::protobuf;
+ let schema = self.schema().as_ref().try_into()?;
+ Ok(Some(protobuf::PhysicalPlanNode {
+ physical_plan_type: Some(
+ protobuf::physical_plan_node::PhysicalPlanType::PlaceholderRow(
+ protobuf::PlaceholderRowExecNode {
+ schema: Some(schema),
+ partitions: self
+ .properties()
+ .output_partitioning()
+ .partition_count() as u32,
+ },
+ ),
+ ),
+ }))
+ }
+}
+
+#[cfg(feature = "proto")]
+impl PlaceholderRowExec {
+ /// Reconstruct a [`PlaceholderRowExec`] from its protobuf representation.
+ pub fn try_from_proto(
+ node: &datafusion_proto_models::protobuf::PhysicalPlanNode,
+ _ctx: &crate::proto::ExecutionPlanDecodeCtx<'_>,
+ ) -> Result> {
+ use datafusion_proto_models::protobuf;
+ let placeholder = crate::expect_plan_variant!(
+ node,
+ protobuf::physical_plan_node::PhysicalPlanType::PlaceholderRow,
+ "PlaceholderRowExec",
+ );
+ let schema = placeholder.schema.as_ref().ok_or_else(|| {
+ datafusion_common::internal_datafusion_err!(
+ "PlaceholderRowExec is missing required field 'schema'"
+ )
+ })?;
+ let schema = Arc::new(Schema::try_from(schema)?);
+ // A zero (absent) partition count comes from a plan encoded before the
+ // field existed, which always meant a single partition.
+ let partitions = placeholder.partitions.max(1) as usize;
+ Ok(Arc::new(
+ PlaceholderRowExec::new(schema).with_partitions(partitions),
+ ))
+ }
}
#[cfg(test)]
diff --git a/datafusion/proto/src/physical_plan/mod.rs b/datafusion/proto/src/physical_plan/mod.rs
index d62bafa883441..b459368bcb1da 100644
--- a/datafusion/proto/src/physical_plan/mod.rs
+++ b/datafusion/proto/src/physical_plan/mod.rs
@@ -814,11 +814,11 @@ pub trait PhysicalPlanNodeExt: Sized {
PhysicalPlanType::CrossJoin(_) => {
CrossJoinExec::try_from_proto(self.node(), &decode_ctx)
}
- PhysicalPlanType::Empty(empty) => {
- self.try_into_empty_physical_plan(empty, ctx, proto_converter)
+ PhysicalPlanType::Empty(_) => {
+ EmptyExec::try_from_proto(self.node(), &decode_ctx)
}
- PhysicalPlanType::PlaceholderRow(placeholder) => {
- self.try_into_placeholder_row_physical_plan(placeholder, ctx)
+ PhysicalPlanType::PlaceholderRow(_) => {
+ PlaceholderRowExec::try_from_proto(self.node(), &decode_ctx)
}
PhysicalPlanType::Sort(_) => {
SortExec::try_from_proto(self.node(), &decode_ctx)
@@ -925,16 +925,6 @@ pub trait PhysicalPlanNodeExt: Sized {
);
}
- if let Some(empty) = plan.downcast_ref::() {
- return protobuf::PhysicalPlanNode::try_from_empty_exec(empty, codec);
- }
-
- if let Some(empty) = plan.downcast_ref::() {
- return protobuf::PhysicalPlanNode::try_from_placeholder_row_exec(
- empty, codec,
- );
- }
-
if let Some(data_source_exec) = plan.downcast_ref::()
&& let Some(node) = protobuf::PhysicalPlanNode::try_from_data_source_exec(
data_source_exec,
@@ -1934,31 +1924,48 @@ pub trait PhysicalPlanNodeExt: Sized {
CrossJoinExec::try_from_proto(&node, &decode_ctx)
}
+ #[deprecated(
+ since = "55.0.0",
+ note = "unused by DataFusion; `EmptyExec` deserializes itself via `EmptyExec::try_from_proto`"
+ )]
fn try_into_empty_physical_plan(
&self,
empty: &protobuf::EmptyExecNode,
- _ctx: &PhysicalPlanDecodeContext<'_>,
- _proto_converter: &dyn PhysicalProtoConverterExtension,
+ ctx: &PhysicalPlanDecodeContext<'_>,
+ proto_converter: &dyn PhysicalProtoConverterExtension,
) -> Result> {
- let schema = Arc::new(convert_required!(empty.schema)?);
- // A zero (absent) partition count comes from a plan encoded before the
- // field existed, which always meant a single partition.
- let partitions = empty.partitions.max(1) as usize;
- Ok(Arc::new(EmptyExec::new(schema).with_partitions(partitions)))
+ let node = protobuf::PhysicalPlanNode {
+ physical_plan_type: Some(PhysicalPlanType::Empty(empty.clone())),
+ };
+ let decoder = ConverterPlanDecoder {
+ ctx,
+ proto_converter,
+ };
+ let decode_ctx = ExecutionPlanDecodeCtx::new(&decoder);
+ EmptyExec::try_from_proto(&node, &decode_ctx)
}
+ #[deprecated(
+ since = "55.0.0",
+ note = "unused by DataFusion; `PlaceholderRowExec` deserializes itself via `PlaceholderRowExec::try_from_proto`"
+ )]
fn try_into_placeholder_row_physical_plan(
&self,
placeholder: &protobuf::PlaceholderRowExecNode,
- _ctx: &PhysicalPlanDecodeContext<'_>,
+ ctx: &PhysicalPlanDecodeContext<'_>,
) -> Result> {
- let schema = Arc::new(convert_required!(placeholder.schema)?);
- // A zero (absent) partition count comes from a plan encoded before the
- // field existed, which always meant a single partition.
- let partitions = placeholder.partitions.max(1) as usize;
- Ok(Arc::new(
- PlaceholderRowExec::new(schema).with_partitions(partitions),
- ))
+ let node = protobuf::PhysicalPlanNode {
+ physical_plan_type: Some(PhysicalPlanType::PlaceholderRow(
+ placeholder.clone(),
+ )),
+ };
+ let proto_converter = DefaultPhysicalProtoConverter {};
+ let decoder = ConverterPlanDecoder {
+ ctx,
+ proto_converter: &proto_converter,
+ };
+ let decode_ctx = ExecutionPlanDecodeCtx::new(&decoder);
+ PlaceholderRowExec::try_from_proto(&node, &decode_ctx)
}
#[deprecated(
@@ -2833,33 +2840,41 @@ pub trait PhysicalPlanNodeExt: Sized {
})
}
+ #[deprecated(
+ since = "55.0.0",
+ note = "unused by DataFusion; `EmptyExec` serializes itself via `ExecutionPlan::try_to_proto`"
+ )]
fn try_from_empty_exec(
empty: &EmptyExec,
- _codec: &dyn PhysicalExtensionCodec,
+ codec: &dyn PhysicalExtensionCodec,
) -> Result {
- let schema = empty.schema().as_ref().try_into()?;
- Ok(protobuf::PhysicalPlanNode {
- physical_plan_type: Some(PhysicalPlanType::Empty(protobuf::EmptyExecNode {
- schema: Some(schema),
- partitions: empty.properties().output_partitioning().partition_count()
- as u32,
- })),
+ let proto_converter = DefaultPhysicalProtoConverter {};
+ let encoder = ConverterPlanEncoder {
+ codec,
+ proto_converter: &proto_converter,
+ };
+ let ctx = ExecutionPlanEncodeCtx::new(&encoder);
+ empty.try_to_proto(&ctx)?.ok_or_else(|| {
+ internal_datafusion_err!("EmptyExec::try_to_proto returned None")
})
}
+ #[deprecated(
+ since = "55.0.0",
+ note = "unused by DataFusion; `PlaceholderRowExec` serializes itself via `ExecutionPlan::try_to_proto`"
+ )]
fn try_from_placeholder_row_exec(
- empty: &PlaceholderRowExec,
- _codec: &dyn PhysicalExtensionCodec,
+ placeholder: &PlaceholderRowExec,
+ codec: &dyn PhysicalExtensionCodec,
) -> Result {
- let schema = empty.schema().as_ref().try_into()?;
- Ok(protobuf::PhysicalPlanNode {
- physical_plan_type: Some(PhysicalPlanType::PlaceholderRow(
- protobuf::PlaceholderRowExecNode {
- schema: Some(schema),
- partitions: empty.properties().output_partitioning().partition_count()
- as u32,
- },
- )),
+ let proto_converter = DefaultPhysicalProtoConverter {};
+ let encoder = ConverterPlanEncoder {
+ codec,
+ proto_converter: &proto_converter,
+ };
+ let ctx = ExecutionPlanEncodeCtx::new(&encoder);
+ placeholder.try_to_proto(&ctx)?.ok_or_else(|| {
+ internal_datafusion_err!("PlaceholderRowExec::try_to_proto returned None")
})
}
From 18b1e359c3c547ec0d649932f85abbe33144ef19 Mon Sep 17 00:00:00 2001
From: kid <19265318+u70b3@users.noreply.github.com>
Date: Fri, 24 Jul 2026 17:15:20 +0800
Subject: [PATCH 002/109] fix: grouped first_value/last_value FILTER excludes
NULL predicate rows (#23707)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## Which issue does this PR close?
- Closes #22666
## Rationale for this change
Under SQL aggregate `FILTER` semantics, a row passes only when the
predicate evaluates to `true`; rows where the predicate is `null` must
be excluded. Grouped `first_value` / `last_value` checked only
`BooleanArray::value(idx)`, without checking validity, so a NULL
predicate row whose underlying value bit is set (as produced by
comparison kernels, e.g. `null::int < 1`) was treated as passing:
```sql
SELECT g, first_value(a ORDER BY a) FILTER (WHERE b < 1) AS fv
FROM (VALUES (0, 10, CAST(NULL AS INT)), (0, 20, 2)) AS t(g, a, b)
GROUP BY g;
-- returned fv = 10, must return fv = NULL
-- (row 1: b < 1 is NULL; row 2: b < 1 is FALSE — no row satisfies `true`)
```
## What changes are included in this PR?
`datafusion/functions-aggregate/src/first_last.rs`, in
`FirstLastGroupsAccumulator::get_filtered_extreme_of_each_group` (shared
by `first_value` and `last_value`, and by both the `update_batch` and
`merge_batch` paths):
- `passed_filter` now requires `is_valid(idx) && value(idx)` (the
`Some(true)` semantics), matching the convention already used by
`variance.rs` / `correlation.rs`.
- The `is_set_arr` read gets the same validity check. This is *not* only
an internal bitmap: `convert_to_state` stores the user FILTER clause
(including its nulls) in the last state column, so on the merge path
(e.g. skip-partial-aggregation) NULL predicate rows were likewise
treated as set. Verified with a forced skip-partial run (100k unique
groups, all-NULL predicates): 83,616 groups were incorrectly assigned
non-NULL values before the fix, 0 after. For genuine internal bitmaps
(no nulls) the added check is trivially true, so behavior there is
unchanged.
Regression coverage:
- sqllogictest (`aggregate.slt`): the issue reproducer, the `last_value`
counterpart, mixed TRUE/FALSE/NULL predicates, all-TRUE and no-FILTER
controls, the (already correct) non-grouped path, and a window-function
no-regression case.
- Unit tests: `test_group_acc_filter_null_predicate` (update path) and
`test_group_acc_merge_null_is_set` (merge path via `convert_to_state` →
`merge_batch`), both constructing `BooleanArray`s whose null slots carry
a set value bit.
## Are these changes tested?
Yes — see above. Verified `./dev/rust_lint.sh`, `cargo test -p
datafusion-functions-aggregate --lib`, the `aggregate`/`window`
sqllogictest files, and `datafusion-cli` end-to-end (grouped
first/last_value now return NULL for the issue reproducer;
mixed-predicate and non-grouped results unchanged).
Also audited the rest of `functions-aggregate` for the same
validity-blind pattern: shared helpers (`nulls.rs::filter_to_validity`,
`accumulate.rs`, `prim_op.rs`, `count.rs`, `array_agg.rs`,
`variance.rs`, `correlation.rs`) already handle validity correctly, and
non-grouped paths pre-filter with arrow's `filter` kernel (which drops
NULL predicate rows), so no other aggregate needs changes.
## Performance
`functions-aggregate/benches/first_last.rs` was run against `main`. The
added checks are one validity-bit test per row on the grouped path;
measured deltas were within the machine's noise floor (±5% on unchanged
`filter=false` cases). A variant hoisting the null check out of the row
loop showed no measurable benefit beyond noise, so the simple idiomatic
form is kept.
## Are there any user-facing changes?
Only the bug fix: grouped `first_value`/`last_value` with a nullable
`FILTER` predicate now correctly exclude NULL-predicate rows, matching
SQL semantics and the behavior of other aggregates. No API or
configuration changes.
---------
Co-authored-by: Claude
---
.../functions-aggregate/src/first_last.rs | 124 +++++++++++++++++-
.../sqllogictest/test_files/aggregate.slt | 63 +++++++++
2 files changed, 185 insertions(+), 2 deletions(-)
diff --git a/datafusion/functions-aggregate/src/first_last.rs b/datafusion/functions-aggregate/src/first_last.rs
index cecb277cb844a..7c2540aa03a2b 100644
--- a/datafusion/functions-aggregate/src/first_last.rs
+++ b/datafusion/functions-aggregate/src/first_last.rs
@@ -555,8 +555,15 @@ impl FirstLastGroupsAccumulator {
for (idx_in_val, group_idx) in group_indices.iter().enumerate() {
let group_idx = *group_idx;
- let passed_filter = opt_filter.is_none_or(|x| x.value(idx_in_val));
- let is_set = is_set_arr.is_none_or(|x| x.value(idx_in_val));
+ // A row passes the FILTER clause only when the predicate is
+ // `true`; rows whose predicate evaluates to `null` are excluded.
+ let passed_filter =
+ opt_filter.is_none_or(|x| x.is_valid(idx_in_val) && x.value(idx_in_val));
+ // `is_set_arr` carries the user FILTER clause (including its
+ // nulls) when the state was produced by `convert_to_state`, so
+ // the validity check is required here as well (#22666).
+ let is_set =
+ is_set_arr.is_none_or(|x| x.is_valid(idx_in_val) && x.value(idx_in_val));
if !passed_filter || !is_set {
continue;
@@ -1415,6 +1422,7 @@ mod tests {
use arrow::{
array::{BooleanArray, Int64Array, ListArray, PrimitiveArray, StringArray},
+ buffer::NullBuffer,
compute::SortOptions,
datatypes::Schema,
};
@@ -1773,6 +1781,118 @@ mod tests {
Ok(())
}
+ /// Rows whose FILTER predicate evaluates to `null` must not pass the
+ /// filter, even when the underlying value bit at the null slot is `true`
+ /// (#22666).
+ #[test]
+ fn test_group_acc_filter_null_predicate() -> Result<()> {
+ let schema = Arc::new(Schema::new(vec![
+ Field::new("a", DataType::Int64, true),
+ Field::new("c", DataType::Int64, true),
+ ]));
+
+ let sort_keys = [PhysicalSortExpr {
+ expr: col("c", &schema).unwrap(),
+ options: SortOptions::default(),
+ }];
+
+ let mut group_acc = FirstLastGroupsAccumulator::try_new(
+ PrimitiveValueState::::new(DataType::Int64),
+ sort_keys.into(),
+ true,
+ &[DataType::Int64],
+ true,
+ )?;
+
+ let val_with_orderings: Vec = vec![
+ Arc::new(Int64Array::from(vec![10, 20, 30])),
+ Arc::new(Int64Array::from(vec![10, 20, 30])),
+ ];
+
+ // Row 0: predicate is null (but its value bit is true, as produced by
+ // kernels such as `b < 1` when the null slot's underlying value is 0)
+ // Row 1: predicate is false
+ // Row 2: predicate is true
+ let filter = BooleanArray::new(
+ BooleanBuffer::from(vec![false, true, false, true]),
+ Some(NullBuffer::from(BooleanBuffer::from(vec![
+ true, false, true, true,
+ ]))),
+ )
+ .slice(1, 3);
+ assert_eq!(filter.offset(), 1);
+
+ group_acc.update_batch(&val_with_orderings, &[0, 0, 1], Some(&filter), 2)?;
+
+ let binding = group_acc.evaluate(EmitTo::All)?;
+ let eval_result = binding.as_any().downcast_ref::().unwrap();
+
+ // Group 0 has no row with a `true` predicate, so it must stay unset.
+ // Group 1 takes the only row with a `true` predicate.
+ let expect: PrimitiveArray = Int64Array::from(vec![None, Some(30)]);
+ assert_eq!(eval_result, &expect);
+
+ Ok(())
+ }
+
+ /// `convert_to_state` stores the user FILTER clause (including its nulls)
+ /// in the `is_set` state column, so `merge_batch` must not treat a null
+ /// `is_set` entry with a set value bit as "is set" (#22666).
+ #[test]
+ fn test_group_acc_merge_null_is_set() -> Result<()> {
+ let schema = Arc::new(Schema::new(vec![
+ Field::new("a", DataType::Int64, true),
+ Field::new("c", DataType::Int64, true),
+ ]));
+
+ let sort_keys = [PhysicalSortExpr {
+ expr: col("c", &schema).unwrap(),
+ options: SortOptions::default(),
+ }];
+
+ let group_acc = FirstLastGroupsAccumulator::try_new(
+ PrimitiveValueState::::new(DataType::Int64),
+ sort_keys.clone().into(),
+ true,
+ &[DataType::Int64],
+ true,
+ )?;
+
+ let val_with_orderings: Vec = vec![
+ Arc::new(Int64Array::from(vec![10, 20])),
+ Arc::new(Int64Array::from(vec![10, 20])),
+ ];
+
+ // Same null-with-set-value-bit filter as above, carried into the state
+ let filter = BooleanArray::new(
+ BooleanBuffer::from(vec![true, true]),
+ Some(NullBuffer::from(BooleanBuffer::from(vec![false, true]))),
+ );
+
+ let state = group_acc.convert_to_state(&val_with_orderings, Some(&filter))?;
+ assert_eq!(state.len(), 3);
+
+ let mut merging_acc = FirstLastGroupsAccumulator::try_new(
+ PrimitiveValueState::::new(DataType::Int64),
+ sort_keys.into(),
+ true,
+ &[DataType::Int64],
+ true,
+ )?;
+
+ merging_acc.merge_batch(&state, &[0, 0], 1)?;
+
+ let binding = merging_acc.evaluate(EmitTo::All)?;
+ let eval_result = binding.as_any().downcast_ref::().unwrap();
+
+ // Only the second row is valid and passes; the null-predicate row must
+ // be skipped even though its value bit is true.
+ let expect: PrimitiveArray = Int64Array::from(vec![Some(20)]);
+ assert_eq!(eval_result, &expect);
+
+ Ok(())
+ }
+
#[test]
fn test_first_list_acc_size() -> Result<()> {
fn size_after_batch(values: &[ArrayRef]) -> Result {
diff --git a/datafusion/sqllogictest/test_files/aggregate.slt b/datafusion/sqllogictest/test_files/aggregate.slt
index 9400a09a5d4bf..1515e17e3fdff 100644
--- a/datafusion/sqllogictest/test_files/aggregate.slt
+++ b/datafusion/sqllogictest/test_files/aggregate.slt
@@ -6689,6 +6689,69 @@ GROUP BY g
----
0 0
+# first_value_with_group_by_and_nullable_filter
+# Rows whose FILTER predicate evaluates to NULL must be excluded (#22666)
+query II rowsort
+SELECT g, first_value(a ORDER BY a) FILTER (WHERE b < 1) AS fv
+FROM (VALUES (0, 10, CAST(NULL AS INT)), (0, 20, 2)) AS t(g, a, b)
+GROUP BY g
+----
+0 NULL
+
+# last_value_with_group_by_and_nullable_filter
+query II rowsort
+SELECT g, last_value(a ORDER BY a) FILTER (WHERE b < 1) AS lv
+FROM (VALUES (0, 10, CAST(NULL AS INT)), (0, 20, 2)) AS t(g, a, b)
+GROUP BY g
+----
+0 NULL
+
+# first_last_value_with_group_by_and_mixed_filter_results
+# Only rows whose FILTER predicate is TRUE participate: a = 10 (b = 1) and
+# a = 20 (b = 0) in group 0. The NULL-predicate row (a = 5) and the
+# FALSE-predicate row (a = 30) are excluded. No row passes the filter in
+# group 1, so the aggregates return NULL there.
+query III rowsort
+SELECT g,
+ first_value(a ORDER BY a) FILTER (WHERE b < 2) AS fv,
+ last_value(a ORDER BY a) FILTER (WHERE b < 2) AS lv
+FROM (VALUES (0, 5, CAST(NULL AS INT)), (0, 10, 1), (0, 30, 2), (0, 20, 0),
+ (1, 100, CAST(NULL AS INT)), (1, 50, 3)) AS t(g, a, b)
+GROUP BY g
+----
+0 10 20
+1 NULL NULL
+
+# first_last_value_with_group_by_filter_all_true_and_no_filter
+# Behavior is unchanged when every row passes the FILTER or there is no FILTER
+query IIIII rowsort
+SELECT g,
+ first_value(a ORDER BY a) FILTER (WHERE a > 0) AS fv,
+ last_value(a ORDER BY a) FILTER (WHERE a > 0) AS lv,
+ first_value(a ORDER BY a) AS fv_no_filter,
+ last_value(a ORDER BY a) AS lv_no_filter
+FROM (VALUES (0, 5, CAST(NULL AS INT)), (0, 10, 1), (0, 30, 2), (0, 20, 0)) AS t(g, a, b)
+GROUP BY g
+----
+0 5 30 5 30
+
+# first_value_without_group_by_and_nullable_filter
+query I rowsort
+SELECT first_value(a ORDER BY a) FILTER (WHERE b < 1) AS fv
+FROM (VALUES (10, CAST(NULL AS INT)), (20, 2)) AS t(a, b)
+----
+NULL
+
+# first_value_window_function_no_regression
+query II
+SELECT a, first_value(a) OVER (ORDER BY a) AS fv
+FROM (VALUES (10), (20), (5)) AS t(a)
+ORDER BY a
+----
+5 5
+10 5
+20 5
+
# query_with_untyped_null_filter
query I
SELECT count(*) FILTER (WHERE NULL)
From 5d1c3cdee976895dd80d28e520a0b31bf2bdf21b Mon Sep 17 00:00:00 2001
From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com>
Date: Fri, 24 Jul 2026 06:34:15 -0500
Subject: [PATCH 003/109] Unwrap widening Date32 -> Date64 casts in comparison
predicates (#23729)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## Which issue does this PR close?
- N/A. Small, self-contained enhancement to `unwrap_cast_in_comparison`;
happy to file a tracking issue if preferred.
## Rationale for this change
`unwrap_cast_in_comparison` could not unwrap a cast between the two date
types, so a predicate like `CAST(date32_col AS Date64) ` never folded onto the bare column. Folding it lets the literal
be compared against the unmodified column, keeping predicate pushdown /
pruning effective on date columns.
There were two coupled causes in `try_cast_literal_to_type`
(`datafusion/expr-common/src/casts.rs`):
1. `try_cast_numeric_literal` scaled both `Date32` and `Date64` literals
by the same target multiplier (`mul = 1`). But `Date32` counts **days**
since the epoch while `Date64` counts **milliseconds**, so a cross
conversion needs a factor of `MILLISECONDS_IN_DAY` (86_400_000).
2. `is_lossy_temporal_cast` classified every `Date <-> temporal` pair as
lossy, which swept in `Date32 <-> Date64` and blocked the unwrap
outright.
The reverse direction is subtle and unsound if handled naively:
narrowing a `Date64` **column** down to `Date32` truncates milliseconds
to the day (many-to-one), so `CAST(date64 AS Date32) = ` matches
any millisecond within that day. arrow-rs does not require `Date64`
values to be whole-day (apache/arrow-rs#5288), so the column may carry
sub-day values the planner cannot see, and unwrapping would drop those
rows. That direction is therefore explicitly blocked.
## What changes are included in this PR?
- Relax `is_lossy_temporal_cast` so a date-to-date (and identity) cast
is not pre-classified as lossy; per-value exactness is enforced
downstream.
- Add `scale_date_literal` with exact-only semantics: `Date32 -> Date64`
multiplies by `MILLISECONDS_IN_DAY` (overflow-guarded with checked
arithmetic); `Date64 -> Date32` divides only on a whole-day boundary and
otherwise returns `None`. This mirrors the existing Decimal scaling path
in the same function.
- Add `is_date_narrowing_cast` and block the narrowing `Date64 ->
Date32` column cast in the two logical-optimizer gates (comparison and
in-list) **and** in the physical-expr simplifier, mirroring
`is_timestamp_precision_narrowing_cast`. The physical-expr guard is
required for soundness on the pruning / row-group-filter path (verified
by a unit test that fails without it); the widening `Date32 -> Date64`
column cast is injective and stays supported.
Scope is intentionally limited to `Date32 <-> Date64` scaling, the
narrowing gate, and tests.
## Are these changes tested?
Yes, at two levels.
**End-to-end (`datafusion/sqllogictest/test_files/simplify_expr.slt`)**
— the PR is structured as three commits so the behavior change is
legible in the diff:
1. `test:` characterizes current behavior (passes on unmodified `main`):
neither direction is unwrapped, results are correct. The fixture stores
sub-day and pre-epoch `Date64` values on purpose.
2. `feat:` applies only the code change; the recorded widening `EXPLAIN`
plans now fail intentionally.
3. `test:` regenerates the expectations. The commit-3 diff is exactly
the widening plans flipping from `CAST(d32 AS Date64) Date64(..)`
to `d32 Date32(..)`; **every result row and every narrowing plan is
byte-identical**, which is the soundness proof. Coverage includes
`=`/`<`/`<=`/`>`/`>=`/`IN`, whole-day vs sub-day literals (the latter
yields zero rows and is left as-is), the narrowing soundness case (the
noon row is still returned), pre-epoch dates (arrow's toward-zero
truncation is pinned), and NULL three-valued logic.
**Unit** — `scale_date_literal` exactness and `i32::MIN`/`i32::MAX`
overflow, `is_date_narrowing_cast`, the relaxed `is_lossy_temporal_cast`
date-pair behavior, and the physical-expr narrowing guard.
`cargo fmt --check` is clean, `cargo clippy -p datafusion-expr-common --
-D warnings` passes, and the full sqllogictest suite passes.
## Are there any user-facing changes?
No public API changes. The optimizer now additionally rewrites widening
`Date32 -> Date64` cast comparisons where it previously left them
untouched; results are unchanged, plans are simplified. Narrowing
`Date64 -> Date32` cast comparisons are deliberately left as-is.
## Note for reviewers
The open PR #23727 adds the `if from_type == to_type { return false }`
identity guard to this same function. This PR is based independently on
`main` and includes that identity line as part of the clean gate shape
here, so depending on merge order the two may need a trivial rebase
where those lines overlap.
---------
Signed-off-by: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8
---
datafusion/expr-common/src/casts.rs | 248 +++++++++++++++--
.../src/simplify_expressions/unwrap_cast.rs | 11 +-
.../src/simplifier/unwrap_cast.rs | 24 +-
.../sqllogictest/test_files/simplify_expr.slt | 252 ++++++++++++++++++
4 files changed, 509 insertions(+), 26 deletions(-)
diff --git a/datafusion/expr-common/src/casts.rs b/datafusion/expr-common/src/casts.rs
index 8c9616f7b8285..3518c02772672 100644
--- a/datafusion/expr-common/src/casts.rs
+++ b/datafusion/expr-common/src/casts.rs
@@ -28,7 +28,9 @@ use arrow::datatypes::{
MAX_DECIMAL128_FOR_EACH_PRECISION, MIN_DECIMAL32_FOR_EACH_PRECISION,
MIN_DECIMAL64_FOR_EACH_PRECISION, MIN_DECIMAL128_FOR_EACH_PRECISION, TimeUnit,
};
-use arrow::temporal_conversions::{MICROSECONDS, MILLISECONDS, NANOSECONDS};
+use arrow::temporal_conversions::{
+ MICROSECONDS, MILLISECONDS, MILLISECONDS_IN_DAY, NANOSECONDS,
+};
use datafusion_common::ScalarValue;
/// Convert a literal [`ScalarValue`] to `target_type`, preserving the exact value.
@@ -100,17 +102,24 @@ fn is_date_type(data_type: &DataType) -> bool {
/// 00:00:00'` matches only midnight.
///
/// An identity cast (`from_type == to_type`, e.g. `Date32 -> Date32`) never
-/// changes comparison semantics and is therefore not lossy. This has to be
-/// handled explicitly because `DataType::is_temporal()` is true for both
-/// `Date32` and `Date64`, so `is_date_type(from) && to.is_temporal()` would
-/// otherwise report an identity `Date -> Date` cast as lossy and block the
-/// rewrite. Note this is deliberately limited to *identical* types: a genuine
-/// `Date32 <-> Date64` cast changes units (days vs milliseconds) and must
-/// still be treated as lossy here.
+/// changes comparison semantics and is therefore not lossy.
+///
+/// A cast between the two date types (`Date32` <-> `Date64`) is not pre-filtered
+/// as lossy here, because whether it loses information is a per-value question
+/// rather than a per-type one. `Date32` -> `Date64` is always exact (a day scaled
+/// to midnight in milliseconds). `Date64` -> `Date32` is exact only when the value
+/// lands on a day boundary: Arrow nominally defines `Date64` as whole days encoded
+/// in milliseconds, but arrow-rs does not enforce that (see arrow-rs#5288), so a
+/// `Date64` carrying sub-day milliseconds would lose them. This is not a licence to
+/// drop them - [`try_cast_numeric_literal`] returns `None` for a `Date64` value not
+/// divisible by 86_400_000, so an inexact `Date64` -> `Date32` fold never happens.
fn is_lossy_temporal_cast(from_type: &DataType, to_type: &DataType) -> bool {
if from_type == to_type {
return false;
}
+ if is_date_type(from_type) && is_date_type(to_type) {
+ return false;
+ }
(is_date_type(from_type) && to_type.is_temporal())
|| (is_date_type(to_type) && from_type.is_temporal())
}
@@ -135,6 +144,19 @@ pub fn is_timestamp_precision_narrowing_cast(
timestamp_unit_scale(from_unit) > timestamp_unit_scale(to_unit)
}
+/// Returns true when casting a date column from `from_type` to `to_type` narrows
+/// `Date64` (milliseconds) to `Date32` (days).
+///
+/// Like [`is_timestamp_precision_narrowing_cast`], this guards comparison cast
+/// unwrapping against a many-to-one column cast. `CAST(date64 AS Date32) = lit_day`
+/// matches any millisecond within that day, but the rewritten `date64 = lit_ms`
+/// matches only midnight. Arrow does not require `Date64` values to be whole days
+/// (see arrow-rs#5288), so the column may carry sub-day values the planner cannot
+/// see; the widening direction (`Date32 -> Date64`) is injective and stays allowed.
+pub fn is_date_narrowing_cast(from_type: &DataType, to_type: &DataType) -> bool {
+ matches!((from_type, to_type), (DataType::Date64, DataType::Date32))
+}
+
fn timestamp_unit_scale(unit: &TimeUnit) -> i128 {
match unit {
TimeUnit::Second => 1,
@@ -183,6 +205,36 @@ fn is_supported_binary_type(data_type: &DataType) -> bool {
matches!(data_type, DataType::Binary | DataType::FixedSizeBinary(_))
}
+/// Scale a `Date32`/`Date64` literal value into the units of `target_type`,
+/// returning `None` when the conversion is not exact.
+///
+/// `Date32` counts **days** since the Unix epoch while `Date64` counts
+/// **milliseconds** since the Unix epoch, so a cross conversion scales by
+/// [`MILLISECONDS_IN_DAY`]:
+/// * `Date32` -> `Date64` is always exact: `days * MILLISECONDS_IN_DAY`
+/// (guarded against `i64`/`i128` overflow).
+/// * `Date64` -> `Date32` is exact only when the millisecond value lands on a
+/// whole-day boundary; otherwise it returns `None` so the cast unwrap is
+/// skipped (correct for every operator, including `=`).
+///
+/// For a same-type date cast or a date/integer cast the generic `mul`
+/// multiplier already applies, so this returns `value * mul`.
+fn scale_date_literal(
+ value: i128,
+ from_type: &DataType,
+ target_type: &DataType,
+ mul: i128,
+) -> Option {
+ const MILLIS_PER_DAY: i128 = MILLISECONDS_IN_DAY as i128;
+ match (from_type, target_type) {
+ (DataType::Date32, DataType::Date64) => value.checked_mul(MILLIS_PER_DAY),
+ (DataType::Date64, DataType::Date32) => {
+ (value % MILLIS_PER_DAY == 0).then_some(value / MILLIS_PER_DAY)
+ }
+ _ => value.checked_mul(mul),
+ }
+}
+
/// Convert a numeric value from one numeric data type to another
fn try_cast_numeric_literal(
lit_value: &ScalarValue,
@@ -258,8 +310,12 @@ fn try_cast_numeric_literal(
ScalarValue::UInt16(Some(v)) => (*v as i128).checked_mul(mul),
ScalarValue::UInt32(Some(v)) => (*v as i128).checked_mul(mul),
ScalarValue::UInt64(Some(v)) => (*v as i128).checked_mul(mul),
- ScalarValue::Date32(Some(v)) => (*v as i128).checked_mul(mul),
- ScalarValue::Date64(Some(v)) => (*v as i128).checked_mul(mul),
+ ScalarValue::Date32(Some(v)) => {
+ scale_date_literal(*v as i128, &lit_data_type, target_type, mul)
+ }
+ ScalarValue::Date64(Some(v)) => {
+ scale_date_literal(*v as i128, &lit_data_type, target_type, mul)
+ }
ScalarValue::TimestampSecond(Some(v), _) => (*v as i128).checked_mul(mul),
ScalarValue::TimestampMillisecond(Some(v), _) => (*v as i128).checked_mul(mul),
ScalarValue::TimestampMicrosecond(Some(v), _) => (*v as i128).checked_mul(mul),
@@ -855,25 +911,91 @@ mod tests {
}
#[test]
- fn test_try_cast_date32_date64_still_blocked() {
- // `Date32` counts days and `Date64` counts milliseconds, but
- // try_cast_numeric_literal uses mul = 1 for both, so a cross cast would
- // convert units wrongly. The identity short-circuit must NOT open this
- // up: Date32 <-> Date64 has to stay blocked.
- assert!(is_lossy_temporal_cast(&DataType::Date32, &DataType::Date64));
- assert!(is_lossy_temporal_cast(&DataType::Date64, &DataType::Date32));
-
+ fn test_try_cast_between_date32_and_date64() {
+ // 2025-01-01 is day 20089 since the Unix epoch, which is
+ // 20089 * 86_400_000 = 1_735_689_600_000 milliseconds.
+ const DAY_2025_01_01: i32 = 20089;
+ const MS_2025_01_01: i64 = 1_735_689_600_000;
+ assert_eq!(DAY_2025_01_01 as i64 * MILLISECONDS_IN_DAY, MS_2025_01_01);
+
+ // Date32 -> Date64 is always exact (days scaled up to milliseconds).
expect_cast(
- ScalarValue::Date32(Some(1)),
+ ScalarValue::Date32(Some(DAY_2025_01_01)),
DataType::Date64,
- ExpectedCast::NoValue,
+ ExpectedCast::Value(ScalarValue::Date64(Some(MS_2025_01_01))),
);
+ // Date64 -> Date32 is exact only on a whole-day boundary.
expect_cast(
- ScalarValue::Date64(Some(86_400_000)),
+ ScalarValue::Date64(Some(MS_2025_01_01)),
+ DataType::Date32,
+ ExpectedCast::Value(ScalarValue::Date32(Some(DAY_2025_01_01))),
+ );
+
+ // A Date64 value that is not on a day boundary cannot be represented as
+ // a Date32 exactly, so no rewrite is produced.
+ expect_cast(
+ ScalarValue::Date64(Some(MS_2025_01_01 + 1)),
DataType::Date32,
ExpectedCast::NoValue,
);
+ expect_cast(
+ ScalarValue::Date64(Some(MS_2025_01_01 - 1)),
+ DataType::Date32,
+ ExpectedCast::NoValue,
+ );
+
+ // The epoch and negative (pre-epoch) days round-trip exactly.
+ expect_cast(
+ ScalarValue::Date32(Some(0)),
+ DataType::Date64,
+ ExpectedCast::Value(ScalarValue::Date64(Some(0))),
+ );
+ expect_cast(
+ ScalarValue::Date32(Some(-1)),
+ DataType::Date64,
+ ExpectedCast::Value(ScalarValue::Date64(Some(-MILLISECONDS_IN_DAY))),
+ );
+ expect_cast(
+ ScalarValue::Date64(Some(-MILLISECONDS_IN_DAY)),
+ DataType::Date32,
+ ExpectedCast::Value(ScalarValue::Date32(Some(-1))),
+ );
+
+ // Same-type date casts remain identity conversions.
+ expect_cast(
+ ScalarValue::Date32(Some(DAY_2025_01_01)),
+ DataType::Date32,
+ ExpectedCast::Value(ScalarValue::Date32(Some(DAY_2025_01_01))),
+ );
+ expect_cast(
+ ScalarValue::Date64(Some(MS_2025_01_01)),
+ DataType::Date64,
+ ExpectedCast::Value(ScalarValue::Date64(Some(MS_2025_01_01))),
+ );
+ }
+
+ #[test]
+ fn test_is_lossy_temporal_cast_date_pairs() {
+ // Date <-> Date is let through the pre-filter (per-value exactness is
+ // enforced downstream in try_cast_numeric_literal, not here).
+ assert!(!is_lossy_temporal_cast(
+ &DataType::Date32,
+ &DataType::Date64
+ ));
+ assert!(!is_lossy_temporal_cast(
+ &DataType::Date64,
+ &DataType::Date32
+ ));
+ // Identity is not lossy.
+ assert!(!is_lossy_temporal_cast(
+ &DataType::Date32,
+ &DataType::Date32
+ ));
+ // Date <-> Timestamp remains lossy.
+ let ts = DataType::Timestamp(TimeUnit::Millisecond, None);
+ assert!(is_lossy_temporal_cast(&DataType::Date32, &ts));
+ assert!(is_lossy_temporal_cast(&ts, &DataType::Date32));
}
#[test]
@@ -893,6 +1015,90 @@ mod tests {
));
}
+ #[test]
+ fn test_is_date_narrowing_cast() {
+ // Only Date64 -> Date32 narrows (ms -> days, many-to-one).
+ assert!(is_date_narrowing_cast(&DataType::Date64, &DataType::Date32));
+ // The widening direction is injective and must not be flagged.
+ assert!(!is_date_narrowing_cast(
+ &DataType::Date32,
+ &DataType::Date64
+ ));
+ // Identity and non-date pairs are not date-narrowing casts.
+ assert!(!is_date_narrowing_cast(
+ &DataType::Date32,
+ &DataType::Date32
+ ));
+ assert!(!is_date_narrowing_cast(
+ &DataType::Date64,
+ &DataType::Date64
+ ));
+ assert!(!is_date_narrowing_cast(&DataType::Int64, &DataType::Date32));
+ }
+
+ #[test]
+ fn test_scale_date_literal_exactness_and_overflow() {
+ const MS_PER_DAY: i128 = MILLISECONDS_IN_DAY as i128;
+
+ // Date32 -> Date64 is always exact: days scaled to midnight milliseconds.
+ // 2025-01-01 is day 20089 = 1_735_689_600_000 ms.
+ assert_eq!(
+ scale_date_literal(20089, &DataType::Date32, &DataType::Date64, 1),
+ Some(1_735_689_600_000)
+ );
+ assert_eq!(
+ scale_date_literal(0, &DataType::Date32, &DataType::Date64, 1),
+ Some(0)
+ );
+ // Negative (pre-epoch) whole day: 1969-12-31 is day -1 = -86_400_000 ms.
+ assert_eq!(
+ scale_date_literal(-1, &DataType::Date32, &DataType::Date64, 1),
+ Some(-86_400_000)
+ );
+
+ // Date64 -> Date32 is exact only on a whole-day boundary.
+ assert_eq!(
+ scale_date_literal(
+ 1_735_689_600_000,
+ &DataType::Date64,
+ &DataType::Date32,
+ 1
+ ),
+ Some(20089)
+ );
+ assert_eq!(
+ scale_date_literal(-86_400_000, &DataType::Date64, &DataType::Date32, 1),
+ Some(-1)
+ );
+ // Sub-day values are not exactly representable as a Date32, in both the
+ // positive and the pre-epoch negative direction -> None (no fold).
+ assert_eq!(
+ scale_date_literal(
+ 1_735_732_800_000,
+ &DataType::Date64,
+ &DataType::Date32,
+ 1
+ ),
+ None
+ );
+ assert_eq!(
+ scale_date_literal(-43_200_000, &DataType::Date64, &DataType::Date32, 1),
+ None
+ );
+
+ // Extremes: a Date32 at i32::MIN / i32::MAX widens with checked i128
+ // arithmetic, producing the exact millisecond value without overflow or
+ // panic.
+ assert_eq!(
+ scale_date_literal(i32::MAX as i128, &DataType::Date32, &DataType::Date64, 1),
+ Some(i32::MAX as i128 * MS_PER_DAY)
+ );
+ assert_eq!(
+ scale_date_literal(i32::MIN as i128, &DataType::Date32, &DataType::Date64, 1),
+ Some(i32::MIN as i128 * MS_PER_DAY)
+ );
+ }
+
#[test]
fn test_try_cast_to_type_unsupported() {
// int64 to list
diff --git a/datafusion/optimizer/src/simplify_expressions/unwrap_cast.rs b/datafusion/optimizer/src/simplify_expressions/unwrap_cast.rs
index c7f20a6b6f50e..ef0bfa516fe41 100644
--- a/datafusion/optimizer/src/simplify_expressions/unwrap_cast.rs
+++ b/datafusion/optimizer/src/simplify_expressions/unwrap_cast.rs
@@ -60,7 +60,8 @@ use datafusion_common::{internal_err, tree_node::Transformed};
use datafusion_expr::{BinaryExpr, lit};
use datafusion_expr::{Cast, Expr, Operator, TryCast, simplify::SimplifyContext};
use datafusion_expr_common::casts::{
- is_supported_type, is_timestamp_precision_narrowing_cast, try_cast_literal_to_type,
+ is_date_narrowing_cast, is_supported_type, is_timestamp_precision_narrowing_cast,
+ try_cast_literal_to_type,
};
pub(super) fn unwrap_cast_in_comparison_for_binary(
@@ -134,7 +135,9 @@ pub(super) fn is_cast_expr_and_support_unwrap_cast_in_comparison_for_binary(
return false;
};
- if is_timestamp_precision_narrowing_cast(&expr_type, field.data_type()) {
+ if is_timestamp_precision_narrowing_cast(&expr_type, field.data_type())
+ || is_date_narrowing_cast(&expr_type, field.data_type())
+ {
return false;
}
@@ -177,7 +180,9 @@ pub(super) fn is_cast_expr_and_support_unwrap_cast_in_comparison_for_inlist(
return false;
}
- if is_timestamp_precision_narrowing_cast(&expr_type, field.data_type()) {
+ if is_timestamp_precision_narrowing_cast(&expr_type, field.data_type())
+ || is_date_narrowing_cast(&expr_type, field.data_type())
+ {
return false;
}
diff --git a/datafusion/physical-expr/src/simplifier/unwrap_cast.rs b/datafusion/physical-expr/src/simplifier/unwrap_cast.rs
index 5caee00962b49..3e67fc8291a4e 100644
--- a/datafusion/physical-expr/src/simplifier/unwrap_cast.rs
+++ b/datafusion/physical-expr/src/simplifier/unwrap_cast.rs
@@ -37,7 +37,8 @@ use arrow::datatypes::{DataType, Schema};
use datafusion_common::{Result, ScalarValue, tree_node::Transformed};
use datafusion_expr::Operator;
use datafusion_expr_common::casts::{
- is_timestamp_precision_narrowing_cast, try_cast_literal_to_type,
+ is_date_narrowing_cast, is_timestamp_precision_narrowing_cast,
+ try_cast_literal_to_type,
};
use crate::PhysicalExpr;
@@ -129,7 +130,9 @@ fn try_unwrap_cast_comparison(
// Get the data type of the inner expression
let inner_type = inner_expr.data_type(schema)?;
- if is_timestamp_precision_narrowing_cast(&inner_type, cast_type) {
+ if is_timestamp_precision_narrowing_cast(&inner_type, cast_type)
+ || is_date_narrowing_cast(&inner_type, cast_type)
+ {
return Ok(None);
}
@@ -231,6 +234,23 @@ mod tests {
assert_eq!(*optimized_binary.op(), Operator::Gt);
}
+ #[test]
+ fn test_no_unwrap_date64_to_date32_narrowing() {
+ let schema = Schema::new(vec![Field::new("d64", DataType::Date64, false)]);
+
+ // cast(d64 AS Date32) = Date32(20089) must NOT unwrap: narrowing a Date64
+ // column to Date32 truncates milliseconds to the day (many-to-one), so the
+ // rewritten `d64 = ` would drop sub-day rows.
+ let column_expr = col("d64", &schema).unwrap();
+ let cast_expr = Arc::new(CastExpr::new(column_expr, DataType::Date32, None));
+ let literal_expr = lit(ScalarValue::Date32(Some(20089)));
+ let binary_expr =
+ Arc::new(BinaryExpr::new(cast_expr, Operator::Eq, literal_expr));
+
+ let result = unwrap_cast_in_comparison(binary_expr, &schema).unwrap();
+ assert!(!result.transformed);
+ }
+
#[test]
fn test_no_unwrap_when_types_unsupported() {
let schema = Schema::new(vec![Field::new("f1", DataType::Float32, false)]);
diff --git a/datafusion/sqllogictest/test_files/simplify_expr.slt b/datafusion/sqllogictest/test_files/simplify_expr.slt
index a291740b914f5..57dc440407dc0 100644
--- a/datafusion/sqllogictest/test_files/simplify_expr.slt
+++ b/datafusion/sqllogictest/test_files/simplify_expr.slt
@@ -179,3 +179,255 @@ physical_plan
statement ok
drop table dates;
+
+# ------------------------------------------------------------------------
+# Unwrapping Date32 <-> Date64 casts in comparison predicates.
+#
+# `Date32` counts whole days since the epoch; `Date64` counts milliseconds.
+# Widening a `Date32` column up to `Date64` (`date32_col -> Date64`) is
+# injective, so a comparison against a whole-day `Date64` literal can be
+# rewritten onto the bare `Date32` column. Narrowing a `Date64` column down to
+# `Date32` truncates the milliseconds to the day (many-to-one) and must NOT be
+# rewritten: `CAST(date64 AS Date32) = ` matches any millisecond within
+# that day. Arrow does not require `Date64` values to fall on a day boundary
+# (arrow-rs#5288), so the table below intentionally stores sub-day `Date64`
+# values (ids 2 and 4) to exercise that hazard.
+#
+# The `Date64` column is built from raw millisecond values with `arrow_cast`;
+# `2025-01-01 00:00` = 1735689600000 ms (day 20089), `2025-01-01 12:00` adds
+# 43200000 ms. `1969-12-31 00:00` = -86400000 ms (day -1); `1969-12-31 12:00`
+# = -43200000 ms (a pre-epoch sub-day value).
+statement ok
+create table date_unwrap as
+select
+ c.id,
+ arrow_cast(c.d32, 'Date32') as d32,
+ arrow_cast(c.d64ms, 'Date64') as d64
+from (values
+ (1, '2025-01-01', 1735689600000),
+ (2, '2025-01-01', 1735732800000),
+ (3, '1969-12-31', -86400000),
+ (4, '1969-12-31', -43200000),
+ (5, NULL, NULL)
+) as c(id, d32, d64ms);
+
+query IDD
+select id, d32, d64 from date_unwrap order by id;
+----
+1 2025-01-01 2025-01-01T00:00:00
+2 2025-01-01 2025-01-01T12:00:00
+3 1969-12-31 1969-12-31T00:00:00
+4 1969-12-31 1969-12-31T12:00:00
+5 NULL NULL
+
+# --- Widening Date32 -> Date64: folds onto the bare column ---------------
+# The plan for these widening queries is what changes when the optimization is
+# enabled: the CAST moves off the column and onto the (whole-day) literal.
+query TT
+explain select id from date_unwrap where arrow_cast(d32, 'Date64') = arrow_cast(1735689600000, 'Date64');
+----
+logical_plan
+01)Projection: date_unwrap.id
+02)--Filter: date_unwrap.d32 = Date32("2025-01-01")
+03)----TableScan: date_unwrap projection=[id, d32]
+physical_plan
+01)FilterExec: d32@1 = 2025-01-01, projection=[id@0]
+02)--DataSourceExec: partitions=1, partition_sizes=[1]
+
+query I
+select id from date_unwrap where arrow_cast(d32, 'Date64') = arrow_cast(1735689600000, 'Date64') order by id;
+----
+1
+2
+
+# Range operators fold too (Date32 -> Date64 is monotonic).
+query TT
+explain select id from date_unwrap where arrow_cast(d32, 'Date64') < arrow_cast(1735689600000, 'Date64');
+----
+logical_plan
+01)Projection: date_unwrap.id
+02)--Filter: date_unwrap.d32 < Date32("2025-01-01")
+03)----TableScan: date_unwrap projection=[id, d32]
+physical_plan
+01)FilterExec: d32@1 < 2025-01-01, projection=[id@0]
+02)--DataSourceExec: partitions=1, partition_sizes=[1]
+
+query TT
+explain select id from date_unwrap where arrow_cast(d32, 'Date64') >= arrow_cast(1735689600000, 'Date64');
+----
+logical_plan
+01)Projection: date_unwrap.id
+02)--Filter: date_unwrap.d32 >= Date32("2025-01-01")
+03)----TableScan: date_unwrap projection=[id, d32]
+physical_plan
+01)FilterExec: d32@1 >= 2025-01-01, projection=[id@0]
+02)--DataSourceExec: partitions=1, partition_sizes=[1]
+
+query I
+select id from date_unwrap where arrow_cast(d32, 'Date64') < arrow_cast(1735689600000, 'Date64') order by id;
+----
+3
+4
+
+query I
+select id from date_unwrap where arrow_cast(d32, 'Date64') <= arrow_cast(1735689600000, 'Date64') order by id;
+----
+1
+2
+3
+4
+
+query I
+select id from date_unwrap where arrow_cast(d32, 'Date64') > arrow_cast(1735689600000, 'Date64') order by id;
+----
+
+query I
+select id from date_unwrap where arrow_cast(d32, 'Date64') >= arrow_cast(1735689600000, 'Date64') order by id;
+----
+1
+2
+
+# Reversed operands fold too: with the Date64 literal on the LEFT, logical
+# simplification moves the bare column to the left and swaps the operator
+# (`literal < CAST(col)` becomes `col > literal`).
+query TT
+explain select id from date_unwrap where arrow_cast(-86400000, 'Date64') < arrow_cast(d32, 'Date64');
+----
+logical_plan
+01)Projection: date_unwrap.id
+02)--Filter: date_unwrap.d32 > Date32("1969-12-31")
+03)----TableScan: date_unwrap projection=[id, d32]
+physical_plan
+01)FilterExec: d32@1 > 1969-12-31, projection=[id@0]
+02)--DataSourceExec: partitions=1, partition_sizes=[1]
+
+query I
+select id from date_unwrap where arrow_cast(-86400000, 'Date64') < arrow_cast(d32, 'Date64') order by id;
+----
+1
+2
+
+# IN-list widening also folds.
+query TT
+explain select id from date_unwrap where arrow_cast(d32, 'Date64') in (arrow_cast(1735689600000, 'Date64'), arrow_cast(-86400000, 'Date64'));
+----
+logical_plan
+01)Projection: date_unwrap.id
+02)--Filter: date_unwrap.d32 = Date32("2025-01-01") OR date_unwrap.d32 = Date32("1969-12-31")
+03)----TableScan: date_unwrap projection=[id, d32]
+physical_plan
+01)FilterExec: d32@1 = 2025-01-01 OR d32@1 = 1969-12-31, projection=[id@0]
+02)--DataSourceExec: partitions=1, partition_sizes=[1]
+
+query I
+select id from date_unwrap where arrow_cast(d32, 'Date64') in (arrow_cast(1735689600000, 'Date64'), arrow_cast(-86400000, 'Date64')) order by id;
+----
+1
+2
+3
+4
+
+# A NON-whole-day literal is NOT foldable: a Date32-derived Date64 is always at
+# midnight, so it can never equal a sub-day literal. The plan keeps the CAST and
+# the query returns zero rows.
+query TT
+explain select id from date_unwrap where arrow_cast(d32, 'Date64') = arrow_cast(1735732800000, 'Date64');
+----
+logical_plan
+01)Projection: date_unwrap.id
+02)--Filter: CAST(date_unwrap.d32 AS Date64) = Date64("2025-01-01")
+03)----TableScan: date_unwrap projection=[id, d32]
+physical_plan
+01)FilterExec: CAST(d32@1 AS Date64) = 2025-01-01, projection=[id@0]
+02)--DataSourceExec: partitions=1, partition_sizes=[1]
+
+query I
+select id from date_unwrap where arrow_cast(d32, 'Date64') = arrow_cast(1735732800000, 'Date64') order by id;
+----
+
+# NULL comparison semantics are unchanged by the rewrite (three-valued logic:
+# the NULL row yields NULL, not a dropped row).
+query IB
+select id, arrow_cast(d32, 'Date64') = arrow_cast(1735689600000, 'Date64') as eq from date_unwrap order by id;
+----
+1 true
+2 true
+3 false
+4 false
+5 NULL
+
+# --- Narrowing Date64 -> Date32: must NOT fold (soundness) ---------------
+# The plan for these queries is invariant: the CAST stays on the column. If it
+# were unwrapped, the sub-day rows (ids 2 and 4) would be dropped.
+query TT
+explain select id from date_unwrap where cast(d64 as date) = DATE '2025-01-01';
+----
+logical_plan
+01)Projection: date_unwrap.id
+02)--Filter: CAST(date_unwrap.d64 AS Date32) = Date32("2025-01-01")
+03)----TableScan: date_unwrap projection=[id, d64]
+physical_plan
+01)FilterExec: CAST(d64@1 AS Date32) = 2025-01-01, projection=[id@0]
+02)--DataSourceExec: partitions=1, partition_sizes=[1]
+
+# id 2 is 2025-01-01 12:00 - it truncates to 2025-01-01 and MUST be returned.
+query I
+select id from date_unwrap where cast(d64 as date) = DATE '2025-01-01' order by id;
+----
+1
+2
+
+query TT
+explain select id from date_unwrap where cast(d64 as date) < DATE '2025-01-01';
+----
+logical_plan
+01)Projection: date_unwrap.id
+02)--Filter: CAST(date_unwrap.d64 AS Date32) < Date32("2025-01-01")
+03)----TableScan: date_unwrap projection=[id, d64]
+physical_plan
+01)FilterExec: CAST(d64@1 AS Date32) < 2025-01-01, projection=[id@0]
+02)--DataSourceExec: partitions=1, partition_sizes=[1]
+
+# IN-list narrowing is guarded as well.
+query TT
+explain select id from date_unwrap where cast(d64 as date) in (DATE '2025-01-01');
+----
+logical_plan
+01)Projection: date_unwrap.id
+02)--Filter: CAST(date_unwrap.d64 AS Date32) = Date32("2025-01-01")
+03)----TableScan: date_unwrap projection=[id, d64]
+physical_plan
+01)FilterExec: CAST(d64@1 AS Date32) = 2025-01-01, projection=[id@0]
+02)--DataSourceExec: partitions=1, partition_sizes=[1]
+
+query I
+select id from date_unwrap where cast(d64 as date) in (DATE '2025-01-01') order by id;
+----
+1
+2
+
+# Pre-epoch dates. Arrow's Date64 -> Date32 cast divides by 86_400_000 and
+# truncates toward zero, so the pre-epoch sub-day value (id 4, -43200000 ms)
+# truncates to day 0 (1970-01-01), not to 1969-12-31. This is arrow's runtime
+# behavior; `scale_date_literal` only ever folds on exact whole-day multiples,
+# so it can never disagree with the value the cast actually produces.
+query ID
+select id, cast(d64 as date) as truncated from date_unwrap where d64 is not null order by id;
+----
+1 2025-01-01
+2 2025-01-01
+3 1969-12-31
+4 1970-01-01
+
+query I
+select id from date_unwrap where cast(d64 as date) = DATE '1969-12-31' order by id;
+----
+3
+
+query I
+select id from date_unwrap where cast(d64 as date) = DATE '1970-01-01' order by id;
+----
+4
+
+statement ok
+drop table date_unwrap;
From 7dfeeb041211907c487285e2756d438d2af7d28b Mon Sep 17 00:00:00 2001
From: Naman Modi
Date: Fri, 24 Jul 2026 17:49:55 +0530
Subject: [PATCH 004/109] test (slt): add memory-limited aggregation
sqllogictests (#23838)
## Which issue does this PR close?
- Part of #22710.
## Rationale for this change
There's almost no slt coverage for grouped aggregation under a memory
limit, where the aggregate spills to disk and re-groups the spilled
state. #23657 covers the ordered path & this covers the unordered/hash
path.
## What changes are included in this PR?
- Adds `aggregate_memory_limit.slt` in which the high-cardinality "GROUP
BY" is run under a 1M limit, so the hash aggregate spills. The group key
is scrambled with `(v * 7) % 100000` (a bijection, so still 100000
groups) to keep the input unsorted; otherwise it takes the streaming
path and never spills.
- Scoped to a single partition (`target_partitions = 1`), so the spill
happens in one aggregate operator with no repartition.
- Cases cover different accumulator states: single-column, multi-column,
count(DISTINCT), sum/min/max, avg (widening), array_agg (growable).
## Are these changes tested?
This PR is tests. All pass locally.
## Are there any user-facing changes?
No.
---
.../test_files/aggregate_memory_spill.slt | 228 ++++++++++++++++++
1 file changed, 228 insertions(+)
create mode 100644 datafusion/sqllogictest/test_files/aggregate_memory_spill.slt
diff --git a/datafusion/sqllogictest/test_files/aggregate_memory_spill.slt b/datafusion/sqllogictest/test_files/aggregate_memory_spill.slt
new file mode 100644
index 0000000000000..7615209255394
--- /dev/null
+++ b/datafusion/sqllogictest/test_files/aggregate_memory_spill.slt
@@ -0,0 +1,228 @@
+# 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.
+
+# Memory-limited (spilling) grouped hash aggregation.
+#
+# High-cardinality GROUP BY under a tight memory limit: the aggregate spills to
+# disk, re-groups the spilled state, and must still return the right answer.
+#
+# The group key is scrambled with `(v * 7) % 100000` because generate_series is
+# sorted, which would take the streaming path that never spills. gcd(7, 100000)
+# = 1, so it's a bijection over 1..100000. Still 100000 groups, just unsorted,
+# so the hash table grows and spills.
+#
+# Each query aggregates over the grouped result, so the expected output is one
+# row. sum(1..100000) = 5000050000, and every v lands in one group, so the
+# per-group sums always add back to that total.
+
+# Single partition keeps the aggregation in one operator (no repartition).
+statement ok
+SET datafusion.execution.target_partitions = 1
+
+statement ok
+SET datafusion.runtime.memory_limit = '1M'
+
+# --- Case A: single-column high-cardinality GROUP BY ---
+query II
+SELECT count(*), sum(total)
+FROM (
+ SELECT (v * 7) % 100000 AS k, sum(v) AS total
+ FROM generate_series(1, 100000) AS t(v)
+ GROUP BY (v * 7) % 100000
+)
+----
+100000 5000050000
+
+# Prove the inner aggregate actually spills (else these tests would silently stop covering the spill path).
+# Only `spill_count` is pinned; the other metrics vary per run.
+query TT
+EXPLAIN ANALYZE
+SELECT count(*), sum(total)
+FROM (
+ SELECT (v * 7) % 100000 AS k, sum(v) AS total
+ FROM generate_series(1, 100000) AS t(v)
+ GROUP BY (v * 7) % 100000
+)
+----
+Plan with Metrics
+01)ProjectionExec: expr=[count(Int64(1))@0 as count(*), sum(total)@1 as sum(total)], metrics=[]
+02)--AggregateExec: mode=Single, gby=[], aggr=[count(Int64(1)), sum(total)], metrics=[]
+03)----ProjectionExec: expr=[sum(t.v)@1 as total], metrics=[]
+04)------AggregateExec: mode=Single, gby=[v@0 * 7 % 100000 as t.v * Int64(7) % Int64(100000)], aggr=[sum(t.v)], metrics=[spill_count=9,]
+05)--------ProjectionExec: expr=[value@0 as v], metrics=[]
+06)----------LazyMemoryExec: partitions=1, batch_generators=[generate_series: start=1, end=100000, batch_size=8192], metrics=[]
+
+# --- Case B: multi-column GROUP BY (is_single() = false) ---
+# Both keys are bijections of v, so each (a, b) pair is unique: 100000 groups.
+query II
+SELECT count(*), sum(total)
+FROM (
+ SELECT (v * 7) % 100000 AS a, (v * 13) % 100000 AS b, sum(v) AS total
+ FROM generate_series(1, 100000) AS t(v)
+ GROUP BY (v * 7) % 100000, (v * 13) % 100000
+)
+----
+100000 5000050000
+
+# Assert this case spills too.
+query TT
+EXPLAIN ANALYZE
+SELECT count(*), sum(total)
+FROM (
+ SELECT (v * 7) % 100000 AS a, (v * 13) % 100000 AS b, sum(v) AS total
+ FROM generate_series(1, 100000) AS t(v)
+ GROUP BY (v * 7) % 100000, (v * 13) % 100000
+)
+----
+Plan with Metrics
+01)ProjectionExec: expr=[count(Int64(1))@0 as count(*), sum(total)@1 as sum(total)], metrics=[]
+02)--AggregateExec: mode=Single, gby=[], aggr=[count(Int64(1)), sum(total)], metrics=[]
+03)----ProjectionExec: expr=[sum(t.v)@2 as total], metrics=[]
+04)------AggregateExec: mode=Single, gby=[v@0 * 7 % 100000 as t.v * Int64(7) % Int64(100000), v@0 * 13 % 100000 as t.v * Int64(13) % Int64(100000)], aggr=[sum(t.v)], metrics=[spill_count=11,]
+05)--------ProjectionExec: expr=[value@0 as v], metrics=[]
+06)----------LazyMemoryExec: partitions=1, batch_generators=[generate_series: start=1, end=100000, batch_size=8192], metrics=[]
+
+# --- Case C: DISTINCT aggregate under memory limit ---
+# One distinct value per group, so each count(DISTINCT v) = 1.
+query II
+SELECT count(*), sum(d)
+FROM (
+ SELECT (v * 7) % 100000 AS k, count(DISTINCT v) AS d
+ FROM generate_series(1, 100000) AS t(v)
+ GROUP BY (v * 7) % 100000
+)
+----
+100000 100000
+
+# Assert this case spills too.
+query TT
+EXPLAIN ANALYZE
+SELECT count(*), sum(d)
+FROM (
+ SELECT (v * 7) % 100000 AS k, count(DISTINCT v) AS d
+ FROM generate_series(1, 100000) AS t(v)
+ GROUP BY (v * 7) % 100000
+)
+----
+Plan with Metrics
+01)ProjectionExec: expr=[count(Int64(1))@0 as count(*), sum(d)@1 as sum(d)], metrics=[]
+02)--AggregateExec: mode=Single, gby=[], aggr=[count(Int64(1)), sum(d)], metrics=[]
+03)----ProjectionExec: expr=[count(alias1)@1 as d], metrics=[]
+04)------AggregateExec: mode=Single, gby=[group_alias_0@0 as group_alias_0], aggr=[count(alias1)], metrics=[spill_count=18,]
+05)--------AggregateExec: mode=Single, gby=[v@0 * 7 % 100000 as group_alias_0, v@0 as alias1], aggr=[], ordering_mode=Sorted, metrics=[]
+06)----------ProjectionExec: expr=[value@0 as v], metrics=[]
+07)------------LazyMemoryExec: partitions=1, batch_generators=[generate_series: start=1, end=100000, batch_size=8192], metrics=[]
+
+# --- Case D: multiple aggregates (sum/min/max) under memory limit ---
+# Each group holds a single v, so min(v) = max(v) = v within the group.
+query IIII
+SELECT count(*), sum(s), min(mn), max(mx)
+FROM (
+ SELECT (v * 7) % 100000 AS k, sum(v) AS s, min(v) AS mn, max(v) AS mx
+ FROM generate_series(1, 100000) AS t(v)
+ GROUP BY (v * 7) % 100000
+)
+----
+100000 5000050000 1 100000
+
+# Assert this case spills too.
+query TT
+EXPLAIN ANALYZE
+SELECT count(*), sum(s), min(mn), max(mx)
+FROM (
+ SELECT (v * 7) % 100000 AS k, sum(v) AS s, min(v) AS mn, max(v) AS mx
+ FROM generate_series(1, 100000) AS t(v)
+ GROUP BY (v * 7) % 100000
+)
+----
+Plan with Metrics
+01)ProjectionExec: expr=[count(Int64(1))@0 as count(*), sum(s)@1 as sum(s), min(mn)@2 as min(mn), max(mx)@3 as max(mx)], metrics=[]
+02)--AggregateExec: mode=Single, gby=[], aggr=[count(Int64(1)), sum(s), min(mn), max(mx)], metrics=[]
+03)----ProjectionExec: expr=[sum(t.v)@1 as s, min(t.v)@2 as mn, max(t.v)@3 as mx], metrics=[]
+04)------AggregateExec: mode=Single, gby=[v@0 * 7 % 100000 as t.v * Int64(7) % Int64(100000)], aggr=[sum(t.v), min(t.v), max(t.v)], metrics=[spill_count=27,]
+05)--------ProjectionExec: expr=[value@0 as v], metrics=[]
+06)----------LazyMemoryExec: partitions=1, batch_generators=[generate_series: start=1, end=100000, batch_size=8192], metrics=[]
+
+# --- Case E: avg() aggregate (Float64 output) under memory limit ---
+# Each group holds a single v, so avg(v) = v within the group.
+query IRR
+SELECT count(*), min(a), max(a)
+FROM (
+ SELECT (v * 7) % 100000 AS k, avg(v) AS a
+ FROM generate_series(1, 100000) AS t(v)
+ GROUP BY (v * 7) % 100000
+)
+----
+100000 1 100000
+
+# Assert this case spills too.
+query TT
+EXPLAIN ANALYZE
+SELECT count(*), min(a), max(a)
+FROM (
+ SELECT (v * 7) % 100000 AS k, avg(v) AS a
+ FROM generate_series(1, 100000) AS t(v)
+ GROUP BY (v * 7) % 100000
+)
+----
+Plan with Metrics
+01)ProjectionExec: expr=[count(Int64(1))@0 as count(*), min(a)@1 as min(a), max(a)@2 as max(a)], metrics=[]
+02)--AggregateExec: mode=Single, gby=[], aggr=[count(Int64(1)), min(a), max(a)], metrics=[]
+03)----ProjectionExec: expr=[avg(t.v)@1 as a], metrics=[]
+04)------AggregateExec: mode=Single, gby=[v@0 * 7 % 100000 as t.v * Int64(7) % Int64(100000)], aggr=[avg(t.v)], metrics=[spill_count=11,]
+05)--------ProjectionExec: expr=[value@0 as v], metrics=[]
+06)----------LazyMemoryExec: partitions=1, batch_generators=[generate_series: start=1, end=100000, batch_size=8192], metrics=[]
+
+# --- Case F: array_agg() aggregate (growable state) under memory limit ---
+# Each group holds a single v, so array_length(array_agg(v)) = 1.
+query II
+SELECT count(*), sum(l)
+FROM (
+ SELECT (v * 7) % 100000 AS k, array_length(array_agg(v)) AS l
+ FROM generate_series(1, 100000) AS t(v)
+ GROUP BY (v * 7) % 100000
+)
+----
+100000 100000
+
+# Assert this case spills too.
+query TT
+EXPLAIN ANALYZE
+SELECT count(*), sum(l)
+FROM (
+ SELECT (v * 7) % 100000 AS k, array_length(array_agg(v)) AS l
+ FROM generate_series(1, 100000) AS t(v)
+ GROUP BY (v * 7) % 100000
+)
+----
+Plan with Metrics
+01)ProjectionExec: expr=[count(Int64(1))@0 as count(*), sum(l)@1 as sum(l)], metrics=[]
+02)--AggregateExec: mode=Single, gby=[], aggr=[count(Int64(1)), sum(l)], metrics=[]
+03)----ProjectionExec: expr=[array_length(array_agg(t.v)@1) as l], metrics=[]
+04)------AggregateExec: mode=Single, gby=[v@0 * 7 % 100000 as t.v * Int64(7) % Int64(100000)], aggr=[array_agg(t.v)], metrics=[spill_count=10,]
+05)--------ProjectionExec: expr=[value@0 as v], metrics=[]
+06)----------LazyMemoryExec: partitions=1, batch_generators=[generate_series: start=1, end=100000, batch_size=8192], metrics=[]
+
+# Restore settings to slt runner defaults
+statement ok
+RESET datafusion.runtime.memory_limit
+
+statement ok
+SET datafusion.execution.target_partitions = 4
+
+statement ok
+RESET datafusion.catalog.create_default_catalog_and_schema
From e9a75bf47a962cd7ee11e563bc948f3f0d0a51c8 Mon Sep 17 00:00:00 2001
From: Kumar Ujjawal
Date: Fri, 24 Jul 2026 17:50:56 +0530
Subject: [PATCH 005/109] feat: add OR pre-selection short-circuit (#22979)
## Which issue does this PR close?
- Closes #22342.
## Rationale for this change
`BinaryExpr` already uses pre-selection for `AND` when only a small set
of LHS rows can affect the final result. This adds the matching
optimization for `OR` when most LHS rows are already true.
## What changes are included in this PR?
This PR extends pre-selection short-circuiting to `OR`.
For `OR`, the RHS is evaluated only for rows where the LHS is false.
Rows where the LHS is true are filled directly as true. The existing
`AND` path is kept and the scatter logic is shared.
## Are these changes tested?
Yes
## Are there any user-facing changes?
No Public API Change
Co-authored-by: Andrew Lamb
---
.../physical-expr/src/expressions/binary.rs | 368 ++++++++++++------
1 file changed, 259 insertions(+), 109 deletions(-)
diff --git a/datafusion/physical-expr/src/expressions/binary.rs b/datafusion/physical-expr/src/expressions/binary.rs
index a39691674d18b..89828620a5930 100644
--- a/datafusion/physical-expr/src/expressions/binary.rs
+++ b/datafusion/physical-expr/src/expressions/binary.rs
@@ -482,41 +482,50 @@ impl PhysicalExpr for BinaryExpr {
let rhs = self.right.evaluate(batch)?;
return Ok(rhs);
}
- ShortCircuitStrategy::PreSelection(selection) => {
- // The function `evaluate_selection` was not called for filtering and calculation,
- // as it takes into account cases where the selection contains null values.
- let batch = filter_record_batch(batch, selection)?;
- let right_ret = self.right.evaluate(&batch)?;
+ ShortCircuitStrategy::PreSelection { mask, fill_value } => {
+ // `mask` selects the rows whose result depends on the RHS; the
+ // unselected rows are all `fill_value` (see `ShortCircuitStrategy`).
+ //
+ // Use `filter_record_batch` directly because `evaluate_selection`
+ // scatters the RHS back to the original batch length.
+ let selection_batch = filter_record_batch(batch, &mask)?;
+ let right_ret = self.right.evaluate(&selection_batch)?;
match &right_ret {
ColumnarValue::Array(array) => {
- // When the array on the right is all true or all false, skip the scatter process
let boolean_array = array.as_boolean();
- if boolean_array.null_count() == 0 && !boolean_array.has_false() {
- return Ok(lhs);
- } else if boolean_array.null_count() == 0
- && !boolean_array.has_true()
- {
- // If the right-hand array is returned at this point,the lengths will be inconsistent;
- // returning a scalar can avoid this issue
- return Ok(ColumnarValue::Scalar(ScalarValue::Boolean(
- Some(false),
- )));
+ // If the RHS is uniform on the selected rows, the whole
+ // expression collapses and no scatter is needed.
+ if boolean_array.null_count() == 0 {
+ let rhs_value = if !boolean_array.has_false() {
+ Some(true)
+ } else if !boolean_array.has_true() {
+ Some(false)
+ } else {
+ None
+ };
+ if let Some(rhs_value) = rhs_value {
+ return Ok(uniform_pre_selection_result(
+ rhs_value, fill_value, lhs,
+ ));
+ }
}
- return pre_selection_scatter(selection, Some(boolean_array));
+ return pre_selection_scatter(
+ &mask,
+ Some(boolean_array),
+ fill_value,
+ );
}
ColumnarValue::Scalar(scalar) => {
if let ScalarValue::Boolean(v) = scalar {
- // When the scalar is true or false, skip the scatter process
+ // A scalar RHS applies uniformly to all selected rows.
if let Some(v) = v {
- if *v {
- return Ok(lhs);
- } else {
- return Ok(right_ret);
- }
+ return Ok(uniform_pre_selection_result(
+ *v, fill_value, lhs,
+ ));
} else {
- return pre_selection_scatter(selection, None);
+ return pre_selection_scatter(&mask, None, fill_value);
}
} else {
return internal_err!(
@@ -1038,16 +1047,28 @@ impl BinaryExpr {
}
}
-enum ShortCircuitStrategy<'a> {
+enum ShortCircuitStrategy {
None,
ReturnLeft,
ReturnRight,
- PreSelection(&'a BooleanArray),
+ /// Evaluate the right-hand side only on the rows selected by `mask`, then
+ /// scatter the results back, filling the unselected rows with `fill_value`.
+ ///
+ /// - For `AND`, `mask` selects the rows where the LHS is `true` and
+ /// `fill_value` is `false` (rows where the LHS is `false` are `false`).
+ /// - For `OR`, `mask` selects the rows where the LHS is `false` and
+ /// `fill_value` is `true` (rows where the LHS is `true` are `true`).
+ PreSelection {
+ mask: BooleanArray,
+ fill_value: bool,
+ },
}
/// Based on the results calculated from the left side of the short-circuit operation,
-/// if the proportion of `true` is less than 0.2 and the current operation is an `and`,
-/// the `RecordBatch` will be filtered in advance.
+/// pre-selection filters the `RecordBatch` before evaluating the right-hand side when
+/// the side that cannot short-circuit the operator is rare:
+/// - for `AND`, when the proportion of `true` is less than or equal to 0.2
+/// - for `OR`, when the proportion of `false` is less than or equal to 0.2
const PRE_SELECTION_THRESHOLD: f32 = 0.2;
/// Checks if a logical operator (`AND`/`OR`) can short-circuit evaluation based on the left-hand side (lhs) result.
@@ -1056,24 +1077,21 @@ const PRE_SELECTION_THRESHOLD: f32 = 0.2;
/// - For `AND`:
/// - if LHS is all false => short-circuit → return LHS
/// - if LHS is all true => short-circuit → return RHS
-/// - if LHS is mixed and true_count/sum_count <= [`PRE_SELECTION_THRESHOLD`] -> pre-selection
+/// - if LHS is mixed and true_count / len <= [`PRE_SELECTION_THRESHOLD`] -> pre-selection
/// - For `OR`:
/// - if LHS is all true => short-circuit → return LHS
/// - if LHS is all false => short-circuit → return RHS
+/// - if LHS is mixed and false_count / len <= [`PRE_SELECTION_THRESHOLD`] -> pre-selection
/// # Arguments
/// * `lhs` - The left-hand side (lhs) columnar value (array or scalar)
-/// * `lhs` - The left-hand side (lhs) columnar value (array or scalar)
/// * `op` - The logical operator (`AND` or `OR`)
///
/// # Implementation Notes
/// 1. Only works with Boolean-typed arguments (other types automatically return `false`)
/// 2. Handles both scalar values and array values
/// 3. For arrays, uses optimized bit counting techniques for boolean arrays
-fn check_short_circuit<'a>(
- lhs: &'a ColumnarValue,
- op: &Operator,
-) -> ShortCircuitStrategy<'a> {
- // Quick reject for non-logical operators,and quick judgment when op is and
+fn check_short_circuit(lhs: &ColumnarValue, op: &Operator) -> ShortCircuitStrategy {
+ // Only logical operators can use this path.
let is_and = match op {
Operator::And => true,
Operator::Or => false,
@@ -1101,36 +1119,42 @@ fn check_short_circuit<'a>(
let true_count = bool_array.values().count_set_bits();
if is_and {
- // For AND, prioritize checking for all-false (short circuit case)
- // Uses optimized false_count() method provided by Arrow
-
- // Short circuit if all values are false
if true_count == 0 {
return ShortCircuitStrategy::ReturnLeft;
}
- // If no false values, then all must be true
if true_count == len {
return ShortCircuitStrategy::ReturnRight;
}
- // determine if we can pre-selection
if true_count as f32 / len as f32 <= PRE_SELECTION_THRESHOLD {
- return ShortCircuitStrategy::PreSelection(bool_array);
+ // Select rows where the LHS is true; rows where the LHS
+ // is false are false regardless of the RHS.
+ return ShortCircuitStrategy::PreSelection {
+ mask: bool_array.clone(),
+ fill_value: false,
+ };
}
} else {
- // For OR, prioritize checking for all-true (short circuit case)
- // Uses optimized true_count() method provided by Arrow
-
- // Short circuit if all values are true
if true_count == len {
return ShortCircuitStrategy::ReturnLeft;
}
- // If no true values, then all must be false
if true_count == 0 {
return ShortCircuitStrategy::ReturnRight;
}
+
+ let false_count = len - true_count;
+ if false_count as f32 / len as f32 <= PRE_SELECTION_THRESHOLD {
+ // Select rows where the LHS is false; rows where the LHS
+ // is true are true regardless of the RHS. The LHS has no
+ // nulls here, so negating its bits is infallible.
+ let mask = BooleanArray::new(!bool_array.values(), None);
+ return ShortCircuitStrategy::PreSelection {
+ mask,
+ fill_value: true,
+ };
+ }
}
}
}
@@ -1153,62 +1177,54 @@ fn check_short_circuit<'a>(
ShortCircuitStrategy::None
}
-/// Creates a new boolean array based on the evaluation of the right expression,
-/// but only for positions where the left_result is true.
+/// Collapses a pre-selected expression whose RHS is uniformly `rhs_value` across
+/// every selected row, avoiding a scatter:
+/// - when it equals `fill_value`, every row is `fill_value` (a scalar);
+/// - otherwise the selected rows already equal the RHS, which matches the LHS
+/// there, and the unselected rows are the LHS value too, so the result is `lhs`.
+fn uniform_pre_selection_result(
+ rhs_value: bool,
+ fill_value: bool,
+ lhs: ColumnarValue,
+) -> ColumnarValue {
+ if rhs_value == fill_value {
+ ColumnarValue::Scalar(ScalarValue::Boolean(Some(fill_value)))
+ } else {
+ lhs
+ }
+}
+
+/// Creates a boolean array by scattering compact RHS results into the positions
+/// selected by `mask`.
///
-/// This function is used for short-circuit evaluation optimization of logical AND operations:
-/// - When left_result has few true values, we only evaluate the right expression for those positions
-/// - Values are copied from right_array where left_result is true
-/// - All other positions are filled with false values
+/// This function is used for short-circuit evaluation optimization of logical AND/OR operations:
+/// - Only selected rows are evaluated on the RHS
+/// - Values are copied from `right_result` where `mask` is true
+/// - All other positions are filled with `fill_value` (`false` for AND, `true` for OR)
///
/// # Parameters
-/// - `left_result` Boolean array with selection mask (typically from left side of AND)
+/// - `mask` Boolean array with the rows whose result depends on the RHS
/// - `right_result` Result of evaluating right side of expression (only for selected positions)
+/// - `fill_value` The value for the unselected positions (`false` for AND, `true` for OR)
///
/// # Returns
-/// A combined ColumnarValue with values from right_result where left_result is true
-///
-/// # Example
-/// Initial Data: { 1, 2, 3, 4, 5 }
-/// Left Evaluation
-/// (Condition: Equal to 2 or 3)
-/// ↓
-/// Filtered Data: {2, 3}
-/// Left Bitmap: { 0, 1, 1, 0, 0 }
-/// ↓
-/// Right Evaluation
-/// (Condition: Even numbers)
-/// ↓
-/// Right Data: { 2 }
-/// Right Bitmap: { 1, 0 }
-/// ↓
-/// Combine Results
-/// Final Bitmap: { 0, 1, 0, 0, 0 }
-///
-/// # Note
-/// Perhaps it would be better to modify `left_result` directly without creating a copy?
-/// In practice, `left_result` should have only one owner, so making changes should be safe.
-/// However, this is difficult to achieve under the immutable constraints of [`Arc`] and [`BooleanArray`].
+/// A combined `ColumnarValue` with the same length as `mask`.
fn pre_selection_scatter(
- left_result: &BooleanArray,
+ mask: &BooleanArray,
right_result: Option<&BooleanArray>,
+ fill_value: bool,
) -> Result {
- let result_len = left_result.len();
+ let result_len = mask.len();
let mut result_array_builder = BooleanArray::builder(result_len);
- // keep track of current position we have in right boolean array
let mut right_array_pos = 0;
-
- // keep track of how much is filled
let mut last_end = 0;
- // reduce if condition in for_each
match right_result {
Some(right_result) => {
- SlicesIterator::new(left_result).for_each(|(start, end)| {
- // the gap needs to be filled with false
+ SlicesIterator::new(mask).for_each(|(start, end)| {
if start > last_end {
- result_array_builder.append_n(start - last_end, false);
+ result_array_builder.append_n(start - last_end, fill_value);
}
// copy values from right array for this slice
@@ -1222,13 +1238,11 @@ fn pre_selection_scatter(
last_end = end;
});
}
- None => SlicesIterator::new(left_result).for_each(|(start, end)| {
- // the gap needs to be filled with false
+ None => SlicesIterator::new(mask).for_each(|(start, end)| {
if start > last_end {
- result_array_builder.append_n(start - last_end, false);
+ result_array_builder.append_n(start - last_end, fill_value);
}
- // append nulls for this slice derictly
let len = end - start;
result_array_builder.append_nulls(len);
@@ -1236,9 +1250,9 @@ fn pre_selection_scatter(
}),
}
- // Fill any remaining positions with false
+ // Fill any remaining positions with `fill_value`
if last_end < result_len {
- result_array_builder.append_n(result_len - last_end, false);
+ result_array_builder.append_n(result_len - last_end, fill_value);
}
let boolean_result = result_array_builder.finish();
@@ -5400,14 +5414,17 @@ mod tests {
let ColumnarValue::Array(array) = &left_value else {
panic!("Expected ColumnarValue::Array");
};
- let ShortCircuitStrategy::PreSelection(value) =
+ let ShortCircuitStrategy::PreSelection { mask, fill_value } =
check_short_circuit(&left_value, &Operator::And)
else {
panic!("Expected ShortCircuitStrategy::PreSelection");
};
+ // For AND, the mask selects the rows where the LHS is true and the
+ // unselected rows are filled with `false`.
+ assert!(!fill_value);
let expected_boolean_arr: Vec<_> =
as_boolean_array(array).unwrap().iter().collect();
- let boolean_arr: Vec<_> = value.iter().collect();
+ let boolean_arr: Vec<_> = mask.iter().collect();
assert_eq!(expected_boolean_arr, boolean_arr);
// op: OR left: all true
@@ -5418,10 +5435,33 @@ mod tests {
ShortCircuitStrategy::ReturnLeft
));
- // op: OR left: not all true
+ // 20% false: OR can pre-select the false rows.
let left_expr: Arc =
logical2physical(&logical_col("a").gt(expr_lit(2)), &schema);
let left_value = left_expr.evaluate(&batch).unwrap();
+ let ColumnarValue::Array(array) = &left_value else {
+ panic!("Expected ColumnarValue::Array");
+ };
+ let ShortCircuitStrategy::PreSelection { mask, fill_value } =
+ check_short_circuit(&left_value, &Operator::Or)
+ else {
+ panic!("Expected ShortCircuitStrategy::PreSelection");
+ };
+ // For OR, the mask selects the rows where the LHS is false (the negation
+ // of the LHS) and the unselected rows are filled with `true`.
+ assert!(fill_value);
+ let negated_lhs: Vec<_> = as_boolean_array(array)
+ .unwrap()
+ .iter()
+ .map(|v| v.map(|b| !b))
+ .collect();
+ let boolean_arr: Vec<_> = mask.iter().collect();
+ assert_eq!(negated_lhs, boolean_arr);
+
+ // 60% false: OR falls back to normal evaluation.
+ let left_expr: Arc =
+ logical2physical(&logical_col("a").gt(expr_lit(4)), &schema);
+ let left_value = left_expr.evaluate(&batch).unwrap();
assert!(matches!(
check_short_circuit(&left_value, &Operator::Or),
ShortCircuitStrategy::None
@@ -5525,15 +5565,10 @@ mod tests {
));
}
- /// Test for [pre_selection_scatter]
- /// Since [check_short_circuit] ensures that the left side does not contain null and is neither all_true nor all_false, as well as not being empty,
- /// the following tests have been designed:
- /// 1. Test sparse left with interleaved true/false
- /// 2. Test multiple consecutive true blocks
- /// 3. Test multiple consecutive true blocks
- /// 4. Test single true at first position
- /// 5. Test single true at last position
- /// 6. Test nulls in right array
+ /// Test for [pre_selection_scatter].
+ ///
+ /// `check_short_circuit` only calls this helper with a non-empty,
+ /// non-null mask that is neither all true nor all false.
#[test]
fn test_pre_selection_scatter() {
fn create_bool_array(bools: Vec) -> BooleanArray {
@@ -5546,7 +5581,7 @@ mod tests {
let left = create_bool_array(vec![true, false, true, false, true]);
let right = create_bool_array(vec![false, true, false]);
- let result = pre_selection_scatter(&left, Some(&right)).unwrap();
+ let result = pre_selection_scatter(&left, Some(&right), false).unwrap();
let result_arr = result.into_array(left.len()).unwrap();
let expected = create_bool_array(vec![false, false, true, false, false]);
@@ -5560,7 +5595,7 @@ mod tests {
create_bool_array(vec![false, true, true, false, true, true, true]);
let right = create_bool_array(vec![true, false, false, true, false]);
- let result = pre_selection_scatter(&left, Some(&right)).unwrap();
+ let result = pre_selection_scatter(&left, Some(&right), false).unwrap();
let result_arr = result.into_array(left.len()).unwrap();
let expected =
@@ -5574,7 +5609,7 @@ mod tests {
let left = create_bool_array(vec![true, false, false]);
let right = create_bool_array(vec![false]);
- let result = pre_selection_scatter(&left, Some(&right)).unwrap();
+ let result = pre_selection_scatter(&left, Some(&right), false).unwrap();
let result_arr = result.into_array(left.len()).unwrap();
let expected = create_bool_array(vec![false, false, false]);
@@ -5587,7 +5622,7 @@ mod tests {
let left = create_bool_array(vec![false, false, true]);
let right = create_bool_array(vec![false]);
- let result = pre_selection_scatter(&left, Some(&right)).unwrap();
+ let result = pre_selection_scatter(&left, Some(&right), false).unwrap();
let result_arr = result.into_array(left.len()).unwrap();
let expected = create_bool_array(vec![false, false, false]);
@@ -5600,7 +5635,7 @@ mod tests {
let left = create_bool_array(vec![false, true, false, true]);
let right = BooleanArray::from(vec![None, Some(false)]);
- let result = pre_selection_scatter(&left, Some(&right)).unwrap();
+ let result = pre_selection_scatter(&left, Some(&right), false).unwrap();
let result_arr = result.into_array(left.len()).unwrap();
let expected = BooleanArray::from(vec![
@@ -5611,6 +5646,38 @@ mod tests {
]);
assert_eq!(&expected, result_arr.as_boolean());
}
+ // OR semantics: selected rows take the RHS, unselected rows become true.
+ {
+ // Selection (LHS false rows): [T, F, T, F, T]
+ // Right (RHS on those rows): [F, T, F]
+ let left = create_bool_array(vec![true, false, true, false, true]);
+ let right = create_bool_array(vec![false, true, false]);
+
+ let result = pre_selection_scatter(&left, Some(&right), true).unwrap();
+ let result_arr = result.into_array(left.len()).unwrap();
+
+ // selected rows take the RHS value; unselected rows are `true`
+ let expected = create_bool_array(vec![false, true, true, true, false]);
+ assert_eq!(&expected, result_arr.as_boolean());
+ }
+ // OR semantics with nulls in the right array.
+ {
+ // Selection (LHS false rows): [F, T, F, T]
+ // Right: [None, Some(false)]
+ let left = create_bool_array(vec![false, true, false, true]);
+ let right = BooleanArray::from(vec![None, Some(false)]);
+
+ let result = pre_selection_scatter(&left, Some(&right), true).unwrap();
+ let result_arr = result.into_array(left.len()).unwrap();
+
+ let expected = BooleanArray::from(vec![
+ Some(true), // unselected => true
+ None, // null from right
+ Some(true), // unselected => true
+ Some(false),
+ ]);
+ assert_eq!(&expected, result_arr.as_boolean());
+ }
}
#[test]
@@ -5637,6 +5704,89 @@ mod tests {
);
}
+ #[test]
+ fn test_or_false_preselection_returns_lhs() {
+ // `c OR false` over a mostly-true `c` triggers OR pre-selection; the
+ // result must equal `c`.
+ let schema =
+ Arc::new(Schema::new(vec![Field::new("c", DataType::Boolean, false)]));
+ let c_array =
+ Arc::new(BooleanArray::from(vec![true, false, true, true, true])) as ArrayRef;
+ let batch = RecordBatch::try_new(Arc::clone(&schema), vec![Arc::clone(&c_array)])
+ .unwrap();
+
+ let expr = logical2physical(&logical_col("c").or(expr_lit(false)), &schema);
+
+ let result = expr.evaluate(&batch).unwrap();
+ let ColumnarValue::Array(result_arr) = result else {
+ panic!("Expected ColumnarValue::Array");
+ };
+
+ let expected: Vec<_> = c_array.as_boolean().iter().collect();
+ let actual: Vec<_> = result_arr.as_boolean().iter().collect();
+ assert_eq!(
+ expected, actual,
+ "OR with FALSE must equal LHS even with PreSelection"
+ );
+ }
+
+ #[test]
+ fn test_or_preselection_matches_kleene() {
+ // The OR pre-selection path must match full-batch Kleene OR.
+ use arrow::compute::kernels::boolean::or_kleene;
+
+ let schema = Arc::new(Schema::new(vec![
+ Field::new("c", DataType::Boolean, true),
+ Field::new("d", DataType::Boolean, true),
+ ]));
+
+ // `c` is mostly true (2/10 false => 20% <= threshold) so OR pre-selects.
+ let c = BooleanArray::from(vec![
+ true, true, false, true, true, true, true, false, true, true,
+ ]);
+
+ let d_cases = vec![
+ // Mixed RHS with nulls exercises scatter and null copy.
+ BooleanArray::from(vec![
+ Some(false),
+ Some(true),
+ Some(true),
+ Some(false),
+ Some(false),
+ Some(true),
+ Some(false),
+ None,
+ Some(true),
+ None,
+ ]),
+ // RHS true on selected rows exercises the uniform-fill path.
+ BooleanArray::from(vec![Some(true); 10]),
+ // RHS false on selected rows exercises the return-LHS path.
+ BooleanArray::from(vec![Some(false); 10]),
+ ];
+
+ for d in d_cases {
+ let batch = RecordBatch::try_new(
+ Arc::clone(&schema),
+ vec![
+ Arc::new(c.clone()) as ArrayRef,
+ Arc::new(d.clone()) as ArrayRef,
+ ],
+ )
+ .unwrap();
+
+ let expr = logical2physical(&logical_col("c").or(logical_col("d")), &schema);
+ let result = expr.evaluate(&batch).unwrap().into_array(c.len()).unwrap();
+
+ let expected = or_kleene(&c, &d).unwrap();
+ assert_eq!(
+ expected,
+ *result.as_boolean(),
+ "OR pre-selection must match Kleene OR for d = {d:?}"
+ );
+ }
+ }
+
#[test]
fn test_evaluate_bounds_int32() {
let schema = Schema::new(vec![
From b8d3b0b525afaf7049320c426a2be3884600dd77 Mon Sep 17 00:00:00 2001
From: Andy Grove
Date: Fri, 24 Jul 2026 06:21:28 -0600
Subject: [PATCH 006/109] perf: optimize `find_in_set` (up to 24x faster)
(#23460)
## Which issue does this PR close?
N/A
## Rationale for this change
Improve performance of existing expression.
## What changes are included in this PR?
Replace per-row O(set_len) linear scan in find_in_set's constant-list
path with a one-time HashMap lookup (threshold-guarded so short lists
keep the linear scan), giving O(1) per-row probing for large sets.
## Are these changes tested?
Existing tests + new tests
Benchmark (criterion):
- long_list_256: 95.827% faster (base 1246412ns -> cand 52009ns) - ~24x
faster
- short_list_4: 2.13% faster (base 64343ns -> cand 62972ns)
- long_list_64: 88.562% faster (base 422083ns -> cand 48276ns)
## Are there any user-facing changes?
No
Co-authored-by: Jeffrey Vo
---
datafusion/functions/Cargo.toml | 5 +
.../functions/benches/find_in_set_literal.rs | 98 +++++++++++++++++++
.../functions/src/unicode/find_in_set.rs | 75 +++++++++++++-
3 files changed, 173 insertions(+), 5 deletions(-)
create mode 100644 datafusion/functions/benches/find_in_set_literal.rs
diff --git a/datafusion/functions/Cargo.toml b/datafusion/functions/Cargo.toml
index d0ce0d0be3b15..83f8c0f2a3299 100644
--- a/datafusion/functions/Cargo.toml
+++ b/datafusion/functions/Cargo.toml
@@ -330,6 +330,11 @@ harness = false
name = "find_in_set"
required-features = ["unicode_expressions"]
+[[bench]]
+harness = false
+name = "find_in_set_literal"
+required-features = ["unicode_expressions"]
+
[[bench]]
harness = false
name = "contains"
diff --git a/datafusion/functions/benches/find_in_set_literal.rs b/datafusion/functions/benches/find_in_set_literal.rs
new file mode 100644
index 0000000000000..013c7c2081668
--- /dev/null
+++ b/datafusion/functions/benches/find_in_set_literal.rs
@@ -0,0 +1,98 @@
+// 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.
+
+//! Benchmarks the `find_in_set(column, constant_list)` path where the set is a
+//! scalar literal. A long list exercises the pre-built lookup; a short list
+//! stays on the per-row linear scan.
+
+use arrow::array::StringArray;
+use arrow::datatypes::{DataType, Field};
+use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main};
+use datafusion_common::ScalarValue;
+use datafusion_common::config::ConfigOptions;
+use datafusion_expr::{ColumnarValue, ScalarFunctionArgs};
+use rand::prelude::StdRng;
+use rand::{Rng, SeedableRng};
+use std::hint::black_box;
+use std::sync::Arc;
+
+const N_ROWS: usize = 8192;
+
+/// Builds a string column whose values are drawn from `entries` plus a small
+/// fraction of misses, so both hits and misses are exercised.
+fn build_column(entries: &[String]) -> StringArray {
+ let mut rng = StdRng::seed_from_u64(42);
+ let values: Vec> = (0..N_ROWS)
+ .map(|_| {
+ let r = rng.random::();
+ if r < 0.1 {
+ None
+ } else if r < 0.4 {
+ Some("__miss__".to_string())
+ } else {
+ let idx = rng.random_range(0..entries.len());
+ Some(entries[idx].clone())
+ }
+ })
+ .collect();
+ StringArray::from(values)
+}
+
+fn bench_case(c: &mut Criterion, label: &str, num_entries: usize) {
+ let find_in_set = datafusion_functions::unicode::find_in_set();
+ let entries: Vec = (0..num_entries).map(|i| format!("item{i}")).collect();
+ let list = entries.join(",");
+
+ let column = build_column(&entries);
+ let args = vec![
+ ColumnarValue::Array(Arc::new(column)),
+ ColumnarValue::Scalar(ScalarValue::Utf8(Some(list))),
+ ];
+ let arg_fields = args
+ .iter()
+ .map(|arg| Field::new("a", arg.data_type().clone(), true).into())
+ .collect::>();
+ let return_field = Arc::new(Field::new("f", DataType::Int32, true));
+ let config_options = Arc::new(ConfigOptions::default());
+
+ c.bench_with_input(
+ BenchmarkId::new("find_in_set_literal", label),
+ &num_entries,
+ |b, _| {
+ b.iter(|| {
+ black_box(find_in_set.invoke_with_args(ScalarFunctionArgs {
+ args: args.clone(),
+ arg_fields: arg_fields.clone(),
+ number_rows: N_ROWS,
+ return_field: Arc::clone(&return_field),
+ config_options: Arc::clone(&config_options),
+ }))
+ })
+ },
+ );
+}
+
+fn criterion_benchmark(c: &mut Criterion) {
+ // Short list stays on the linear scan (below the lookup threshold).
+ bench_case(c, "short_list_4", 4);
+ // Long lists exercise the pre-built lookup.
+ bench_case(c, "long_list_64", 64);
+ bench_case(c, "long_list_256", 256);
+}
+
+criterion_group!(benches, criterion_benchmark);
+criterion_main!(benches);
diff --git a/datafusion/functions/src/unicode/find_in_set.rs b/datafusion/functions/src/unicode/find_in_set.rs
index 0a83eb3ed61ef..fa23532406ce1 100644
--- a/datafusion/functions/src/unicode/find_in_set.rs
+++ b/datafusion/functions/src/unicode/find_in_set.rs
@@ -25,7 +25,7 @@ use arrow_buffer::NullBuffer;
use crate::utils::utf8_to_int_type;
use datafusion_common::{
- Result, ScalarValue, exec_err, internal_err, utils::take_function_args,
+ HashMap, Result, ScalarValue, exec_err, internal_err, utils::take_function_args,
};
use datafusion_expr::TypeSignature::Exact;
use datafusion_expr::{
@@ -316,6 +316,11 @@ where
Ok(Arc::new(PrimitiveArray::::new(values.into(), nulls)) as ArrayRef)
}
+/// Minimum set length at which a pre-built lookup beats a per-row linear scan.
+/// Below this, the linear scan's small constant factor wins, so short sets are
+/// left untouched to avoid regressing them.
+const FIND_IN_SET_LOOKUP_THRESHOLD: usize = 16;
+
fn find_in_set_right_literal<'a, T, V>(
string_array: V,
str_list: &[&str],
@@ -329,16 +334,34 @@ where
let nulls = string_array.nulls().cloned();
let zero = T::Native::from_usize(0).unwrap();
+ // The set (`str_list`) is constant across all rows. For a large set, the
+ // per-row `position` linear scan is O(set_len). Building a lookup from each
+ // distinct entry to its 1-based position once turns each row into an O(1)
+ // probe (first occurrence wins, exactly matching `position`). Below the
+ // threshold the linear scan's small constant factor is faster, so the map is
+ // built at most once here rather than per row.
+ let map: Option> =
+ (str_list.len() >= FIND_IN_SET_LOOKUP_THRESHOLD).then(|| {
+ let mut map = HashMap::with_capacity(str_list.len());
+ for (idx, entry) in str_list.iter().enumerate() {
+ map.entry(*entry).or_insert(idx + 1);
+ }
+ map
+ });
+
let values: Vec = (0..len)
.map(|i| {
if nulls.as_ref().is_some_and(|n| n.is_null(i)) {
return zero;
}
let string = string_array.value(i);
- let position = str_list
- .iter()
- .position(|s| *s == string)
- .map_or(0, |idx| idx + 1);
+ let position = match &map {
+ Some(map) => map.get(string).copied().unwrap_or(0),
+ None => str_list
+ .iter()
+ .position(|s| *s == string)
+ .map_or(0, |idx| idx + 1),
+ };
T::Native::from_usize(position).unwrap()
})
.collect();
@@ -545,4 +568,46 @@ mod tests {
],
Int32Array::from(vec![None::; 3])
);
+
+ // Exercises both the lookup-map path (list length >= threshold) and the
+ // linear-scan path (short list), including a duplicate entry to confirm the
+ // first occurrence wins in both.
+ #[test]
+ fn test_right_literal_lookup_matches_linear() {
+ use super::find_in_set_right_literal;
+ use arrow::datatypes::Int32Type;
+
+ // 40 unique entries plus a duplicate of "item5" appended at index 40, so
+ // the length is well over FIND_IN_SET_LOOKUP_THRESHOLD.
+ let mut long_list: Vec = (0..40).map(|i| format!("item{i}")).collect();
+ long_list.push("item5".to_string());
+ let long_refs: Vec<&str> = long_list.iter().map(|s| s.as_str()).collect();
+ let short_refs = ["a", "b", "c"];
+
+ let strings = StringArray::from(vec![
+ Some("item0"),
+ Some("item39"),
+ Some("item5"),
+ Some("missing"),
+ None,
+ Some("b"),
+ ]);
+
+ let long =
+ find_in_set_right_literal::(&strings, &long_refs).unwrap();
+ let long = long.as_any().downcast_ref::().unwrap();
+ assert_eq!(long.value(0), 1);
+ assert_eq!(long.value(1), 40);
+ assert_eq!(long.value(2), 6); // first occurrence of "item5"
+ assert_eq!(long.value(3), 0);
+ assert!(long.is_null(4));
+ assert_eq!(long.value(5), 0);
+
+ let short =
+ find_in_set_right_literal::(&strings, &short_refs).unwrap();
+ let short = short.as_any().downcast_ref::().unwrap();
+ assert_eq!(short.value(0), 0);
+ assert!(short.is_null(4));
+ assert_eq!(short.value(5), 2); // "b" at position 2
+ }
}
From 82f1b3646d8cb2c580e007d9db5713e77a7fc31f Mon Sep 17 00:00:00 2001
From: Nathan <56370526+nathanb9@users.noreply.github.com>
Date: Fri, 24 Jul 2026 08:21:56 -0400
Subject: [PATCH 007/109] fix: NOT IN with NULL subquery returns wrong results
under SortMergeJoin (#22810)
## Problem
`NOT IN (subquery)` is a null-aware anti join: when the subquery yields
a NULL the predicate is never TRUE, so the query must return zero rows.
With `prefer_hash_join = false` and multiple partitions, the planner
routed the null-aware anti join to `SortMergeJoinExec`, which is not
null-aware, so it returned wrong results. HashJoin (the default) was
already correct.
## Proof
```sql
set datafusion.optimizer.prefer_hash_join = false;
create table t1(x int) as values (1);
create table t2(y int) as values (NULL);
select x from t1 where x not in (select y from t2);
```
Expected 0 rows (the subquery contains a NULL). Before this change it
returned `1`. With `prefer_hash_join = true` it correctly returned 0
rows. `EXPLAIN` showed the wrong config selecting `SortMergeJoinExec:
join_type=LeftAnti`.
## Solution
The planner already requires null-aware joins to use the CollectLeft
HashJoin, and the HashJoin branch guards on `!null_aware`. The
SortMergeJoin branch was missing the same guard, so this adds `&&
!*null_aware` to it. Null-aware anti joins now fall through to the
CollectLeft HashJoin regardless of `prefer_hash_join`.
`SortMergeJoinExec` has no `null_aware` parameter and cannot honor these
semantics.
Added a regression test in `subquery.slt` (under `prefer_hash_join =
false`) covering both a null-containing subquery (zero rows) and a
null-free subquery (normal anti join). All 61 SortMergeJoin unit tests
pass.
---
datafusion/core/src/physical_planner.rs | 5 ++
.../sqllogictest/test_files/subquery.slt | 58 +++++++++++++++++++
2 files changed, 63 insertions(+)
diff --git a/datafusion/core/src/physical_planner.rs b/datafusion/core/src/physical_planner.rs
index aef8036c749a8..4e914556b4cc0 100644
--- a/datafusion/core/src/physical_planner.rs
+++ b/datafusion/core/src/physical_planner.rs
@@ -1803,6 +1803,11 @@ impl DefaultPhysicalPlanner {
} else if session_state.config().target_partitions() > 1
&& session_state.config().repartition_joins()
&& !prefer_hash_join
+ && !*null_aware
+ // Null-aware joins (e.g. `NOT IN` with a nullable subquery) must
+ // use the CollectLeft HashJoin below: SortMergeJoinExec does not
+ // implement null-aware anti-join semantics and would return wrong
+ // results when the right side contains a null join key.
{
// Use SortMergeJoin if hash join is not preferred
let join_on_len = join_on.len();
diff --git a/datafusion/sqllogictest/test_files/subquery.slt b/datafusion/sqllogictest/test_files/subquery.slt
index 325cff62d3986..dcca13c4164c5 100644
--- a/datafusion/sqllogictest/test_files/subquery.slt
+++ b/datafusion/sqllogictest/test_files/subquery.slt
@@ -2599,3 +2599,61 @@ DROP TABLE sq_count_customer;
statement ok
DROP TABLE sq_count_orders;
+
+# Regression test: `NOT IN` is a null-aware anti join. When the subquery yields a
+# NULL the predicate is never TRUE, so the query must return zero rows. This must
+# hold regardless of the chosen physical join operator. Previously, with
+# prefer_hash_join = false and multiple partitions, the planner routed the
+# null-aware anti join to SortMergeJoin (which is not null-aware) and returned
+# wrong results; null-aware anti joins must use the CollectLeft HashJoin.
+
+statement ok
+set datafusion.optimizer.prefer_hash_join = false;
+
+statement ok
+CREATE TABLE nia_left(x INT) AS VALUES (1), (2), (3), (4);
+
+statement ok
+CREATE TABLE nia_right_with_null(y INT) AS VALUES (2), (NULL);
+
+statement ok
+CREATE TABLE nia_right_no_null(y INT) AS VALUES (2), (4);
+
+# Subquery contains a NULL -> NOT IN must return no rows.
+query I
+SELECT x FROM nia_left WHERE x NOT IN (SELECT y FROM nia_right_with_null) ORDER BY x;
+----
+
+# The null-aware anti join must be planned as a CollectLeft HashJoinExec even with
+# prefer_hash_join = false: SortMergeJoinExec is not null-aware and must not be used.
+query TT
+EXPLAIN SELECT x FROM nia_left WHERE x NOT IN (SELECT y FROM nia_right_with_null);
+----
+logical_plan
+01)LeftAnti Join: nia_left.x = __correlated_sq_1.y null_aware
+02)--TableScan: nia_left projection=[x]
+03)--SubqueryAlias: __correlated_sq_1
+04)----TableScan: nia_right_with_null projection=[y]
+physical_plan
+01)HashJoinExec: mode=CollectLeft, join_type=LeftAnti, on=[(x@0, y@0)], null_aware
+02)--DataSourceExec: partitions=1, partition_sizes=[1]
+03)--DataSourceExec: partitions=1, partition_sizes=[1]
+
+# Subquery has no NULL -> NOT IN behaves like a normal anti join.
+query I
+SELECT x FROM nia_left WHERE x NOT IN (SELECT y FROM nia_right_no_null) ORDER BY x;
+----
+1
+3
+
+statement ok
+DROP TABLE nia_left;
+
+statement ok
+DROP TABLE nia_right_with_null;
+
+statement ok
+DROP TABLE nia_right_no_null;
+
+statement ok
+reset datafusion.optimizer.prefer_hash_join;
From 3f816137306112f75b412e2806abd24fed0b916f Mon Sep 17 00:00:00 2001
From: linfeng <33561138+lyne7-sc@users.noreply.github.com>
Date: Fri, 24 Jul 2026 20:22:36 +0800
Subject: [PATCH 008/109] Remove `GroupsAccumulator::supports_convert_to_state`
and require `convert_to_state` (#23489)
## Which issue does this PR close?
- Closes #23081.
## Rationale for this change
Following #23275, all `GroupsAccumulator` implementations now provide
`convert_to_state`.
The `supports_convert_to_state` capability flag is therefore no longer
needed.
## What changes are included in this PR?
- Make `GroupsAccumulator::convert_to_state` a required trait method.
- Remove `GroupsAccumulator::supports_convert_to_state` and its
implementations.
- Remove the corresponding capability checks from hash aggregation.
- Simplify skip-partial aggregation to use the required
`convert_to_state` implementation directly.
- Add a regression test covering the partial hash aggregation skip path.
- Document the breaking trait change in the 55.0.0 upgrading guide.
- Remove `FFI_GroupsAccumulator::supports_convert_to_state`. This
changes the FFI ABI layout, so providers and consumers must be rebuilt
against DataFusion 55.
## Are these changes tested?
Yes. Added a regression test verifying that skip-partial aggregation
uses the required `convert_to_state` implementation without a capability
flag.
Existing physical-plan and FFI tests continue to pass.
## Are there any user-facing changes?
Yes. This is a breaking Rust API change for external `GroupsAccumulator`
implementations:
- `convert_to_state` must now be implemented.
- `supports_convert_to_state` should be removed.
The migration is documented in the 55.0.0 upgrading guide.
The `FFI_GroupsAccumulator` layout has changed. FFI providers and
consumers must be rebuilt against DataFusion 55 and must not exchange
this struct with older major versions.
---
.../examples/udf/advanced_udaf.rs | 5 ---
.../user_defined/user_defined_aggregates.rs | 5 ---
.../expr-common/src/groups_accumulator.rs | 16 +++------
datafusion/ffi/src/udaf/groups_accumulator.rs | 8 -----
.../src/aggregate/count_distinct/groups.rs | 5 ---
.../src/aggregate/groups_accumulator.rs | 4 ---
.../aggregate/groups_accumulator/bool_op.rs | 4 ---
.../aggregate/groups_accumulator/prim_op.rs | 5 ---
.../src/approx_distinct.rs | 5 ---
.../functions-aggregate/src/array_agg.rs | 5 ---
datafusion/functions-aggregate/src/average.rs | 5 ---
.../functions-aggregate/src/correlation.rs | 5 ---
datafusion/functions-aggregate/src/count.rs | 5 ---
.../functions-aggregate/src/first_last.rs | 5 ---
datafusion/functions-aggregate/src/median.rs | 5 ---
.../src/min_max/min_max_bytes.rs | 5 ---
.../src/min_max/min_max_struct.rs | 5 ---
.../src/percentile_cont.rs | 5 ---
datafusion/functions-aggregate/src/stddev.rs | 5 ---
.../functions-aggregate/src/string_agg.rs | 5 ---
.../functions-aggregate/src/variance.rs | 5 ---
.../aggregates/aggregate_hash_table/common.rs | 4 ---
.../aggregate_hash_table/partial_table.rs | 8 -----
.../src/aggregates/grouped_hash_stream.rs | 8 +----
.../src/aggregates/hash_stream.rs | 4 +--
.../physical-plan/src/aggregates/mod.rs | 16 +++++++++
.../spark/src/function/aggregate/avg.rs | 11 ------
.../library-user-guide/upgrading/55.0.0.md | 34 +++++++++++++++++++
28 files changed, 56 insertions(+), 146 deletions(-)
diff --git a/datafusion-examples/examples/udf/advanced_udaf.rs b/datafusion-examples/examples/udf/advanced_udaf.rs
index 096753d2b5d7b..bca4c7edab2c5 100644
--- a/datafusion-examples/examples/udf/advanced_udaf.rs
+++ b/datafusion-examples/examples/udf/advanced_udaf.rs
@@ -393,11 +393,6 @@ impl GroupsAccumulator for GeometricMeanGroupsAccumulator {
Arc::new(counts) as ArrayRef,
])
}
-
- fn supports_convert_to_state(&self) -> bool {
- true
- }
-
fn size(&self) -> usize {
self.counts.capacity() * size_of::()
+ self.prods.capacity() * size_of::()
diff --git a/datafusion/core/tests/user_defined/user_defined_aggregates.rs b/datafusion/core/tests/user_defined/user_defined_aggregates.rs
index 1d4b22230147f..323925bcfaf82 100644
--- a/datafusion/core/tests/user_defined/user_defined_aggregates.rs
+++ b/datafusion/core/tests/user_defined/user_defined_aggregates.rs
@@ -888,11 +888,6 @@ impl GroupsAccumulator for TestGroupsAccumulator {
as ArrayRef,
])
}
-
- fn supports_convert_to_state(&self) -> bool {
- true
- }
-
fn size(&self) -> usize {
size_of::()
}
diff --git a/datafusion/expr-common/src/groups_accumulator.rs b/datafusion/expr-common/src/groups_accumulator.rs
index 13b2f853c95dc..5c01418e04ce7 100644
--- a/datafusion/expr-common/src/groups_accumulator.rs
+++ b/datafusion/expr-common/src/groups_accumulator.rs
@@ -18,7 +18,7 @@
//! Vectorized [`GroupsAccumulator`]
use arrow::array::{ArrayRef, BooleanArray};
-use datafusion_common::{Result, not_impl_err, utils::split_vec_min_alloc};
+use datafusion_common::{Result, utils::split_vec_min_alloc};
/// Describes how many rows should be emitted during grouping.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -231,17 +231,9 @@ pub trait GroupsAccumulator: Send + std::any::Any {
/// [`Accumulator::state`]: crate::accumulator::Accumulator::state
fn convert_to_state(
&self,
- _values: &[ArrayRef],
- _opt_filter: Option<&BooleanArray>,
- ) -> Result> {
- not_impl_err!("Input batch conversion to state not implemented")
- }
-
- /// Returns `true` if [`Self::convert_to_state`] is implemented to support
- /// intermediate aggregate state conversion.
- fn supports_convert_to_state(&self) -> bool {
- false
- }
+ values: &[ArrayRef],
+ opt_filter: Option<&BooleanArray>,
+ ) -> Result>;
/// Amount of memory used to store the state of this accumulator,
/// in bytes.
diff --git a/datafusion/ffi/src/udaf/groups_accumulator.rs b/datafusion/ffi/src/udaf/groups_accumulator.rs
index 272afdb6abfb1..4d1b0b4be0a2b 100644
--- a/datafusion/ffi/src/udaf/groups_accumulator.rs
+++ b/datafusion/ffi/src/udaf/groups_accumulator.rs
@@ -73,8 +73,6 @@ pub struct FFI_GroupsAccumulator {
opt_filter: FFI_Option,
) -> FFI_Result>,
- pub supports_convert_to_state: bool,
-
/// Release the memory of the private data when it is no longer being used.
pub release: unsafe extern "C" fn(accumulator: &mut Self),
@@ -247,7 +245,6 @@ impl From> for FFI_GroupsAccumulator {
return accumulator.accumulator;
}
- let supports_convert_to_state = accumulator.supports_convert_to_state();
let private_data = GroupsAccumulatorPrivateData { accumulator };
Self {
@@ -257,7 +254,6 @@ impl From> for FFI_GroupsAccumulator {
state: state_fn_wrapper,
merge_batch: merge_batch_fn_wrapper,
convert_to_state: convert_to_state_fn_wrapper,
- supports_convert_to_state,
release: release_fn_wrapper,
private_data: Box::into_raw(Box::new(private_data)) as *mut c_void,
@@ -421,10 +417,6 @@ impl GroupsAccumulator for ForeignGroupsAccumulator {
.collect()
}
}
-
- fn supports_convert_to_state(&self) -> bool {
- self.accumulator.supports_convert_to_state
- }
}
#[repr(C)]
diff --git a/datafusion/functions-aggregate-common/src/aggregate/count_distinct/groups.rs b/datafusion/functions-aggregate-common/src/aggregate/count_distinct/groups.rs
index 986d4ec0d71ae..10aa21c3acad2 100644
--- a/datafusion/functions-aggregate-common/src/aggregate/count_distinct/groups.rs
+++ b/datafusion/functions-aggregate-common/src/aggregate/count_distinct/groups.rs
@@ -207,11 +207,6 @@ where
Ok(vec![Arc::new(builder.finish())])
}
-
- fn supports_convert_to_state(&self) -> bool {
- true
- }
-
fn size(&self) -> usize {
size_of::()
+ self.seen.capacity() * (size_of::<(usize, T::Native)>() + size_of::())
diff --git a/datafusion/functions-aggregate-common/src/aggregate/groups_accumulator.rs b/datafusion/functions-aggregate-common/src/aggregate/groups_accumulator.rs
index b412b4ffe09f2..b5610419166df 100644
--- a/datafusion/functions-aggregate-common/src/aggregate/groups_accumulator.rs
+++ b/datafusion/functions-aggregate-common/src/aggregate/groups_accumulator.rs
@@ -441,10 +441,6 @@ impl GroupsAccumulator for GroupsAccumulatorAdapter {
Ok(arrays)
}
-
- fn supports_convert_to_state(&self) -> bool {
- true
- }
}
/// Extension trait for [`Vec`] to account for allocations.
diff --git a/datafusion/functions-aggregate-common/src/aggregate/groups_accumulator/bool_op.rs b/datafusion/functions-aggregate-common/src/aggregate/groups_accumulator/bool_op.rs
index afb1dec24a484..77bb7598e2747 100644
--- a/datafusion/functions-aggregate-common/src/aggregate/groups_accumulator/bool_op.rs
+++ b/datafusion/functions-aggregate-common/src/aggregate/groups_accumulator/bool_op.rs
@@ -156,8 +156,4 @@ where
Ok(vec![Arc::new(values_filtered)])
}
-
- fn supports_convert_to_state(&self) -> bool {
- true
- }
}
diff --git a/datafusion/functions-aggregate-common/src/aggregate/groups_accumulator/prim_op.rs b/datafusion/functions-aggregate-common/src/aggregate/groups_accumulator/prim_op.rs
index 474899d8f3c6a..c5d74978664c9 100644
--- a/datafusion/functions-aggregate-common/src/aggregate/groups_accumulator/prim_op.rs
+++ b/datafusion/functions-aggregate-common/src/aggregate/groups_accumulator/prim_op.rs
@@ -189,11 +189,6 @@ where
Ok(vec![Arc::new(state_values)])
}
-
- fn supports_convert_to_state(&self) -> bool {
- true
- }
-
fn size(&self) -> usize {
self.values.capacity() * size_of::() + self.null_state.size()
}
diff --git a/datafusion/functions-aggregate/src/approx_distinct.rs b/datafusion/functions-aggregate/src/approx_distinct.rs
index f36c658e7f385..1746edd8239f2 100644
--- a/datafusion/functions-aggregate/src/approx_distinct.rs
+++ b/datafusion/functions-aggregate/src/approx_distinct.rs
@@ -615,11 +615,6 @@ impl GroupsAccumulator for HllGroupsAccumulator {
Ok(vec![Arc::new(builder.finish())])
}
-
- fn supports_convert_to_state(&self) -> bool {
- true
- }
-
fn size(&self) -> usize {
self.groups.capacity() * size_of::()
+ self.allocated_bytes
diff --git a/datafusion/functions-aggregate/src/array_agg.rs b/datafusion/functions-aggregate/src/array_agg.rs
index 1937f17973950..b563a6389ec7e 100644
--- a/datafusion/functions-aggregate/src/array_agg.rs
+++ b/datafusion/functions-aggregate/src/array_agg.rs
@@ -793,11 +793,6 @@ impl GroupsAccumulator for ArrayAggGroupsAccumulator {
Ok(vec![Arc::new(list_array)])
}
-
- fn supports_convert_to_state(&self) -> bool {
- true
- }
-
fn size(&self) -> usize {
self.batches
.iter()
diff --git a/datafusion/functions-aggregate/src/average.rs b/datafusion/functions-aggregate/src/average.rs
index f1159f22b2de0..e5030bf39e409 100644
--- a/datafusion/functions-aggregate/src/average.rs
+++ b/datafusion/functions-aggregate/src/average.rs
@@ -1133,11 +1133,6 @@ where
Ok(vec![Arc::new(counts) as ArrayRef, Arc::new(sums)])
}
-
- fn supports_convert_to_state(&self) -> bool {
- true
- }
-
fn size(&self) -> usize {
// Heap buffers
self.counts.capacity() * size_of::()
diff --git a/datafusion/functions-aggregate/src/correlation.rs b/datafusion/functions-aggregate/src/correlation.rs
index 2e90cac6d9298..b9bc57dfa989c 100644
--- a/datafusion/functions-aggregate/src/correlation.rs
+++ b/datafusion/functions-aggregate/src/correlation.rs
@@ -539,11 +539,6 @@ impl GroupsAccumulator for CorrelationGroupsAccumulator {
Arc::new(Float64Array::from(sum_yy)),
])
}
-
- fn supports_convert_to_state(&self) -> bool {
- true
- }
-
fn merge_batch(
&mut self,
values: &[ArrayRef],
diff --git a/datafusion/functions-aggregate/src/count.rs b/datafusion/functions-aggregate/src/count.rs
index 983828ea90b7c..f0de9d9848627 100644
--- a/datafusion/functions-aggregate/src/count.rs
+++ b/datafusion/functions-aggregate/src/count.rs
@@ -773,11 +773,6 @@ impl GroupsAccumulator for CountGroupsAccumulator {
Ok(vec![state_array])
}
-
- fn supports_convert_to_state(&self) -> bool {
- true
- }
-
fn size(&self) -> usize {
self.counts.capacity() * size_of::()
}
diff --git a/datafusion/functions-aggregate/src/first_last.rs b/datafusion/functions-aggregate/src/first_last.rs
index 7c2540aa03a2b..e2da7ec753aa5 100644
--- a/datafusion/functions-aggregate/src/first_last.rs
+++ b/datafusion/functions-aggregate/src/first_last.rs
@@ -719,11 +719,6 @@ impl GroupsAccumulator for FirstLastGroupsAccumulator()
+ self.extreme_of_each_group_buf.1.capacity() / 8
}
-
- fn supports_convert_to_state(&self) -> bool {
- true
- }
-
fn convert_to_state(
&self,
values: &[ArrayRef],
diff --git a/datafusion/functions-aggregate/src/median.rs b/datafusion/functions-aggregate/src/median.rs
index 9a6ef3e7e5fc5..7a399f73ec8e2 100644
--- a/datafusion/functions-aggregate/src/median.rs
+++ b/datafusion/functions-aggregate/src/median.rs
@@ -535,11 +535,6 @@ impl GroupsAccumulator for MedianGroupsAccumulator bool {
- true
- }
-
fn size(&self) -> usize {
self.group_values
.iter()
diff --git a/datafusion/functions-aggregate/src/min_max/min_max_bytes.rs b/datafusion/functions-aggregate/src/min_max/min_max_bytes.rs
index 7a3c605d82e4d..efeaea314c4f5 100644
--- a/datafusion/functions-aggregate/src/min_max/min_max_bytes.rs
+++ b/datafusion/functions-aggregate/src/min_max/min_max_bytes.rs
@@ -325,11 +325,6 @@ impl GroupsAccumulator for MinMaxBytesAccumulator {
let output = apply_filter_as_nulls(&values[0], opt_filter)?;
Ok(vec![output])
}
-
- fn supports_convert_to_state(&self) -> bool {
- true
- }
-
fn size(&self) -> usize {
self.inner.size()
}
diff --git a/datafusion/functions-aggregate/src/min_max/min_max_struct.rs b/datafusion/functions-aggregate/src/min_max/min_max_struct.rs
index 15df0f1d44eff..d1bac4e2f90db 100644
--- a/datafusion/functions-aggregate/src/min_max/min_max_struct.rs
+++ b/datafusion/functions-aggregate/src/min_max/min_max_struct.rs
@@ -150,11 +150,6 @@ impl GroupsAccumulator for MinMaxStructAccumulator {
let output = apply_filter_as_nulls(&values[0], opt_filter)?;
Ok(vec![output])
}
-
- fn supports_convert_to_state(&self) -> bool {
- true
- }
-
fn size(&self) -> usize {
self.inner.size()
}
diff --git a/datafusion/functions-aggregate/src/percentile_cont.rs b/datafusion/functions-aggregate/src/percentile_cont.rs
index e8e6fd127e65d..cfab1303028ad 100644
--- a/datafusion/functions-aggregate/src/percentile_cont.rs
+++ b/datafusion/functions-aggregate/src/percentile_cont.rs
@@ -652,11 +652,6 @@ where
Ok(vec![Arc::new(converted_list_array)])
}
-
- fn supports_convert_to_state(&self) -> bool {
- true
- }
-
fn size(&self) -> usize {
self.group_values
.iter()
diff --git a/datafusion/functions-aggregate/src/stddev.rs b/datafusion/functions-aggregate/src/stddev.rs
index a31517b93e003..15511bf4a565f 100644
--- a/datafusion/functions-aggregate/src/stddev.rs
+++ b/datafusion/functions-aggregate/src/stddev.rs
@@ -352,11 +352,6 @@ impl GroupsAccumulator for StddevGroupsAccumulator {
) -> Result> {
self.variance.convert_to_state(values, opt_filter)
}
-
- fn supports_convert_to_state(&self) -> bool {
- true
- }
-
fn size(&self) -> usize {
self.variance.size()
}
diff --git a/datafusion/functions-aggregate/src/string_agg.rs b/datafusion/functions-aggregate/src/string_agg.rs
index 6b0665f479d78..3fe2b0a186ae3 100644
--- a/datafusion/functions-aggregate/src/string_agg.rs
+++ b/datafusion/functions-aggregate/src/string_agg.rs
@@ -432,11 +432,6 @@ impl GroupsAccumulator for StringAggGroupsAccumulator {
};
Ok(vec![result])
}
-
- fn supports_convert_to_state(&self) -> bool {
- true
- }
-
fn size(&self) -> usize {
self.total_data_bytes
+ self.values.capacity() * size_of::>()
diff --git a/datafusion/functions-aggregate/src/variance.rs b/datafusion/functions-aggregate/src/variance.rs
index 0278ce2c233e4..df652731ff4f4 100644
--- a/datafusion/functions-aggregate/src/variance.rs
+++ b/datafusion/functions-aggregate/src/variance.rs
@@ -613,11 +613,6 @@ impl GroupsAccumulator for VarianceGroupsAccumulator {
Arc::new(Float64Array::new(m2s.into(), None)),
])
}
-
- fn supports_convert_to_state(&self) -> bool {
- true
- }
-
fn size(&self) -> usize {
self.m2s.capacity() * size_of::()
+ self.means.capacity() * size_of::()
diff --git a/datafusion/physical-plan/src/aggregates/aggregate_hash_table/common.rs b/datafusion/physical-plan/src/aggregates/aggregate_hash_table/common.rs
index eaf39929ced62..42014f336f3d8 100644
--- a/datafusion/physical-plan/src/aggregates/aggregate_hash_table/common.rs
+++ b/datafusion/physical-plan/src/aggregates/aggregate_hash_table/common.rs
@@ -569,10 +569,6 @@ impl HashAggregateAccumulator {
self.accumulator.state(emit_to)
}
- pub(super) fn supports_convert_to_state(&self) -> bool {
- self.accumulator.supports_convert_to_state()
- }
-
pub(super) fn convert_to_state(
&mut self,
values: &EvaluatedAccumulatorArgs,
diff --git a/datafusion/physical-plan/src/aggregates/aggregate_hash_table/partial_table.rs b/datafusion/physical-plan/src/aggregates/aggregate_hash_table/partial_table.rs
index ffac42feaa3b3..4bcacb49afb04 100644
--- a/datafusion/physical-plan/src/aggregates/aggregate_hash_table/partial_table.rs
+++ b/datafusion/physical-plan/src/aggregates/aggregate_hash_table/partial_table.rs
@@ -68,14 +68,6 @@ impl AggregateHashTable {
self.next_output_batch_inner(HashAggregateAccumulator::state)
}
- pub(in crate::aggregates) fn can_skip_aggregation(&self) -> bool {
- self.state
- .building()
- .accumulators
- .iter()
- .all(|acc| acc.supports_convert_to_state())
- }
-
/// In skip-partial-aggregation optimization, when a decision has been made to skip
/// partial stage, build a typed hash table only for aggregation state conversion
/// row-by-row.
diff --git a/datafusion/physical-plan/src/aggregates/grouped_hash_stream.rs b/datafusion/physical-plan/src/aggregates/grouped_hash_stream.rs
index 0d00e5c4d0d86..99c101199459f 100644
--- a/datafusion/physical-plan/src/aggregates/grouped_hash_stream.rs
+++ b/datafusion/physical-plan/src/aggregates/grouped_hash_stream.rs
@@ -217,8 +217,7 @@ enum OutOfMemoryMode {
/// aggregator must store the intermediate state for each group.
///
/// If the ratio of the number of groups to the number of input rows exceeds a
-/// threshold, and [`GroupsAccumulator::supports_convert_to_state`] is
-/// supported, this operator will stop applying Partial aggregation and directly
+/// threshold, this operator will stop applying Partial aggregation and directly
/// pass the input rows to the next aggregation phase.
///
/// [`Accumulator::state`]: datafusion_expr::Accumulator::state
@@ -545,14 +544,9 @@ impl GroupedHashAggregateStream {
// - aggregation mode is Partial
// - input is not ordered by GROUP BY expressions,
// since Final mode expects unique group values as its input
- // - all accumulators support input batch to intermediate
- // aggregate state conversion
// - there is only one GROUP BY expressions set
let skip_aggregation_probe = if agg.mode == AggregateMode::Partial
&& matches!(group_ordering, GroupOrdering::None)
- && accumulators
- .iter()
- .all(|acc| acc.supports_convert_to_state())
&& agg_group_by.is_single()
{
let options = &context.session_config().options().execution;
diff --git a/datafusion/physical-plan/src/aggregates/hash_stream.rs b/datafusion/physical-plan/src/aggregates/hash_stream.rs
index 62b92965030ae..e7f0f075b33a5 100644
--- a/datafusion/physical-plan/src/aggregates/hash_stream.rs
+++ b/datafusion/physical-plan/src/aggregates/hash_stream.rs
@@ -293,9 +293,7 @@ impl PartialHashAggregateStream {
Arc::clone(&schema),
batch_size,
)?;
- let can_skip_aggregation =
- agg.group_by.is_single() && hash_table.can_skip_aggregation();
- let skip_aggregation_probe = if can_skip_aggregation {
+ let skip_aggregation_probe = if agg.group_by.is_single() {
let options = &context.session_config().options().execution;
let probe_ratio_threshold =
options.skip_partial_aggregation_probe_ratio_threshold;
diff --git a/datafusion/physical-plan/src/aggregates/mod.rs b/datafusion/physical-plan/src/aggregates/mod.rs
index 7d93cd739815d..e3cf1c4568009 100644
--- a/datafusion/physical-plan/src/aggregates/mod.rs
+++ b/datafusion/physical-plan/src/aggregates/mod.rs
@@ -7132,6 +7132,22 @@ mod tests {
Ok(vec![self.emit_counts(emit_to)?])
}
+ fn convert_to_state(
+ &self,
+ values: &[ArrayRef],
+ opt_filter: Option<&BooleanArray>,
+ ) -> Result> {
+ assert_eq!(values.len(), 1, "one argument to convert_to_state");
+ let counts = match opt_filter {
+ Some(filter) => filter
+ .iter()
+ .map(|value| i64::from(value.unwrap_or(false)))
+ .collect::>(),
+ None => vec![1; values[0].len()],
+ };
+ Ok(vec![Arc::new(Int64Array::from(counts))])
+ }
+
fn merge_batch(
&mut self,
_values: &[ArrayRef],
diff --git a/datafusion/spark/src/function/aggregate/avg.rs b/datafusion/spark/src/function/aggregate/avg.rs
index 6ca3c59309e70..46e63013dbafb 100644
--- a/datafusion/spark/src/function/aggregate/avg.rs
+++ b/datafusion/spark/src/function/aggregate/avg.rs
@@ -367,11 +367,6 @@ where
Arc::new(counts) as ArrayRef,
])
}
-
- fn supports_convert_to_state(&self) -> bool {
- true
- }
-
fn size(&self) -> usize {
self.counts.capacity() * size_of::() + self.sums.capacity() * size_of::()
}
@@ -387,12 +382,6 @@ mod tests {
Ok(sum / count as f64)
})
}
-
- #[test]
- fn supports_convert_to_state() {
- assert!(make_acc().supports_convert_to_state());
- }
-
#[test]
fn convert_to_state_basic() {
let acc = make_acc();
diff --git a/docs/source/library-user-guide/upgrading/55.0.0.md b/docs/source/library-user-guide/upgrading/55.0.0.md
index 57da7b7dac248..5f1609af30fc6 100644
--- a/docs/source/library-user-guide/upgrading/55.0.0.md
+++ b/docs/source/library-user-guide/upgrading/55.0.0.md
@@ -276,6 +276,40 @@ it was `None`), that code can simply be deleted.
See [issue #22775](https://github.com/apache/datafusion/issues/22775) for details.
+### `GroupsAccumulator::convert_to_state` is now required
+
+`datafusion_expr_common::groups_accumulator::GroupsAccumulator::convert_to_state`
+no longer provides a default implementation, and the
+`GroupsAccumulator::supports_convert_to_state` capability method has been
+removed. All `GroupsAccumulator` implementations must now support converting
+input batches directly to intermediate aggregate state.
+
+**Who is affected:**
+
+- Users with custom `GroupsAccumulator` implementations.
+- FFI providers and consumers that use `FFI_GroupsAccumulator`.
+
+**Migration guide:**
+
+Custom `GroupsAccumulator` implementations must now provide their own
+`convert_to_state` implementation.
+
+Delete `supports_convert_to_state` implementations because `convert_to_state`
+is now required:
+
+```diff
+- fn supports_convert_to_state(&self) -> bool {
+- true
+- }
+```
+
+The `supports_convert_to_state` field has also been removed from
+`datafusion_ffi::udaf::groups_accumulator::FFI_GroupsAccumulator`, changing its
+ABI layout. Rebuild both FFI providers and consumers against DataFusion 55, and
+do not exchange this struct with libraries built against older major versions.
+
+See [issue #23081](https://github.com/apache/datafusion/issues/23081) for details.
+
### `is_dynamic_physical_expr` is deprecated
`datafusion_physical_expr_common::physical_expr::is_dynamic_physical_expr` is
From 7d0ca3e8f81d7263d382279bf56c37d398340e42 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Fri, 24 Jul 2026 07:58:35 -0700
Subject: [PATCH 009/109] chore(deps-dev): bump ws from 8.18.2 to 8.21.1 in
/datafusion/wasmtest/datafusion-wasm-app (#23866)
Bumps [ws](https://github.com/websockets/ws) from 8.18.2 to 8.21.1.
Release notes
Sourced from ws's
releases .
8.21.1
Bug fixes
Empty fragments are now counted toward the limit (a2f4e7c0).
The default values of the maxBufferedChunks and
maxFragments options have
been reduced (f197ac65).
8.21.0
Features
Introduced the maxBufferedChunks and
maxFragments options (2b2abd45).
Bug fixes
Fixed a remote memory exhaustion DoS vulnerability (2b2abd45).
A high volume of tiny fragments and data chunks could be sent by a
peer, using
modest network traffic, to crash a ws server or client due
to OOM.
import { WebSocket, WebSocketServer } from 'ws';
const wss = new WebSocketServer({ port: 0 }, function () {
const data = Buffer.alloc(1);
const options = { fin: false };
const { port } = wss.address();
const ws = new WebSocket(ws://localhost:${port});
ws.on('open', function () {
(function send() {
ws.send(data, options, function (err) {
if (err) return;
send();
});
})();
});
ws.on('error', console.error);
ws.on('close', function (code, reason) {
console.log(client close - code: ${code} reason:
${reason.toString()});
});
});
wss.on('connection', function (ws) {
ws.on('error', console.error);
ws.on('close', function (code, reason) {
console.log(server close - code: ${code} reason:
${reason.toString()});
});
});
... (truncated)
Commits
ae1de54
[dist] 8.21.1
8e9511b
[ci] Trust Coveralls Homebrew tap
f197ac6
[fix] Lower default values of maxBufferedChunks and
maxFragments
8df8265
[ci] Update actions/checkout action to v7
a2f4e7c
[fix] Count empty fragments toward the limit (#2329 )
e79f912
[pkg] Approve install scripts for bufferutil and utf-8-validate
4ea355d
[doc] Document 32-bit signed integer coercion for option values
2120f4c
[example] Remove uuid dependency
4c534a6
[security] Add latest vulnerability to SECURITY.md
bca91ad
[dist] 8.21.0
Additional commits viewable in compare
view
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/apache/datafusion/network/alerts).
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
.../wasmtest/datafusion-wasm-app/package-lock.json | 13 ++++++-------
1 file changed, 6 insertions(+), 7 deletions(-)
diff --git a/datafusion/wasmtest/datafusion-wasm-app/package-lock.json b/datafusion/wasmtest/datafusion-wasm-app/package-lock.json
index b2a72228e8115..7f51bac7a1c59 100644
--- a/datafusion/wasmtest/datafusion-wasm-app/package-lock.json
+++ b/datafusion/wasmtest/datafusion-wasm-app/package-lock.json
@@ -4158,11 +4158,10 @@
"dev": true
},
"node_modules/ws": {
- "version": "8.18.2",
- "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz",
- "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==",
+ "version": "8.21.1",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.1.tgz",
+ "integrity": "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==",
"dev": true,
- "license": "MIT",
"engines": {
"node": ">=10.0.0"
},
@@ -7225,9 +7224,9 @@
"dev": true
},
"ws": {
- "version": "8.18.2",
- "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz",
- "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==",
+ "version": "8.21.1",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.1.tgz",
+ "integrity": "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==",
"dev": true,
"requires": {}
}
From 0a8eacf1fb93523aea71707ae77bb290801d8040 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Fri, 24 Jul 2026 07:58:54 -0700
Subject: [PATCH 010/109] chore(deps-dev): bump http-proxy-middleware from
2.0.9 to 2.0.10 in /datafusion/wasmtest/datafusion-wasm-app (#23865)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Bumps
[http-proxy-middleware](https://github.com/chimurai/http-proxy-middleware)
from 2.0.9 to 2.0.10.
Release notes
Sourced from http-proxy-middleware's
releases .
v2.0.10-beta.0
What's Changed
New Contributors
Full Changelog : https://github.com/chimurai/http-proxy-middleware/compare/v2.0.9...v2.0.10-beta.0
Changelog
Sourced from http-proxy-middleware's
changelog .
fix(router): harden proxy-table matching (exact host for host+path
keys, prefix-only path matching) to prevent routing bypass
Commits
Maintainer changes
This version was pushed to npm by GitHub Actions , a new
releaser for http-proxy-middleware since your current version.
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/apache/datafusion/network/alerts).
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
.../wasmtest/datafusion-wasm-app/package-lock.json | 13 ++++++-------
1 file changed, 6 insertions(+), 7 deletions(-)
diff --git a/datafusion/wasmtest/datafusion-wasm-app/package-lock.json b/datafusion/wasmtest/datafusion-wasm-app/package-lock.json
index 7f51bac7a1c59..e863fe5e8da15 100644
--- a/datafusion/wasmtest/datafusion-wasm-app/package-lock.json
+++ b/datafusion/wasmtest/datafusion-wasm-app/package-lock.json
@@ -2142,11 +2142,10 @@
}
},
"node_modules/http-proxy-middleware": {
- "version": "2.0.9",
- "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz",
- "integrity": "sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==",
+ "version": "2.0.10",
+ "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.10.tgz",
+ "integrity": "sha512-RKzRWNPxUZqbuk3BC5mGVJbBnWgr+diEnjJexIOytFbBzDy88Fbh/YvBr3DsNrl1jYAfjWfpATEv0NO35FDuPQ==",
"dev": true,
- "license": "MIT",
"dependencies": {
"@types/http-proxy": "^1.17.8",
"http-proxy": "^1.18.1",
@@ -5839,9 +5838,9 @@
}
},
"http-proxy-middleware": {
- "version": "2.0.9",
- "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz",
- "integrity": "sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==",
+ "version": "2.0.10",
+ "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.10.tgz",
+ "integrity": "sha512-RKzRWNPxUZqbuk3BC5mGVJbBnWgr+diEnjJexIOytFbBzDy88Fbh/YvBr3DsNrl1jYAfjWfpATEv0NO35FDuPQ==",
"dev": true,
"requires": {
"@types/http-proxy": "^1.17.8",
From 592eeab9cca76fc84fc12a4c03e46967eaa895e2 Mon Sep 17 00:00:00 2001
From: Jeffrey Vo
Date: Fri, 24 Jul 2026 23:59:51 +0900
Subject: [PATCH 011/109] Add codecov badge to README (#23860)
add badge; easier to see at a glance what the coverage is, and also
easier to access our codecov page
---
README.md | 3 +++
1 file changed, 3 insertions(+)
diff --git a/README.md b/README.md
index dfffcbddfaae7..73c4409ef9b54 100644
--- a/README.md
+++ b/README.md
@@ -28,6 +28,7 @@
[![Discord chat][discord-badge]][discord-url]
[![Linkedin][linkedin-badge]][linkedin-url]
![Crates.io MSRV][msrv-badge]
+[![Codecov][codecov-badge]][codecov-url]
[crates-badge]: https://img.shields.io/crates/v/datafusion.svg
[crates-url]: https://crates.io/crates/datafusion
@@ -45,6 +46,8 @@
[linkedin-badge]: https://img.shields.io/badge/Follow-Linkedin-blue
[linkedin-url]: https://www.linkedin.com/company/apache-datafusion/
[msrv-badge]: https://img.shields.io/crates/msrv/datafusion?label=Min%20Rust%20Version
+[codecov-badge]: https://codecov.io/github/apache/datafusion/graph/badge.svg
+[codecov-url]: https://app.codecov.io/github/apache/datafusion/tree/main
[Website](https://datafusion.apache.org/) |
[API Docs](https://docs.rs/datafusion/latest/datafusion/) |
From 582453b680e7a9b927d659753edb8f33a444a3c8 Mon Sep 17 00:00:00 2001
From: Matthew Kim <38759997+friendlymatthew@users.noreply.github.com>
Date: Fri, 24 Jul 2026 11:15:05 -0400
Subject: [PATCH 012/109] fix: align physical CASE nullability through casts
(#23844)
## Rationale for this change
Logical `CASE` nullability unwraps null preserving casts before
analyzing guarded branches, but physical `CASE` nullability did not.
Type coercion could therefore produce conflicting schemas and cause
valid aggregation queries to fail during planning
---------
Co-authored-by: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8
---
.../physical-expr/src/expressions/case.rs | 81 ++++++++++++++++++-
datafusion/sqllogictest/test_files/case.slt | 13 +++
2 files changed, 92 insertions(+), 2 deletions(-)
diff --git a/datafusion/physical-expr/src/expressions/case.rs b/datafusion/physical-expr/src/expressions/case.rs
index 8a0f15467c47b..17288a9737699 100644
--- a/datafusion/physical-expr/src/expressions/case.rs
+++ b/datafusion/physical-expr/src/expressions/case.rs
@@ -19,7 +19,9 @@ mod literal_lookup_table;
use super::{Column, Literal};
use crate::PhysicalExpr;
-use crate::expressions::{LambdaVariable, lit, try_cast};
+use crate::expressions::{
+ CastExpr, LambdaVariable, NegativeExpr, NotExpr, lit, try_cast,
+};
use arrow::array::*;
use arrow::compute::kernels::zip::zip;
use arrow::compute::{
@@ -1278,7 +1280,11 @@ impl PhysicalExpr for CaseExpr {
// it would evaluate to null.
// Replace the `then` expression with `NULL` in the `when` expression
- let with_null = match replace_with_null(w, t.as_ref(), input_schema) {
+ let with_null = match replace_with_null(
+ w,
+ unwrap_certainly_null_expr(t.as_ref()),
+ input_schema,
+ ) {
Err(e) => return Some(Err(e)),
Ok(e) => e,
};
@@ -1537,6 +1543,25 @@ fn replace_with_null(
Ok(with_null)
}
+/// Returns the innermost [`PhysicalExpr`] that is provably null if `expr` is null.
+///
+/// Keep this in sync with the logical-plan equivalent, `unwrap_certainly_null_expr`
+/// in `datafusion/expr/src/expr_schema.rs`. If the two disagree on which wrappers
+/// are null-preserving, `CASE` nullability computed by the logical and physical
+/// planners can diverge and cause a schema mismatch during planning.
+/// See for rationale.
+fn unwrap_certainly_null_expr(expr: &dyn PhysicalExpr) -> &dyn PhysicalExpr {
+ if let Some(expr) = expr.downcast_ref::() {
+ unwrap_certainly_null_expr(expr.arg().as_ref())
+ } else if let Some(expr) = expr.downcast_ref::() {
+ unwrap_certainly_null_expr(expr.arg().as_ref())
+ } else if let Some(expr) = expr.downcast_ref::() {
+ unwrap_certainly_null_expr(expr.expr.as_ref())
+ } else {
+ expr
+ }
+}
+
/// Create a CASE expression
pub fn case(
expr: Option>,
@@ -2577,10 +2602,45 @@ mod tests {
let zero = lit(0);
let foo_eq_zero =
binary(Arc::clone(&foo), Operator::Eq, Arc::clone(&zero), &schema)?;
+ let cast_foo = cast(Arc::clone(&foo), &schema, DataType::Int64)?;
+ let negative_foo = expressions::negative(Arc::clone(&foo), &schema)?;
assert_not_nullable(when_then_else(&foo_is_not_null, &foo, &zero)?, &schema);
assert_not_nullable(when_then_else(¬_foo_is_null, &foo, &zero)?, &schema);
assert_not_nullable(when_then_else(&foo_eq_zero, &foo, &zero)?, &schema);
+ assert_not_nullable(
+ when_then_else(&foo_is_not_null, &cast_foo, &lit(0i64))?,
+ &schema,
+ );
+ assert_not_nullable(
+ when_then_else(&foo_is_not_null, &negative_foo, &zero)?,
+ &schema,
+ );
+
+ // Nested null-preserving wrappers must be unwrapped recursively. `CAST(-foo)`
+ // still collapses `foo IS NOT NULL` to `false`, so the branch is
+ // unreachable-as-null and the `CASE` is not nullable.
+ let cast_negative_foo = cast(
+ expressions::negative(Arc::clone(&foo), &schema)?,
+ &schema,
+ DataType::Int64,
+ )?;
+ assert_not_nullable(
+ when_then_else(&foo_is_not_null, &cast_negative_foo, &lit(0i64))?,
+ &schema,
+ );
+
+ // `TRY_CAST` is intentionally NOT treated as null-preserving: it yields
+ // NULL on a failed cast even for a non-null input, so a guarded `TRY_CAST`
+ // branch is still reachable-as-null and the `CASE` stays nullable. This must
+ // stay consistent with the logical planner (`unwrap_certainly_null_expr` in
+ // `datafusion/expr/src/expr_schema.rs`); unwrapping it on only one side would
+ // reintroduce a logical/physical schema mismatch.
+ let try_cast_foo = try_cast(Arc::clone(&foo), &schema, DataType::Int64)?;
+ assert_nullable(
+ when_then_else(&foo_is_not_null, &try_cast_foo, &lit(0i64))?,
+ &schema,
+ );
assert_not_nullable(
when_then_else(
@@ -2702,6 +2762,23 @@ mod tests {
&schema,
);
+ let boolean_schema =
+ Schema::new(vec![Field::new("predicate", DataType::Boolean, true)]);
+ let predicate = col("predicate", &boolean_schema)?;
+ let predicate_is_not_null = is_not_null(Arc::clone(&predicate))?;
+ let not_predicate = expressions::not(Arc::clone(&predicate))?;
+ assert_not_nullable(
+ when_then_else(&predicate_is_not_null, ¬_predicate, &lit(false))?,
+ &boolean_schema,
+ );
+
+ // Nested `NOT` is likewise unwrapped recursively.
+ let not_not_predicate = expressions::not(Arc::clone(¬_predicate))?;
+ assert_not_nullable(
+ when_then_else(&predicate_is_not_null, ¬_not_predicate, &lit(false))?,
+ &boolean_schema,
+ );
+
Ok(())
}
diff --git a/datafusion/sqllogictest/test_files/case.slt b/datafusion/sqllogictest/test_files/case.slt
index 3953878ceb666..f7ae380242942 100644
--- a/datafusion/sqllogictest/test_files/case.slt
+++ b/datafusion/sqllogictest/test_files/case.slt
@@ -41,6 +41,19 @@ NULL
6
7
+# CASE nullability remains consistent through type coercion
+query I
+SELECT count(endpoint)
+FROM (
+ SELECT CASE
+ WHEN a IS NOT NULL THEN CAST(a AS BIGINT)
+ ELSE CAST(0 AS BIGINT)
+ END AS endpoint
+ FROM foo
+)
+----
+6
+
# column or explicit null
query I
SELECT CASE WHEN a > 2 THEN b ELSE null END FROM foo
From efa84e8309ced6614e62d1607899c47ed0b6b08a Mon Sep 17 00:00:00 2001
From: Andrew Lamb
Date: Fri, 24 Jul 2026 12:22:52 -0400
Subject: [PATCH 013/109] test: add functional_dependencies.slt covering
functional dependency driven optimizations (#23821)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## Which issue does this PR close?
This PR adds test coverage rather than closing an issue. It documents
the current behavior of these bugs so that fixing them shows up as a
test change:
- https://github.com/apache/datafusion/issues/23634
- https://github.com/apache/datafusion/issues/23818
- https://github.com/apache/datafusion/issues/23819
- https://github.com/apache/datafusion/issues/23820
## Rationale for this change
While reviewing #23636 @neilconway and I kept coming up with more
examples of bad plans, and it was not obvious which of the surrounding
wrong answers were pre-existing and which the PR introduced.
## What changes are included in this PR?
A new `datafusion/sqllogictest/test_files/functional_dependencies.slt`
with one section per consumer of functional dependencies:
1. `ReplaceDistinctWithAggregate` — removing `DISTINCT`
2. `eliminate_duplicated_expr` — dropping trailing `ORDER BY` keys
3. `optimize_projections` — dropping `GROUP BY` expressions
4. `add_group_by_exprs_from_dependencies` — selecting non-grouped
columns
5. `GROUP BY` derived keys on the NULL-padded side of an outer join
Cases that currently return wrong answers are labelled `BUG` with the
expected
result and a link to the issue:
| Case | Symptom | Issue |
| --- | --- | --- |
| 1.2 | `DISTINCT` over a nullable `UNIQUE` column returns both `NULL`
rows | #23634 |
| 2.2 | `ORDER BY x, y` drops the `y` key, so the `NULL` rows come back
unordered | #23818 |
| 3.2 | `GROUP BY x, y` drops `y`, merging the two `NULL` groups and
losing a row | #23819 |
| 4.2 | `SELECT x, y ... GROUP BY x` returns two rows for the `x = NULL`
group | #23820 |
## Are these changes tested?
CI
## Are there any user-facing changes?
No.
---------
Co-authored-by: Claude Opus 4.8 (1M context)
---
.../test_files/functional_dependencies.slt | 314 ++++++++++++++++++
1 file changed, 314 insertions(+)
create mode 100644 datafusion/sqllogictest/test_files/functional_dependencies.slt
diff --git a/datafusion/sqllogictest/test_files/functional_dependencies.slt b/datafusion/sqllogictest/test_files/functional_dependencies.slt
new file mode 100644
index 0000000000000..92aedf66e69e1
--- /dev/null
+++ b/datafusion/sqllogictest/test_files/functional_dependencies.slt
@@ -0,0 +1,314 @@
+# 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.
+
+##########
+# Tests for functional dependencies
+# (`datafusion/common/src/functional_dependencies.rs`)
+#
+# A functional dependency records that one set of columns (the *determinant*)
+# determines the values of the others. DataFusion derives them from PRIMARY
+# KEY / UNIQUE constraints and from GROUP BY keys, and four optimizer rules
+# consume them to remove redundant work, each tested here in a different section.
+#
+# NULL handling is (as always) important:
+#
+# * A PRIMARY KEY is unique AND not nullable.
+# * A `UNIQUE` constraint permits *multiple NULL rows*, because NULLs
+# compare distinct.
+#
+# It is important not to mix `UNIQUE` columns with `DISTINCT` or `GROUP BY`,
+# which treat NULLs as equal and can produce wrong answers.
+##########
+
+# These rules all run during logical optimization, so show only logical plans.
+statement ok
+set datafusion.explain.logical_plan_only = true;
+
+# Set target_partitions explicitly so query results are stable.
+statement ok
+set datafusion.execution.target_partitions = 4;
+
+##########
+## Test tables
+##########
+
+statement ok
+CREATE TABLE t_pk (x INT, y INT, PRIMARY KEY (x)) AS VALUES (1, 10), (2, 20);
+
+statement ok
+CREATE TABLE t_uniq (x INT UNIQUE, y INT) AS VALUES (NULL, 2), (NULL, 1), (1, 3);
+
+query II rowsort
+SELECT x, y FROM t_uniq;
+----
+1 3
+NULL 1
+NULL 2
+
+
+# 1.1 PRIMARY KEY: rows are unique; the DISTINCT is removed and no
+# Aggregate appears in the plan.
+query TT
+EXPLAIN SELECT DISTINCT x FROM t_pk;
+----
+logical_plan TableScan: t_pk projection=[x]
+
+# 1.2 Nullable UNIQUE: the DISTINCT must be KEPT. UNIQUE allows several NULL
+# rows, but DISTINCT treats NULLs as equal and has to collapse them into one.
+#
+# BUG: the DISTINCT is removed and both NULL rows are returned.
+# Expected: `1`, `NULL`.
+# Issue: https://github.com/apache/datafusion/issues/23634
+query I
+SELECT DISTINCT x FROM t_uniq ORDER BY x NULLS LAST;
+----
+1
+NULL
+NULL
+
+query TT
+EXPLAIN SELECT DISTINCT x FROM t_uniq;
+----
+logical_plan TableScan: t_uniq projection=[x]
+
+# 1.3 A PRIMARY KEY downgraded to a non-unique dependency by a LEFT JOIN
+# so the DISTINCT must be KEPT.
+# Fixed by: https://github.com/apache/datafusion/pull/23548
+statement ok
+CREATE TABLE t_orders (x INT, amount INT) AS VALUES (1, 10), (1, 20), (2, 30);
+
+query I
+SELECT DISTINCT p.x FROM t_pk p LEFT JOIN t_orders o ON p.x = o.x ORDER BY p.x;
+----
+1
+2
+
+query TT
+EXPLAIN SELECT DISTINCT p.x FROM t_pk p LEFT JOIN t_orders o ON p.x = o.x;
+----
+logical_plan
+01)Aggregate: groupBy=[[p.x]], aggr=[[]]
+02)--Projection: p.x
+03)----Left Join: p.x = o.x
+04)------SubqueryAlias: p
+05)--------TableScan: t_pk projection=[x]
+06)------SubqueryAlias: o
+07)--------TableScan: t_orders projection=[x]
+
+statement ok
+drop table t_orders;
+
+# 1.4 DISTINCT over a GROUP BY output. Grouping collapses the multiple NULL
+# rows, (NULL included) and the DISTINCT can be removed.
+query I
+SELECT DISTINCT x FROM (SELECT x FROM t_uniq GROUP BY x) ORDER BY x NULLS LAST;
+----
+1
+NULL
+
+query TT
+EXPLAIN SELECT DISTINCT x FROM (SELECT x FROM t_uniq GROUP BY x);
+----
+logical_plan
+01)Aggregate: groupBy=[[t_uniq.x]], aggr=[[]]
+02)--TableScan: t_uniq projection=[x]
+
+
+# 2.1 PRIMARY KEY: `x` determines `y`, so `ORDER BY x, y` is equivalent to
+# `ORDER BY x` and the `y` key is dropped from the plan.
+query TT
+EXPLAIN SELECT x, y FROM t_pk ORDER BY x, y;
+----
+logical_plan
+01)Sort: t_pk.x ASC NULLS LAST
+02)--TableScan: t_pk projection=[x, y]
+
+# 2.2 Nullable UNIQUE: `x` does NOT determine `y` across the two NULL rows,
+# so the `y` sort key must be kept.
+#
+# BUG:
+# Expected: `1 3`, `NULL 1`, `NULL 2`.
+# Issue: https://github.com/apache/datafusion/issues/23818
+query II
+SELECT x, y FROM t_uniq ORDER BY x NULLS LAST, y;
+----
+1 3
+NULL 2
+NULL 1
+
+query TT
+EXPLAIN SELECT x, y FROM t_uniq ORDER BY x NULLS LAST, y;
+----
+logical_plan
+01)Sort: t_uniq.x ASC NULLS LAST
+02)--TableScan: t_uniq projection=[x, y]
+
+# 2.3 After `GROUP BY x` the `x` does determine `cnt`, so can drop `cnt` from sort
+query TT
+EXPLAIN SELECT x, cnt FROM (SELECT x, count(*) AS cnt FROM t_uniq GROUP BY x) ORDER BY x, cnt;
+----
+logical_plan
+01)Sort: t_uniq.x ASC NULLS LAST
+02)--Projection: t_uniq.x, count(Int64(1)) AS cnt
+03)----Aggregate: groupBy=[[t_uniq.x]], aggr=[[count(Int64(1))]]
+04)------TableScan: t_uniq projection=[x]
+
+
+# 3.1 PRIMARY KEY: `x` determines `y`, and `y` is not selected, so grouping
+# by `x, y` is the same as grouping by `x`.
+query TT
+EXPLAIN SELECT x FROM t_pk GROUP BY x, y;
+----
+logical_plan
+01)Aggregate: groupBy=[[t_pk.x]], aggr=[[]]
+02)--TableScan: t_pk projection=[x]
+
+# 3.2 Nullable UNIQUE: grouping by `x, y` is NOT the same as grouping by
+# `x` -- two NULL rows differ in `y` and belong in separate groups.
+#
+# BUG: `y` is dropped from the GROUP BY and the two NULL groups are merged,
+# so one row goes missing.
+# Expected: `1`, `NULL`, `NULL` (three rows).
+# Issue: https://github.com/apache/datafusion/issues/23819
+query I rowsort
+SELECT x FROM t_uniq GROUP BY x, y;
+----
+1
+NULL
+
+query TT
+EXPLAIN SELECT x FROM t_uniq GROUP BY x, y;
+----
+logical_plan
+01)Aggregate: groupBy=[[t_uniq.x]], aggr=[[]]
+02)--TableScan: t_uniq projection=[x]
+
+# 3.3 The same grouping, but with `y` selected so the parent needs it: no
+# column can be dropped and the answer is right.
+query II rowsort
+SELECT x, y FROM t_uniq GROUP BY x, y;
+----
+1 3
+NULL 1
+NULL 2
+
+# 4.1 PRIMARY KEY: `x` determines `y`, so `y` has a single well-defined
+# value per group and one row is returned per `x`.
+query II rowsort
+SELECT x, y FROM t_pk GROUP BY x;
+----
+1 10
+2 20
+
+query TT
+EXPLAIN SELECT x, y FROM t_pk GROUP BY x;
+----
+logical_plan
+01)Aggregate: groupBy=[[t_pk.x, t_pk.y]], aggr=[[]]
+02)--TableScan: t_pk projection=[x, y]
+
+# 4.2 Nullable UNIQUE: `x` does NOT determine `y`, so there is no
+# well-defined `y` for the `x = NULL` group.
+#
+# BUG: `y` is appended to the GROUP BY anyway, so `GROUP BY x` returns TWO
+# rows for `x = NULL`.
+# Expected: one row per distinct `x` (or a planning error -- postgres
+# rejects this query, and accepts the 4.1 PRIMARY KEY form).
+# Issue: https://github.com/apache/datafusion/issues/23820
+query II rowsort
+SELECT x, y FROM t_uniq GROUP BY x;
+----
+1 3
+NULL 1
+NULL 2
+
+query TT
+EXPLAIN SELECT x, y FROM t_uniq GROUP BY x;
+----
+logical_plan
+01)Aggregate: groupBy=[[t_uniq.x, t_uniq.y]], aggr=[[]]
+02)--TableScan: t_uniq projection=[x, y]
+
+
+statement ok
+CREATE TABLE t_null (x INT) AS VALUES (NULL), (NULL);
+
+statement ok
+CREATE TABLE t_probe (z INT) AS VALUES (0), (2);
+
+# 5.1 Grouping by `g.x, g.cnt` must keep both columns: `g.x` alone does not
+# determine `g.cnt` after NULL padding.
+query II
+SELECT g.x, count(*) AS c
+ FROM t_probe a
+ LEFT JOIN (SELECT x, count(*) AS cnt FROM t_null GROUP BY x) g
+ ON a.z = g.cnt
+ GROUP BY g.x, g.cnt
+ ORDER BY c;
+----
+NULL 1
+NULL 1
+
+query TT
+EXPLAIN SELECT g.x, count(*) AS c
+ FROM t_probe a
+ LEFT JOIN (SELECT x, count(*) AS cnt FROM t_null GROUP BY x) g
+ ON a.z = g.cnt
+ GROUP BY g.x, g.cnt;
+----
+logical_plan
+01)Projection: g.x, count(Int64(1)) AS count(*) AS c
+02)--Aggregate: groupBy=[[g.x, g.cnt]], aggr=[[count(Int64(1))]]
+03)----Projection: g.x, g.cnt
+04)------Left Join: CAST(a.z AS Int64) = g.cnt
+05)--------SubqueryAlias: a
+06)----------TableScan: t_probe projection=[z]
+07)--------SubqueryAlias: g
+08)----------Projection: t_null.x, count(Int64(1)) AS count(*) AS cnt
+09)------------Aggregate: groupBy=[[t_null.x]], aggr=[[count(Int64(1))]]
+10)--------------TableScan: t_null projection=[x]
+
+# 5.2 The ORDER BY variant: `g.x` is NULL for both rows, so the `g.cnt`
+# tie-breaker is what orders them.
+query II
+SELECT g.x, g.cnt
+ FROM t_probe a
+ LEFT JOIN (SELECT x, count(*) AS cnt FROM t_null GROUP BY x) g
+ ON a.z = g.cnt
+ ORDER BY g.x, g.cnt;
+----
+NULL 2
+NULL NULL
+
+statement ok
+drop table t_null;
+
+statement ok
+drop table t_probe;
+
+##########
+## Cleanup
+##########
+
+statement ok
+drop table t_pk;
+
+statement ok
+drop table t_uniq;
+
+statement ok
+RESET datafusion.explain.logical_plan_only;
From 7269d13e63aa6b67d5db08655134d9c6f9c71ce8 Mon Sep 17 00:00:00 2001
From: Neil Conway
Date: Fri, 24 Jul 2026 14:48:43 -0400
Subject: [PATCH 014/109] chore: Enable `unused_async` lint, make some
functions sync (#23679)
## Which issue does this PR close?
- Closes: N/A
## Rationale for this change
There are a reasonable number of places where we have functions marked
as `async` that don't need to be so. Fix this by enabling the
`unused_async` lint, and fixing up the resulting breakage. There are a
handful of spots that need an `expect(clippy::unused_async)` -- mostly
mocks of `async` methods and example code.
## What changes are included in this PR?
* Enable `unused_async` lint
* Remove unnecessary `async` annotations in a bunch of places
* Add `expect(clippy::unused_async)` where necessary
## Are these changes tested?
Yes, covered by existing tests (no behavioral change expected).
## Are there any user-facing changes?
Yes: this PR updates a few public APIs:
- `datafusion_cli::command::OutputFormat::execute`
- `datafusion::test_util::parquet::TestParquetFile::create_scan`
-
`datafusion_datasource_csv::file_format::CsvFormat::read_to_delimited_chunks_from_stream`
- `datafusion_substrait::serializer::deserialize_bytes`
Migration is mostly straightforward (e.g., removing `await` from calling
code).
---
Cargo.toml | 1 +
benchmarks/src/imdb/run.rs | 4 +-
datafusion-cli/src/command.rs | 2 +-
datafusion-cli/src/exec.rs | 2 +-
datafusion-cli/src/object_storage.rs | 12 ++--
.../custom_data_source/custom_datasource.rs | 4 +-
.../examples/data_io/remote_catalog.rs | 3 +
.../proto/composed_extension_codec.rs | 10 ++--
.../proto/expression_deduplication.rs | 2 +-
datafusion-examples/examples/proto/main.rs | 4 +-
.../examples/query_planning/expr_api.rs | 2 +-
.../examples/query_planning/main.rs | 4 +-
.../examples/query_planning/pruning.rs | 2 +-
datafusion/catalog/src/information_schema.rs | 4 +-
datafusion/core/benches/filter_query_sql.rs | 13 ++---
datafusion/core/benches/struct_query_sql.rs | 5 +-
datafusion/core/benches/topk_aggregate.rs | 42 ++++----------
.../core/src/datasource/file_format/csv.rs | 3 +-
datafusion/core/src/execution/context/mod.rs | 34 +++++-------
datafusion/core/src/test_util/parquet.rs | 2 +-
datafusion/core/tests/fuzz_cases/pruning.rs | 19 +++----
datafusion/core/tests/memory_limit/mod.rs | 10 ++--
.../core/tests/parquet/filter_pushdown.rs | 1 -
datafusion/core/tests/parquet/mod.rs | 8 +--
.../core/tests/parquet/schema_coercion.rs | 6 +-
.../physical_optimizer/enforce_sorting.rs | 14 ++---
.../physical_optimizer/join_selection.rs | 26 ++++-----
.../core/tests/sql/aggregates/dict_nulls.rs | 8 +--
datafusion/core/tests/sql/aggregates/mod.rs | 45 +++++++--------
.../user_defined_async_scalar_functions.rs | 1 +
datafusion/datasource-csv/src/file_format.rs | 3 +-
datafusion/datasource-parquet/src/metadata.rs | 6 +-
datafusion/datasource-parquet/src/sink.rs | 16 +++---
datafusion/execution/src/async_stream.rs | 4 ++
datafusion/physical-plan/src/limit.rs | 55 ++++++++-----------
datafusion/physical-plan/src/sorts/sort.rs | 12 ++--
datafusion/sqllogictest/bin/sqllogictests.rs | 12 ++++
datafusion/sqllogictest/src/test_context.rs | 18 +++---
.../consumer/expr/scalar_function.rs | 40 +++++++-------
datafusion/substrait/src/serializer.rs | 6 +-
.../tests/cases/roundtrip_logical_plan.rs | 11 ++--
.../library-user-guide/upgrading/55.0.0.md | 29 ++++++++++
42 files changed, 251 insertions(+), 254 deletions(-)
diff --git a/Cargo.toml b/Cargo.toml
index 6f4c10f8e7552..87c23cc456651 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -222,6 +222,7 @@ needless_pass_by_value = "warn"
# https://github.com/apache/datafusion/issues/18881
allow_attributes = "warn"
assigning_clones = "warn"
+unused_async = "warn"
[workspace.lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = [
diff --git a/benchmarks/src/imdb/run.rs b/benchmarks/src/imdb/run.rs
index e0e302e466840..a8e202888794f 100644
--- a/benchmarks/src/imdb/run.rs
+++ b/benchmarks/src/imdb/run.rs
@@ -355,7 +355,7 @@ impl RunOpt {
async fn register_tables(&self, ctx: &SessionContext) -> Result<()> {
for table in IMDB_TABLES {
- let table_provider = { self.get_table(ctx, table).await? };
+ let table_provider = { self.get_table(ctx, table)? };
if self.mem_table {
println!("Loading table '{table}' into memory");
@@ -416,7 +416,7 @@ impl RunOpt {
Ok(result)
}
- async fn get_table(
+ fn get_table(
&self,
ctx: &SessionContext,
table: &str,
diff --git a/datafusion-cli/src/command.rs b/datafusion-cli/src/command.rs
index 8aaa8025d1c3a..e847f7fdb501b 100644
--- a/datafusion-cli/src/command.rs
+++ b/datafusion-cli/src/command.rs
@@ -259,7 +259,7 @@ impl FromStr for OutputFormat {
}
impl OutputFormat {
- pub async fn execute(&self, print_options: &mut PrintOptions) -> Result<()> {
+ pub fn execute(&self, print_options: &mut PrintOptions) -> Result<()> {
match self {
Self::ChangeFormat(format) => {
if let Ok(format) = format.parse::() {
diff --git a/datafusion-cli/src/exec.rs b/datafusion-cli/src/exec.rs
index bc2c15f48debb..fc230d5362346 100644
--- a/datafusion-cli/src/exec.rs
+++ b/datafusion-cli/src/exec.rs
@@ -148,7 +148,7 @@ pub async fn exec_from_repl(
Command::OutputFormat(subcommand) => {
if let Some(subcommand) = subcommand {
if let Ok(command) = subcommand.parse::() {
- if let Err(e) = command.execute(print_options).await {
+ if let Err(e) = command.execute(print_options) {
eprintln!("{e}")
}
} else {
diff --git a/datafusion-cli/src/object_storage.rs b/datafusion-cli/src/object_storage.rs
index 4293788e0c03a..e2ba992961c40 100644
--- a/datafusion-cli/src/object_storage.rs
+++ b/datafusion-cli/src/object_storage.rs
@@ -56,6 +56,10 @@ use object_store::aws::resolve_bucket_region;
// Provide a local mock when running tests so we don't make network calls
#[cfg(test)]
+#[expect(
+ clippy::unused_async,
+ reason = "matches object_store::aws::resolve_bucket_region"
+)]
async fn resolve_bucket_region(
_bucket: &str,
_client_options: &ClientOptions,
@@ -600,7 +604,7 @@ mod tests {
#[tokio::test]
async fn s3_object_store_builder_default() -> Result<()> {
- if let Err(DataFusionError::Execution(e)) = check_aws_envs().await {
+ if let Err(DataFusionError::Execution(e)) = check_aws_envs() {
// Skip test if AWS envs are not set
eprintln!("{e}");
return Ok(());
@@ -765,7 +769,7 @@ mod tests {
#[tokio::test]
async fn s3_object_store_builder_resolves_region_when_none_provided() -> Result<()> {
- if let Err(DataFusionError::Execution(e)) = check_aws_envs().await {
+ if let Err(DataFusionError::Execution(e)) = check_aws_envs() {
// Skip test if AWS envs are not set
eprintln!("{e}");
return Ok(());
@@ -798,7 +802,7 @@ mod tests {
#[tokio::test]
async fn s3_object_store_builder_overrides_region_when_resolve_region_enabled()
-> Result<()> {
- if let Err(DataFusionError::Execution(e)) = check_aws_envs().await {
+ if let Err(DataFusionError::Execution(e)) = check_aws_envs() {
// Skip test if AWS envs are not set
eprintln!("{e}");
return Ok(());
@@ -909,7 +913,7 @@ mod tests {
table_options
}
- async fn check_aws_envs() -> Result<()> {
+ fn check_aws_envs() -> Result<()> {
let aws_envs = [
"AWS_ACCESS_KEY_ID",
"AWS_SECRET_ACCESS_KEY",
diff --git a/datafusion-examples/examples/custom_data_source/custom_datasource.rs b/datafusion-examples/examples/custom_data_source/custom_datasource.rs
index a67738520b010..a2d7d7699927f 100644
--- a/datafusion-examples/examples/custom_data_source/custom_datasource.rs
+++ b/datafusion-examples/examples/custom_data_source/custom_datasource.rs
@@ -145,7 +145,7 @@ impl Debug for CustomDataSource {
}
impl CustomDataSource {
- pub(crate) async fn create_physical_plan(
+ pub(crate) fn create_physical_plan(
&self,
projections: Option<&Vec>,
schema: SchemaRef,
@@ -207,7 +207,7 @@ impl TableProvider for CustomDataSource {
_filters: &[Expr],
_limit: Option,
) -> Result> {
- return self.create_physical_plan(projection, self.schema()).await;
+ self.create_physical_plan(projection, self.schema())
}
}
diff --git a/datafusion-examples/examples/data_io/remote_catalog.rs b/datafusion-examples/examples/data_io/remote_catalog.rs
index 16814752b3ec2..a24ca2238181d 100644
--- a/datafusion-examples/examples/data_io/remote_catalog.rs
+++ b/datafusion-examples/examples/data_io/remote_catalog.rs
@@ -130,6 +130,7 @@ struct RemoteCatalogInterface {}
impl RemoteCatalogInterface {
/// Establish a connection to the remote catalog
+ #[expect(clippy::unused_async)]
pub async fn connect() -> Result {
// In a real implementation this method might connect to a remote
// catalog, validate credentials, cache basic information, etc
@@ -137,6 +138,7 @@ impl RemoteCatalogInterface {
}
/// Fetches information for a specific table
+ #[expect(clippy::unused_async)]
pub async fn table_info(&self, name: &str) -> Result> {
if name != "remote_table" {
return Ok(None);
@@ -155,6 +157,7 @@ impl RemoteCatalogInterface {
}
/// Fetches data for a table from a remote data source
+ #[expect(clippy::unused_async)]
pub async fn read_data(&self, name: &str) -> Result {
if name != "remote_table" {
return plan_err!("Remote table not found: {}", name);
diff --git a/datafusion-examples/examples/proto/composed_extension_codec.rs b/datafusion-examples/examples/proto/composed_extension_codec.rs
index 2581f4a2ce247..6077a982c320d 100644
--- a/datafusion-examples/examples/proto/composed_extension_codec.rs
+++ b/datafusion-examples/examples/proto/composed_extension_codec.rs
@@ -47,8 +47,8 @@ use datafusion_proto::physical_plan::{
use datafusion_proto::protobuf;
/// Example of using multiple extension codecs for serialization / deserialization
-pub async fn composed_extension_codec() -> Result<()> {
- // build execution plan that has both types of nodes
+pub fn composed_extension_codec() -> Result<()> {
+ // Build execution plan that has both types of nodes
//
// Note each node requires a different `PhysicalExtensionCodec` to decode
let exec_plan = Arc::new(ParentExec {
@@ -63,18 +63,18 @@ pub async fn composed_extension_codec() -> Result<()> {
Arc::new(ChildPhysicalExtensionCodec {}),
]);
- // serialize execution plan to proto
+ // Serialize execution plan to proto
let proto: protobuf::PhysicalPlanNode =
protobuf::PhysicalPlanNode::try_from_physical_plan(
exec_plan.clone(),
&composed_codec,
)?;
- // deserialize proto back to execution plan
+ // Deserialize proto back to execution plan
let result_exec_plan: Arc =
proto.try_into_physical_plan(&ctx.task_ctx(), &composed_codec)?;
- // assert that the original and deserialized execution plans are equal
+ // Assert that the original and deserialized execution plans are equal
assert_eq!(format!("{exec_plan:?}"), format!("{result_exec_plan:?}"));
Ok(())
diff --git a/datafusion-examples/examples/proto/expression_deduplication.rs b/datafusion-examples/examples/proto/expression_deduplication.rs
index 31bb234e287f5..8ee59fa14d9cd 100644
--- a/datafusion-examples/examples/proto/expression_deduplication.rs
+++ b/datafusion-examples/examples/proto/expression_deduplication.rs
@@ -72,7 +72,7 @@ use prost::Message;
/// In real scenarios, expressions can be much more complex, e.g. a large InList
/// expression could be megabytes in size, so deduplication can save significant memory
/// in addition to more correctly representing the original plan structure.
-pub async fn expression_deduplication() -> Result<()> {
+pub fn expression_deduplication() -> Result<()> {
println!("=== Expression Deduplication Example ===\n");
// Create a schema for our test expressions
diff --git a/datafusion-examples/examples/proto/main.rs b/datafusion-examples/examples/proto/main.rs
index 3f525b5d46afa..d534eda24ba64 100644
--- a/datafusion-examples/examples/proto/main.rs
+++ b/datafusion-examples/examples/proto/main.rs
@@ -64,10 +64,10 @@ impl ExampleKind {
}
}
ExampleKind::ComposedExtensionCodec => {
- composed_extension_codec::composed_extension_codec().await?
+ composed_extension_codec::composed_extension_codec()?
}
ExampleKind::ExpressionDeduplication => {
- expression_deduplication::expression_deduplication().await?
+ expression_deduplication::expression_deduplication()?
}
}
Ok(())
diff --git a/datafusion-examples/examples/query_planning/expr_api.rs b/datafusion-examples/examples/query_planning/expr_api.rs
index dd5145def3cfe..08efff7777691 100644
--- a/datafusion-examples/examples/query_planning/expr_api.rs
+++ b/datafusion-examples/examples/query_planning/expr_api.rs
@@ -58,7 +58,7 @@ use datafusion::prelude::*;
/// 5. Analyze predicates for boundary ranges: [`range_analysis_demo`]
/// 6. Get the types of the expressions: [`expression_type_demo`]
/// 7. Apply type coercion to expressions: [`type_coercion_demo`]
-pub async fn expr_api() -> Result<()> {
+pub fn expr_api() -> Result<()> {
// The easiest way to do create expressions is to use the
// "fluent"-style API:
let expr = col("a") + lit(5);
diff --git a/datafusion-examples/examples/query_planning/main.rs b/datafusion-examples/examples/query_planning/main.rs
index d3f99aedceb3d..2e4310082c9dd 100644
--- a/datafusion-examples/examples/query_planning/main.rs
+++ b/datafusion-examples/examples/query_planning/main.rs
@@ -94,12 +94,12 @@ impl ExampleKind {
}
}
ExampleKind::AnalyzerRule => analyzer_rule::analyzer_rule().await?,
- ExampleKind::ExprApi => expr_api::expr_api().await?,
+ ExampleKind::ExprApi => expr_api::expr_api()?,
ExampleKind::OptimizerRule => optimizer_rule::optimizer_rule().await?,
ExampleKind::ParseSqlExpr => parse_sql_expr::parse_sql_expr().await?,
ExampleKind::PlanToSql => plan_to_sql::plan_to_sql_examples().await?,
ExampleKind::PlannerApi => planner_api::planner_api().await?,
- ExampleKind::Pruning => pruning::pruning().await?,
+ ExampleKind::Pruning => pruning::pruning()?,
ExampleKind::ThreadPools => thread_pools::thread_pools().await?,
}
Ok(())
diff --git a/datafusion-examples/examples/query_planning/pruning.rs b/datafusion-examples/examples/query_planning/pruning.rs
index df26aa57b6bc1..dad57cd261600 100644
--- a/datafusion-examples/examples/query_planning/pruning.rs
+++ b/datafusion-examples/examples/query_planning/pruning.rs
@@ -44,7 +44,7 @@ use datafusion::prelude::*;
/// one might do as part of a higher level storage engine. See
/// `parquet_index.rs` for an example that uses pruning in the context of an
/// individual query.
-pub async fn pruning() -> Result<()> {
+pub fn pruning() -> Result<()> {
// In this example, we'll use the PruningPredicate to determine if
// the expression `x = 5 AND y = 10` can never be true based on statistics
diff --git a/datafusion/catalog/src/information_schema.rs b/datafusion/catalog/src/information_schema.rs
index ca5060896f787..d9ad7791af67c 100644
--- a/datafusion/catalog/src/information_schema.rs
+++ b/datafusion/catalog/src/information_schema.rs
@@ -151,7 +151,7 @@ impl InformationSchemaConfig {
Ok(())
}
- async fn make_schemata(&self, builder: &mut InformationSchemataBuilder) {
+ fn make_schemata(&self, builder: &mut InformationSchemataBuilder) {
for catalog_name in self.catalog_list.catalog_names() {
let catalog = self.catalog_list.catalog(&catalog_name).unwrap();
@@ -1152,7 +1152,7 @@ impl PartitionStream for InformationSchemata {
Arc::clone(&self.schema),
// TODO: Stream this
futures::stream::once(async move {
- config.make_schemata(&mut builder).await;
+ config.make_schemata(&mut builder);
builder.finish()
}),
))
diff --git a/datafusion/core/benches/filter_query_sql.rs b/datafusion/core/benches/filter_query_sql.rs
index 3b80518d32dcd..6ddf6fa31820a 100644
--- a/datafusion/core/benches/filter_query_sql.rs
+++ b/datafusion/core/benches/filter_query_sql.rs
@@ -23,12 +23,11 @@ use arrow::{
use criterion::{Criterion, criterion_group, criterion_main};
use datafusion::prelude::SessionContext;
use datafusion::{datasource::MemTable, error::Result};
-use futures::executor::block_on;
use std::hint::black_box;
use std::sync::Arc;
use tokio::runtime::Runtime;
-async fn query(ctx: &SessionContext, rt: &Runtime, sql: &str) {
+fn query(ctx: &SessionContext, rt: &Runtime, sql: &str) {
// execute the query
let df = rt.block_on(ctx.sql(sql)).unwrap();
black_box(rt.block_on(df.collect()).unwrap());
@@ -71,28 +70,28 @@ fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("filter_array", |b| {
let ctx = create_context(array_len, batch_size).unwrap();
- b.iter(|| block_on(query(&ctx, &rt, "select f32, f64 from t where f32 >= f64")))
+ b.iter(|| query(&ctx, &rt, "select f32, f64 from t where f32 >= f64"))
});
c.bench_function("filter_scalar", |b| {
let ctx = create_context(array_len, batch_size).unwrap();
b.iter(|| {
- block_on(query(
+ query(
&ctx,
&rt,
"select f32, f64 from t where f32 >= 250 and f64 > 250",
- ))
+ )
})
});
c.bench_function("filter_scalar in list", |b| {
let ctx = create_context(array_len, batch_size).unwrap();
b.iter(|| {
- block_on(query(
+ query(
&ctx,
&rt,
"select f32, f64 from t where f32 in (10, 20, 30, 40)",
- ))
+ )
})
});
}
diff --git a/datafusion/core/benches/struct_query_sql.rs b/datafusion/core/benches/struct_query_sql.rs
index 96434fc379ea6..848d5a3c3e5de 100644
--- a/datafusion/core/benches/struct_query_sql.rs
+++ b/datafusion/core/benches/struct_query_sql.rs
@@ -23,12 +23,11 @@ use arrow::{
use criterion::{Criterion, criterion_group, criterion_main};
use datafusion::prelude::SessionContext;
use datafusion::{datasource::MemTable, error::Result};
-use futures::executor::block_on;
use std::hint::black_box;
use std::sync::Arc;
use tokio::runtime::Runtime;
-async fn query(ctx: &SessionContext, rt: &Runtime, sql: &str) {
+fn query(ctx: &SessionContext, rt: &Runtime, sql: &str) {
// execute the query
let df = rt.block_on(ctx.sql(sql)).unwrap();
black_box(rt.block_on(df.collect()).unwrap());
@@ -71,7 +70,7 @@ fn criterion_benchmark(c: &mut Criterion) {
let rt = Runtime::new().unwrap();
c.bench_function("struct", |b| {
- b.iter(|| block_on(query(&ctx, &rt, "select struct(f32, f64) from t")))
+ b.iter(|| query(&ctx, &rt, "select struct(f32, f64) from t"))
});
}
diff --git a/datafusion/core/benches/topk_aggregate.rs b/datafusion/core/benches/topk_aggregate.rs
index c78b1ea494407..d8ca0d58b8d21 100644
--- a/datafusion/core/benches/topk_aggregate.rs
+++ b/datafusion/core/benches/topk_aggregate.rs
@@ -74,7 +74,7 @@ fn test_distinct_schema() -> SchemaRef {
Arc::new(Schema::new(vec![Field::new("id", DataType::Int64, false)]))
}
-async fn create_context(
+fn create_context(
partition_cnt: i32,
sample_cnt: i32,
asc: bool,
@@ -94,7 +94,7 @@ async fn create_context(
Ok(ctx)
}
-async fn create_context_distinct(
+fn create_context_distinct(
partition_cnt: i32,
sample_cnt: i32,
use_topk: bool,
@@ -306,12 +306,8 @@ fn assert_utf8_utf8view_match(
asc: bool,
use_topk: bool,
) {
- let ctx_utf8 = rt
- .block_on(create_context(partitions, samples, asc, use_topk, false))
- .unwrap();
- let ctx_view = rt
- .block_on(create_context(partitions, samples, asc, use_topk, true))
- .unwrap();
+ let ctx_utf8 = create_context(partitions, samples, asc, use_topk, false).unwrap();
+ let ctx_view = create_context(partitions, samples, asc, use_topk, true).unwrap();
let batches_utf8 = rt
.block_on(aggregate_string(ctx_utf8, limit, use_topk))
.unwrap();
@@ -390,15 +386,9 @@ fn criterion_benchmark(c: &mut Criterion) {
.name_tpl
.replace("{rows}", &total_rows.to_string())
.replace("{limit}", &limit.to_string());
- let ctx = rt
- .block_on(create_context(
- partitions,
- samples,
- case.asc,
- case.use_topk,
- case.use_view,
- ))
- .unwrap();
+ let ctx =
+ create_context(partitions, samples, case.asc, case.use_topk, case.use_view)
+ .unwrap();
c.bench_function(&name, |b| {
b.iter(|| run(&rt, ctx.clone(), limit, case.use_topk, case.asc))
});
@@ -462,15 +452,9 @@ fn criterion_benchmark(c: &mut Criterion) {
} else {
format!("string aggregate {total_rows} {scenario} rows [{type_label}]")
};
- let ctx = rt
- .block_on(create_context(
- partitions,
- samples,
- case.asc,
- case.use_topk,
- case.use_view,
- ))
- .unwrap();
+ let ctx =
+ create_context(partitions, samples, case.asc, case.use_topk, case.use_view)
+ .unwrap();
c.bench_function(&name, |b| {
b.iter(|| run_string(&rt, ctx.clone(), limit, case.use_topk))
});
@@ -478,11 +462,7 @@ fn criterion_benchmark(c: &mut Criterion) {
// DISTINCT benchmarks
for use_topk in [false, true] {
- let ctx = rt.block_on(async {
- create_context_distinct(partitions, samples, use_topk)
- .await
- .unwrap()
- });
+ let ctx = create_context_distinct(partitions, samples, use_topk).unwrap();
let topk_label = if use_topk { "TopK" } else { "no TopK" };
for asc in [false, true] {
let dir = if asc { "asc" } else { "desc" };
diff --git a/datafusion/core/src/datasource/file_format/csv.rs b/datafusion/core/src/datasource/file_format/csv.rs
index 651a15d776e4d..90d7eb3b41388 100644
--- a/datafusion/core/src/datasource/file_format/csv.rs
+++ b/datafusion/core/src/datasource/file_format/csv.rs
@@ -591,8 +591,7 @@ mod tests {
//convert compressed_stream to decoded_stream
let decoded_stream = compressed_csv
- .read_to_delimited_chunks_from_stream(compressed_stream.unwrap())
- .await;
+ .read_to_delimited_chunks_from_stream(compressed_stream.unwrap());
let (schema, records_read) = compressed_csv
.infer_schema_from_stream(&session_state, records_to_read, decoded_stream)
.await?;
diff --git a/datafusion/core/src/execution/context/mod.rs b/datafusion/core/src/execution/context/mod.rs
index 281cb4dd79d4d..cd30193e307e3 100644
--- a/datafusion/core/src/execution/context/mod.rs
+++ b/datafusion/core/src/execution/context/mod.rs
@@ -687,8 +687,8 @@ impl SessionContext {
pub async fn execute_logical_plan(&self, plan: LogicalPlan) -> Result {
match plan {
LogicalPlan::Ddl(ddl) => {
- // Box::pin avoids allocating the stack space within this function's frame
- // for every one of these individual async functions, decreasing the risk of
+ // Box async DDL handlers to avoid reserving space for all of their
+ // futures in this function's state machine, decreasing the risk of
// stack overflows.
match ddl {
DdlStatement::CreateExternalTable(cmd) => {
@@ -703,32 +703,26 @@ impl SessionContext {
Box::pin(self.create_view(cmd)).await
}
DdlStatement::CreateCatalogSchema(cmd) => {
- Box::pin(self.create_catalog_schema(cmd)).await
- }
- DdlStatement::CreateCatalog(cmd) => {
- Box::pin(self.create_catalog(cmd)).await
+ self.create_catalog_schema(cmd)
}
+ DdlStatement::CreateCatalog(cmd) => self.create_catalog(cmd),
DdlStatement::DropTable(cmd) => Box::pin(self.drop_table(cmd)).await,
DdlStatement::DropView(cmd) => Box::pin(self.drop_view(cmd)).await,
- DdlStatement::DropCatalogSchema(cmd) => {
- Box::pin(self.drop_schema(cmd)).await
- }
+ DdlStatement::DropCatalogSchema(cmd) => self.drop_schema(cmd),
DdlStatement::CreateFunction(cmd) => {
Box::pin(self.create_function(*cmd)).await
}
- DdlStatement::DropFunction(cmd) => {
- Box::pin(self.drop_function(cmd)).await
- }
+ DdlStatement::DropFunction(cmd) => self.drop_function(&cmd),
ddl => Ok(DataFrame::new(self.state(), LogicalPlan::Ddl(ddl))),
}
}
// TODO what about the other statements (like TransactionStart and TransactionEnd)
LogicalPlan::Statement(Statement::SetVariable(stmt)) => {
- self.set_variable(stmt).await?;
+ self.set_variable(stmt)?;
self.return_empty_dataframe()
}
LogicalPlan::Statement(Statement::ResetVariable(stmt)) => {
- self.reset_variable(stmt).await?;
+ self.reset_variable(stmt)?;
self.return_empty_dataframe()
}
LogicalPlan::Statement(Statement::Prepare(Prepare {
@@ -987,7 +981,7 @@ impl SessionContext {
Ok(())
}
- async fn create_catalog_schema(&self, cmd: CreateCatalogSchema) -> Result {
+ fn create_catalog_schema(&self, cmd: CreateCatalogSchema) -> Result {
let CreateCatalogSchema {
schema_name,
if_not_exists,
@@ -1028,7 +1022,7 @@ impl SessionContext {
}
}
- async fn create_catalog(&self, cmd: CreateCatalog) -> Result {
+ fn create_catalog(&self, cmd: CreateCatalog) -> Result {
let CreateCatalog {
catalog_name,
if_not_exists,
@@ -1078,7 +1072,7 @@ impl SessionContext {
}
}
- async fn drop_schema(&self, cmd: DropCatalogSchema) -> Result {
+ fn drop_schema(&self, cmd: DropCatalogSchema) -> Result {
let DropCatalogSchema {
name,
if_exists: allow_missing,
@@ -1113,7 +1107,7 @@ impl SessionContext {
exec_err!("Schema '{schema_ref}' doesn't exist.")
}
- async fn set_variable(&self, stmt: SetVariable) -> Result<()> {
+ fn set_variable(&self, stmt: SetVariable) -> Result<()> {
let SetVariable {
variable, value, ..
} = stmt;
@@ -1148,7 +1142,7 @@ impl SessionContext {
Ok(())
}
- async fn reset_variable(&self, stmt: ResetVariable) -> Result<()> {
+ fn reset_variable(&self, stmt: ResetVariable) -> Result<()> {
let variable = stmt.variable;
if variable.starts_with("datafusion.runtime.") {
return self.reset_runtime_variable(&variable);
@@ -1531,7 +1525,7 @@ impl SessionContext {
self.return_empty_dataframe()
}
- async fn drop_function(&self, stmt: DropFunction) -> Result {
+ fn drop_function(&self, stmt: &DropFunction) -> Result {
// we don't know function type at this point
// decision has been made to drop all functions
let mut dropped = false;
diff --git a/datafusion/core/src/test_util/parquet.rs b/datafusion/core/src/test_util/parquet.rs
index d1018f3fb0f04..e25fe746695cf 100644
--- a/datafusion/core/src/test_util/parquet.rs
+++ b/datafusion/core/src/test_util/parquet.rs
@@ -150,7 +150,7 @@ impl TestParquetFile {
/// ```
///
/// Otherwise if `maybe_filter` is None, return just a `DataSourceExec`
- pub async fn create_scan(
+ pub fn create_scan(
&self,
ctx: &SessionContext,
maybe_filter: Option,
diff --git a/datafusion/core/tests/fuzz_cases/pruning.rs b/datafusion/core/tests/fuzz_cases/pruning.rs
index 8ce5207f91190..7624c97cf47f7 100644
--- a/datafusion/core/tests/fuzz_cases/pruning.rs
+++ b/datafusion/core/tests/fuzz_cases/pruning.rs
@@ -249,12 +249,7 @@ impl Utf8Test {
for (idx, truncation_length) in [Some(1), Some(2), None].iter().enumerate() {
// parquet files only support 32767 row groups per file, so chunk up into multiple files so we don't error if running on a large number of row groups
for (rg_idx, row_groups) in row_groups.chunks(32766).enumerate() {
- let buf = write_parquet_file(
- *truncation_length,
- Arc::clone(&schema),
- row_groups.to_vec(),
- )
- .await;
+ let buf = write_parquet_file(*truncation_length, &schema, row_groups);
let filename = format!("test_fuzz_utf8_{idx}_{rg_idx}.parquet");
let size = buf.len();
let path = Path::from(filename);
@@ -314,10 +309,10 @@ async fn execute_with_predicate(
values
}
-async fn write_parquet_file(
+fn write_parquet_file(
truncation_length: Option,
- schema: Arc,
- row_groups: Vec>,
+ schema: &Arc,
+ row_groups: &[Vec],
) -> Bytes {
let mut buf = BytesMut::new().writer();
let props = WriterProperties::builder()
@@ -326,11 +321,11 @@ async fn write_parquet_file(
let props = props.build();
{
let mut writer =
- ArrowWriter::try_new(&mut buf, schema.clone(), Some(props)).unwrap();
- for rg_values in row_groups.iter() {
+ ArrowWriter::try_new(&mut buf, Arc::clone(schema), Some(props)).unwrap();
+ for rg_values in row_groups {
let arr = StringArray::from_iter_values(rg_values.iter());
let batch =
- RecordBatch::try_new(schema.clone(), vec![Arc::new(arr)]).unwrap();
+ RecordBatch::try_new(Arc::clone(schema), vec![Arc::new(arr)]).unwrap();
writer.write(&batch).unwrap();
writer.flush().unwrap(); // finishes the current row group and starts a new one
}
diff --git a/datafusion/core/tests/memory_limit/mod.rs b/datafusion/core/tests/memory_limit/mod.rs
index ebbe4312b1e1a..d6e38b5d01995 100644
--- a/datafusion/core/tests/memory_limit/mod.rs
+++ b/datafusion/core/tests/memory_limit/mod.rs
@@ -614,7 +614,7 @@ async fn test_sort_skewed_batches_spill() {
// ------------------------------------------------------------------
// Create a new `SessionContext` with specified disk limit, memory pool limit, and spill compression codec
-async fn setup_context(
+fn setup_context(
disk_limit: u64,
memory_pool_limit: usize,
spill_compression: SpillCompression,
@@ -655,7 +655,7 @@ async fn setup_context(
#[tokio::test]
async fn test_disk_spill_limit_reached() -> Result<()> {
let spill_compression = SpillCompression::Uncompressed;
- let ctx = setup_context(1024 * 1024, 1024 * 1024, spill_compression).await?; // 1MB disk limit, 1MB memory limit
+ let ctx = setup_context(1024 * 1024, 1024 * 1024, spill_compression)?; // 1MB disk limit, 1MB memory limit
let df = ctx
.sql("select * from generate_series(1, 1000000000000) as t1(v1) order by v1 desc")
@@ -683,7 +683,7 @@ async fn test_disk_spill_limit_reached() -> Result<()> {
async fn test_disk_spill_limit_not_reached() -> Result<()> {
let disk_spill_limit = 1024 * 1024; // 1MB
let spill_compression = SpillCompression::Uncompressed;
- let ctx = setup_context(disk_spill_limit, 128 * 1024, spill_compression).await?; // 1MB disk limit, 128KB memory limit
+ let ctx = setup_context(disk_spill_limit, 128 * 1024, spill_compression)?; // 1MB disk limit, 128KB memory limit
let df = ctx
.sql("select * from generate_series(1, 10000) as t1(v1) order by v1 desc")
@@ -719,7 +719,7 @@ async fn test_disk_spill_limit_not_reached() -> Result<()> {
async fn test_spill_file_compressed_with_zstd() -> Result<()> {
let disk_spill_limit = 1024 * 1024; // 1MB
let spill_compression = SpillCompression::Zstd;
- let ctx = setup_context(disk_spill_limit, 128 * 1024, spill_compression).await?; // 1MB disk limit, 128KB memory limit, zstd
+ let ctx = setup_context(disk_spill_limit, 128 * 1024, spill_compression)?; // 1MB disk limit, 128KB memory limit, zstd
let df = ctx
.sql("select * from generate_series(1, 100000) as t1(v1) order by v1 desc")
@@ -755,7 +755,7 @@ async fn test_spill_file_compressed_with_zstd() -> Result<()> {
async fn test_spill_file_compressed_with_lz4_frame() -> Result<()> {
let disk_spill_limit = 1024 * 1024; // 1MB
let spill_compression = SpillCompression::Lz4Frame;
- let ctx = setup_context(disk_spill_limit, 128 * 1024, spill_compression).await?; // 1MB disk limit, 128KB memory limit, lz4_frame
+ let ctx = setup_context(disk_spill_limit, 128 * 1024, spill_compression)?; // 1MB disk limit, 128KB memory limit, lz4_frame
let df = ctx
.sql("select * from generate_series(1, 100000) as t1(v1) order by v1 desc")
diff --git a/datafusion/core/tests/parquet/filter_pushdown.rs b/datafusion/core/tests/parquet/filter_pushdown.rs
index 5dfcd50c014c9..dabb2f35b24b1 100644
--- a/datafusion/core/tests/parquet/filter_pushdown.rs
+++ b/datafusion/core/tests/parquet/filter_pushdown.rs
@@ -515,7 +515,6 @@ impl<'a> TestCase<'a> {
let exec = self
.test_parquet_file
.create_scan(&ctx, Some(filter.clone()))
- .await
.unwrap();
let result = collect(exec.clone(), ctx.task_ctx()).await.unwrap();
diff --git a/datafusion/core/tests/parquet/mod.rs b/datafusion/core/tests/parquet/mod.rs
index 1cc4bb32d9eba..7066a4147c017 100644
--- a/datafusion/core/tests/parquet/mod.rs
+++ b/datafusion/core/tests/parquet/mod.rs
@@ -330,11 +330,10 @@ impl ContextWithParquet {
custom_schema,
custom_batches,
)
- .await
}
Unit::Page(row_per_page) => {
config = config.with_parquet_page_index_pruning(true);
- make_test_file_page(scenario, row_per_page).await
+ make_test_file_page(scenario, row_per_page)
}
Unit::RowGroupAndPage(row_per_group, row_per_page) => {
config = config.with_parquet_bloom_filter_pruning(true);
@@ -347,7 +346,6 @@ impl ContextWithParquet {
custom_schema,
custom_batches,
)
- .await
}
};
let parquet_path = file.path().to_string_lossy();
@@ -1173,7 +1171,7 @@ fn create_data_batch(scenario: Scenario) -> Vec {
}
/// Create a test parquet file with various data types
-async fn make_test_file_rg(
+fn make_test_file_rg(
scenario: Scenario,
row_per_group: usize,
row_per_page: Option,
@@ -1219,7 +1217,7 @@ async fn make_test_file_rg(
output_file
}
-async fn make_test_file_page(scenario: Scenario, row_per_page: usize) -> NamedTempFile {
+fn make_test_file_page(scenario: Scenario, row_per_page: usize) -> NamedTempFile {
let mut output_file = tempfile::Builder::new()
.prefix("parquet_page_pruning")
.suffix(".parquet")
diff --git a/datafusion/core/tests/parquet/schema_coercion.rs b/datafusion/core/tests/parquet/schema_coercion.rs
index 6f7e2e328d0c3..be45ab38dabad 100644
--- a/datafusion/core/tests/parquet/schema_coercion.rs
+++ b/datafusion/core/tests/parquet/schema_coercion.rs
@@ -53,7 +53,7 @@ async fn multi_parquet_coercion() {
// batch2: c2(int64), c3(float32)
let batch2 = RecordBatch::try_from_iter(vec![("c2", c2), ("c3", c3)]).unwrap();
- let (meta, _files) = store_parquet(vec![batch1, batch2]).await.unwrap();
+ let (meta, _files) = store_parquet(vec![batch1, batch2]).unwrap();
let file_group = meta.into_iter().map(Into::into).collect();
// cast c1 to utf8, c2 to int32, c3 to float64
@@ -107,7 +107,7 @@ async fn multi_parquet_coercion_projection() {
let batch2 =
RecordBatch::try_from_iter(vec![("c2", c2), ("c1", c1s), ("c3", c3)]).unwrap();
- let (meta, _files) = store_parquet(vec![batch1, batch2]).await.unwrap();
+ let (meta, _files) = store_parquet(vec![batch1, batch2]).unwrap();
let file_group = meta.into_iter().map(Into::into).collect();
// cast c1 to utf8, c2 to int32, c3 to float64
@@ -146,7 +146,7 @@ async fn multi_parquet_coercion_projection() {
}
/// Writes `batches` to a temporary parquet file
-pub async fn store_parquet(
+pub fn store_parquet(
batches: Vec,
) -> Result<(Vec, Vec)> {
// Each batch writes to their own file
diff --git a/datafusion/core/tests/physical_optimizer/enforce_sorting.rs b/datafusion/core/tests/physical_optimizer/enforce_sorting.rs
index e9ad978b2e0cb..9338fcc0bce35 100644
--- a/datafusion/core/tests/physical_optimizer/enforce_sorting.rs
+++ b/datafusion/core/tests/physical_optimizer/enforce_sorting.rs
@@ -425,12 +425,12 @@ async fn test_union_inputs_different_sorted2() -> Result<()> {
Ok(())
}
-#[tokio::test]
+#[test]
// Test with `repartition_sorts` enabled to preserve pre-sorted partitions and avoid resorting
-async fn union_with_mix_of_presorted_and_explicitly_resorted_inputs_with_repartition_sorts_true()
+fn union_with_mix_of_presorted_and_explicitly_resorted_inputs_with_repartition_sorts_true()
-> Result<()> {
assert_snapshot!(
- union_with_mix_of_presorted_and_explicitly_resorted_inputs_impl(true).await?,
+ union_with_mix_of_presorted_and_explicitly_resorted_inputs_impl(true)?,
@r"
Input Plan:
OutputRequirementExec: order_by=[(nullable_col@0, asc)], dist_by=SinglePartition
@@ -451,12 +451,12 @@ async fn union_with_mix_of_presorted_and_explicitly_resorted_inputs_with_reparti
Ok(())
}
-#[tokio::test]
+#[test]
// Test with `repartition_sorts` disabled, causing a full resort of the data
-async fn union_with_mix_of_presorted_and_explicitly_resorted_inputs_with_repartition_sorts_false()
+fn union_with_mix_of_presorted_and_explicitly_resorted_inputs_with_repartition_sorts_false()
-> Result<()> {
assert_snapshot!(
- union_with_mix_of_presorted_and_explicitly_resorted_inputs_impl(false).await?,
+ union_with_mix_of_presorted_and_explicitly_resorted_inputs_impl(false)?,
@r"
Input Plan:
OutputRequirementExec: order_by=[(nullable_col@0, asc)], dist_by=SinglePartition
@@ -477,7 +477,7 @@ async fn union_with_mix_of_presorted_and_explicitly_resorted_inputs_with_reparti
Ok(())
}
-async fn union_with_mix_of_presorted_and_explicitly_resorted_inputs_impl(
+fn union_with_mix_of_presorted_and_explicitly_resorted_inputs_impl(
repartition_sorts: bool,
) -> Result {
let schema = create_test_schema()?;
diff --git a/datafusion/core/tests/physical_optimizer/join_selection.rs b/datafusion/core/tests/physical_optimizer/join_selection.rs
index cca54909a1375..3827e6e98b5e6 100644
--- a/datafusion/core/tests/physical_optimizer/join_selection.rs
+++ b/datafusion/core/tests/physical_optimizer/join_selection.rs
@@ -1271,8 +1271,8 @@ struct TestCase {
expecting_swap: bool,
}
-#[tokio::test]
-async fn test_join_with_swap_full() -> Result<()> {
+#[test]
+fn test_join_with_swap_full() -> Result<()> {
// NOTE: Currently, some initial conditions are not viable after join order selection.
// For example, full join always comes in partitioned mode. See the warning in
// function "swap". If this changes in the future, we should update these tests.
@@ -1319,13 +1319,13 @@ async fn test_join_with_swap_full() -> Result<()> {
},
];
for case in cases.into_iter() {
- test_join_with_maybe_swap_unbounded_case(case).await?
+ test_join_with_maybe_swap_unbounded_case(case)?
}
Ok(())
}
-#[tokio::test]
-async fn test_cases_without_collect_left_check() -> Result<()> {
+#[test]
+fn test_cases_without_collect_left_check() -> Result<()> {
let mut cases = vec![];
let join_types = vec![JoinType::LeftSemi, JoinType::Inner];
for join_type in join_types {
@@ -1412,13 +1412,13 @@ async fn test_cases_without_collect_left_check() -> Result<()> {
}
for case in cases.into_iter() {
- test_join_with_maybe_swap_unbounded_case(case).await?
+ test_join_with_maybe_swap_unbounded_case(case)?
}
Ok(())
}
-#[tokio::test]
-async fn test_not_support_collect_left() -> Result<()> {
+#[test]
+fn test_not_support_collect_left() -> Result<()> {
let mut cases = vec![];
// After [JoinSelection] optimization, these join types cannot run in CollectLeft mode except
// [JoinType::LeftSemi]
@@ -1467,13 +1467,13 @@ async fn test_not_support_collect_left() -> Result<()> {
}
for case in cases.into_iter() {
- test_join_with_maybe_swap_unbounded_case(case).await?
+ test_join_with_maybe_swap_unbounded_case(case)?
}
Ok(())
}
-#[tokio::test]
-async fn test_not_supporting_swaps_possible_collect_left() -> Result<()> {
+#[test]
+fn test_not_supporting_swaps_possible_collect_left() -> Result<()> {
let mut cases = vec![];
let the_ones_not_support_collect_left =
vec![JoinType::Right, JoinType::RightAnti, JoinType::RightSemi];
@@ -1567,12 +1567,12 @@ async fn test_not_supporting_swaps_possible_collect_left() -> Result<()> {
}
for case in cases.into_iter() {
- test_join_with_maybe_swap_unbounded_case(case).await?
+ test_join_with_maybe_swap_unbounded_case(case)?
}
Ok(())
}
-async fn test_join_with_maybe_swap_unbounded_case(t: TestCase) -> Result<()> {
+fn test_join_with_maybe_swap_unbounded_case(t: TestCase) -> Result<()> {
let left_unbounded = t.initial_sources_unbounded.0 == SourceType::Unbounded;
let right_unbounded = t.initial_sources_unbounded.1 == SourceType::Unbounded;
let left_exec = Arc::new(UnboundedExec::new(
diff --git a/datafusion/core/tests/sql/aggregates/dict_nulls.rs b/datafusion/core/tests/sql/aggregates/dict_nulls.rs
index 8733b9e87b57a..c6c3f02829c43 100644
--- a/datafusion/core/tests/sql/aggregates/dict_nulls.rs
+++ b/datafusion/core/tests/sql/aggregates/dict_nulls.rs
@@ -292,7 +292,7 @@ async fn test_first_last_value_group_by_dict_nulls() -> Result<()> {
/// Test MAX with dictionary columns containing null keys and values as specified in the SQL query
#[tokio::test]
async fn test_max_with_fuzz_table_dict_nulls() -> Result<()> {
- let (ctx_single, ctx_multi) = setup_fuzz_test_contexts().await?;
+ let (ctx_single, ctx_multi) = setup_fuzz_test_contexts()?;
// Execute the SQL query with MAX aggregations
let sql = "SELECT
@@ -333,7 +333,7 @@ async fn test_max_with_fuzz_table_dict_nulls() -> Result<()> {
/// Test MIN with fuzz table containing dictionary columns with null keys and values and timestamp data (single and multiple partitions)
#[tokio::test]
async fn test_min_timestamp_with_fuzz_table_dict_nulls() -> Result<()> {
- let (ctx_single, ctx_multi) = setup_fuzz_timestamp_test_contexts().await?;
+ let (ctx_single, ctx_multi) = setup_fuzz_timestamp_test_contexts()?;
// Execute the SQL query with MIN aggregation on timestamp
let sql = "SELECT
@@ -373,7 +373,7 @@ async fn test_min_timestamp_with_fuzz_table_dict_nulls() -> Result<()> {
/// Test COUNT and COUNT DISTINCT with fuzz table containing dictionary columns with null keys and values (single and multiple partitions)
#[tokio::test]
async fn test_count_distinct_with_fuzz_table_dict_nulls() -> Result<()> {
- let (ctx_single, ctx_multi) = setup_fuzz_count_test_contexts().await?;
+ let (ctx_single, ctx_multi) = setup_fuzz_count_test_contexts()?;
// Execute the SQL query with COUNT and COUNT DISTINCT aggregations
let sql = "SELECT
@@ -414,7 +414,7 @@ async fn test_count_distinct_with_fuzz_table_dict_nulls() -> Result<()> {
/// Test MEDIAN and MEDIAN DISTINCT with fuzz table containing various numeric types and dictionary columns with null keys and values (single and multiple partitions)
#[tokio::test]
async fn test_median_distinct_with_fuzz_table_dict_nulls() -> Result<()> {
- let (ctx_single, ctx_multi) = setup_fuzz_median_test_contexts().await?;
+ let (ctx_single, ctx_multi) = setup_fuzz_median_test_contexts()?;
// Execute the SQL query with MEDIAN and MEDIAN DISTINCT aggregations
let sql = "SELECT
diff --git a/datafusion/core/tests/sql/aggregates/mod.rs b/datafusion/core/tests/sql/aggregates/mod.rs
index ede40d5c4ceca..b209e91cc81e7 100644
--- a/datafusion/core/tests/sql/aggregates/mod.rs
+++ b/datafusion/core/tests/sql/aggregates/mod.rs
@@ -259,20 +259,20 @@ impl TestData {
}
/// Sets up test contexts for TestData with both single and multiple partitions
-pub async fn setup_test_contexts(
+pub fn setup_test_contexts(
test_data: &TestData,
) -> Result<(SessionContext, SessionContext)> {
// Single partition context
- let ctx_single = create_context_with_partitions(test_data, 1).await?;
+ let ctx_single = create_context_with_partitions(test_data, 1)?;
// Multiple partition context
- let ctx_multi = create_context_with_partitions(test_data, 3).await?;
+ let ctx_multi = create_context_with_partitions(test_data, 3)?;
Ok((ctx_single, ctx_multi))
}
/// Creates a session context with the specified number of partitions and registers test data
-pub async fn create_context_with_partitions(
+pub fn create_context_with_partitions(
test_data: &TestData,
num_partitions: usize,
) -> Result {
@@ -348,7 +348,7 @@ pub async fn run_snapshot_test(
test_data: &TestData,
sql: &str,
) -> Result> {
- let (ctx_single, ctx_multi) = setup_test_contexts(test_data).await?;
+ let (ctx_single, ctx_multi) = setup_test_contexts(test_data)?;
let results = test_query_consistency(&ctx_single, &ctx_multi, sql).await?;
Ok(results)
}
@@ -430,20 +430,20 @@ impl FuzzTestData {
}
/// Sets up test contexts for fuzz table with both single and multiple partitions
-pub async fn setup_fuzz_test_contexts() -> Result<(SessionContext, SessionContext)> {
+pub fn setup_fuzz_test_contexts() -> Result<(SessionContext, SessionContext)> {
let test_data = FuzzTestData::new();
// Single partition context
- let ctx_single = create_fuzz_context_with_partitions(&test_data, 1).await?;
+ let ctx_single = create_fuzz_context_with_partitions(&test_data, 1)?;
// Multiple partition context
- let ctx_multi = create_fuzz_context_with_partitions(&test_data, 3).await?;
+ let ctx_multi = create_fuzz_context_with_partitions(&test_data, 3)?;
Ok((ctx_single, ctx_multi))
}
/// Creates a session context with fuzz table partitioned into specified number of partitions
-pub async fn create_fuzz_context_with_partitions(
+pub fn create_fuzz_context_with_partitions(
test_data: &FuzzTestData,
num_partitions: usize,
) -> Result {
@@ -604,21 +604,20 @@ impl FuzzCountTestData {
}
/// Sets up test contexts for fuzz table with duration/binary columns and both single and multiple partitions
-pub async fn setup_fuzz_count_test_contexts() -> Result<(SessionContext, SessionContext)>
-{
+pub fn setup_fuzz_count_test_contexts() -> Result<(SessionContext, SessionContext)> {
let test_data = FuzzCountTestData::new();
// Single partition context
- let ctx_single = create_fuzz_count_context_with_partitions(&test_data, 1).await?;
+ let ctx_single = create_fuzz_count_context_with_partitions(&test_data, 1)?;
// Multiple partition context
- let ctx_multi = create_fuzz_count_context_with_partitions(&test_data, 3).await?;
+ let ctx_multi = create_fuzz_count_context_with_partitions(&test_data, 3)?;
Ok((ctx_single, ctx_multi))
}
/// Creates a session context with fuzz count table partitioned into specified number of partitions
-pub async fn create_fuzz_count_context_with_partitions(
+pub fn create_fuzz_count_context_with_partitions(
test_data: &FuzzCountTestData,
num_partitions: usize,
) -> Result {
@@ -808,21 +807,20 @@ impl FuzzMedianTestData {
}
/// Sets up test contexts for fuzz table with numeric types for median testing and both single and multiple partitions
-pub async fn setup_fuzz_median_test_contexts() -> Result<(SessionContext, SessionContext)>
-{
+pub fn setup_fuzz_median_test_contexts() -> Result<(SessionContext, SessionContext)> {
let test_data = FuzzMedianTestData::new();
// Single partition context
- let ctx_single = create_fuzz_median_context_with_partitions(&test_data, 1).await?;
+ let ctx_single = create_fuzz_median_context_with_partitions(&test_data, 1)?;
// Multiple partition context
- let ctx_multi = create_fuzz_median_context_with_partitions(&test_data, 3).await?;
+ let ctx_multi = create_fuzz_median_context_with_partitions(&test_data, 3)?;
Ok((ctx_single, ctx_multi))
}
/// Creates a session context with fuzz median table partitioned into specified number of partitions
-pub async fn create_fuzz_median_context_with_partitions(
+pub fn create_fuzz_median_context_with_partitions(
test_data: &FuzzMedianTestData,
num_partitions: usize,
) -> Result {
@@ -959,21 +957,20 @@ impl FuzzTimestampTestData {
}
/// Sets up test contexts for fuzz table with timestamps and both single and multiple partitions
-pub async fn setup_fuzz_timestamp_test_contexts()
--> Result<(SessionContext, SessionContext)> {
+pub fn setup_fuzz_timestamp_test_contexts() -> Result<(SessionContext, SessionContext)> {
let test_data = FuzzTimestampTestData::new();
// Single partition context
- let ctx_single = create_fuzz_timestamp_context_with_partitions(&test_data, 1).await?;
+ let ctx_single = create_fuzz_timestamp_context_with_partitions(&test_data, 1)?;
// Multiple partition context
- let ctx_multi = create_fuzz_timestamp_context_with_partitions(&test_data, 3).await?;
+ let ctx_multi = create_fuzz_timestamp_context_with_partitions(&test_data, 3)?;
Ok((ctx_single, ctx_multi))
}
/// Creates a session context with fuzz timestamp table partitioned into specified number of partitions
-pub async fn create_fuzz_timestamp_context_with_partitions(
+pub fn create_fuzz_timestamp_context_with_partitions(
test_data: &FuzzTimestampTestData,
num_partitions: usize,
) -> Result {
diff --git a/datafusion/core/tests/user_defined/user_defined_async_scalar_functions.rs b/datafusion/core/tests/user_defined/user_defined_async_scalar_functions.rs
index dd91267d583fe..5b552e5369ef7 100644
--- a/datafusion/core/tests/user_defined/user_defined_async_scalar_functions.rs
+++ b/datafusion/core/tests/user_defined/user_defined_async_scalar_functions.rs
@@ -267,6 +267,7 @@ impl AsyncScalarUDFImpl for TestAsyncUDFImpl {
}
/// Simulates calling an async external service
+#[expect(clippy::unused_async)]
async fn call_external_service(arg1: ColumnarValue) -> Result {
Ok(arg1)
}
diff --git a/datafusion/datasource-csv/src/file_format.rs b/datafusion/datasource-csv/src/file_format.rs
index 89c3d374e68fc..a7f01f6ffec13 100644
--- a/datafusion/datasource-csv/src/file_format.rs
+++ b/datafusion/datasource-csv/src/file_format.rs
@@ -158,7 +158,6 @@ impl CsvFormat {
.map_err(|e| DataFusionError::ObjectStore(Box::new(e)))
.boxed(),
)
- .await
.map_err(DataFusionError::from)
.left_stream(),
Err(e) => {
@@ -170,7 +169,7 @@ impl CsvFormat {
/// Convert a stream of bytes into a stream of [`Bytes`] containing newline
/// delimited CSV records, while accounting for `\` and `"`.
- pub async fn read_to_delimited_chunks_from_stream<'a>(
+ pub fn read_to_delimited_chunks_from_stream<'a>(
&self,
stream: BoxStream<'a, Result>,
) -> BoxStream<'a, Result> {
diff --git a/datafusion/datasource-parquet/src/metadata.rs b/datafusion/datasource-parquet/src/metadata.rs
index ad1caa59b8d32..56abf52144028 100644
--- a/datafusion/datasource-parquet/src/metadata.rs
+++ b/datafusion/datasource-parquet/src/metadata.rs
@@ -181,14 +181,14 @@ impl<'a> DFParquetMetadata<'a> {
Self::load_page_index(self.store, self.object_meta, cached_metadata)
.await?;
if cache_metadata {
- self.cache_metadata(Arc::clone(&metadata)).await?;
+ self.cache_metadata(Arc::clone(&metadata))?;
}
return Ok(metadata);
}
let metadata = self.fetch_metadata_from_store(page_index_policy).await?;
if cache_metadata {
- self.cache_metadata(Arc::clone(&metadata)).await?;
+ self.cache_metadata(Arc::clone(&metadata))?;
}
Ok(metadata)
}
@@ -207,7 +207,7 @@ impl<'a> DFParquetMetadata<'a> {
metadata.column_index().is_some() && metadata.offset_index().is_some()
}
- async fn cache_metadata(&self, metadata: Arc) -> Result<()> {
+ fn cache_metadata(&self, metadata: Arc) -> Result<()> {
if let Some(file_metadata_cache) = &self.file_metadata_cache {
file_metadata_cache.put(
&self.object_meta.location,
diff --git a/datafusion/datasource-parquet/src/sink.rs b/datafusion/datasource-parquet/src/sink.rs
index f15f67aab0a87..df2f17c6be22d 100644
--- a/datafusion/datasource-parquet/src/sink.rs
+++ b/datafusion/datasource-parquet/src/sink.rs
@@ -171,7 +171,7 @@ impl ParquetSink {
/// Creates an AsyncArrowWriter which serializes a parquet file to an ObjectStore
/// AsyncArrowWriters are used when individual parquet file serialization is not parallelized
- async fn create_async_arrow_writer(
+ fn create_async_arrow_writer(
&self,
location: &Path,
object_store: Arc,
@@ -296,14 +296,12 @@ impl FileSink for ParquetSink {
if !parquet_opts.global.allow_single_file_parallelism
|| parquet_opts.global.content_defined_chunking.enabled
{
- let mut writer = self
- .create_async_arrow_writer(
- &path,
- Arc::clone(&object_store),
- context,
- parquet_props.clone(),
- )
- .await?;
+ let mut writer = self.create_async_arrow_writer(
+ &path,
+ Arc::clone(&object_store),
+ context,
+ parquet_props.clone(),
+ )?;
let reservation = MemoryConsumer::new(format!("ParquetSink[{path}]"))
.register(context.memory_pool());
file_write_tasks.spawn(
diff --git a/datafusion/execution/src/async_stream.rs b/datafusion/execution/src/async_stream.rs
index a84984d192d1f..e271145e03de6 100644
--- a/datafusion/execution/src/async_stream.rs
+++ b/datafusion/execution/src/async_stream.rs
@@ -388,6 +388,7 @@ mod test {
async fn unit_emit_in_select() {
use tokio::select;
+ #[expect(clippy::unused_async)]
async fn do_stuff_async() {}
let s = async_stream(|mut emitter| async move {
@@ -405,7 +406,9 @@ mod test {
async fn emit_with_select() {
use tokio::select;
+ #[expect(clippy::unused_async)]
async fn do_stuff_async() {}
+ #[expect(clippy::unused_async)]
async fn more_async_work() {}
let s = async_stream(|mut emitter| async move {
@@ -556,6 +559,7 @@ mod test {
fn inner_try_stream() {
use tokio::select;
+ #[expect(clippy::unused_async)]
async fn do_stuff_async() {}
let _ = async_stream(|mut emitter| async move {
diff --git a/datafusion/physical-plan/src/limit.rs b/datafusion/physical-plan/src/limit.rs
index a1f6074cb9ae6..ddce680fc18ad 100644
--- a/datafusion/physical-plan/src/limit.rs
+++ b/datafusion/physical-plan/src/limit.rs
@@ -839,80 +839,73 @@ mod tests {
Ok(())
}
- #[tokio::test]
- async fn test_row_number_statistics_for_global_limit() -> Result<()> {
- let row_count = row_number_statistics_for_global_limit(0, Some(10)).await?;
+ #[test]
+ fn test_row_number_statistics_for_global_limit() -> Result<()> {
+ let row_count = row_number_statistics_for_global_limit(0, Some(10))?;
assert_eq!(row_count, Precision::Exact(10));
- let row_count = row_number_statistics_for_global_limit(5, Some(10)).await?;
+ let row_count = row_number_statistics_for_global_limit(5, Some(10))?;
assert_eq!(row_count, Precision::Exact(10));
- let row_count = row_number_statistics_for_global_limit(400, Some(10)).await?;
+ let row_count = row_number_statistics_for_global_limit(400, Some(10))?;
assert_eq!(row_count, Precision::Exact(0));
- let row_count = row_number_statistics_for_global_limit(398, Some(10)).await?;
+ let row_count = row_number_statistics_for_global_limit(398, Some(10))?;
assert_eq!(row_count, Precision::Exact(2));
- let row_count = row_number_statistics_for_global_limit(398, Some(1)).await?;
+ let row_count = row_number_statistics_for_global_limit(398, Some(1))?;
assert_eq!(row_count, Precision::Exact(1));
- let row_count = row_number_statistics_for_global_limit(398, None).await?;
+ let row_count = row_number_statistics_for_global_limit(398, None)?;
assert_eq!(row_count, Precision::Exact(2));
- let row_count =
- row_number_statistics_for_global_limit(0, Some(usize::MAX)).await?;
+ let row_count = row_number_statistics_for_global_limit(0, Some(usize::MAX))?;
assert_eq!(row_count, Precision::Exact(400));
- let row_count =
- row_number_statistics_for_global_limit(398, Some(usize::MAX)).await?;
+ let row_count = row_number_statistics_for_global_limit(398, Some(usize::MAX))?;
assert_eq!(row_count, Precision::Exact(2));
- let row_count =
- row_number_inexact_statistics_for_global_limit(0, Some(10)).await?;
+ let row_count = row_number_inexact_statistics_for_global_limit(0, Some(10))?;
assert_eq!(row_count, Precision::Inexact(10));
- let row_count =
- row_number_inexact_statistics_for_global_limit(5, Some(10)).await?;
+ let row_count = row_number_inexact_statistics_for_global_limit(5, Some(10))?;
assert_eq!(row_count, Precision::Inexact(10));
// Input was Inexact, so an `nr <= skip` outcome must remain Inexact:
// the inexact estimate could be wrong, so we cannot promote 0 to
// Exact.
- let row_count =
- row_number_inexact_statistics_for_global_limit(400, Some(10)).await?;
+ let row_count = row_number_inexact_statistics_for_global_limit(400, Some(10))?;
assert_eq!(row_count, Precision::Inexact(0));
- let row_count =
- row_number_inexact_statistics_for_global_limit(398, Some(10)).await?;
+ let row_count = row_number_inexact_statistics_for_global_limit(398, Some(10))?;
assert_eq!(row_count, Precision::Inexact(2));
- let row_count =
- row_number_inexact_statistics_for_global_limit(398, Some(1)).await?;
+ let row_count = row_number_inexact_statistics_for_global_limit(398, Some(1))?;
assert_eq!(row_count, Precision::Inexact(1));
- let row_count = row_number_inexact_statistics_for_global_limit(398, None).await?;
+ let row_count = row_number_inexact_statistics_for_global_limit(398, None)?;
assert_eq!(row_count, Precision::Inexact(2));
let row_count =
- row_number_inexact_statistics_for_global_limit(0, Some(usize::MAX)).await?;
+ row_number_inexact_statistics_for_global_limit(0, Some(usize::MAX))?;
assert_eq!(row_count, Precision::Inexact(400));
let row_count =
- row_number_inexact_statistics_for_global_limit(398, Some(usize::MAX)).await?;
+ row_number_inexact_statistics_for_global_limit(398, Some(usize::MAX))?;
assert_eq!(row_count, Precision::Inexact(2));
Ok(())
}
- #[tokio::test]
- async fn test_row_number_statistics_for_local_limit() -> Result<()> {
- let row_count = row_number_statistics_for_local_limit(4, 10).await?;
+ #[test]
+ fn test_row_number_statistics_for_local_limit() -> Result<()> {
+ let row_count = row_number_statistics_for_local_limit(4, 10)?;
assert_eq!(row_count, Precision::Exact(10));
Ok(())
}
- async fn row_number_statistics_for_global_limit(
+ fn row_number_statistics_for_global_limit(
skip: usize,
fetch: Option,
) -> Result> {
@@ -940,7 +933,7 @@ mod tests {
PhysicalGroupBy::new_single(group_by_expr.clone())
}
- async fn row_number_inexact_statistics_for_global_limit(
+ fn row_number_inexact_statistics_for_global_limit(
skip: usize,
fetch: Option,
) -> Result> {
@@ -971,7 +964,7 @@ mod tests {
.num_rows)
}
- async fn row_number_statistics_for_local_limit(
+ fn row_number_statistics_for_local_limit(
num_partitions: usize,
fetch: usize,
) -> Result> {
diff --git a/datafusion/physical-plan/src/sorts/sort.rs b/datafusion/physical-plan/src/sorts/sort.rs
index a9b754dee68bd..4b30aede7d02a 100644
--- a/datafusion/physical-plan/src/sorts/sort.rs
+++ b/datafusion/physical-plan/src/sorts/sort.rs
@@ -406,7 +406,7 @@ impl ExternalSorter {
/// Appending globally sorted batches to the in-progress spill file, and clears
/// the `globally_sorted_batches` (also its memory reservation) afterwards.
- async fn consume_and_spill_append(
+ fn consume_and_spill_append(
&mut self,
globally_sorted_batches: &mut Vec,
) -> Result<()> {
@@ -445,7 +445,7 @@ impl ExternalSorter {
}
/// Finishes the in-progress spill file and moves it to the finished spill files.
- async fn spill_finish(&mut self) -> Result<()> {
+ fn spill_finish(&mut self) -> Result<()> {
let (mut in_progress_file, max_record_batch_memory) =
self.in_progress_spill_file.take().ok_or_else(|| {
internal_datafusion_err!("Should be called after `spill_append`")
@@ -500,8 +500,7 @@ impl ExternalSorter {
// already in memory, so it's okay to combine it with previously
// sorted batches, and spill together.
globally_sorted_batches.push(batch);
- self.consume_and_spill_append(&mut globally_sorted_batches)
- .await?; // reservation is freed in spill()
+ self.consume_and_spill_append(&mut globally_sorted_batches)?; // reservation is freed in spill()
} else {
globally_sorted_batches.push(batch);
}
@@ -511,9 +510,8 @@ impl ExternalSorter {
// upcoming `self.reserve_memory_for_merge()` may fail due to insufficient memory.
drop(sorted_stream);
- self.consume_and_spill_append(&mut globally_sorted_batches)
- .await?;
- self.spill_finish().await?;
+ self.consume_and_spill_append(&mut globally_sorted_batches)?;
+ self.spill_finish()?;
// Sanity check after spilling
let buffers_cleared_property =
diff --git a/datafusion/sqllogictest/bin/sqllogictests.rs b/datafusion/sqllogictest/bin/sqllogictests.rs
index cd51dc47ef5fc..da0beb0c29a28 100644
--- a/datafusion/sqllogictest/bin/sqllogictests.rs
+++ b/datafusion/sqllogictest/bin/sqllogictests.rs
@@ -473,6 +473,10 @@ async fn run_test_file_substrait_round_trip(
}
#[cfg(not(feature = "substrait"))]
+#[expect(
+ clippy::unused_async,
+ reason = "matches the substrait-enabled implementation"
+)]
async fn run_test_file_substrait_round_trip(
_test_file: TestFile,
_validator: Validator,
@@ -646,6 +650,10 @@ async fn run_test_file_with_postgres(
}
#[cfg(not(feature = "postgres"))]
+#[expect(
+ clippy::unused_async,
+ reason = "matches the postgres-enabled implementation"
+)]
async fn run_test_file_with_postgres(
_test_file: TestFile,
_validator: Validator,
@@ -771,6 +779,10 @@ async fn run_complete_file_with_postgres(
}
#[cfg(not(feature = "postgres"))]
+#[expect(
+ clippy::unused_async,
+ reason = "matches the postgres-enabled implementation"
+)]
async fn run_complete_file_with_postgres(
_test_file: TestFile,
_validator: Validator,
diff --git a/datafusion/sqllogictest/src/test_context.rs b/datafusion/sqllogictest/src/test_context.rs
index fdb04edc05101..99c3179ef1056 100644
--- a/datafusion/sqllogictest/src/test_context.rs
+++ b/datafusion/sqllogictest/src/test_context.rs
@@ -142,15 +142,15 @@ impl TestContext {
}
"information_schema_table_types.slt" => {
info!("Registering local temporary table");
- register_temp_table(test_ctx.session_ctx()).await;
+ register_temp_table(test_ctx.session_ctx());
}
"information_schema_columns.slt" => {
info!("Registering table with many types");
- register_table_with_many_types(test_ctx.session_ctx()).await;
+ register_table_with_many_types(test_ctx.session_ctx());
}
"map.slt" => {
info!("Registering table with map");
- register_table_with_map(test_ctx.session_ctx()).await;
+ register_table_with_map(test_ctx.session_ctx());
}
"avro.slt" => {
#[cfg(feature = "avro")]
@@ -173,7 +173,7 @@ impl TestContext {
test_ctx.ctx.register_udf(example_udf);
register_partition_table(&mut test_ctx).await;
info!("Registering table with many types");
- register_table_with_many_types(test_ctx.session_ctx()).await;
+ register_table_with_many_types(test_ctx.session_ctx());
}
"range_partitioning.slt" => {
info!("Registering range partitioned table");
@@ -181,7 +181,7 @@ impl TestContext {
}
"metadata.slt" | "arrow_field.slt" => {
info!("Registering metadata table tables");
- register_metadata_tables(test_ctx.session_ctx()).await;
+ register_metadata_tables(test_ctx.session_ctx());
}
"union_function.slt" => {
info!("Registering table with union column");
@@ -370,7 +370,7 @@ pub async fn register_partition_table(test_ctx: &mut TestContext) {
}
// registers a LOCAL TEMPORARY table.
-pub async fn register_temp_table(ctx: &SessionContext) {
+pub fn register_temp_table(ctx: &SessionContext) {
#[derive(Debug)]
struct TestTable(TableType);
@@ -402,7 +402,7 @@ pub async fn register_temp_table(ctx: &SessionContext) {
.unwrap();
}
-pub async fn register_table_with_many_types(ctx: &SessionContext) {
+pub fn register_table_with_many_types(ctx: &SessionContext) {
let catalog = MemoryCatalogProvider::new();
let schema = MemorySchemaProvider::new();
@@ -418,7 +418,7 @@ pub async fn register_table_with_many_types(ctx: &SessionContext) {
.unwrap();
}
-pub async fn register_table_with_map(ctx: &SessionContext) {
+pub fn register_table_with_map(ctx: &SessionContext) {
let key = Field::new("key", DataType::Int64, false);
let value = Field::new("value", DataType::Int64, true);
let map_field =
@@ -468,7 +468,7 @@ fn table_with_many_types() -> Arc {
}
/// Registers a table_with_metadata that contains both field level and Table level metadata
-pub async fn register_metadata_tables(ctx: &SessionContext) {
+pub fn register_metadata_tables(ctx: &SessionContext) {
let id = Field::new("id", DataType::Int32, true).with_metadata(HashMap::from([(
String::from("metadata_key"),
String::from("the id field"),
diff --git a/datafusion/substrait/src/logical_plan/consumer/expr/scalar_function.rs b/datafusion/substrait/src/logical_plan/consumer/expr/scalar_function.rs
index 4cd856fc562e8..47a944504c510 100644
--- a/datafusion/substrait/src/logical_plan/consumer/expr/scalar_function.rs
+++ b/datafusion/substrait/src/logical_plan/consumer/expr/scalar_function.rs
@@ -88,7 +88,7 @@ pub async fn from_scalar_function(
// In those cases we build a balanced tree of BinaryExprs
arg_list_to_binary_op_tree(op, args)
} else if let Some(builder) = BuiltinExprBuilder::try_from_name(fn_name) {
- builder.build(consumer, f, args).await
+ builder.build(consumer, f, args)
} else {
not_impl_err!("Unsupported function name: {fn_name:?}")
}
@@ -206,34 +206,32 @@ impl BuiltinExprBuilder {
}
}
- pub async fn build(
+ pub fn build(
self,
consumer: &impl SubstraitConsumer,
f: &ScalarFunction,
args: Vec,
) -> Result {
match self.expr_name.as_str() {
- "like" => Self::build_like_expr(false, false, f, args).await,
- "ilike" => Self::build_like_expr(true, false, f, args).await,
- "like_match" => Self::build_like_expr(false, false, f, args).await,
- "like_imatch" => Self::build_like_expr(true, false, f, args).await,
- "like_not_match" => Self::build_like_expr(false, true, f, args).await,
- "like_not_imatch" => Self::build_like_expr(true, true, f, args).await,
+ "like" => Self::build_like_expr(false, false, f, args),
+ "ilike" => Self::build_like_expr(true, false, f, args),
+ "like_match" => Self::build_like_expr(false, false, f, args),
+ "like_imatch" => Self::build_like_expr(true, false, f, args),
+ "like_not_match" => Self::build_like_expr(false, true, f, args),
+ "like_not_imatch" => Self::build_like_expr(true, true, f, args),
"not" | "negative" | "negate" | "is_null" | "is_not_null" | "is_true"
| "is_false" | "is_not_true" | "is_not_false" | "is_unknown"
- | "is_not_unknown" => Self::build_unary_expr(&self.expr_name, args).await,
- "and_not" | "xor" => Self::build_binary_expr(&self.expr_name, args).await,
- "between" => Self::build_between_expr(&self.expr_name, args).await,
- "logb" => {
- Self::build_custom_handling_expr(consumer, &self.expr_name, args).await
- }
+ | "is_not_unknown" => Self::build_unary_expr(&self.expr_name, args),
+ "and_not" | "xor" => Self::build_binary_expr(&self.expr_name, args),
+ "between" => Self::build_between_expr(&self.expr_name, args),
+ "logb" => Self::build_custom_handling_expr(consumer, &self.expr_name, args),
_ => {
not_impl_err!("Unsupported builtin expression: {}", self.expr_name)
}
}
}
- async fn build_unary_expr(fn_name: &str, args: Vec) -> Result {
+ fn build_unary_expr(fn_name: &str, args: Vec) -> Result {
let [arg] = match args.try_into() {
Ok(args_arr) => args_arr,
Err(_) => return substrait_err!("Expected one argument for {fn_name} expr"),
@@ -257,7 +255,7 @@ impl BuiltinExprBuilder {
Ok(expr)
}
- async fn build_like_expr(
+ fn build_like_expr(
case_insensitive: bool,
negated: bool,
f: &ScalarFunction,
@@ -306,7 +304,7 @@ impl BuiltinExprBuilder {
}))
}
- async fn build_binary_expr(fn_name: &str, args: Vec) -> Result {
+ fn build_binary_expr(fn_name: &str, args: Vec) -> Result {
let [a, b] = match args.try_into() {
Ok(args_arr) => args_arr,
Err(_) => {
@@ -330,7 +328,7 @@ impl BuiltinExprBuilder {
Self::build_and_not_expr(or_expr, and_expr)
}
- async fn build_between_expr(fn_name: &str, args: Vec) -> Result {
+ fn build_between_expr(fn_name: &str, args: Vec) -> Result {
let [expression, low, high] = match args.try_into() {
Ok(args_arr) => args_arr,
Err(_) => {
@@ -347,18 +345,18 @@ impl BuiltinExprBuilder {
}
//This handles any functions that require custom handling
- async fn build_custom_handling_expr(
+ fn build_custom_handling_expr(
consumer: &impl SubstraitConsumer,
fn_name: &str,
args: Vec,
) -> Result {
match fn_name {
- "logb" => Self::build_logb_expr(consumer, args).await,
+ "logb" => Self::build_logb_expr(consumer, args),
_ => not_impl_err!("Unsupported custom handled expression: {}", fn_name),
}
}
- async fn build_logb_expr(
+ fn build_logb_expr(
consumer: &impl SubstraitConsumer,
args: Vec,
) -> Result {
diff --git a/datafusion/substrait/src/serializer.rs b/datafusion/substrait/src/serializer.rs
index ee71bc3121afe..bcc9f5cf50eac 100644
--- a/datafusion/substrait/src/serializer.rs
+++ b/datafusion/substrait/src/serializer.rs
@@ -70,12 +70,12 @@ pub async fn deserialize(path: impl AsRef) -> Result> {
let mut file = OpenOptions::new().read(true).open(path).await?;
file.read_to_end(&mut protobuf_in).await?;
- deserialize_bytes(protobuf_in).await
+ deserialize_bytes(&protobuf_in)
}
/// Deserializes a plan from the bytes.
-pub async fn deserialize_bytes(proto_bytes: Vec) -> Result> {
- Ok(Box::new(Message::decode(&*proto_bytes).map_err(|e| {
+pub fn deserialize_bytes(proto_bytes: &[u8]) -> Result> {
+ Ok(Box::new(Message::decode(proto_bytes).map_err(|e| {
DataFusionError::Substrait(format!("Failed to decode plan: {e}"))
})?))
}
diff --git a/datafusion/substrait/tests/cases/roundtrip_logical_plan.rs b/datafusion/substrait/tests/cases/roundtrip_logical_plan.rs
index 018e1aef80ea1..f084d3170edcc 100644
--- a/datafusion/substrait/tests/cases/roundtrip_logical_plan.rs
+++ b/datafusion/substrait/tests/cases/roundtrip_logical_plan.rs
@@ -2224,7 +2224,7 @@ fn check_post_join_filters(rel: &Rel) -> Result<()> {
}
}
-async fn verify_post_join_filter_value(proto: Box) -> Result<()> {
+fn verify_post_join_filter_value(proto: &Plan) -> Result<()> {
for relation in &proto.relations {
match relation.rel_type.as_ref() {
Some(rt) => match rt {
@@ -2263,10 +2263,7 @@ fn count_read_filters(rel: &Rel, filter_count: &mut u32) -> Result<()> {
}
}
-async fn assert_read_filter_count(
- proto: Box,
- expected_filter_count: u32,
-) -> Result<()> {
+fn assert_read_filter_count(proto: &Plan, expected_filter_count: u32) -> Result<()> {
let mut filter_count: u32 = 0;
for relation in &proto.relations {
match relation.rel_type.as_ref() {
@@ -2644,7 +2641,7 @@ async fn roundtrip_verify_post_join_filter(sql: &str) -> Result<()> {
let proto = roundtrip_with_ctx(sql, ctx).await?;
// verify that the join filters are None
- verify_post_join_filter_value(proto).await
+ verify_post_join_filter_value(&proto)
}
async fn roundtrip_verify_read_filter_count(
@@ -2655,7 +2652,7 @@ async fn roundtrip_verify_read_filter_count(
let proto = roundtrip_with_ctx(sql, ctx).await?;
// verify that filter counts in read relations are as expected
- assert_read_filter_count(proto, expected_filter_count).await
+ assert_read_filter_count(&proto, expected_filter_count)
}
async fn roundtrip_all_types(sql: &str) -> Result<()> {
diff --git a/docs/source/library-user-guide/upgrading/55.0.0.md b/docs/source/library-user-guide/upgrading/55.0.0.md
index 5f1609af30fc6..6ca4d71a7883f 100644
--- a/docs/source/library-user-guide/upgrading/55.0.0.md
+++ b/docs/source/library-user-guide/upgrading/55.0.0.md
@@ -847,6 +847,35 @@ fn catalog_list(&self) -> Arc {
See [PR #23703](https://github.com/apache/datafusion/pull/23703) for details.
+### Unused `async` removed from several public functions
+
+Public functions that were declared `async` but never awaited anything are now
+synchronous:
+
+- `CsvFormat::read_to_delimited_chunks_from_stream` (in
+ `datafusion_datasource_csv`, re-exported as
+ `datafusion::datasource::file_format::csv::CsvFormat`)
+- `datafusion_substrait::serializer::deserialize_bytes`, which now also borrows
+ its input as `&[u8]` instead of taking an owned `Vec`
+- `datafusion::test_util::parquet::TestParquetFile::create_scan`
+
+**Migration guide:**
+
+Remove `.await` from call sites; the compiler flags each one, since `.await`
+on a non-future value does not compile:
+
+```rust,ignore
+// Before
+let stream = csv_format
+ .read_to_delimited_chunks_from_stream(input)
+ .await;
+let plan = deserialize_bytes(proto_bytes).await?;
+
+// After
+let stream = csv_format.read_to_delimited_chunks_from_stream(input);
+let plan = deserialize_bytes(&proto_bytes)?;
+```
+
### `MSRV` updated to 1.94.0
The Minimum Supported Rust Version (MSRV) has been updated to [`1.94.0`].
From 0d8482c76984e615990b594a44fbc596c6b24a34 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Fri, 24 Jul 2026 15:56:39 -0400
Subject: [PATCH 015/109] chore(deps-dev): bump webpack-dev-server from 5.2.6
to 6.0.0 in /datafusion/wasmtest/datafusion-wasm-app (#23868)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Bumps
[webpack-dev-server](https://github.com/webpack/webpack-dev-server) from
5.2.6 to 6.0.0.
Release notes
Sourced from webpack-dev-server's
releases .
v6.0.0
Major Changes
Bump Express to v5. See the Express 5
migration guide for the full list of breaking changes. (by @bjohansebas in
#5674 )
Bump the webpack peer dependency range from
^5.0.0 to ^5.101.0. (by @bjohansebas in
#5674 )
Drop support for Node.js < 22.15.0. (by @bjohansebas in
#5674 )
Convert the source to native ES modules. The package keeps
"type": "module" and now exposes both
an ESM and a CommonJS build via the exports field: ESM
consumers import the native lib/, while CommonJS consumers
require() a transpiled dist/ build, allowing
the package to be consumed from both ESM and CommonJS without relying on
require(ESM) for CommonJS consumers. (by @bjohansebas in
#5674 )
Remove CLI flags. Use the serve command from
webpack-cli together with a configuration file or the
programmatic API instead. (by @bjohansebas in
#5674 )
Remove the internalIP and internalIPSync
static methods from Server. Resolve the local IP yourself
if you need it. (by @bjohansebas in
#5674 )
Remove the bypass option from proxy configuration. Use
the router or context options provided by
http-proxy-middleware instead. (by @bjohansebas in
#5674 )
Remove SockJS support. The webSocketServer option no
longer accepts "sockjs"; use the default
"ws" transport instead. (by @bjohansebas in
#5674 )
Remove the spdy dependency. Use the built-in
node:http2 module via the server option for
HTTP/2 support. (by @bjohansebas in
#5674 )
Update http-proxy-middleware to v4. See the http-proxy-middleware
v3 release notes and v4
release notes for the full list of breaking changes. (by @bjohansebas in
#5674 )
Update webpack-dev-middleware to v8 and sync
originalUrl for middleware compatibility.
server.middleware.getFilenameFromUrl() is now asynchronous
and resolves to { filename, extra: { stats, outputFileSystem }
}. See the webpack-dev-middleware
v8 release notes for details. (by @bjohansebas in
#5674 )
Minor Changes
Add plugin support. webpack-dev-server can now be used
as a webpack plugin, integrating with the compiler lifecycle without
explicitly passing a compiler, preventing multiple server starts on
recompilation, ensuring clean shutdown, and supporting
MultiCompiler setups with multiple independent plugin
servers. (by @bjohansebas in
#5674 )
Enable the compression middleware for HTTP/2 connections. (by @bjohansebas in
#5674 )
Remove the colorette dependency in favor of native ANSI
styling. (by @bjohansebas in
#5674 )
Update chokidar to v5 and extend
watchFiles.options.ignored to support glob string patterns
via tinyglobby. (by @bjohansebas in
#5674 )
Use compiler.platform to determine the target
environment instead of inspecting the resolved target
string. Universal targets ("universal" or
["web", "node"], where
compiler.platform.universal is true since
webpack 5.108.0) are treated as web targets so the client
runtime is injected. (by @bjohansebas in
#5674 )
Use the WHATWG URL API instead of the deprecated
url.parse. (by @bjohansebas in
#5674 )
Patch Changes
Bump production dependencies, notably open to v11 and
p-retry to v8. (by @bjohansebas in
#5674 )
Reject cross-site requests to the internal open-editor
and invalidate endpoints. They performed state-changing
actions (opening a file in the editor, forcing a recompilation) on any
GET request, so a page the developer visited could trigger them. They
now require a same-origin request, validated via
Sec-Fetch-Site with an
Origin/Host fallback. (by @bjohansebas in
#5691 )
Treat loopback aliases (127.0.0.1, ::1,
localhost) as equivalent in isSameOrigin so
the WebSocket client does not reject valid same-origin connections. (by
@bjohansebas
in #5674 )
Migrate the test suite from Jest to node:test and set up
the jsdom environment. (by @bjohansebas in
#5674 )
... (truncated)
Changelog
Sourced from webpack-dev-server's
changelog .
6.0.0
Major Changes
Bump Express to v5. See the Express 5
migration guide for the full list of breaking changes. (by @bjohansebas in
#5674 )
Bump the webpack peer dependency range from
^5.0.0 to ^5.101.0. (by @bjohansebas in
#5674 )
Drop support for Node.js < 22.15.0. (by @bjohansebas in
#5674 )
Convert the source to native ES modules. The package keeps
"type": "module" and now exposes both
an ESM and a CommonJS build via the exports field: ESM
consumers import the native lib/, while
CommonJS consumers require() a transpiled
dist/ build — so the package works from both ESM and
CommonJS, including environments where require(ESM) is not
supported. (by @bjohansebas in
#5674 )
Remove CLI flags. Use the serve command from
webpack-cli together with a configuration file or the
programmatic API instead. (by @bjohansebas in
#5674 )
Remove the internalIP and internalIPSync
static methods from Server. Resolve the local IP yourself
if you need it. (by @bjohansebas in
#5674 )
Remove the bypass option from proxy configuration. Use
the router or context options provided by
http-proxy-middleware instead. (by @bjohansebas in
#5674 )
Remove SockJS support. The webSocketServer option no
longer accepts "sockjs"; use the default
"ws" transport instead. (by @bjohansebas in
#5674 )
Remove the spdy dependency. Use the built-in
node:http2 module via the server option for
HTTP/2 support. (by @bjohansebas in
#5674 )
Update http-proxy-middleware to v4. See the http-proxy-middleware
v3 release notes and v4
release notes for the full list of breaking changes. (by @bjohansebas in
#5674 )
Update webpack-dev-middleware to v8 and sync
originalUrl for middleware compatibility.
server.middleware.getFilenameFromUrl() is now asynchronous
and resolves to { filename, extra: { stats, outputFileSystem }
}. See the webpack-dev-middleware
v8 release notes for details. (by @bjohansebas in
#5674 )
Minor Changes
Add plugin support. webpack-dev-server can now be used
as a webpack plugin, integrating with the compiler lifecycle without
explicitly passing a compiler, preventing multiple server starts on
recompilation, ensuring clean shutdown, and supporting
MultiCompiler setups with multiple independent plugin
servers. (by @bjohansebas in
#5674 )
Enable the compression middleware for HTTP/2 connections. (by @bjohansebas in
#5674 )
Remove the colorette dependency in favor of native ANSI
styling. (by @bjohansebas in
#5674 )
Update chokidar to v5 and extend
watchFiles.options.ignored to support glob string patterns
via tinyglobby. (by @bjohansebas in
#5674 )
Use compiler.platform to determine the target
environment instead of inspecting the resolved target
string. Universal targets ("universal" or
["web", "node"], where
compiler.platform.universal is true since
webpack 5.108.0) are treated as web targets so the client
runtime is injected. (by @bjohansebas in
#5674 )
Use the WHATWG URL API instead of the deprecated
url.parse. (by @bjohansebas in
#5674 )
Patch Changes
Bump production dependencies, notably open to v11 and
p-retry to v8. (by @bjohansebas in
#5674 )
Reject cross-site requests to the internal open-editor
and invalidate endpoints. They performed state-changing
actions (opening a file in the editor, forcing a recompilation) on any
GET request, so a page the developer visited could trigger them. They
now require a same-origin request, validated via
Sec-Fetch-Site with an
Origin/Host fallback. (by @bjohansebas in
#5691 )
Treat loopback aliases (127.0.0.1, ::1,
localhost) as equivalent in isSameOrigin so
the WebSocket client does not reject valid same-origin connections. (by
@bjohansebas
in #5674 )
Migrate the test suite from Jest to node:test and set up
the jsdom environment. (by @bjohansebas in
#5674 )
... (truncated)
Commits
05cb792
chore(release): new release (#5692 )
a451839
fix: handle middleware teardown in plugin mode (#5703 )
c2d23a7
fix: load ESM-only dependencies with native import() in the CommonJS
build (#...
ba54764
fix: reject cross-site requests to open-editor and invalidate endpoints
(#5691 )
2b369b3
fixup!
08a0ea7
fix: ensure undefined options default to an empty object in Server
constructor
797b9e7
fix: handle undefined options in Server constructor
e90221c
feat: plugin support (#5650 )
4c351e1
feat: support universal platform as a web target (#5690 )
2236aa4
chore: update http-proxy-middleware to version 4.1.1 and add tests for
pathRe...
Additional commits viewable in compare
view
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/apache/datafusion/network/alerts).
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
.../datafusion-wasm-app/package-lock.json | 3635 ++++++++---------
.../wasmtest/datafusion-wasm-app/package.json | 2 +-
2 files changed, 1709 insertions(+), 1928 deletions(-)
diff --git a/datafusion/wasmtest/datafusion-wasm-app/package-lock.json b/datafusion/wasmtest/datafusion-wasm-app/package-lock.json
index e863fe5e8da15..6fd3fb8ab0646 100644
--- a/datafusion/wasmtest/datafusion-wasm-app/package-lock.json
+++ b/datafusion/wasmtest/datafusion-wasm-app/package-lock.json
@@ -15,7 +15,7 @@
"copy-webpack-plugin": "14.0.0",
"webpack": "5.105.0",
"webpack-cli": "5.1.4",
- "webpack-dev-server": "5.2.6"
+ "webpack-dev-server": "6.0.0"
}
},
"../pkg": {
@@ -391,21 +391,20 @@
"dev": true
},
"node_modules/@types/express": {
- "version": "4.17.25",
- "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.25.tgz",
- "integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==",
+ "version": "5.0.6",
+ "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz",
+ "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==",
"dev": true,
"dependencies": {
"@types/body-parser": "*",
- "@types/express-serve-static-core": "^4.17.33",
- "@types/qs": "*",
- "@types/serve-static": "^1"
+ "@types/express-serve-static-core": "^5.0.0",
+ "@types/serve-static": "^2"
}
},
"node_modules/@types/express-serve-static-core": {
- "version": "4.17.36",
- "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.36.tgz",
- "integrity": "sha512-zbivROJ0ZqLAtMzgzIUC4oNqDG9iF0lSsAqpOD9kbs5xcIM3dTiyuHvBc7R8MtWBp3AAWGaovJa+wzWPjLYW7Q==",
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.2.tgz",
+ "integrity": "sha512-d3KvEXBSo/lOAMc2u6fkyDHBvetBHeqD7wm/AcXfLpSOQwlmG9D/aQ0SFswVjv05p7ullQS7Mjohj6/VdbZuTg==",
"dev": true,
"dependencies": {
"@types/node": "*",
@@ -415,20 +414,11 @@
}
},
"node_modules/@types/http-errors": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.2.tgz",
- "integrity": "sha512-lPG6KlZs88gef6aD85z3HNkztpj7w2R7HmR3gygjfXCQmsLloWNARFkMuzKiiY8FGdh1XDpgBdrSf4aKDiA7Kg==",
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz",
+ "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==",
"dev": true
},
- "node_modules/@types/http-proxy": {
- "version": "1.17.12",
- "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.12.tgz",
- "integrity": "sha512-kQtujO08dVtQ2wXAuSFfk9ASy3sug4+ogFR8Kd8UgP8PEuc1/G/8yjYRmp//PcDNJEUKOza/MrQu15bouEUCiw==",
- "dev": true,
- "dependencies": {
- "@types/node": "*"
- }
- },
"node_modules/@types/json-schema": {
"version": "7.0.15",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
@@ -459,13 +449,6 @@
"integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==",
"dev": true
},
- "node_modules/@types/retry": {
- "version": "0.12.2",
- "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz",
- "integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/@types/send": {
"version": "0.17.1",
"resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.1.tgz",
@@ -487,24 +470,12 @@
}
},
"node_modules/@types/serve-static": {
- "version": "1.15.7",
- "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz",
- "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==",
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz",
+ "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==",
"dev": true,
- "license": "MIT",
"dependencies": {
"@types/http-errors": "*",
- "@types/node": "*",
- "@types/send": "*"
- }
- },
- "node_modules/@types/sockjs": {
- "version": "0.3.36",
- "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz",
- "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
"@types/node": "*"
}
},
@@ -721,18 +692,43 @@
"dev": true
},
"node_modules/accepts": {
- "version": "1.3.8",
- "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
- "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
+ "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
"dev": true,
"dependencies": {
- "mime-types": "~2.1.34",
- "negotiator": "0.6.3"
+ "mime-types": "^3.0.0",
+ "negotiator": "^1.0.0"
},
"engines": {
"node": ">= 0.6"
}
},
+ "node_modules/accepts/node_modules/mime-db": {
+ "version": "1.54.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/accepts/node_modules/mime-types": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
+ "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
+ "dev": true,
+ "dependencies": {
+ "mime-db": "^1.54.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
"node_modules/acorn": {
"version": "8.15.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
@@ -814,27 +810,6 @@
"ansi-html": "bin/ansi-html"
}
},
- "node_modules/anymatch": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
- "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "normalize-path": "^3.0.0",
- "picomatch": "^2.0.4"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/array-flatten": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
- "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/asn1js": {
"version": "3.0.10",
"resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.10.tgz",
@@ -867,101 +842,46 @@
"node_modules/batch": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz",
- "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=",
+ "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==",
"dev": true
},
- "node_modules/binary-extensions": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
- "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/body-parser": {
- "version": "1.20.5",
- "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz",
- "integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==",
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz",
+ "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==",
"dev": true,
"dependencies": {
- "bytes": "~3.1.2",
- "content-type": "~1.0.5",
- "debug": "2.6.9",
- "depd": "2.0.0",
- "destroy": "~1.2.0",
- "http-errors": "~2.0.1",
- "iconv-lite": "~0.4.24",
- "on-finished": "~2.4.1",
- "qs": "~6.15.1",
- "raw-body": "~2.5.3",
- "type-is": "~1.6.18",
- "unpipe": "~1.0.0"
+ "bytes": "^3.1.2",
+ "content-type": "^2.0.0",
+ "debug": "^4.4.3",
+ "http-errors": "^2.0.1",
+ "iconv-lite": "^0.7.2",
+ "on-finished": "^2.4.1",
+ "qs": "^6.15.2",
+ "raw-body": "^3.0.2",
+ "type-is": "^2.1.0"
},
"engines": {
- "node": ">= 0.8",
- "npm": "1.2.8000 || >= 1.4.16"
- }
- },
- "node_modules/body-parser/node_modules/debug": {
- "version": "2.6.9",
- "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
- "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ms": "2.0.0"
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
}
},
- "node_modules/body-parser/node_modules/depd": {
+ "node_modules/body-parser/node_modules/content-type": {
"version": "2.0.0",
- "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
- "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/body-parser/node_modules/http-errors": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
- "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz",
+ "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==",
"dev": true,
- "dependencies": {
- "depd": "~2.0.0",
- "inherits": "~2.0.4",
- "setprototypeof": "~1.2.0",
- "statuses": "~2.0.2",
- "toidentifier": "~1.0.1"
- },
"engines": {
- "node": ">= 0.8"
+ "node": ">=18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
- "node_modules/body-parser/node_modules/inherits": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
- "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
- "dev": true
- },
- "node_modules/body-parser/node_modules/statuses": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
- "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
- "dev": true,
- "engines": {
- "node": ">= 0.8"
- }
- },
"node_modules/bonjour-service": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.3.0.tgz",
@@ -1029,7 +949,6 @@
"resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz",
"integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==",
"dev": true,
- "license": "MIT",
"dependencies": {
"run-applescript": "^7.0.0"
},
@@ -1063,7 +982,6 @@
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"dev": true,
- "license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"function-bind": "^1.1.2"
@@ -1077,7 +995,6 @@
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
"dev": true,
- "license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"get-intrinsic": "^1.3.0"
@@ -1110,28 +1027,18 @@
]
},
"node_modules/chokidar": {
- "version": "3.6.0",
- "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
- "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz",
+ "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==",
"dev": true,
- "license": "MIT",
"dependencies": {
- "anymatch": "~3.1.2",
- "braces": "~3.0.2",
- "glob-parent": "~5.1.2",
- "is-binary-path": "~2.1.0",
- "is-glob": "~4.0.1",
- "normalize-path": "~3.0.0",
- "readdirp": "~3.6.0"
+ "readdirp": "^5.0.0"
},
"engines": {
- "node": ">= 8.10.0"
+ "node": ">= 20.19.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
- },
- "optionalDependencies": {
- "fsevents": "~2.3.2"
}
},
"node_modules/chrome-trace-event": {
@@ -1250,65 +1157,44 @@
}
},
"node_modules/content-disposition": {
- "version": "0.5.4",
- "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
- "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz",
+ "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==",
"dev": true,
- "license": "MIT",
- "dependencies": {
- "safe-buffer": "5.2.1"
- },
"engines": {
- "node": ">= 0.6"
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
}
},
- "node_modules/content-disposition/node_modules/safe-buffer": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
- "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT"
- },
"node_modules/content-type": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
"dev": true,
- "license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/cookie": {
- "version": "0.7.1",
- "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
- "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
"dev": true,
- "license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/cookie-signature": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
- "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
+ "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
"dev": true,
- "license": "MIT"
+ "engines": {
+ "node": ">=6.6.0"
+ }
},
"node_modules/copy-webpack-plugin": {
"version": "14.0.0",
@@ -1355,12 +1241,6 @@
"node": ">=20.0.0"
}
},
- "node_modules/core-util-is": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
- "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
- "dev": true
- },
"node_modules/cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@@ -1380,27 +1260,33 @@
"link": true
},
"node_modules/debug": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
- "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
- "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)",
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"dev": true,
"dependencies": {
- "ms": "^2.1.1"
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
}
},
"node_modules/debug/node_modules/ms": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true
},
"node_modules/default-browser": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz",
- "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==",
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz",
+ "integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==",
"dev": true,
- "license": "MIT",
"dependencies": {
"bundle-name": "^4.1.0",
"default-browser-id": "^5.0.0"
@@ -1413,11 +1299,10 @@
}
},
"node_modules/default-browser-id": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.0.tgz",
- "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==",
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz",
+ "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==",
"dev": true,
- "license": "MIT",
"engines": {
"node": ">=18"
},
@@ -1430,7 +1315,6 @@
"resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz",
"integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==",
"dev": true,
- "license": "MIT",
"engines": {
"node": ">=12"
},
@@ -1439,31 +1323,14 @@
}
},
"node_modules/depd": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
- "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=",
- "dev": true,
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/destroy": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
- "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
"dev": true,
- "license": "MIT",
"engines": {
- "node": ">= 0.8",
- "npm": "1.2.8000 || >= 1.4.16"
+ "node": ">= 0.8"
}
},
- "node_modules/detect-node": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.4.tgz",
- "integrity": "sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==",
- "dev": true
- },
"node_modules/dns-packet": {
"version": "5.6.1",
"resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz",
@@ -1482,7 +1349,6 @@
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"dev": true,
- "license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
"es-errors": "^1.3.0",
@@ -1496,8 +1362,7 @@
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
- "dev": true,
- "license": "MIT"
+ "dev": true
},
"node_modules/electron-to-chromium": {
"version": "1.5.286",
@@ -1510,7 +1375,6 @@
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
"dev": true,
- "license": "MIT",
"engines": {
"node": ">= 0.8"
}
@@ -1545,7 +1409,6 @@
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"dev": true,
- "license": "MIT",
"engines": {
"node": ">= 0.4"
}
@@ -1555,7 +1418,6 @@
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"dev": true,
- "license": "MIT",
"engines": {
"node": ">= 0.4"
}
@@ -1567,11 +1429,10 @@
"dev": true
},
"node_modules/es-object-atoms": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
- "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz",
+ "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==",
"dev": true,
- "license": "MIT",
"dependencies": {
"es-errors": "^1.3.0"
},
@@ -1591,7 +1452,7 @@
"node_modules/escape-html": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
- "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=",
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
"dev": true
},
"node_modules/eslint-scope": {
@@ -1642,17 +1503,10 @@
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
"dev": true,
- "license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
- "node_modules/eventemitter3": {
- "version": "4.0.7",
- "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
- "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
- "dev": true
- },
"node_modules/events": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
@@ -1663,112 +1517,83 @@
}
},
"node_modules/express": {
- "version": "4.22.2",
- "resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz",
- "integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==",
- "dev": true,
- "dependencies": {
- "accepts": "~1.3.8",
- "array-flatten": "1.1.1",
- "body-parser": "~1.20.5",
- "content-disposition": "~0.5.4",
- "content-type": "~1.0.4",
- "cookie": "~0.7.1",
- "cookie-signature": "~1.0.6",
- "debug": "2.6.9",
- "depd": "2.0.0",
- "encodeurl": "~2.0.0",
- "escape-html": "~1.0.3",
- "etag": "~1.8.1",
- "finalhandler": "~1.3.1",
- "fresh": "~0.5.2",
- "http-errors": "~2.0.0",
- "merge-descriptors": "1.0.3",
- "methods": "~1.1.2",
- "on-finished": "~2.4.1",
- "parseurl": "~1.3.3",
- "path-to-regexp": "~0.1.12",
- "proxy-addr": "~2.0.7",
- "qs": "~6.15.1",
- "range-parser": "~1.2.1",
- "safe-buffer": "5.2.1",
- "send": "~0.19.0",
- "serve-static": "~1.16.2",
- "setprototypeof": "1.2.0",
- "statuses": "~2.0.1",
- "type-is": "~1.6.18",
- "utils-merge": "1.0.1",
- "vary": "~1.1.2"
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
+ "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
+ "dev": true,
+ "dependencies": {
+ "accepts": "^2.0.0",
+ "body-parser": "^2.2.1",
+ "content-disposition": "^1.0.0",
+ "content-type": "^1.0.5",
+ "cookie": "^0.7.1",
+ "cookie-signature": "^1.2.1",
+ "debug": "^4.4.0",
+ "depd": "^2.0.0",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "etag": "^1.8.1",
+ "finalhandler": "^2.1.0",
+ "fresh": "^2.0.0",
+ "http-errors": "^2.0.0",
+ "merge-descriptors": "^2.0.0",
+ "mime-types": "^3.0.0",
+ "on-finished": "^2.4.1",
+ "once": "^1.4.0",
+ "parseurl": "^1.3.3",
+ "proxy-addr": "^2.0.7",
+ "qs": "^6.14.0",
+ "range-parser": "^1.2.1",
+ "router": "^2.2.0",
+ "send": "^1.1.0",
+ "serve-static": "^2.2.0",
+ "statuses": "^2.0.1",
+ "type-is": "^2.0.1",
+ "vary": "^1.1.2"
},
"engines": {
- "node": ">= 0.10.0"
+ "node": ">= 18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
- "node_modules/express/node_modules/debug": {
- "version": "2.6.9",
- "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
- "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "node_modules/express/node_modules/mime-db": {
+ "version": "1.54.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
"dev": true,
- "license": "MIT",
- "dependencies": {
- "ms": "2.0.0"
+ "engines": {
+ "node": ">= 0.6"
}
},
- "node_modules/express/node_modules/depd": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
- "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+ "node_modules/express/node_modules/mime-types": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
+ "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
"dev": true,
- "license": "MIT",
+ "dependencies": {
+ "mime-db": "^1.54.0"
+ },
"engines": {
- "node": ">= 0.8"
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
}
},
- "node_modules/express/node_modules/safe-buffer": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
- "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT"
- },
- "node_modules/express/node_modules/statuses": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
- "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/fast-deep-equal": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
- "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
- "dev": true
- },
- "node_modules/fast-uri": {
- "version": "3.1.4",
- "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.4.tgz",
- "integrity": "sha512-8JnbkQ4juDyvYs4mgFGQqg4yCYtFDtUtmp2QIQq11ZZe5CFQ5wcqm1rqDgAh/QdMySuBnPzMUiJUNZG5N/AiQw==",
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true
+ },
+ "node_modules/fast-uri": {
+ "version": "3.1.4",
+ "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.4.tgz",
+ "integrity": "sha512-8JnbkQ4juDyvYs4mgFGQqg4yCYtFDtUtmp2QIQq11ZZe5CFQ5wcqm1rqDgAh/QdMySuBnPzMUiJUNZG5N/AiQw==",
"dev": true,
"funding": [
{
@@ -1790,18 +1615,6 @@
"node": ">= 4.9.1"
}
},
- "node_modules/faye-websocket": {
- "version": "0.11.4",
- "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz",
- "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==",
- "dev": true,
- "dependencies": {
- "websocket-driver": ">=0.5.1"
- },
- "engines": {
- "node": ">=0.8.0"
- }
- },
"node_modules/fill-range": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
@@ -1815,42 +1628,24 @@
}
},
"node_modules/finalhandler": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
- "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz",
+ "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==",
"dev": true,
- "license": "MIT",
"dependencies": {
- "debug": "2.6.9",
- "encodeurl": "~2.0.0",
- "escape-html": "~1.0.3",
- "on-finished": "2.4.1",
- "parseurl": "~1.3.3",
- "statuses": "2.0.1",
- "unpipe": "~1.0.0"
+ "debug": "^4.4.0",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "on-finished": "^2.4.1",
+ "parseurl": "^1.3.3",
+ "statuses": "^2.0.1"
},
"engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/finalhandler/node_modules/debug": {
- "version": "2.6.9",
- "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
- "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ms": "2.0.0"
- }
- },
- "node_modules/finalhandler/node_modules/statuses": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
- "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
+ "node": ">= 18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
}
},
"node_modules/find-up": {
@@ -1866,59 +1661,22 @@
"node": ">=8"
}
},
- "node_modules/follow-redirects": {
- "version": "1.16.0",
- "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz",
- "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==",
- "dev": true,
- "funding": [
- {
- "type": "individual",
- "url": "https://github.com/sponsors/RubenVerborgh"
- }
- ],
- "engines": {
- "node": ">=4.0"
- },
- "peerDependenciesMeta": {
- "debug": {
- "optional": true
- }
- }
- },
"node_modules/forwarded": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
"integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
"dev": true,
- "license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/fresh": {
- "version": "0.5.2",
- "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
- "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/fsevents": {
- "version": "2.3.3",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
- "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
+ "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
"dev": true,
- "hasInstallScript": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
"engines": {
- "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ "node": ">= 0.8"
}
},
"node_modules/function-bind": {
@@ -1935,7 +1693,6 @@
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"dev": true,
- "license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"es-define-property": "^1.0.1",
@@ -1960,7 +1717,6 @@
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"dev": true,
- "license": "MIT",
"dependencies": {
"dunder-proto": "^1.0.1",
"es-object-atoms": "^1.0.0"
@@ -1969,18 +1725,6 @@
"node": ">= 0.4"
}
},
- "node_modules/glob-parent": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
- "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
- "dev": true,
- "dependencies": {
- "is-glob": "^4.0.1"
- },
- "engines": {
- "node": ">= 6"
- }
- },
"node_modules/glob-to-regexp": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz",
@@ -1992,7 +1736,6 @@
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"dev": true,
- "license": "MIT",
"engines": {
"node": ">= 0.4"
},
@@ -2006,12 +1749,6 @@
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
"dev": true
},
- "node_modules/handle-thing": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz",
- "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==",
- "dev": true
- },
"node_modules/has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
@@ -2038,7 +1775,6 @@
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"dev": true,
- "license": "MIT",
"engines": {
"node": ">= 0.4"
},
@@ -2047,11 +1783,10 @@
}
},
"node_modules/hasown": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
- "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
+ "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
"dev": true,
- "license": "MIT",
"dependencies": {
"function-bind": "^1.1.2"
},
@@ -2059,132 +1794,71 @@
"node": ">= 0.4"
}
},
- "node_modules/hpack.js": {
- "version": "2.1.6",
- "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz",
- "integrity": "sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=",
- "dev": true,
- "dependencies": {
- "inherits": "^2.0.1",
- "obuf": "^1.0.0",
- "readable-stream": "^2.0.1",
- "wbuf": "^1.1.0"
- }
- },
- "node_modules/http-deceiver": {
- "version": "1.2.7",
- "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz",
- "integrity": "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=",
- "dev": true
- },
"node_modules/http-errors": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
- "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
+ "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
"dev": true,
- "license": "MIT",
"dependencies": {
- "depd": "2.0.0",
- "inherits": "2.0.4",
- "setprototypeof": "1.2.0",
- "statuses": "2.0.1",
- "toidentifier": "1.0.1"
+ "depd": "~2.0.0",
+ "inherits": "~2.0.4",
+ "setprototypeof": "~1.2.0",
+ "statuses": "~2.0.2",
+ "toidentifier": "~1.0.1"
},
"engines": {
"node": ">= 0.8"
- }
- },
- "node_modules/http-errors/node_modules/depd": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
- "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/http-errors/node_modules/inherits": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
- "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/http-errors/node_modules/statuses": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
- "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/http-parser-js": {
- "version": "0.5.8",
- "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz",
- "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==",
- "dev": true
- },
- "node_modules/http-proxy": {
- "version": "1.18.1",
- "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz",
- "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==",
- "dev": true,
- "dependencies": {
- "eventemitter3": "^4.0.0",
- "follow-redirects": "^1.0.0",
- "requires-port": "^1.0.0"
},
- "engines": {
- "node": ">=8.0.0"
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
}
},
"node_modules/http-proxy-middleware": {
- "version": "2.0.10",
- "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.10.tgz",
- "integrity": "sha512-RKzRWNPxUZqbuk3BC5mGVJbBnWgr+diEnjJexIOytFbBzDy88Fbh/YvBr3DsNrl1jYAfjWfpATEv0NO35FDuPQ==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-4.2.0.tgz",
+ "integrity": "sha512-ZA+oNOoM+GLoFTIzhkJptVQov73Srep2LBqhF8hG8CIPKO3nam1jonXVQ/QUH8RbwsmaaVz2SOJdzBNBHNtKbw==",
"dev": true,
"dependencies": {
- "@types/http-proxy": "^1.17.8",
- "http-proxy": "^1.18.1",
- "is-glob": "^4.0.1",
- "is-plain-obj": "^3.0.0",
- "micromatch": "^4.0.2"
+ "debug": "^4.4.3",
+ "httpxy": "^0.5.4",
+ "is-glob": "^4.0.3",
+ "is-plain-obj": "^4.1.0",
+ "micromatch": "^4.0.8"
},
"engines": {
- "node": ">=12.0.0"
- },
- "peerDependencies": {
- "@types/express": "^4.17.13"
- },
- "peerDependenciesMeta": {
- "@types/express": {
- "optional": true
- }
+ "node": "^22.15.0 || ^24.0.0 || >=26.0.0"
}
},
+ "node_modules/httpxy": {
+ "version": "0.5.5",
+ "resolved": "https://registry.npmjs.org/httpxy/-/httpxy-0.5.5.tgz",
+ "integrity": "sha512-uDjmnPyp1q4Sgzf3w+J/Fc6UqcCEj0x4Wjp7OqK5dGhNeDgpyrAmnS6ey8QWrX3SWDon2DMKf9sBa5X9+CVyMA==",
+ "dev": true
+ },
"node_modules/hyperdyperid": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz",
"integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==",
"dev": true,
- "license": "MIT",
"engines": {
"node": ">=10.18"
}
},
"node_modules/iconv-lite": {
- "version": "0.4.24",
- "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
- "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+ "version": "0.7.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz",
+ "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==",
"dev": true,
"dependencies": {
- "safer-buffer": ">= 2.1.2 < 3"
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
}
},
"node_modules/import-local": {
@@ -2207,9 +1881,9 @@
}
},
"node_modules/inherits": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
- "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"dev": true
},
"node_modules/interpret": {
@@ -2222,27 +1896,14 @@
}
},
"node_modules/ipaddr.js": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.1.0.tgz",
- "integrity": "sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ==",
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.4.0.tgz",
+ "integrity": "sha512-9VGk3HGanVE6JoZXHiCpnGy5X0jYDnN4EA4lntFPj+1vIWlFhIylq2CrrCOJH9EAhc5CYhq18F2Av2tgoAPsYQ==",
"dev": true,
"engines": {
"node": ">= 10"
}
},
- "node_modules/is-binary-path": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
- "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "binary-extensions": "^2.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/is-core-module": {
"version": "2.13.0",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz",
@@ -2260,7 +1921,6 @@
"resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz",
"integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==",
"dev": true,
- "license": "MIT",
"bin": {
"is-docker": "cli.js"
},
@@ -2292,12 +1952,23 @@
"node": ">=0.10.0"
}
},
+ "node_modules/is-in-ssh": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-in-ssh/-/is-in-ssh-1.0.0.tgz",
+ "integrity": "sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==",
+ "dev": true,
+ "engines": {
+ "node": ">=20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/is-inside-container": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz",
"integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==",
"dev": true,
- "license": "MIT",
"dependencies": {
"is-docker": "^3.0.0"
},
@@ -2312,11 +1983,10 @@
}
},
"node_modules/is-network-error": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.1.0.tgz",
- "integrity": "sha512-tUdRRAnhT+OtCZR/LxZelH/C7QtjtFrTu5tXCA8pl55eTUElUHT+GPYV8MBMBvea/j+NxQqVt3LbWMRir7Gx9g==",
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.2.tgz",
+ "integrity": "sha512-PhBY86zaxNZUuWP6h13Vu5oFe0XY6/UlKzQnYFELzGVHygP3MxmvTfYSG7GN3aIab/iWudSMgjSnG9Dq+nHrgA==",
"dev": true,
- "license": "MIT",
"engines": {
"node": ">=16"
},
@@ -2334,12 +2004,12 @@
}
},
"node_modules/is-plain-obj": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz",
- "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==",
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz",
+ "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==",
"dev": true,
"engines": {
- "node": ">=10"
+ "node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
@@ -2357,12 +2027,17 @@
"node": ">=0.10.0"
}
},
+ "node_modules/is-promise": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
+ "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
+ "dev": true
+ },
"node_modules/is-wsl": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz",
- "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==",
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz",
+ "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==",
"dev": true,
- "license": "MIT",
"dependencies": {
"is-inside-container": "^1.0.0"
},
@@ -2373,12 +2048,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/isarray": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
- "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
- "dev": true
- },
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
@@ -2469,39 +2138,50 @@
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
"dev": true,
- "license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/media-typer": {
- "version": "0.3.0",
- "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
- "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.1.tgz",
+ "integrity": "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==",
"dev": true,
- "license": "MIT",
"engines": {
- "node": ">= 0.6"
+ "node": ">= 0.8"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
}
},
"node_modules/memfs": {
- "version": "4.17.2",
- "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.17.2.tgz",
- "integrity": "sha512-NgYhCOWgovOXSzvYgUW0LQ7Qy72rWQMGGFJDoWg4G30RHd3z77VbYdtJ4fembJXBy8pMIUA31XNAupobOQlwdg==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@jsonjoy.com/json-pack": "^1.0.3",
- "@jsonjoy.com/util": "^1.3.0",
- "tree-dump": "^1.0.1",
+ "version": "4.64.0",
+ "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.64.0.tgz",
+ "integrity": "sha512-Kw72fgY7Wn+sD8KmtNWSafl1dz0UvAsE/PHs3YVfLiaZuA3HxNm9sRLqAu0ATiBGJvME1PxZXbBZPv5GycDeAw==",
+ "dev": true,
+ "dependencies": {
+ "@jsonjoy.com/fs-core": "4.64.0",
+ "@jsonjoy.com/fs-fsa": "4.64.0",
+ "@jsonjoy.com/fs-node": "4.64.0",
+ "@jsonjoy.com/fs-node-builtins": "4.64.0",
+ "@jsonjoy.com/fs-node-to-fsa": "4.64.0",
+ "@jsonjoy.com/fs-node-utils": "4.64.0",
+ "@jsonjoy.com/fs-print": "4.64.0",
+ "@jsonjoy.com/fs-snapshot": "4.64.0",
+ "@jsonjoy.com/json-pack": "^1.11.0",
+ "@jsonjoy.com/util": "^1.9.0",
+ "glob-to-regex.js": "^1.0.1",
+ "thingies": "^2.5.0",
+ "tree-dump": "^1.0.3",
"tslib": "^2.0.0"
},
- "engines": {
- "node": ">= 4.0.0"
- },
"funding": {
"type": "github",
"url": "https://github.com/sponsors/streamich"
+ },
+ "peerDependencies": {
+ "tslib": "2"
}
},
"node_modules/memfs/node_modules/@jsonjoy.com/base64": {
@@ -2509,7 +2189,6 @@
"resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz",
"integrity": "sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==",
"dev": true,
- "license": "Apache-2.0",
"engines": {
"node": ">=10.0"
},
@@ -2521,18 +2200,11 @@
"tslib": "2"
}
},
- "node_modules/memfs/node_modules/@jsonjoy.com/json-pack": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.2.0.tgz",
- "integrity": "sha512-io1zEbbYcElht3tdlqEOFxZ0dMTYrHz9iMf0gqn1pPjZFTCgM5R4R5IMA20Chb2UPYYsxjzs8CgZ7Nb5n2K2rA==",
+ "node_modules/memfs/node_modules/@jsonjoy.com/buffers": {
+ "version": "17.67.0",
+ "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-17.67.0.tgz",
+ "integrity": "sha512-tfExRpYxBvi32vPs9ZHaTjSP4fHAfzSmcahOfNxtvGHcyJel+aibkPlGeBB+7AoC6hL7lXIE++8okecBxx7lcw==",
"dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@jsonjoy.com/base64": "^1.1.1",
- "@jsonjoy.com/util": "^1.1.2",
- "hyperdyperid": "^1.2.0",
- "thingies": "^1.20.0"
- },
"engines": {
"node": ">=10.0"
},
@@ -2544,12 +2216,11 @@
"tslib": "2"
}
},
- "node_modules/memfs/node_modules/@jsonjoy.com/util": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.6.0.tgz",
- "integrity": "sha512-sw/RMbehRhN68WRtcKCpQOPfnH6lLP4GJfqzi3iYej8tnzpZUDr6UkZYJjcjjC0FWEJOJbyM3PTIwxucUmDG2A==",
+ "node_modules/memfs/node_modules/@jsonjoy.com/codegen": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@jsonjoy.com/codegen/-/codegen-1.0.0.tgz",
+ "integrity": "sha512-E8Oy+08cmCf0EK/NMxpaJZmOxPqM+6iSe2S4nlSBrPZOORoDJILxtbSUEDKQyTamm/BVAhIGllOBNU79/dwf0g==",
"dev": true,
- "license": "Apache-2.0",
"engines": {
"node": ">=10.0"
},
@@ -2561,25 +2232,38 @@
"tslib": "2"
}
},
- "node_modules/memfs/node_modules/thingies": {
- "version": "1.21.0",
- "resolved": "https://registry.npmjs.org/thingies/-/thingies-1.21.0.tgz",
- "integrity": "sha512-hsqsJsFMsV+aD4s3CWKk85ep/3I9XzYV/IXaSouJMYIoDlgyi11cBhsqYe9/geRfB0YIikBQg6raRaM+nIMP9g==",
+ "node_modules/memfs/node_modules/@jsonjoy.com/fs-core": {
+ "version": "4.64.0",
+ "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-core/-/fs-core-4.64.0.tgz",
+ "integrity": "sha512-zs2TAq7Six5jgMuoMNjpspAvOP3mhtgq/k1UyQodEzCtQi/N83y2/y+zcvnZSGp/Rxq96DBN+bValOBQAyn/ew==",
"dev": true,
- "license": "Unlicense",
+ "dependencies": {
+ "@jsonjoy.com/fs-node-builtins": "4.64.0",
+ "@jsonjoy.com/fs-node-utils": "4.64.0",
+ "thingies": "^2.5.0"
+ },
"engines": {
- "node": ">=10.18"
+ "node": ">=10.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/streamich"
},
"peerDependencies": {
- "tslib": "^2"
+ "tslib": "2"
}
},
- "node_modules/memfs/node_modules/tree-dump": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.0.3.tgz",
- "integrity": "sha512-il+Cv80yVHFBwokQSfd4bldvr1Md951DpgAGfmhydt04L+YzHgubm2tQ7zueWDcGENKHq0ZvGFR/hjvNXilHEg==",
+ "node_modules/memfs/node_modules/@jsonjoy.com/fs-fsa": {
+ "version": "4.64.0",
+ "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-fsa/-/fs-fsa-4.64.0.tgz",
+ "integrity": "sha512-nMWOVbkLFyEgmXZih3wyvxA9XpgyyqyfrINMHvEFqhi7uqfRl7c9ERJt6yX7vgMPrB9Uo+OJO+Spa0cFzPD01w==",
"dev": true,
- "license": "Apache-2.0",
+ "dependencies": {
+ "@jsonjoy.com/fs-core": "4.64.0",
+ "@jsonjoy.com/fs-node-builtins": "4.64.0",
+ "@jsonjoy.com/fs-node-utils": "4.64.0",
+ "thingies": "^2.5.0"
+ },
"engines": {
"node": ">=10.0"
},
@@ -2591,19 +2275,387 @@
"tslib": "2"
}
},
- "node_modules/memfs/node_modules/tslib": {
- "version": "2.8.1",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
- "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "node_modules/memfs/node_modules/@jsonjoy.com/fs-node": {
+ "version": "4.64.0",
+ "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node/-/fs-node-4.64.0.tgz",
+ "integrity": "sha512-dO+NNkODbUli4uV42bcNrrLvq5rE7SNpdZ5TNd0dtbLsAaNK3MDiIC9lUi+brboGoIjW6vd2fB1qao60nrk5xA==",
+ "dev": true,
+ "dependencies": {
+ "@jsonjoy.com/fs-core": "4.64.0",
+ "@jsonjoy.com/fs-node-builtins": "4.64.0",
+ "@jsonjoy.com/fs-node-utils": "4.64.0",
+ "@jsonjoy.com/fs-print": "4.64.0",
+ "@jsonjoy.com/fs-snapshot": "4.64.0",
+ "glob-to-regex.js": "^1.0.0",
+ "thingies": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=10.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/streamich"
+ },
+ "peerDependencies": {
+ "tslib": "2"
+ }
+ },
+ "node_modules/memfs/node_modules/@jsonjoy.com/fs-node-builtins": {
+ "version": "4.64.0",
+ "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-builtins/-/fs-node-builtins-4.64.0.tgz",
+ "integrity": "sha512-/o7WRFhUWaM/fOrslwLZGnzn4RmRILykn+lAL+mNObqqRNw+CQSiij6hpCeZ+C7buhdoVo7go/OYqzaSUfDYmA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/streamich"
+ },
+ "peerDependencies": {
+ "tslib": "2"
+ }
+ },
+ "node_modules/memfs/node_modules/@jsonjoy.com/fs-node-to-fsa": {
+ "version": "4.64.0",
+ "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-to-fsa/-/fs-node-to-fsa-4.64.0.tgz",
+ "integrity": "sha512-WDD9WVs0hb7UAEKTgZW2f66WDrbj7gIIWwpP3spbLyXa0rghtUaFTB8L4gdR3ZCWwiKIsj38/CNijpVmpnuPUw==",
+ "dev": true,
+ "dependencies": {
+ "@jsonjoy.com/fs-fsa": "4.64.0",
+ "@jsonjoy.com/fs-node-builtins": "4.64.0",
+ "@jsonjoy.com/fs-node-utils": "4.64.0"
+ },
+ "engines": {
+ "node": ">=10.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/streamich"
+ },
+ "peerDependencies": {
+ "tslib": "2"
+ }
+ },
+ "node_modules/memfs/node_modules/@jsonjoy.com/fs-node-utils": {
+ "version": "4.64.0",
+ "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-utils/-/fs-node-utils-4.64.0.tgz",
+ "integrity": "sha512-k5Indsx9hWW9xSF7Y6oSKKwtCUNhzZxadub3owhIlitc+iMRVlPPdX2duTKQWBL3qNWpXya8jykgaaWpheeS4w==",
+ "dev": true,
+ "dependencies": {
+ "@jsonjoy.com/fs-node-builtins": "4.64.0",
+ "glob-to-regex.js": "^1.0.1"
+ },
+ "engines": {
+ "node": ">=10.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/streamich"
+ },
+ "peerDependencies": {
+ "tslib": "2"
+ }
+ },
+ "node_modules/memfs/node_modules/@jsonjoy.com/fs-print": {
+ "version": "4.64.0",
+ "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-print/-/fs-print-4.64.0.tgz",
+ "integrity": "sha512-PHZFccchvkhWrwPWHjmVAhbC3vSHCtyZvlZfJJ3ho2bnzl450hXri6/8e6pbkWdH+SkmLXNml0sV8e5HDAfxKw==",
+ "dev": true,
+ "dependencies": {
+ "@jsonjoy.com/fs-node-utils": "4.64.0",
+ "tree-dump": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=10.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/streamich"
+ },
+ "peerDependencies": {
+ "tslib": "2"
+ }
+ },
+ "node_modules/memfs/node_modules/@jsonjoy.com/fs-snapshot": {
+ "version": "4.64.0",
+ "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-snapshot/-/fs-snapshot-4.64.0.tgz",
+ "integrity": "sha512-oM7UDeL83q6NBzzsfKAsYKXKVXlykKFqqOLh4xZZKAzzROTlInkPbc6LTDGThEOnPiFiUzA7tYziHG9xavd76Q==",
+ "dev": true,
+ "dependencies": {
+ "@jsonjoy.com/buffers": "^17.65.0",
+ "@jsonjoy.com/fs-node-utils": "4.64.0",
+ "@jsonjoy.com/json-pack": "^17.65.0",
+ "@jsonjoy.com/util": "^17.65.0"
+ },
+ "engines": {
+ "node": ">=10.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/streamich"
+ },
+ "peerDependencies": {
+ "tslib": "2"
+ }
+ },
+ "node_modules/memfs/node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/base64": {
+ "version": "17.67.0",
+ "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-17.67.0.tgz",
+ "integrity": "sha512-5SEsJGsm15aP8TQGkDfJvz9axgPwAEm98S5DxOuYe8e1EbfajcDmgeXXzccEjh+mLnjqEKrkBdjHWS5vFNwDdw==",
+ "dev": true,
+ "engines": {
+ "node": ">=10.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/streamich"
+ },
+ "peerDependencies": {
+ "tslib": "2"
+ }
+ },
+ "node_modules/memfs/node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/codegen": {
+ "version": "17.67.0",
+ "resolved": "https://registry.npmjs.org/@jsonjoy.com/codegen/-/codegen-17.67.0.tgz",
+ "integrity": "sha512-idnkUplROpdBOV0HMcwhsCUS5TRUi9poagdGs70A6S4ux9+/aPuKbh8+UYRTLYQHtXvAdNfQWXDqZEx5k4Dj2Q==",
"dev": true,
- "license": "0BSD"
+ "engines": {
+ "node": ">=10.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/streamich"
+ },
+ "peerDependencies": {
+ "tslib": "2"
+ }
+ },
+ "node_modules/memfs/node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/json-pack": {
+ "version": "17.67.0",
+ "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-17.67.0.tgz",
+ "integrity": "sha512-t0ejURcGaZsn1ClbJ/3kFqSOjlryd92eQY465IYrezsXmPcfHPE/av4twRSxf6WE+TkZgLY+71vCZbiIiFKA/w==",
+ "dev": true,
+ "dependencies": {
+ "@jsonjoy.com/base64": "17.67.0",
+ "@jsonjoy.com/buffers": "17.67.0",
+ "@jsonjoy.com/codegen": "17.67.0",
+ "@jsonjoy.com/json-pointer": "17.67.0",
+ "@jsonjoy.com/util": "17.67.0",
+ "hyperdyperid": "^1.2.0",
+ "thingies": "^2.5.0",
+ "tree-dump": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=10.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/streamich"
+ },
+ "peerDependencies": {
+ "tslib": "2"
+ }
+ },
+ "node_modules/memfs/node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/json-pointer": {
+ "version": "17.67.0",
+ "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pointer/-/json-pointer-17.67.0.tgz",
+ "integrity": "sha512-+iqOFInH+QZGmSuaybBUNdh7yvNrXvqR+h3wjXm0N/3JK1EyyFAeGJvqnmQL61d1ARLlk/wJdFKSL+LHJ1eaUA==",
+ "dev": true,
+ "dependencies": {
+ "@jsonjoy.com/util": "17.67.0"
+ },
+ "engines": {
+ "node": ">=10.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/streamich"
+ },
+ "peerDependencies": {
+ "tslib": "2"
+ }
+ },
+ "node_modules/memfs/node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/util": {
+ "version": "17.67.0",
+ "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-17.67.0.tgz",
+ "integrity": "sha512-6+8xBaz1rLSohlGh68D1pdw3AwDi9xydm8QNlAFkvnavCJYSze+pxoW2VKP8p308jtlMRLs5NTHfPlZLd4w7ew==",
+ "dev": true,
+ "dependencies": {
+ "@jsonjoy.com/buffers": "17.67.0",
+ "@jsonjoy.com/codegen": "17.67.0"
+ },
+ "engines": {
+ "node": ">=10.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/streamich"
+ },
+ "peerDependencies": {
+ "tslib": "2"
+ }
+ },
+ "node_modules/memfs/node_modules/@jsonjoy.com/json-pack": {
+ "version": "1.21.0",
+ "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.21.0.tgz",
+ "integrity": "sha512-+AKG+R2cfZMShzrF2uQw34v3zbeDYUqnQ+jg7ORic3BGtfw9p/+N6RJbq/kkV8JmYZaINknaEQ2m0/f693ZPpg==",
+ "dev": true,
+ "dependencies": {
+ "@jsonjoy.com/base64": "^1.1.2",
+ "@jsonjoy.com/buffers": "^1.2.0",
+ "@jsonjoy.com/codegen": "^1.0.0",
+ "@jsonjoy.com/json-pointer": "^1.0.2",
+ "@jsonjoy.com/util": "^1.9.0",
+ "hyperdyperid": "^1.2.0",
+ "thingies": "^2.5.0",
+ "tree-dump": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=10.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/streamich"
+ },
+ "peerDependencies": {
+ "tslib": "2"
+ }
+ },
+ "node_modules/memfs/node_modules/@jsonjoy.com/json-pack/node_modules/@jsonjoy.com/buffers": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-1.2.1.tgz",
+ "integrity": "sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/streamich"
+ },
+ "peerDependencies": {
+ "tslib": "2"
+ }
+ },
+ "node_modules/memfs/node_modules/@jsonjoy.com/json-pointer": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pointer/-/json-pointer-1.0.2.tgz",
+ "integrity": "sha512-Fsn6wM2zlDzY1U+v4Nc8bo3bVqgfNTGcn6dMgs6FjrEnt4ZCe60o6ByKRjOGlI2gow0aE/Q41QOigdTqkyK5fg==",
+ "dev": true,
+ "dependencies": {
+ "@jsonjoy.com/codegen": "^1.0.0",
+ "@jsonjoy.com/util": "^1.9.0"
+ },
+ "engines": {
+ "node": ">=10.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/streamich"
+ },
+ "peerDependencies": {
+ "tslib": "2"
+ }
+ },
+ "node_modules/memfs/node_modules/@jsonjoy.com/util": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.9.0.tgz",
+ "integrity": "sha512-pLuQo+VPRnN8hfPqUTLTHk126wuYdXVxE6aDmjSeV4NCAgyxWbiOIeNJVtID3h1Vzpoi9m4jXezf73I6LgabgQ==",
+ "dev": true,
+ "dependencies": {
+ "@jsonjoy.com/buffers": "^1.0.0",
+ "@jsonjoy.com/codegen": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=10.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/streamich"
+ },
+ "peerDependencies": {
+ "tslib": "2"
+ }
+ },
+ "node_modules/memfs/node_modules/@jsonjoy.com/util/node_modules/@jsonjoy.com/buffers": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-1.2.1.tgz",
+ "integrity": "sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/streamich"
+ },
+ "peerDependencies": {
+ "tslib": "2"
+ }
+ },
+ "node_modules/memfs/node_modules/glob-to-regex.js": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/glob-to-regex.js/-/glob-to-regex.js-1.2.0.tgz",
+ "integrity": "sha512-QMwlOQKU/IzqMUOAZWubUOT8Qft+Y0KQWnX9nK3ch0CJg0tTp4TvGZsTfudYKv2NzoQSyPcnA6TYeIQ3jGichQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=10.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/streamich"
+ },
+ "peerDependencies": {
+ "tslib": "2"
+ }
+ },
+ "node_modules/memfs/node_modules/thingies": {
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/thingies/-/thingies-2.6.0.tgz",
+ "integrity": "sha512-rMHRjmlFLM1R96UYPvpmnc3LYtdFrT33JIB7L9hetGue1qAPfn1N2LJeEjxUSidu1Iku+haLZXDuEXUHNGO/lg==",
+ "dev": true,
+ "engines": {
+ "node": ">=10.18"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/streamich"
+ },
+ "peerDependencies": {
+ "tslib": "^2"
+ }
+ },
+ "node_modules/memfs/node_modules/tree-dump": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.1.0.tgz",
+ "integrity": "sha512-rMuvhU4MCDbcbnleZTFezWsaZXRFemSqAM+7jPnzUl1fo9w3YEKOxAeui0fz3OI4EU4hf23iyA7uQRVko+UaBA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/streamich"
+ },
+ "peerDependencies": {
+ "tslib": "2"
+ }
+ },
+ "node_modules/memfs/node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "dev": true
},
"node_modules/merge-descriptors": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
- "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
+ "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
"dev": true,
- "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
@@ -2614,22 +2666,11 @@
"integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
"dev": true
},
- "node_modules/methods": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
- "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
"node_modules/micromatch": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
"dev": true,
- "license": "MIT",
"dependencies": {
"braces": "^3.0.3",
"picomatch": "^2.3.1"
@@ -2638,19 +2679,6 @@
"node": ">=8.6"
}
},
- "node_modules/mime": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
- "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "mime": "cli.js"
- },
- "engines": {
- "node": ">=4"
- }
- },
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
@@ -2672,12 +2700,6 @@
"node": ">= 0.6"
}
},
- "node_modules/minimalistic-assert": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
- "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==",
- "dev": true
- },
"node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
@@ -2699,9 +2721,9 @@
}
},
"node_modules/negotiator": {
- "version": "0.6.3",
- "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
- "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
+ "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
"dev": true,
"engines": {
"node": ">= 0.6"
@@ -2733,7 +2755,6 @@
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
"integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
"dev": true,
- "license": "MIT",
"engines": {
"node": ">= 0.4"
},
@@ -2741,18 +2762,11 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/obuf": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz",
- "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==",
- "dev": true
- },
"node_modules/on-finished": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
"integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
"dev": true,
- "license": "MIT",
"dependencies": {
"ee-first": "1.1.1"
},
@@ -2769,20 +2783,30 @@
"node": ">= 0.8"
}
},
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "dev": true,
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
"node_modules/open": {
- "version": "10.1.2",
- "resolved": "https://registry.npmjs.org/open/-/open-10.1.2.tgz",
- "integrity": "sha512-cxN6aIDPz6rm8hbebcP7vrQNhvRcveZoJU72Y7vskh4oIm+BZwBECnx5nTmrlres1Qapvx27Qo1Auukpf8PKXw==",
+ "version": "11.0.0",
+ "resolved": "https://registry.npmjs.org/open/-/open-11.0.0.tgz",
+ "integrity": "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==",
"dev": true,
- "license": "MIT",
"dependencies": {
- "default-browser": "^5.2.1",
+ "default-browser": "^5.4.0",
"define-lazy-prop": "^3.0.0",
+ "is-in-ssh": "^1.0.0",
"is-inside-container": "^1.0.0",
- "is-wsl": "^3.1.0"
+ "powershell-utils": "^0.1.0",
+ "wsl-utils": "^0.3.0"
},
"engines": {
- "node": ">=18"
+ "node": ">=20"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
@@ -2816,18 +2840,15 @@
}
},
"node_modules/p-retry": {
- "version": "6.2.1",
- "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-6.2.1.tgz",
- "integrity": "sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ==",
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-8.0.0.tgz",
+ "integrity": "sha512-kFVqH1HxOHp8LupNsOys7bSV09VYTRLxarH/mokO4Rqhk6wGi70E0jh4VzvVGXfEVNggHoHLAMWsQqHyU1Ey9A==",
"dev": true,
- "license": "MIT",
"dependencies": {
- "@types/retry": "0.12.2",
- "is-network-error": "^1.0.0",
- "retry": "^0.13.1"
+ "is-network-error": "^1.3.0"
},
"engines": {
- "node": ">=16.17"
+ "node": ">=22"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
@@ -2876,11 +2897,14 @@
"dev": true
},
"node_modules/path-to-regexp": {
- "version": "0.1.12",
- "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
- "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
+ "version": "8.4.2",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz",
+ "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==",
"dev": true,
- "license": "MIT"
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
},
"node_modules/picocolors": {
"version": "1.1.1",
@@ -2935,18 +2959,23 @@
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"dev": true
},
- "node_modules/process-nextick-args": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz",
- "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==",
- "dev": true
+ "node_modules/powershell-utils": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/powershell-utils/-/powershell-utils-0.1.0.tgz",
+ "integrity": "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==",
+ "dev": true,
+ "engines": {
+ "node": ">=20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
},
"node_modules/proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
"integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
"dev": true,
- "license": "MIT",
"dependencies": {
"forwarded": "0.2.0",
"ipaddr.js": "1.9.1"
@@ -2960,7 +2989,6 @@
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
"dev": true,
- "license": "MIT",
"engines": {
"node": ">= 0.10"
}
@@ -2990,12 +3018,13 @@
}
},
"node_modules/qs": {
- "version": "6.15.2",
- "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz",
- "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==",
+ "version": "6.15.3",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz",
+ "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==",
"dev": true,
"dependencies": {
- "side-channel": "^1.1.0"
+ "es-define-property": "^1.0.1",
+ "side-channel": "^1.1.1"
},
"engines": {
"node": ">=0.6"
@@ -3005,100 +3034,44 @@
}
},
"node_modules/range-parser": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
- "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz",
+ "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==",
"dev": true,
- "license": "MIT",
"engines": {
"node": ">= 0.6"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
}
},
"node_modules/raw-body": {
- "version": "2.5.3",
- "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz",
- "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==",
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz",
+ "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==",
"dev": true,
"dependencies": {
"bytes": "~3.1.2",
"http-errors": "~2.0.1",
- "iconv-lite": "~0.4.24",
+ "iconv-lite": "~0.7.0",
"unpipe": "~1.0.0"
},
"engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/raw-body/node_modules/depd": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
- "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
- "dev": true,
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/raw-body/node_modules/http-errors": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
- "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
- "dev": true,
- "dependencies": {
- "depd": "~2.0.0",
- "inherits": "~2.0.4",
- "setprototypeof": "~1.2.0",
- "statuses": "~2.0.2",
- "toidentifier": "~1.0.1"
- },
- "engines": {
- "node": ">= 0.8"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/express"
- }
- },
- "node_modules/raw-body/node_modules/inherits": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
- "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
- "dev": true
- },
- "node_modules/raw-body/node_modules/statuses": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
- "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
- "dev": true,
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/readable-stream": {
- "version": "2.3.6",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
- "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
- "dev": true,
- "dependencies": {
- "core-util-is": "~1.0.0",
- "inherits": "~2.0.3",
- "isarray": "~1.0.0",
- "process-nextick-args": "~2.0.0",
- "safe-buffer": "~5.1.1",
- "string_decoder": "~1.1.1",
- "util-deprecate": "~1.0.1"
+ "node": ">= 0.10"
}
},
"node_modules/readdirp": {
- "version": "3.6.0",
- "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
- "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz",
+ "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==",
"dev": true,
- "license": "MIT",
- "dependencies": {
- "picomatch": "^2.2.1"
- },
"engines": {
- "node": ">=8.10.0"
+ "node": ">= 20.19.0"
+ },
+ "funding": {
+ "type": "individual",
+ "url": "https://paulmillr.com/funding/"
}
},
"node_modules/rechoir": {
@@ -3128,12 +3101,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/requires-port": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
- "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
- "dev": true
- },
"node_modules/resolve": {
"version": "1.22.6",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.6.tgz",
@@ -3172,22 +3139,27 @@
"node": ">=8"
}
},
- "node_modules/retry": {
- "version": "0.13.1",
- "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz",
- "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==",
+ "node_modules/router": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
+ "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
"dev": true,
- "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.0",
+ "depd": "^2.0.0",
+ "is-promise": "^4.0.0",
+ "parseurl": "^1.3.3",
+ "path-to-regexp": "^8.0.0"
+ },
"engines": {
- "node": ">= 4"
+ "node": ">= 18"
}
},
"node_modules/run-applescript": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.0.0.tgz",
- "integrity": "sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==",
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz",
+ "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==",
"dev": true,
- "license": "MIT",
"engines": {
"node": ">=18"
},
@@ -3195,12 +3167,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/safe-buffer": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
- "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
- "dev": true
- },
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
@@ -3226,12 +3192,6 @@
"url": "https://opencollective.com/webpack"
}
},
- "node_modules/select-hose": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz",
- "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=",
- "dev": true
- },
"node_modules/selfsigned": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-5.5.0.tgz",
@@ -3246,100 +3206,95 @@
}
},
"node_modules/send": {
- "version": "0.19.0",
- "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
- "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "debug": "2.6.9",
- "depd": "2.0.0",
- "destroy": "1.2.0",
- "encodeurl": "~1.0.2",
- "escape-html": "~1.0.3",
- "etag": "~1.8.1",
- "fresh": "0.5.2",
- "http-errors": "2.0.0",
- "mime": "1.6.0",
- "ms": "2.1.3",
- "on-finished": "2.4.1",
- "range-parser": "~1.2.1",
- "statuses": "2.0.1"
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz",
+ "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==",
+ "dev": true,
+ "dependencies": {
+ "debug": "^4.4.3",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "etag": "^1.8.1",
+ "fresh": "^2.0.0",
+ "http-errors": "^2.0.1",
+ "mime-types": "^3.0.2",
+ "ms": "^2.1.3",
+ "on-finished": "^2.4.1",
+ "range-parser": "^1.2.1",
+ "statuses": "^2.0.2"
},
"engines": {
- "node": ">= 0.8.0"
- }
- },
- "node_modules/send/node_modules/debug": {
- "version": "2.6.9",
- "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
- "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ms": "2.0.0"
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
}
},
- "node_modules/send/node_modules/debug/node_modules/ms": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/send/node_modules/depd": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
- "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+ "node_modules/send/node_modules/mime-db": {
+ "version": "1.54.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
"dev": true,
- "license": "MIT",
"engines": {
- "node": ">= 0.8"
+ "node": ">= 0.6"
}
},
- "node_modules/send/node_modules/encodeurl": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
- "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
+ "node_modules/send/node_modules/mime-types": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
+ "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
"dev": true,
- "license": "MIT",
+ "dependencies": {
+ "mime-db": "^1.54.0"
+ },
"engines": {
- "node": ">= 0.8"
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
}
},
"node_modules/send/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/send/node_modules/statuses": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
- "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
+ "dev": true
},
"node_modules/serve-index": {
- "version": "1.9.1",
- "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz",
- "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=",
+ "version": "1.9.2",
+ "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.2.tgz",
+ "integrity": "sha512-KDj11HScOaLmrPxl70KYNW1PksP4Nb/CLL2yvC+Qd2kHMPEEpfc4Re2e4FOay+bC/+XQl/7zAcWON3JVo5v3KQ==",
"dev": true,
"dependencies": {
- "accepts": "~1.3.4",
+ "accepts": "~1.3.8",
"batch": "0.6.1",
"debug": "2.6.9",
"escape-html": "~1.0.3",
- "http-errors": "~1.6.2",
- "mime-types": "~2.1.17",
- "parseurl": "~1.3.2"
+ "http-errors": "~1.8.0",
+ "mime-types": "~2.1.35",
+ "parseurl": "~1.3.3"
},
"engines": {
"node": ">= 0.8.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/serve-index/node_modules/accepts": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
+ "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+ "dev": true,
+ "dependencies": {
+ "mime-types": "~2.1.34",
+ "negotiator": "0.6.3"
+ },
+ "engines": {
+ "node": ">= 0.6"
}
},
"node_modules/serve-index/node_modules/debug": {
@@ -3351,49 +3306,73 @@
"ms": "2.0.0"
}
},
+ "node_modules/serve-index/node_modules/depd": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
+ "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/serve-index/node_modules/http-errors": {
- "version": "1.6.3",
- "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz",
- "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=",
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz",
+ "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==",
"dev": true,
"dependencies": {
"depd": "~1.1.2",
- "inherits": "2.0.3",
- "setprototypeof": "1.1.0",
- "statuses": ">= 1.4.0 < 2"
+ "inherits": "2.0.4",
+ "setprototypeof": "1.2.0",
+ "statuses": ">= 1.5.0 < 2",
+ "toidentifier": "1.0.1"
},
"engines": {
"node": ">= 0.6"
}
},
- "node_modules/serve-index/node_modules/setprototypeof": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz",
- "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==",
- "dev": true
+ "node_modules/serve-index/node_modules/negotiator": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
+ "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/serve-index/node_modules/statuses": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
+ "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.6"
+ }
},
"node_modules/serve-static": {
- "version": "1.16.2",
- "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
- "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz",
+ "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==",
"dev": true,
- "license": "MIT",
"dependencies": {
- "encodeurl": "~2.0.0",
- "escape-html": "~1.0.3",
- "parseurl": "~1.3.3",
- "send": "0.19.0"
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "parseurl": "^1.3.3",
+ "send": "^1.2.0"
},
"engines": {
- "node": ">= 0.8.0"
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
}
},
"node_modules/setprototypeof": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
- "dev": true,
- "license": "ISC"
+ "dev": true
},
"node_modules/shallow-clone": {
"version": "3.0.1",
@@ -3441,15 +3420,14 @@
}
},
"node_modules/side-channel": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
- "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz",
+ "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==",
"dev": true,
- "license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
- "object-inspect": "^1.13.3",
- "side-channel-list": "^1.0.0",
+ "object-inspect": "^1.13.4",
+ "side-channel-list": "^1.0.1",
"side-channel-map": "^1.0.1",
"side-channel-weakmap": "^1.0.2"
},
@@ -3461,14 +3439,13 @@
}
},
"node_modules/side-channel-list": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
- "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz",
+ "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==",
"dev": true,
- "license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
- "object-inspect": "^1.13.3"
+ "object-inspect": "^1.13.4"
},
"engines": {
"node": ">= 0.4"
@@ -3482,7 +3459,6 @@
"resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
"integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
"dev": true,
- "license": "MIT",
"dependencies": {
"call-bound": "^1.0.2",
"es-errors": "^1.3.0",
@@ -3501,7 +3477,6 @@
"resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
"integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
"dev": true,
- "license": "MIT",
"dependencies": {
"call-bound": "^1.0.2",
"es-errors": "^1.3.0",
@@ -3516,17 +3491,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/sockjs": {
- "version": "0.3.24",
- "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz",
- "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==",
- "dev": true,
- "dependencies": {
- "faye-websocket": "^0.11.3",
- "uuid": "^8.3.2",
- "websocket-driver": "^0.7.4"
- }
- },
"node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
@@ -3546,66 +3510,13 @@
"source-map": "^0.6.0"
}
},
- "node_modules/spdy": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz",
- "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==",
- "dev": true,
- "dependencies": {
- "debug": "^4.1.0",
- "handle-thing": "^2.0.0",
- "http-deceiver": "^1.2.7",
- "select-hose": "^2.0.0",
- "spdy-transport": "^3.0.0"
- },
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/spdy-transport": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz",
- "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==",
- "dev": true,
- "dependencies": {
- "debug": "^4.1.0",
- "detect-node": "^2.0.4",
- "hpack.js": "^2.1.6",
- "obuf": "^1.1.2",
- "readable-stream": "^3.0.6",
- "wbuf": "^1.7.3"
- }
- },
- "node_modules/spdy-transport/node_modules/readable-stream": {
- "version": "3.6.0",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
- "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
- "dev": true,
- "dependencies": {
- "inherits": "^2.0.3",
- "string_decoder": "^1.1.1",
- "util-deprecate": "^1.0.1"
- },
- "engines": {
- "node": ">= 6"
- }
- },
"node_modules/statuses": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
- "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=",
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
+ "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
"dev": true,
"engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/string_decoder": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
- "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
- "dev": true,
- "dependencies": {
- "safe-buffer": "~5.1.0"
+ "node": ">= 0.8"
}
},
"node_modules/supports-color": {
@@ -3768,7 +3679,6 @@
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
"dev": true,
- "license": "MIT",
"engines": {
"node": ">=0.6"
}
@@ -3792,25 +3702,66 @@
}
},
"node_modules/type-is": {
- "version": "1.6.18",
- "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
- "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz",
+ "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==",
"dev": true,
- "license": "MIT",
"dependencies": {
- "media-typer": "0.3.0",
- "mime-types": "~2.1.24"
+ "content-type": "^2.0.0",
+ "media-typer": "^1.1.0",
+ "mime-types": "^3.0.0"
},
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/type-is/node_modules/content-type": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz",
+ "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/type-is/node_modules/mime-db": {
+ "version": "1.54.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
+ "dev": true,
"engines": {
"node": ">= 0.6"
}
},
+ "node_modules/type-is/node_modules/mime-types": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
+ "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
+ "dev": true,
+ "dependencies": {
+ "mime-db": "^1.54.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
"node_modules/unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
"dev": true,
- "license": "MIT",
"engines": {
"node": ">= 0.8"
}
@@ -3845,31 +3796,6 @@
"browserslist": ">= 4.21.0"
}
},
- "node_modules/util-deprecate": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
- "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
- "dev": true
- },
- "node_modules/utils-merge": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
- "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.4.0"
- }
- },
- "node_modules/uuid": {
- "version": "8.3.2",
- "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
- "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
- "dev": true,
- "bin": {
- "uuid": "dist/bin/uuid"
- }
- },
"node_modules/vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
@@ -3892,15 +3818,6 @@
"node": ">=10.13.0"
}
},
- "node_modules/wbuf": {
- "version": "1.7.3",
- "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz",
- "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==",
- "dev": true,
- "dependencies": {
- "minimalistic-assert": "^1.0.0"
- }
- },
"node_modules/webpack": {
"version": "5.105.0",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.0.tgz",
@@ -4004,28 +3921,25 @@
}
},
"node_modules/webpack-dev-middleware": {
- "version": "7.4.2",
- "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.4.2.tgz",
- "integrity": "sha512-xOO8n6eggxnwYpy1NlzUKpvrjfJTvae5/D6WOK0S2LSo7vjmo5gCM1DbLUmFqrMTJP+W/0YZNctm7jasWvLuBA==",
+ "version": "8.0.4",
+ "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-8.0.4.tgz",
+ "integrity": "sha512-9dFzIvIfbdnkOlRjXDHEmEKlY/KPsELNIyKWdoNfK4WaHN9Db+JyVG0gi4/APUPX2UVhnCZ6jp7x0EyM7yTq1Q==",
"dev": true,
- "license": "MIT",
"dependencies": {
- "colorette": "^2.0.10",
- "memfs": "^4.6.0",
- "mime-types": "^2.1.31",
- "on-finished": "^2.4.1",
+ "memfs": "^4.56.10",
+ "mime-types": "^3.0.2",
"range-parser": "^1.2.1",
- "schema-utils": "^4.0.0"
+ "schema-utils": "^4.3.3"
},
"engines": {
- "node": ">= 18.12.0"
+ "node": ">= 20.9.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
},
"peerDependencies": {
- "webpack": "^5.0.0"
+ "webpack": "^5.101.0"
},
"peerDependenciesMeta": {
"webpack": {
@@ -4033,53 +3947,75 @@
}
}
},
+ "node_modules/webpack-dev-middleware/node_modules/mime-db": {
+ "version": "1.54.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/webpack-dev-middleware/node_modules/mime-types": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
+ "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
+ "dev": true,
+ "dependencies": {
+ "mime-db": "^1.54.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
"node_modules/webpack-dev-server": {
- "version": "5.2.6",
- "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.2.6.tgz",
- "integrity": "sha512-HNLRmamRvVavZQ+avceZifmv8hmdUjg43t6MI4SqJDwFdW7RPQwH5vzGhDRZSX59SgfbeHhLnq3g+uooWo7pVw==",
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-6.0.0.tgz",
+ "integrity": "sha512-q9SD4ItOGhZLeU6EGT10caDZdHjF50Pz1DtkRZZOPsfluMXOkacWKKOtSBSLVkPqKiF67eFUC0rI88U/tSFPEw==",
"dev": true,
"dependencies": {
"@types/bonjour": "^3.5.13",
"@types/connect-history-api-fallback": "^1.5.4",
- "@types/express": "^4.17.25",
- "@types/express-serve-static-core": "^4.17.21",
+ "@types/express": "^5.0.6",
+ "@types/express-serve-static-core": "^5.1.1",
"@types/serve-index": "^1.9.4",
- "@types/serve-static": "^1.15.5",
- "@types/sockjs": "^0.3.36",
- "@types/ws": "^8.5.10",
+ "@types/serve-static": "^2.2.0",
+ "@types/ws": "^8.18.1",
"ansi-html-community": "^0.0.8",
- "bonjour-service": "^1.2.1",
- "chokidar": "^3.6.0",
- "colorette": "^2.0.10",
+ "bonjour-service": "^1.3.0",
+ "chokidar": "^5.0.0",
"compression": "^1.8.1",
"connect-history-api-fallback": "^2.0.0",
- "express": "^4.22.1",
- "graceful-fs": "^4.2.6",
- "http-proxy-middleware": "^2.0.9",
- "ipaddr.js": "^2.1.0",
+ "express": "^5.2.1",
+ "graceful-fs": "^4.2.11",
+ "http-proxy-middleware": "^4.1.1",
+ "ipaddr.js": "^2.3.0",
"launch-editor": "^2.14.1",
- "open": "^10.0.3",
- "p-retry": "^6.2.0",
- "schema-utils": "^4.2.0",
+ "open": "^11.0.0",
+ "p-retry": "^8.0.0",
+ "schema-utils": "^4.3.3",
"selfsigned": "^5.5.0",
- "serve-index": "^1.9.1",
- "sockjs": "^0.3.24",
- "spdy": "^4.0.2",
- "webpack-dev-middleware": "^7.4.2",
- "ws": "^8.18.0"
+ "serve-index": "^1.9.2",
+ "tinyglobby": "^0.2.15",
+ "webpack-dev-middleware": "^8.0.3",
+ "ws": "^8.20.0"
},
"bin": {
"webpack-dev-server": "bin/webpack-dev-server.js"
},
"engines": {
- "node": ">= 18.12.0"
+ "node": ">= 22.15.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
},
"peerDependencies": {
- "webpack": "^5.0.0"
+ "webpack": "^5.101.0"
},
"peerDependenciesMeta": {
"webpack": {
@@ -4112,29 +4048,6 @@
"node": ">=10.13.0"
}
},
- "node_modules/websocket-driver": {
- "version": "0.7.5",
- "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.5.tgz",
- "integrity": "sha512-ZL2+3c7kMBdIRCMz6l8jQMHyGVxj+UL+xVk74Ombiciboca8rHa15L86B19E5oh1pL9Ii/uj54gtsIrZGMo6zA==",
- "dev": true,
- "dependencies": {
- "http-parser-js": ">=0.5.1",
- "safe-buffer": ">=5.1.0",
- "websocket-extensions": ">=0.1.1"
- },
- "engines": {
- "node": ">=0.8.0"
- }
- },
- "node_modules/websocket-extensions": {
- "version": "0.1.4",
- "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz",
- "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==",
- "dev": true,
- "engines": {
- "node": ">=0.8.0"
- }
- },
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -4156,6 +4069,12 @@
"integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==",
"dev": true
},
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "dev": true
+ },
"node_modules/ws": {
"version": "8.21.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.21.1.tgz",
@@ -4176,6 +4095,22 @@
"optional": true
}
}
+ },
+ "node_modules/wsl-utils": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.3.1.tgz",
+ "integrity": "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==",
+ "dev": true,
+ "dependencies": {
+ "is-wsl": "^3.1.0",
+ "powershell-utils": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
}
},
"dependencies": {
@@ -4554,21 +4489,20 @@
"dev": true
},
"@types/express": {
- "version": "4.17.25",
- "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.25.tgz",
- "integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==",
+ "version": "5.0.6",
+ "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz",
+ "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==",
"dev": true,
"requires": {
"@types/body-parser": "*",
- "@types/express-serve-static-core": "^4.17.33",
- "@types/qs": "*",
- "@types/serve-static": "^1"
+ "@types/express-serve-static-core": "^5.0.0",
+ "@types/serve-static": "^2"
}
},
"@types/express-serve-static-core": {
- "version": "4.17.36",
- "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.36.tgz",
- "integrity": "sha512-zbivROJ0ZqLAtMzgzIUC4oNqDG9iF0lSsAqpOD9kbs5xcIM3dTiyuHvBc7R8MtWBp3AAWGaovJa+wzWPjLYW7Q==",
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.2.tgz",
+ "integrity": "sha512-d3KvEXBSo/lOAMc2u6fkyDHBvetBHeqD7wm/AcXfLpSOQwlmG9D/aQ0SFswVjv05p7ullQS7Mjohj6/VdbZuTg==",
"dev": true,
"requires": {
"@types/node": "*",
@@ -4578,20 +4512,11 @@
}
},
"@types/http-errors": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.2.tgz",
- "integrity": "sha512-lPG6KlZs88gef6aD85z3HNkztpj7w2R7HmR3gygjfXCQmsLloWNARFkMuzKiiY8FGdh1XDpgBdrSf4aKDiA7Kg==",
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz",
+ "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==",
"dev": true
},
- "@types/http-proxy": {
- "version": "1.17.12",
- "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.12.tgz",
- "integrity": "sha512-kQtujO08dVtQ2wXAuSFfk9ASy3sug4+ogFR8Kd8UgP8PEuc1/G/8yjYRmp//PcDNJEUKOza/MrQu15bouEUCiw==",
- "dev": true,
- "requires": {
- "@types/node": "*"
- }
- },
"@types/json-schema": {
"version": "7.0.15",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
@@ -4622,12 +4547,6 @@
"integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==",
"dev": true
},
- "@types/retry": {
- "version": "0.12.2",
- "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz",
- "integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==",
- "dev": true
- },
"@types/send": {
"version": "0.17.1",
"resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.1.tgz",
@@ -4648,22 +4567,12 @@
}
},
"@types/serve-static": {
- "version": "1.15.7",
- "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz",
- "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==",
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz",
+ "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==",
"dev": true,
"requires": {
"@types/http-errors": "*",
- "@types/node": "*",
- "@types/send": "*"
- }
- },
- "@types/sockjs": {
- "version": "0.3.36",
- "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz",
- "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==",
- "dev": true,
- "requires": {
"@types/node": "*"
}
},
@@ -4856,13 +4765,30 @@
"dev": true
},
"accepts": {
- "version": "1.3.8",
- "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
- "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
+ "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
"dev": true,
"requires": {
- "mime-types": "~2.1.34",
- "negotiator": "0.6.3"
+ "mime-types": "^3.0.0",
+ "negotiator": "^1.0.0"
+ },
+ "dependencies": {
+ "mime-db": {
+ "version": "1.54.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
+ "dev": true
+ },
+ "mime-types": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
+ "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
+ "dev": true,
+ "requires": {
+ "mime-db": "^1.54.0"
+ }
+ }
}
},
"acorn": {
@@ -4914,22 +4840,6 @@
"integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==",
"dev": true
},
- "anymatch": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
- "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
- "dev": true,
- "requires": {
- "normalize-path": "^3.0.0",
- "picomatch": "^2.0.4"
- }
- },
- "array-flatten": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
- "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
- "dev": true
- },
"asn1js": {
"version": "3.0.10",
"resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.10.tgz",
@@ -4958,73 +4868,30 @@
"batch": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz",
- "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=",
- "dev": true
- },
- "binary-extensions": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
- "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
+ "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==",
"dev": true
},
"body-parser": {
- "version": "1.20.5",
- "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz",
- "integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==",
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz",
+ "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==",
"dev": true,
"requires": {
- "bytes": "~3.1.2",
- "content-type": "~1.0.5",
- "debug": "2.6.9",
- "depd": "2.0.0",
- "destroy": "~1.2.0",
- "http-errors": "~2.0.1",
- "iconv-lite": "~0.4.24",
- "on-finished": "~2.4.1",
- "qs": "~6.15.1",
- "raw-body": "~2.5.3",
- "type-is": "~1.6.18",
- "unpipe": "~1.0.0"
+ "bytes": "^3.1.2",
+ "content-type": "^2.0.0",
+ "debug": "^4.4.3",
+ "http-errors": "^2.0.1",
+ "iconv-lite": "^0.7.2",
+ "on-finished": "^2.4.1",
+ "qs": "^6.15.2",
+ "raw-body": "^3.0.2",
+ "type-is": "^2.1.0"
},
"dependencies": {
- "debug": {
- "version": "2.6.9",
- "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
- "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
- "dev": true,
- "requires": {
- "ms": "2.0.0"
- }
- },
- "depd": {
+ "content-type": {
"version": "2.0.0",
- "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
- "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
- "dev": true
- },
- "http-errors": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
- "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
- "dev": true,
- "requires": {
- "depd": "~2.0.0",
- "inherits": "~2.0.4",
- "setprototypeof": "~1.2.0",
- "statuses": "~2.0.2",
- "toidentifier": "~1.0.1"
- }
- },
- "inherits": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
- "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
- "dev": true
- },
- "statuses": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
- "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz",
+ "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==",
"dev": true
}
}
@@ -5115,19 +4982,12 @@
"dev": true
},
"chokidar": {
- "version": "3.6.0",
- "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
- "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz",
+ "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==",
"dev": true,
"requires": {
- "anymatch": "~3.1.2",
- "braces": "~3.0.2",
- "fsevents": "~2.3.2",
- "glob-parent": "~5.1.2",
- "is-binary-path": "~2.1.0",
- "is-glob": "~4.0.1",
- "normalize-path": "~3.0.0",
- "readdirp": "~3.6.0"
+ "readdirp": "^5.0.0"
}
},
"chrome-trace-event": {
@@ -5216,21 +5076,10 @@
"dev": true
},
"content-disposition": {
- "version": "0.5.4",
- "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
- "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
- "dev": true,
- "requires": {
- "safe-buffer": "5.2.1"
- },
- "dependencies": {
- "safe-buffer": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
- "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
- "dev": true
- }
- }
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz",
+ "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==",
+ "dev": true
},
"content-type": {
"version": "1.0.5",
@@ -5239,15 +5088,15 @@
"dev": true
},
"cookie": {
- "version": "0.7.1",
- "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
- "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
"dev": true
},
"cookie-signature": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
- "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
+ "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
"dev": true
},
"copy-webpack-plugin": {
@@ -5280,12 +5129,6 @@
}
}
},
- "core-util-is": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
- "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
- "dev": true
- },
"cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@@ -5301,26 +5144,26 @@
"version": "file:../pkg"
},
"debug": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
- "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"dev": true,
"requires": {
- "ms": "^2.1.1"
+ "ms": "^2.1.3"
},
"dependencies": {
"ms": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true
}
}
},
"default-browser": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz",
- "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==",
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz",
+ "integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==",
"dev": true,
"requires": {
"bundle-name": "^4.1.0",
@@ -5328,9 +5171,9 @@
}
},
"default-browser-id": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.0.tgz",
- "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==",
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz",
+ "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==",
"dev": true
},
"define-lazy-prop": {
@@ -5340,21 +5183,9 @@
"dev": true
},
"depd": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
- "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=",
- "dev": true
- },
- "destroy": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
- "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
- "dev": true
- },
- "detect-node": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.4.tgz",
- "integrity": "sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==",
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
"dev": true
},
"dns-packet": {
@@ -5430,9 +5261,9 @@
"dev": true
},
"es-object-atoms": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
- "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz",
+ "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==",
"dev": true,
"requires": {
"es-errors": "^1.3.0"
@@ -5447,7 +5278,7 @@
"escape-html": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
- "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=",
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
"dev": true
},
"eslint-scope": {
@@ -5489,12 +5320,6 @@
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
"dev": true
},
- "eventemitter3": {
- "version": "4.0.7",
- "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
- "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
- "dev": true
- },
"events": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
@@ -5502,70 +5327,55 @@
"dev": true
},
"express": {
- "version": "4.22.2",
- "resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz",
- "integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==",
- "dev": true,
- "requires": {
- "accepts": "~1.3.8",
- "array-flatten": "1.1.1",
- "body-parser": "~1.20.5",
- "content-disposition": "~0.5.4",
- "content-type": "~1.0.4",
- "cookie": "~0.7.1",
- "cookie-signature": "~1.0.6",
- "debug": "2.6.9",
- "depd": "2.0.0",
- "encodeurl": "~2.0.0",
- "escape-html": "~1.0.3",
- "etag": "~1.8.1",
- "finalhandler": "~1.3.1",
- "fresh": "~0.5.2",
- "http-errors": "~2.0.0",
- "merge-descriptors": "1.0.3",
- "methods": "~1.1.2",
- "on-finished": "~2.4.1",
- "parseurl": "~1.3.3",
- "path-to-regexp": "~0.1.12",
- "proxy-addr": "~2.0.7",
- "qs": "~6.15.1",
- "range-parser": "~1.2.1",
- "safe-buffer": "5.2.1",
- "send": "~0.19.0",
- "serve-static": "~1.16.2",
- "setprototypeof": "1.2.0",
- "statuses": "~2.0.1",
- "type-is": "~1.6.18",
- "utils-merge": "1.0.1",
- "vary": "~1.1.2"
- },
- "dependencies": {
- "debug": {
- "version": "2.6.9",
- "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
- "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
+ "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
+ "dev": true,
+ "requires": {
+ "accepts": "^2.0.0",
+ "body-parser": "^2.2.1",
+ "content-disposition": "^1.0.0",
+ "content-type": "^1.0.5",
+ "cookie": "^0.7.1",
+ "cookie-signature": "^1.2.1",
+ "debug": "^4.4.0",
+ "depd": "^2.0.0",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "etag": "^1.8.1",
+ "finalhandler": "^2.1.0",
+ "fresh": "^2.0.0",
+ "http-errors": "^2.0.0",
+ "merge-descriptors": "^2.0.0",
+ "mime-types": "^3.0.0",
+ "on-finished": "^2.4.1",
+ "once": "^1.4.0",
+ "parseurl": "^1.3.3",
+ "proxy-addr": "^2.0.7",
+ "qs": "^6.14.0",
+ "range-parser": "^1.2.1",
+ "router": "^2.2.0",
+ "send": "^1.1.0",
+ "serve-static": "^2.2.0",
+ "statuses": "^2.0.1",
+ "type-is": "^2.0.1",
+ "vary": "^1.1.2"
+ },
+ "dependencies": {
+ "mime-db": {
+ "version": "1.54.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
+ "dev": true
+ },
+ "mime-types": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
+ "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
"dev": true,
"requires": {
- "ms": "2.0.0"
+ "mime-db": "^1.54.0"
}
- },
- "depd": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
- "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
- "dev": true
- },
- "safe-buffer": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
- "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
- "dev": true
- },
- "statuses": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
- "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
- "dev": true
}
}
},
@@ -5587,15 +5397,6 @@
"integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==",
"dev": true
},
- "faye-websocket": {
- "version": "0.11.4",
- "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz",
- "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==",
- "dev": true,
- "requires": {
- "websocket-driver": ">=0.5.1"
- }
- },
"fill-range": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
@@ -5606,35 +5407,17 @@
}
},
"finalhandler": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
- "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz",
+ "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==",
"dev": true,
"requires": {
- "debug": "2.6.9",
- "encodeurl": "~2.0.0",
- "escape-html": "~1.0.3",
- "on-finished": "2.4.1",
- "parseurl": "~1.3.3",
- "statuses": "2.0.1",
- "unpipe": "~1.0.0"
- },
- "dependencies": {
- "debug": {
- "version": "2.6.9",
- "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
- "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
- "dev": true,
- "requires": {
- "ms": "2.0.0"
- }
- },
- "statuses": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
- "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
- "dev": true
- }
+ "debug": "^4.4.0",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "on-finished": "^2.4.1",
+ "parseurl": "^1.3.3",
+ "statuses": "^2.0.1"
}
},
"find-up": {
@@ -5647,12 +5430,6 @@
"path-exists": "^4.0.0"
}
},
- "follow-redirects": {
- "version": "1.16.0",
- "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz",
- "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==",
- "dev": true
- },
"forwarded": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
@@ -5660,18 +5437,11 @@
"dev": true
},
"fresh": {
- "version": "0.5.2",
- "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
- "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
+ "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
"dev": true
},
- "fsevents": {
- "version": "2.3.3",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
- "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
- "dev": true,
- "optional": true
- },
"function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
@@ -5706,15 +5476,6 @@
"es-object-atoms": "^1.0.0"
}
},
- "glob-parent": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
- "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
- "dev": true,
- "requires": {
- "is-glob": "^4.0.1"
- }
- },
"glob-to-regexp": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz",
@@ -5733,12 +5494,6 @@
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
"dev": true
},
- "handle-thing": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz",
- "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==",
- "dev": true
- },
"has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
@@ -5761,95 +5516,46 @@
"dev": true
},
"hasown": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
- "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
+ "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
"dev": true,
"requires": {
"function-bind": "^1.1.2"
}
},
- "hpack.js": {
- "version": "2.1.6",
- "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz",
- "integrity": "sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=",
- "dev": true,
- "requires": {
- "inherits": "^2.0.1",
- "obuf": "^1.0.0",
- "readable-stream": "^2.0.1",
- "wbuf": "^1.1.0"
- }
- },
- "http-deceiver": {
- "version": "1.2.7",
- "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz",
- "integrity": "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=",
- "dev": true
- },
"http-errors": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
- "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
- "dev": true,
- "requires": {
- "depd": "2.0.0",
- "inherits": "2.0.4",
- "setprototypeof": "1.2.0",
- "statuses": "2.0.1",
- "toidentifier": "1.0.1"
- },
- "dependencies": {
- "depd": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
- "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
- "dev": true
- },
- "inherits": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
- "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
- "dev": true
- },
- "statuses": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
- "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
- "dev": true
- }
- }
- },
- "http-parser-js": {
- "version": "0.5.8",
- "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz",
- "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==",
- "dev": true
- },
- "http-proxy": {
- "version": "1.18.1",
- "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz",
- "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==",
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
+ "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
"dev": true,
"requires": {
- "eventemitter3": "^4.0.0",
- "follow-redirects": "^1.0.0",
- "requires-port": "^1.0.0"
+ "depd": "~2.0.0",
+ "inherits": "~2.0.4",
+ "setprototypeof": "~1.2.0",
+ "statuses": "~2.0.2",
+ "toidentifier": "~1.0.1"
}
},
"http-proxy-middleware": {
- "version": "2.0.10",
- "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.10.tgz",
- "integrity": "sha512-RKzRWNPxUZqbuk3BC5mGVJbBnWgr+diEnjJexIOytFbBzDy88Fbh/YvBr3DsNrl1jYAfjWfpATEv0NO35FDuPQ==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-4.2.0.tgz",
+ "integrity": "sha512-ZA+oNOoM+GLoFTIzhkJptVQov73Srep2LBqhF8hG8CIPKO3nam1jonXVQ/QUH8RbwsmaaVz2SOJdzBNBHNtKbw==",
"dev": true,
"requires": {
- "@types/http-proxy": "^1.17.8",
- "http-proxy": "^1.18.1",
- "is-glob": "^4.0.1",
- "is-plain-obj": "^3.0.0",
- "micromatch": "^4.0.2"
+ "debug": "^4.4.3",
+ "httpxy": "^0.5.4",
+ "is-glob": "^4.0.3",
+ "is-plain-obj": "^4.1.0",
+ "micromatch": "^4.0.8"
}
},
+ "httpxy": {
+ "version": "0.5.5",
+ "resolved": "https://registry.npmjs.org/httpxy/-/httpxy-0.5.5.tgz",
+ "integrity": "sha512-uDjmnPyp1q4Sgzf3w+J/Fc6UqcCEj0x4Wjp7OqK5dGhNeDgpyrAmnS6ey8QWrX3SWDon2DMKf9sBa5X9+CVyMA==",
+ "dev": true
+ },
"hyperdyperid": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz",
@@ -5857,12 +5563,12 @@
"dev": true
},
"iconv-lite": {
- "version": "0.4.24",
- "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
- "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+ "version": "0.7.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz",
+ "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==",
"dev": true,
"requires": {
- "safer-buffer": ">= 2.1.2 < 3"
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
}
},
"import-local": {
@@ -5876,9 +5582,9 @@
}
},
"inherits": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
- "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"dev": true
},
"interpret": {
@@ -5888,20 +5594,11 @@
"dev": true
},
"ipaddr.js": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.1.0.tgz",
- "integrity": "sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ==",
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.4.0.tgz",
+ "integrity": "sha512-9VGk3HGanVE6JoZXHiCpnGy5X0jYDnN4EA4lntFPj+1vIWlFhIylq2CrrCOJH9EAhc5CYhq18F2Av2tgoAPsYQ==",
"dev": true
},
- "is-binary-path": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
- "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
- "dev": true,
- "requires": {
- "binary-extensions": "^2.0.0"
- }
- },
"is-core-module": {
"version": "2.13.0",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz",
@@ -5932,6 +5629,12 @@
"is-extglob": "^2.1.1"
}
},
+ "is-in-ssh": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-in-ssh/-/is-in-ssh-1.0.0.tgz",
+ "integrity": "sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==",
+ "dev": true
+ },
"is-inside-container": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz",
@@ -5942,9 +5645,9 @@
}
},
"is-network-error": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.1.0.tgz",
- "integrity": "sha512-tUdRRAnhT+OtCZR/LxZelH/C7QtjtFrTu5tXCA8pl55eTUElUHT+GPYV8MBMBvea/j+NxQqVt3LbWMRir7Gx9g==",
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.2.tgz",
+ "integrity": "sha512-PhBY86zaxNZUuWP6h13Vu5oFe0XY6/UlKzQnYFELzGVHygP3MxmvTfYSG7GN3aIab/iWudSMgjSnG9Dq+nHrgA==",
"dev": true
},
"is-number": {
@@ -5954,9 +5657,9 @@
"dev": true
},
"is-plain-obj": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz",
- "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==",
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz",
+ "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==",
"dev": true
},
"is-plain-object": {
@@ -5968,21 +5671,21 @@
"isobject": "^3.0.1"
}
},
+ "is-promise": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
+ "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
+ "dev": true
+ },
"is-wsl": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz",
- "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==",
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz",
+ "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==",
"dev": true,
"requires": {
"is-inside-container": "^1.0.0"
}
},
- "isarray": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
- "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
- "dev": true
- },
"isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
@@ -6056,20 +5759,30 @@
"dev": true
},
"media-typer": {
- "version": "0.3.0",
- "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
- "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.1.tgz",
+ "integrity": "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==",
"dev": true
},
"memfs": {
- "version": "4.17.2",
- "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.17.2.tgz",
- "integrity": "sha512-NgYhCOWgovOXSzvYgUW0LQ7Qy72rWQMGGFJDoWg4G30RHd3z77VbYdtJ4fembJXBy8pMIUA31XNAupobOQlwdg==",
- "dev": true,
- "requires": {
- "@jsonjoy.com/json-pack": "^1.0.3",
- "@jsonjoy.com/util": "^1.3.0",
- "tree-dump": "^1.0.1",
+ "version": "4.64.0",
+ "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.64.0.tgz",
+ "integrity": "sha512-Kw72fgY7Wn+sD8KmtNWSafl1dz0UvAsE/PHs3YVfLiaZuA3HxNm9sRLqAu0ATiBGJvME1PxZXbBZPv5GycDeAw==",
+ "dev": true,
+ "requires": {
+ "@jsonjoy.com/fs-core": "4.64.0",
+ "@jsonjoy.com/fs-fsa": "4.64.0",
+ "@jsonjoy.com/fs-node": "4.64.0",
+ "@jsonjoy.com/fs-node-builtins": "4.64.0",
+ "@jsonjoy.com/fs-node-to-fsa": "4.64.0",
+ "@jsonjoy.com/fs-node-utils": "4.64.0",
+ "@jsonjoy.com/fs-print": "4.64.0",
+ "@jsonjoy.com/fs-snapshot": "4.64.0",
+ "@jsonjoy.com/json-pack": "^1.11.0",
+ "@jsonjoy.com/util": "^1.9.0",
+ "glob-to-regex.js": "^1.0.1",
+ "thingies": "^2.5.0",
+ "tree-dump": "^1.0.3",
"tslib": "^2.0.0"
},
"dependencies": {
@@ -6080,36 +5793,231 @@
"dev": true,
"requires": {}
},
+ "@jsonjoy.com/buffers": {
+ "version": "17.67.0",
+ "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-17.67.0.tgz",
+ "integrity": "sha512-tfExRpYxBvi32vPs9ZHaTjSP4fHAfzSmcahOfNxtvGHcyJel+aibkPlGeBB+7AoC6hL7lXIE++8okecBxx7lcw==",
+ "dev": true,
+ "requires": {}
+ },
+ "@jsonjoy.com/codegen": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@jsonjoy.com/codegen/-/codegen-1.0.0.tgz",
+ "integrity": "sha512-E8Oy+08cmCf0EK/NMxpaJZmOxPqM+6iSe2S4nlSBrPZOORoDJILxtbSUEDKQyTamm/BVAhIGllOBNU79/dwf0g==",
+ "dev": true,
+ "requires": {}
+ },
+ "@jsonjoy.com/fs-core": {
+ "version": "4.64.0",
+ "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-core/-/fs-core-4.64.0.tgz",
+ "integrity": "sha512-zs2TAq7Six5jgMuoMNjpspAvOP3mhtgq/k1UyQodEzCtQi/N83y2/y+zcvnZSGp/Rxq96DBN+bValOBQAyn/ew==",
+ "dev": true,
+ "requires": {
+ "@jsonjoy.com/fs-node-builtins": "4.64.0",
+ "@jsonjoy.com/fs-node-utils": "4.64.0",
+ "thingies": "^2.5.0"
+ }
+ },
+ "@jsonjoy.com/fs-fsa": {
+ "version": "4.64.0",
+ "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-fsa/-/fs-fsa-4.64.0.tgz",
+ "integrity": "sha512-nMWOVbkLFyEgmXZih3wyvxA9XpgyyqyfrINMHvEFqhi7uqfRl7c9ERJt6yX7vgMPrB9Uo+OJO+Spa0cFzPD01w==",
+ "dev": true,
+ "requires": {
+ "@jsonjoy.com/fs-core": "4.64.0",
+ "@jsonjoy.com/fs-node-builtins": "4.64.0",
+ "@jsonjoy.com/fs-node-utils": "4.64.0",
+ "thingies": "^2.5.0"
+ }
+ },
+ "@jsonjoy.com/fs-node": {
+ "version": "4.64.0",
+ "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node/-/fs-node-4.64.0.tgz",
+ "integrity": "sha512-dO+NNkODbUli4uV42bcNrrLvq5rE7SNpdZ5TNd0dtbLsAaNK3MDiIC9lUi+brboGoIjW6vd2fB1qao60nrk5xA==",
+ "dev": true,
+ "requires": {
+ "@jsonjoy.com/fs-core": "4.64.0",
+ "@jsonjoy.com/fs-node-builtins": "4.64.0",
+ "@jsonjoy.com/fs-node-utils": "4.64.0",
+ "@jsonjoy.com/fs-print": "4.64.0",
+ "@jsonjoy.com/fs-snapshot": "4.64.0",
+ "glob-to-regex.js": "^1.0.0",
+ "thingies": "^2.5.0"
+ }
+ },
+ "@jsonjoy.com/fs-node-builtins": {
+ "version": "4.64.0",
+ "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-builtins/-/fs-node-builtins-4.64.0.tgz",
+ "integrity": "sha512-/o7WRFhUWaM/fOrslwLZGnzn4RmRILykn+lAL+mNObqqRNw+CQSiij6hpCeZ+C7buhdoVo7go/OYqzaSUfDYmA==",
+ "dev": true,
+ "requires": {}
+ },
+ "@jsonjoy.com/fs-node-to-fsa": {
+ "version": "4.64.0",
+ "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-to-fsa/-/fs-node-to-fsa-4.64.0.tgz",
+ "integrity": "sha512-WDD9WVs0hb7UAEKTgZW2f66WDrbj7gIIWwpP3spbLyXa0rghtUaFTB8L4gdR3ZCWwiKIsj38/CNijpVmpnuPUw==",
+ "dev": true,
+ "requires": {
+ "@jsonjoy.com/fs-fsa": "4.64.0",
+ "@jsonjoy.com/fs-node-builtins": "4.64.0",
+ "@jsonjoy.com/fs-node-utils": "4.64.0"
+ }
+ },
+ "@jsonjoy.com/fs-node-utils": {
+ "version": "4.64.0",
+ "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-utils/-/fs-node-utils-4.64.0.tgz",
+ "integrity": "sha512-k5Indsx9hWW9xSF7Y6oSKKwtCUNhzZxadub3owhIlitc+iMRVlPPdX2duTKQWBL3qNWpXya8jykgaaWpheeS4w==",
+ "dev": true,
+ "requires": {
+ "@jsonjoy.com/fs-node-builtins": "4.64.0",
+ "glob-to-regex.js": "^1.0.1"
+ }
+ },
+ "@jsonjoy.com/fs-print": {
+ "version": "4.64.0",
+ "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-print/-/fs-print-4.64.0.tgz",
+ "integrity": "sha512-PHZFccchvkhWrwPWHjmVAhbC3vSHCtyZvlZfJJ3ho2bnzl450hXri6/8e6pbkWdH+SkmLXNml0sV8e5HDAfxKw==",
+ "dev": true,
+ "requires": {
+ "@jsonjoy.com/fs-node-utils": "4.64.0",
+ "tree-dump": "^1.1.0"
+ }
+ },
+ "@jsonjoy.com/fs-snapshot": {
+ "version": "4.64.0",
+ "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-snapshot/-/fs-snapshot-4.64.0.tgz",
+ "integrity": "sha512-oM7UDeL83q6NBzzsfKAsYKXKVXlykKFqqOLh4xZZKAzzROTlInkPbc6LTDGThEOnPiFiUzA7tYziHG9xavd76Q==",
+ "dev": true,
+ "requires": {
+ "@jsonjoy.com/buffers": "^17.65.0",
+ "@jsonjoy.com/fs-node-utils": "4.64.0",
+ "@jsonjoy.com/json-pack": "^17.65.0",
+ "@jsonjoy.com/util": "^17.65.0"
+ },
+ "dependencies": {
+ "@jsonjoy.com/base64": {
+ "version": "17.67.0",
+ "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-17.67.0.tgz",
+ "integrity": "sha512-5SEsJGsm15aP8TQGkDfJvz9axgPwAEm98S5DxOuYe8e1EbfajcDmgeXXzccEjh+mLnjqEKrkBdjHWS5vFNwDdw==",
+ "dev": true,
+ "requires": {}
+ },
+ "@jsonjoy.com/codegen": {
+ "version": "17.67.0",
+ "resolved": "https://registry.npmjs.org/@jsonjoy.com/codegen/-/codegen-17.67.0.tgz",
+ "integrity": "sha512-idnkUplROpdBOV0HMcwhsCUS5TRUi9poagdGs70A6S4ux9+/aPuKbh8+UYRTLYQHtXvAdNfQWXDqZEx5k4Dj2Q==",
+ "dev": true,
+ "requires": {}
+ },
+ "@jsonjoy.com/json-pack": {
+ "version": "17.67.0",
+ "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-17.67.0.tgz",
+ "integrity": "sha512-t0ejURcGaZsn1ClbJ/3kFqSOjlryd92eQY465IYrezsXmPcfHPE/av4twRSxf6WE+TkZgLY+71vCZbiIiFKA/w==",
+ "dev": true,
+ "requires": {
+ "@jsonjoy.com/base64": "17.67.0",
+ "@jsonjoy.com/buffers": "17.67.0",
+ "@jsonjoy.com/codegen": "17.67.0",
+ "@jsonjoy.com/json-pointer": "17.67.0",
+ "@jsonjoy.com/util": "17.67.0",
+ "hyperdyperid": "^1.2.0",
+ "thingies": "^2.5.0",
+ "tree-dump": "^1.1.0"
+ }
+ },
+ "@jsonjoy.com/json-pointer": {
+ "version": "17.67.0",
+ "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pointer/-/json-pointer-17.67.0.tgz",
+ "integrity": "sha512-+iqOFInH+QZGmSuaybBUNdh7yvNrXvqR+h3wjXm0N/3JK1EyyFAeGJvqnmQL61d1ARLlk/wJdFKSL+LHJ1eaUA==",
+ "dev": true,
+ "requires": {
+ "@jsonjoy.com/util": "17.67.0"
+ }
+ },
+ "@jsonjoy.com/util": {
+ "version": "17.67.0",
+ "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-17.67.0.tgz",
+ "integrity": "sha512-6+8xBaz1rLSohlGh68D1pdw3AwDi9xydm8QNlAFkvnavCJYSze+pxoW2VKP8p308jtlMRLs5NTHfPlZLd4w7ew==",
+ "dev": true,
+ "requires": {
+ "@jsonjoy.com/buffers": "17.67.0",
+ "@jsonjoy.com/codegen": "17.67.0"
+ }
+ }
+ }
+ },
"@jsonjoy.com/json-pack": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.2.0.tgz",
- "integrity": "sha512-io1zEbbYcElht3tdlqEOFxZ0dMTYrHz9iMf0gqn1pPjZFTCgM5R4R5IMA20Chb2UPYYsxjzs8CgZ7Nb5n2K2rA==",
+ "version": "1.21.0",
+ "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.21.0.tgz",
+ "integrity": "sha512-+AKG+R2cfZMShzrF2uQw34v3zbeDYUqnQ+jg7ORic3BGtfw9p/+N6RJbq/kkV8JmYZaINknaEQ2m0/f693ZPpg==",
"dev": true,
"requires": {
- "@jsonjoy.com/base64": "^1.1.1",
- "@jsonjoy.com/util": "^1.1.2",
+ "@jsonjoy.com/base64": "^1.1.2",
+ "@jsonjoy.com/buffers": "^1.2.0",
+ "@jsonjoy.com/codegen": "^1.0.0",
+ "@jsonjoy.com/json-pointer": "^1.0.2",
+ "@jsonjoy.com/util": "^1.9.0",
"hyperdyperid": "^1.2.0",
- "thingies": "^1.20.0"
+ "thingies": "^2.5.0",
+ "tree-dump": "^1.1.0"
+ },
+ "dependencies": {
+ "@jsonjoy.com/buffers": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-1.2.1.tgz",
+ "integrity": "sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA==",
+ "dev": true,
+ "requires": {}
+ }
+ }
+ },
+ "@jsonjoy.com/json-pointer": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pointer/-/json-pointer-1.0.2.tgz",
+ "integrity": "sha512-Fsn6wM2zlDzY1U+v4Nc8bo3bVqgfNTGcn6dMgs6FjrEnt4ZCe60o6ByKRjOGlI2gow0aE/Q41QOigdTqkyK5fg==",
+ "dev": true,
+ "requires": {
+ "@jsonjoy.com/codegen": "^1.0.0",
+ "@jsonjoy.com/util": "^1.9.0"
}
},
"@jsonjoy.com/util": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.6.0.tgz",
- "integrity": "sha512-sw/RMbehRhN68WRtcKCpQOPfnH6lLP4GJfqzi3iYej8tnzpZUDr6UkZYJjcjjC0FWEJOJbyM3PTIwxucUmDG2A==",
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.9.0.tgz",
+ "integrity": "sha512-pLuQo+VPRnN8hfPqUTLTHk126wuYdXVxE6aDmjSeV4NCAgyxWbiOIeNJVtID3h1Vzpoi9m4jXezf73I6LgabgQ==",
+ "dev": true,
+ "requires": {
+ "@jsonjoy.com/buffers": "^1.0.0",
+ "@jsonjoy.com/codegen": "^1.0.0"
+ },
+ "dependencies": {
+ "@jsonjoy.com/buffers": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-1.2.1.tgz",
+ "integrity": "sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA==",
+ "dev": true,
+ "requires": {}
+ }
+ }
+ },
+ "glob-to-regex.js": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/glob-to-regex.js/-/glob-to-regex.js-1.2.0.tgz",
+ "integrity": "sha512-QMwlOQKU/IzqMUOAZWubUOT8Qft+Y0KQWnX9nK3ch0CJg0tTp4TvGZsTfudYKv2NzoQSyPcnA6TYeIQ3jGichQ==",
"dev": true,
"requires": {}
},
"thingies": {
- "version": "1.21.0",
- "resolved": "https://registry.npmjs.org/thingies/-/thingies-1.21.0.tgz",
- "integrity": "sha512-hsqsJsFMsV+aD4s3CWKk85ep/3I9XzYV/IXaSouJMYIoDlgyi11cBhsqYe9/geRfB0YIikBQg6raRaM+nIMP9g==",
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/thingies/-/thingies-2.6.0.tgz",
+ "integrity": "sha512-rMHRjmlFLM1R96UYPvpmnc3LYtdFrT33JIB7L9hetGue1qAPfn1N2LJeEjxUSidu1Iku+haLZXDuEXUHNGO/lg==",
"dev": true,
"requires": {}
},
"tree-dump": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.0.3.tgz",
- "integrity": "sha512-il+Cv80yVHFBwokQSfd4bldvr1Md951DpgAGfmhydt04L+YzHgubm2tQ7zueWDcGENKHq0ZvGFR/hjvNXilHEg==",
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.1.0.tgz",
+ "integrity": "sha512-rMuvhU4MCDbcbnleZTFezWsaZXRFemSqAM+7jPnzUl1fo9w3YEKOxAeui0fz3OI4EU4hf23iyA7uQRVko+UaBA==",
"dev": true,
"requires": {}
},
@@ -6122,9 +6030,9 @@
}
},
"merge-descriptors": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
- "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
+ "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
"dev": true
},
"merge-stream": {
@@ -6133,12 +6041,6 @@
"integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
"dev": true
},
- "methods": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
- "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
- "dev": true
- },
"micromatch": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
@@ -6149,12 +6051,6 @@
"picomatch": "^2.3.1"
}
},
- "mime": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
- "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
- "dev": true
- },
"mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
@@ -6170,12 +6066,6 @@
"mime-db": "1.52.0"
}
},
- "minimalistic-assert": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
- "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==",
- "dev": true
- },
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
@@ -6193,9 +6083,9 @@
}
},
"negotiator": {
- "version": "0.6.3",
- "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
- "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
+ "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
"dev": true
},
"neo-async": {
@@ -6222,12 +6112,6 @@
"integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
"dev": true
},
- "obuf": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz",
- "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==",
- "dev": true
- },
"on-finished": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
@@ -6243,16 +6127,27 @@
"integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==",
"dev": true
},
+ "once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "dev": true,
+ "requires": {
+ "wrappy": "1"
+ }
+ },
"open": {
- "version": "10.1.2",
- "resolved": "https://registry.npmjs.org/open/-/open-10.1.2.tgz",
- "integrity": "sha512-cxN6aIDPz6rm8hbebcP7vrQNhvRcveZoJU72Y7vskh4oIm+BZwBECnx5nTmrlres1Qapvx27Qo1Auukpf8PKXw==",
+ "version": "11.0.0",
+ "resolved": "https://registry.npmjs.org/open/-/open-11.0.0.tgz",
+ "integrity": "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==",
"dev": true,
"requires": {
- "default-browser": "^5.2.1",
+ "default-browser": "^5.4.0",
"define-lazy-prop": "^3.0.0",
+ "is-in-ssh": "^1.0.0",
"is-inside-container": "^1.0.0",
- "is-wsl": "^3.1.0"
+ "powershell-utils": "^0.1.0",
+ "wsl-utils": "^0.3.0"
}
},
"p-locate": {
@@ -6276,14 +6171,12 @@
}
},
"p-retry": {
- "version": "6.2.1",
- "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-6.2.1.tgz",
- "integrity": "sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ==",
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-8.0.0.tgz",
+ "integrity": "sha512-kFVqH1HxOHp8LupNsOys7bSV09VYTRLxarH/mokO4Rqhk6wGi70E0jh4VzvVGXfEVNggHoHLAMWsQqHyU1Ey9A==",
"dev": true,
"requires": {
- "@types/retry": "0.12.2",
- "is-network-error": "^1.0.0",
- "retry": "^0.13.1"
+ "is-network-error": "^1.3.0"
}
},
"p-try": {
@@ -6317,9 +6210,9 @@
"dev": true
},
"path-to-regexp": {
- "version": "0.1.12",
- "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
- "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
+ "version": "8.4.2",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz",
+ "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==",
"dev": true
},
"picocolors": {
@@ -6365,10 +6258,10 @@
}
}
},
- "process-nextick-args": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz",
- "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==",
+ "powershell-utils": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/powershell-utils/-/powershell-utils-0.1.0.tgz",
+ "integrity": "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==",
"dev": true
},
"proxy-addr": {
@@ -6413,88 +6306,38 @@
"dev": true
},
"qs": {
- "version": "6.15.2",
- "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz",
- "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==",
+ "version": "6.15.3",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz",
+ "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==",
"dev": true,
"requires": {
- "side-channel": "^1.1.0"
+ "es-define-property": "^1.0.1",
+ "side-channel": "^1.1.1"
}
},
"range-parser": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
- "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz",
+ "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==",
"dev": true
},
"raw-body": {
- "version": "2.5.3",
- "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz",
- "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==",
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz",
+ "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==",
"dev": true,
"requires": {
"bytes": "~3.1.2",
"http-errors": "~2.0.1",
- "iconv-lite": "~0.4.24",
+ "iconv-lite": "~0.7.0",
"unpipe": "~1.0.0"
- },
- "dependencies": {
- "depd": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
- "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
- "dev": true
- },
- "http-errors": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
- "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
- "dev": true,
- "requires": {
- "depd": "~2.0.0",
- "inherits": "~2.0.4",
- "setprototypeof": "~1.2.0",
- "statuses": "~2.0.2",
- "toidentifier": "~1.0.1"
- }
- },
- "inherits": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
- "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
- "dev": true
- },
- "statuses": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
- "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
- "dev": true
- }
- }
- },
- "readable-stream": {
- "version": "2.3.6",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
- "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
- "dev": true,
- "requires": {
- "core-util-is": "~1.0.0",
- "inherits": "~2.0.3",
- "isarray": "~1.0.0",
- "process-nextick-args": "~2.0.0",
- "safe-buffer": "~5.1.1",
- "string_decoder": "~1.1.1",
- "util-deprecate": "~1.0.1"
}
},
"readdirp": {
- "version": "3.6.0",
- "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
- "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
- "dev": true,
- "requires": {
- "picomatch": "^2.2.1"
- }
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz",
+ "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==",
+ "dev": true
},
"rechoir": {
"version": "0.8.0",
@@ -6517,12 +6360,6 @@
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
"dev": true
},
- "requires-port": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
- "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
- "dev": true
- },
"resolve": {
"version": "1.22.6",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.6.tgz",
@@ -6549,22 +6386,23 @@
"integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
"dev": true
},
- "retry": {
- "version": "0.13.1",
- "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz",
- "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==",
- "dev": true
+ "router": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
+ "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
+ "dev": true,
+ "requires": {
+ "debug": "^4.4.0",
+ "depd": "^2.0.0",
+ "is-promise": "^4.0.0",
+ "parseurl": "^1.3.3",
+ "path-to-regexp": "^8.0.0"
+ }
},
"run-applescript": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.0.0.tgz",
- "integrity": "sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==",
- "dev": true
- },
- "safe-buffer": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
- "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz",
+ "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==",
"dev": true
},
"safer-buffer": {
@@ -6585,12 +6423,6 @@
"ajv-keywords": "^5.1.0"
}
},
- "select-hose": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz",
- "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=",
- "dev": true
- },
"selfsigned": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-5.5.0.tgz",
@@ -6602,84 +6434,72 @@
}
},
"send": {
- "version": "0.19.0",
- "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
- "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
- "dev": true,
- "requires": {
- "debug": "2.6.9",
- "depd": "2.0.0",
- "destroy": "1.2.0",
- "encodeurl": "~1.0.2",
- "escape-html": "~1.0.3",
- "etag": "~1.8.1",
- "fresh": "0.5.2",
- "http-errors": "2.0.0",
- "mime": "1.6.0",
- "ms": "2.1.3",
- "on-finished": "2.4.1",
- "range-parser": "~1.2.1",
- "statuses": "2.0.1"
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz",
+ "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==",
+ "dev": true,
+ "requires": {
+ "debug": "^4.4.3",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "etag": "^1.8.1",
+ "fresh": "^2.0.0",
+ "http-errors": "^2.0.1",
+ "mime-types": "^3.0.2",
+ "ms": "^2.1.3",
+ "on-finished": "^2.4.1",
+ "range-parser": "^1.2.1",
+ "statuses": "^2.0.2"
},
"dependencies": {
- "debug": {
- "version": "2.6.9",
- "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
- "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "mime-db": {
+ "version": "1.54.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
+ "dev": true
+ },
+ "mime-types": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
+ "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
"dev": true,
"requires": {
- "ms": "2.0.0"
- },
- "dependencies": {
- "ms": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
- "dev": true
- }
+ "mime-db": "^1.54.0"
}
},
- "depd": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
- "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
- "dev": true
- },
- "encodeurl": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
- "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
- "dev": true
- },
"ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true
- },
- "statuses": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
- "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
- "dev": true
}
}
},
"serve-index": {
- "version": "1.9.1",
- "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz",
- "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=",
+ "version": "1.9.2",
+ "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.2.tgz",
+ "integrity": "sha512-KDj11HScOaLmrPxl70KYNW1PksP4Nb/CLL2yvC+Qd2kHMPEEpfc4Re2e4FOay+bC/+XQl/7zAcWON3JVo5v3KQ==",
"dev": true,
"requires": {
- "accepts": "~1.3.4",
+ "accepts": "~1.3.8",
"batch": "0.6.1",
"debug": "2.6.9",
"escape-html": "~1.0.3",
- "http-errors": "~1.6.2",
- "mime-types": "~2.1.17",
- "parseurl": "~1.3.2"
+ "http-errors": "~1.8.0",
+ "mime-types": "~2.1.35",
+ "parseurl": "~1.3.3"
},
"dependencies": {
+ "accepts": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
+ "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+ "dev": true,
+ "requires": {
+ "mime-types": "~2.1.34",
+ "negotiator": "0.6.3"
+ }
+ },
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
@@ -6689,36 +6509,49 @@
"ms": "2.0.0"
}
},
+ "depd": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
+ "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==",
+ "dev": true
+ },
"http-errors": {
- "version": "1.6.3",
- "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz",
- "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=",
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz",
+ "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==",
"dev": true,
"requires": {
"depd": "~1.1.2",
- "inherits": "2.0.3",
- "setprototypeof": "1.1.0",
- "statuses": ">= 1.4.0 < 2"
+ "inherits": "2.0.4",
+ "setprototypeof": "1.2.0",
+ "statuses": ">= 1.5.0 < 2",
+ "toidentifier": "1.0.1"
}
},
- "setprototypeof": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz",
- "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==",
+ "negotiator": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
+ "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
+ "dev": true
+ },
+ "statuses": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
+ "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==",
"dev": true
}
}
},
"serve-static": {
- "version": "1.16.2",
- "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
- "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz",
+ "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==",
"dev": true,
"requires": {
- "encodeurl": "~2.0.0",
- "escape-html": "~1.0.3",
- "parseurl": "~1.3.3",
- "send": "0.19.0"
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "parseurl": "^1.3.3",
+ "send": "^1.2.0"
}
},
"setprototypeof": {
@@ -6758,26 +6591,26 @@
"dev": true
},
"side-channel": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
- "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz",
+ "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==",
"dev": true,
"requires": {
"es-errors": "^1.3.0",
- "object-inspect": "^1.13.3",
- "side-channel-list": "^1.0.0",
+ "object-inspect": "^1.13.4",
+ "side-channel-list": "^1.0.1",
"side-channel-map": "^1.0.1",
"side-channel-weakmap": "^1.0.2"
}
},
"side-channel-list": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
- "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz",
+ "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==",
"dev": true,
"requires": {
"es-errors": "^1.3.0",
- "object-inspect": "^1.13.3"
+ "object-inspect": "^1.13.4"
}
},
"side-channel-map": {
@@ -6805,17 +6638,6 @@
"side-channel-map": "^1.0.1"
}
},
- "sockjs": {
- "version": "0.3.24",
- "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz",
- "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==",
- "dev": true,
- "requires": {
- "faye-websocket": "^0.11.3",
- "uuid": "^8.3.2",
- "websocket-driver": "^0.7.4"
- }
- },
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
@@ -6832,61 +6654,12 @@
"source-map": "^0.6.0"
}
},
- "spdy": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz",
- "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==",
- "dev": true,
- "requires": {
- "debug": "^4.1.0",
- "handle-thing": "^2.0.0",
- "http-deceiver": "^1.2.7",
- "select-hose": "^2.0.0",
- "spdy-transport": "^3.0.0"
- }
- },
- "spdy-transport": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz",
- "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==",
- "dev": true,
- "requires": {
- "debug": "^4.1.0",
- "detect-node": "^2.0.4",
- "hpack.js": "^2.1.6",
- "obuf": "^1.1.2",
- "readable-stream": "^3.0.6",
- "wbuf": "^1.7.3"
- },
- "dependencies": {
- "readable-stream": {
- "version": "3.6.0",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
- "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
- "dev": true,
- "requires": {
- "inherits": "^2.0.3",
- "string_decoder": "^1.1.1",
- "util-deprecate": "^1.0.1"
- }
- }
- }
- },
"statuses": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
- "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=",
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
+ "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
"dev": true
},
- "string_decoder": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
- "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
- "dev": true,
- "requires": {
- "safe-buffer": "~5.1.0"
- }
- },
"supports-color": {
"version": "8.1.1",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
@@ -6994,13 +6767,37 @@
}
},
"type-is": {
- "version": "1.6.18",
- "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
- "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz",
+ "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==",
"dev": true,
"requires": {
- "media-typer": "0.3.0",
- "mime-types": "~2.1.24"
+ "content-type": "^2.0.0",
+ "media-typer": "^1.1.0",
+ "mime-types": "^3.0.0"
+ },
+ "dependencies": {
+ "content-type": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz",
+ "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==",
+ "dev": true
+ },
+ "mime-db": {
+ "version": "1.54.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
+ "dev": true
+ },
+ "mime-types": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
+ "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
+ "dev": true,
+ "requires": {
+ "mime-db": "^1.54.0"
+ }
+ }
}
},
"unpipe": {
@@ -7019,24 +6816,6 @@
"picocolors": "^1.1.1"
}
},
- "util-deprecate": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
- "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
- "dev": true
- },
- "utils-merge": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
- "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
- "dev": true
- },
- "uuid": {
- "version": "8.3.2",
- "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
- "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
- "dev": true
- },
"vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
@@ -7053,15 +6832,6 @@
"graceful-fs": "^4.1.2"
}
},
- "wbuf": {
- "version": "1.7.3",
- "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz",
- "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==",
- "dev": true,
- "requires": {
- "minimalistic-assert": "^1.0.0"
- }
- },
"webpack": {
"version": "5.105.0",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.0.tgz",
@@ -7125,53 +6895,65 @@
}
},
"webpack-dev-middleware": {
- "version": "7.4.2",
- "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.4.2.tgz",
- "integrity": "sha512-xOO8n6eggxnwYpy1NlzUKpvrjfJTvae5/D6WOK0S2LSo7vjmo5gCM1DbLUmFqrMTJP+W/0YZNctm7jasWvLuBA==",
+ "version": "8.0.4",
+ "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-8.0.4.tgz",
+ "integrity": "sha512-9dFzIvIfbdnkOlRjXDHEmEKlY/KPsELNIyKWdoNfK4WaHN9Db+JyVG0gi4/APUPX2UVhnCZ6jp7x0EyM7yTq1Q==",
"dev": true,
"requires": {
- "colorette": "^2.0.10",
- "memfs": "^4.6.0",
- "mime-types": "^2.1.31",
- "on-finished": "^2.4.1",
+ "memfs": "^4.56.10",
+ "mime-types": "^3.0.2",
"range-parser": "^1.2.1",
- "schema-utils": "^4.0.0"
+ "schema-utils": "^4.3.3"
+ },
+ "dependencies": {
+ "mime-db": {
+ "version": "1.54.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
+ "dev": true
+ },
+ "mime-types": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
+ "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
+ "dev": true,
+ "requires": {
+ "mime-db": "^1.54.0"
+ }
+ }
}
},
"webpack-dev-server": {
- "version": "5.2.6",
- "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.2.6.tgz",
- "integrity": "sha512-HNLRmamRvVavZQ+avceZifmv8hmdUjg43t6MI4SqJDwFdW7RPQwH5vzGhDRZSX59SgfbeHhLnq3g+uooWo7pVw==",
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-6.0.0.tgz",
+ "integrity": "sha512-q9SD4ItOGhZLeU6EGT10caDZdHjF50Pz1DtkRZZOPsfluMXOkacWKKOtSBSLVkPqKiF67eFUC0rI88U/tSFPEw==",
"dev": true,
"requires": {
"@types/bonjour": "^3.5.13",
"@types/connect-history-api-fallback": "^1.5.4",
- "@types/express": "^4.17.25",
- "@types/express-serve-static-core": "^4.17.21",
+ "@types/express": "^5.0.6",
+ "@types/express-serve-static-core": "^5.1.1",
"@types/serve-index": "^1.9.4",
- "@types/serve-static": "^1.15.5",
- "@types/sockjs": "^0.3.36",
- "@types/ws": "^8.5.10",
+ "@types/serve-static": "^2.2.0",
+ "@types/ws": "^8.18.1",
"ansi-html-community": "^0.0.8",
- "bonjour-service": "^1.2.1",
- "chokidar": "^3.6.0",
- "colorette": "^2.0.10",
+ "bonjour-service": "^1.3.0",
+ "chokidar": "^5.0.0",
"compression": "^1.8.1",
"connect-history-api-fallback": "^2.0.0",
- "express": "^4.22.1",
- "graceful-fs": "^4.2.6",
- "http-proxy-middleware": "^2.0.9",
- "ipaddr.js": "^2.1.0",
+ "express": "^5.2.1",
+ "graceful-fs": "^4.2.11",
+ "http-proxy-middleware": "^4.1.1",
+ "ipaddr.js": "^2.3.0",
"launch-editor": "^2.14.1",
- "open": "^10.0.3",
- "p-retry": "^6.2.0",
- "schema-utils": "^4.2.0",
+ "open": "^11.0.0",
+ "p-retry": "^8.0.0",
+ "schema-utils": "^4.3.3",
"selfsigned": "^5.5.0",
- "serve-index": "^1.9.1",
- "sockjs": "^0.3.24",
- "spdy": "^4.0.2",
- "webpack-dev-middleware": "^7.4.2",
- "ws": "^8.18.0"
+ "serve-index": "^1.9.2",
+ "tinyglobby": "^0.2.15",
+ "webpack-dev-middleware": "^8.0.3",
+ "ws": "^8.20.0"
}
},
"webpack-merge": {
@@ -7190,23 +6972,6 @@
"integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==",
"dev": true
},
- "websocket-driver": {
- "version": "0.7.5",
- "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.5.tgz",
- "integrity": "sha512-ZL2+3c7kMBdIRCMz6l8jQMHyGVxj+UL+xVk74Ombiciboca8rHa15L86B19E5oh1pL9Ii/uj54gtsIrZGMo6zA==",
- "dev": true,
- "requires": {
- "http-parser-js": ">=0.5.1",
- "safe-buffer": ">=5.1.0",
- "websocket-extensions": ">=0.1.1"
- }
- },
- "websocket-extensions": {
- "version": "0.1.4",
- "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz",
- "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==",
- "dev": true
- },
"which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -7222,12 +6987,28 @@
"integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==",
"dev": true
},
+ "wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "dev": true
+ },
"ws": {
"version": "8.21.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.21.1.tgz",
"integrity": "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==",
"dev": true,
"requires": {}
+ },
+ "wsl-utils": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.3.1.tgz",
+ "integrity": "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==",
+ "dev": true,
+ "requires": {
+ "is-wsl": "^3.1.0",
+ "powershell-utils": "^0.1.0"
+ }
}
}
}
diff --git a/datafusion/wasmtest/datafusion-wasm-app/package.json b/datafusion/wasmtest/datafusion-wasm-app/package.json
index 1377df28463bd..e9e98f49495f9 100644
--- a/datafusion/wasmtest/datafusion-wasm-app/package.json
+++ b/datafusion/wasmtest/datafusion-wasm-app/package.json
@@ -29,7 +29,7 @@
"devDependencies": {
"webpack": "5.105.0",
"webpack-cli": "5.1.4",
- "webpack-dev-server": "5.2.6",
+ "webpack-dev-server": "6.0.0",
"copy-webpack-plugin": "14.0.0"
}
}
From 562f87dff5e2ee381cb37e45ce6802ddefdebbc3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Burak=20=C5=9Een?=
Date: Fri, 24 Jul 2026 23:45:35 +0300
Subject: [PATCH 016/109] refactor(proto): migrate HashJoinExec serde (#23853)
## Which issue does this PR close?
- Closes #23507.
## Rationale for this change
Part of epic #23494. Moves `HashJoinExec` protobuf serialization from
central dispatch.
## What changes are included in this PR?
Add protobuf serialization and deserialization to the `HashJoinExec`
physical plan implementation and deprecate the corresponding central
proto methods. The wire format remains unchanged.
## Are these changes tested?
Yes, existing HashJoin round-trip tests cover this plan.
## Are there any user-facing changes?
Existing methods are deprecated with no immediate API change.
---
.../physical-plan/src/joins/hash_join/exec.rs | 191 ++++++++++++++
datafusion/proto/src/physical_plan/mod.rs | 249 ++----------------
2 files changed, 217 insertions(+), 223 deletions(-)
diff --git a/datafusion/physical-plan/src/joins/hash_join/exec.rs b/datafusion/physical-plan/src/joins/hash_join/exec.rs
index c746aba028990..ccdb050d168e0 100644
--- a/datafusion/physical-plan/src/joins/hash_join/exec.rs
+++ b/datafusion/physical-plan/src/joins/hash_join/exec.rs
@@ -1761,6 +1761,197 @@ impl ExecutionPlan for HashJoinExec {
.ok()
.map(|exec| Arc::new(exec) as _)
}
+ #[cfg(feature = "proto")]
+ fn try_to_proto(
+ &self,
+ ctx: &crate::proto::ExecutionPlanEncodeCtx<'_>,
+ ) -> Result> {
+ use datafusion_proto_models::protobuf;
+
+ let left = ctx.encode_child(self.left())?;
+ let right = ctx.encode_child(self.right())?;
+
+ let on = self
+ .on()
+ .iter()
+ .map(|(l, r)| -> Result {
+ Ok(protobuf::JoinOn {
+ left: Some(ctx.encode_expr(l)?),
+ right: Some(ctx.encode_expr(r)?),
+ })
+ })
+ .collect::>>()?;
+
+ let join_type = crate::joins::proto::join_type_to_proto(*self.join_type());
+ let null_equality =
+ crate::joins::proto::null_equality_to_proto(self.null_equality());
+ // `PartitionMode` is specific to `HashJoinExec`, so its conversion stays
+ // inline (by-name on purpose: the enums are numbered differently).
+ let partition_mode = match self.partition_mode() {
+ PartitionMode::CollectLeft => protobuf::PartitionMode::CollectLeft,
+ PartitionMode::Partitioned => protobuf::PartitionMode::Partitioned,
+ PartitionMode::Auto => protobuf::PartitionMode::Auto,
+ };
+
+ let filter = self
+ .filter()
+ .map(|f| crate::joins::proto::join_filter_to_proto(f, ctx))
+ .transpose()?;
+
+ let dynamic_filter = self
+ .dynamic_filter_expr()
+ .map(|df| {
+ let df_expr: Arc =
+ Arc::clone(df) as Arc;
+ ctx.encode_expr(&df_expr)
+ })
+ .transpose()?;
+
+ Ok(Some(protobuf::PhysicalPlanNode {
+ physical_plan_type: Some(
+ protobuf::physical_plan_node::PhysicalPlanType::HashJoin(Box::new(
+ protobuf::HashJoinExecNode {
+ left: Some(Box::new(left)),
+ right: Some(Box::new(right)),
+ on,
+ join_type: join_type.into(),
+ partition_mode: partition_mode.into(),
+ null_equality: null_equality.into(),
+ filter,
+ // Proto3 `repeated` cannot distinguish `None` from
+ // `Some(vec![])`. `Some(vec![])` (reachable via
+ // `try_embed_projection` for e.g. `SELECT count(1) … JOIN …`)
+ // changes the output schema, so it is encoded with the
+ // single-element sentinel `[u32::MAX]` (never a valid column
+ // index); every other state is sent as-is. See
+ // `try_from_proto` for the matching decoder.
+ projection: match self.projection.as_ref() {
+ None => Vec::new(),
+ Some(v) if v.is_empty() => vec![u32::MAX],
+ Some(v) => v.iter().map(|x| *x as u32).collect(),
+ },
+ null_aware: self.null_aware,
+ dynamic_filter,
+ },
+ )),
+ ),
+ }))
+ }
+}
+
+#[cfg(feature = "proto")]
+impl HashJoinExec {
+ /// Reconstruct a [`HashJoinExec`] from its protobuf representation.
+ pub fn try_from_proto(
+ node: &datafusion_proto_models::protobuf::PhysicalPlanNode,
+ ctx: &crate::proto::ExecutionPlanDecodeCtx<'_>,
+ ) -> Result> {
+ use datafusion_common::internal_datafusion_err;
+ use datafusion_proto_models::protobuf;
+ use std::any::Any;
+
+ let hashjoin = crate::expect_plan_variant!(
+ node,
+ protobuf::physical_plan_node::PhysicalPlanType::HashJoin,
+ "HashJoinExec",
+ );
+
+ let left =
+ ctx.decode_required_child(hashjoin.left.as_deref(), "HashJoinExec", "left")?;
+ let right = ctx.decode_required_child(
+ hashjoin.right.as_deref(),
+ "HashJoinExec",
+ "right",
+ )?;
+ let left_schema = left.schema();
+ let right_schema = right.schema();
+
+ let on: Vec<(PhysicalExprRef, PhysicalExprRef)> = hashjoin
+ .on
+ .iter()
+ .map(|col| {
+ let l = ctx.decode_required_expr(
+ col.left.as_ref(),
+ left_schema.as_ref(),
+ "HashJoinExec",
+ "on.left",
+ )?;
+ let r = ctx.decode_required_expr(
+ col.right.as_ref(),
+ right_schema.as_ref(),
+ "HashJoinExec",
+ "on.right",
+ )?;
+ Ok((l, r))
+ })
+ .collect::>()?;
+
+ let join_type = crate::joins::proto::join_type_from_proto(
+ hashjoin.join_type,
+ "HashJoinExec",
+ )?;
+ let null_equality = crate::joins::proto::null_equality_from_proto(
+ hashjoin.null_equality,
+ "HashJoinExec",
+ )?;
+ // `PartitionMode` is specific to `HashJoinExec`, so its conversion stays
+ // inline (by-name on purpose: the enums are numbered differently).
+ let partition_mode = match protobuf::PartitionMode::try_from(
+ hashjoin.partition_mode,
+ )
+ .map_err(|_| {
+ internal_datafusion_err!(
+ "HashJoinExec: unknown PartitionMode {}",
+ hashjoin.partition_mode
+ )
+ })? {
+ protobuf::PartitionMode::CollectLeft => PartitionMode::CollectLeft,
+ protobuf::PartitionMode::Partitioned => PartitionMode::Partitioned,
+ protobuf::PartitionMode::Auto => PartitionMode::Auto,
+ };
+
+ let filter = hashjoin
+ .filter
+ .as_ref()
+ .map(|f| crate::joins::proto::join_filter_from_proto(f, ctx, "HashJoinExec"))
+ .transpose()?;
+
+ // Preserve the empty-projection sentinel written by `try_to_proto`.
+ let projection = match hashjoin.projection.as_slice() {
+ [] => None,
+ [u32::MAX] => Some(Vec::new()),
+ indices => Some(indices.iter().map(|i| *i as usize).collect()),
+ };
+
+ let mut hash_join = HashJoinExec::try_new(
+ left,
+ right,
+ on,
+ filter,
+ &join_type,
+ projection,
+ partition_mode,
+ null_equality,
+ hashjoin.null_aware,
+ )?;
+
+ if let Some(dynamic_filter_proto) = &hashjoin.dynamic_filter {
+ // The dynamic filter is a `DynamicFilterPhysicalExpr` over the probe
+ // (right) side; decode against the right schema then downcast.
+ let dynamic_filter_expr =
+ ctx.decode_expr(dynamic_filter_proto, right_schema.as_ref())?;
+ let df = (dynamic_filter_expr as Arc)
+ .downcast::()
+ .map_err(|_| {
+ internal_datafusion_err!(
+ "HashJoinExec dynamic_filter did not decode to a DynamicFilterPhysicalExpr"
+ )
+ })?;
+ hash_join = hash_join.with_dynamic_filter_expr(df)?;
+ }
+
+ Ok(Arc::new(hash_join))
+ }
}
/// Determines which sides of a join are "preserved" for filter pushdown.
diff --git a/datafusion/proto/src/physical_plan/mod.rs b/datafusion/proto/src/physical_plan/mod.rs
index b459368bcb1da..345ab87bf5d5b 100644
--- a/datafusion/proto/src/physical_plan/mod.rs
+++ b/datafusion/proto/src/physical_plan/mod.rs
@@ -27,8 +27,7 @@ use datafusion_common::config::CsvOptions;
use datafusion_common::display::StringifiedPlan;
use datafusion_common::format::ExplainFormat;
use datafusion_common::{
- DataFusionError, JoinType, NullEquality, Result, internal_datafusion_err,
- internal_err, not_impl_err,
+ DataFusionError, Result, internal_datafusion_err, internal_err, not_impl_err,
};
#[cfg(feature = "parquet")]
use datafusion_datasource::file::FileSource;
@@ -60,7 +59,7 @@ use datafusion_functions_table::generate_series::{
use datafusion_physical_expr::aggregate::{AggregateExprBuilder, AggregateFunctionExpr};
use datafusion_physical_expr::async_scalar_function::AsyncFuncExpr;
use datafusion_physical_expr::expressions::DynamicFilterPhysicalExpr;
-use datafusion_physical_expr::{LexOrdering, LexRequirement, PhysicalExprRef};
+use datafusion_physical_expr::{LexOrdering, LexRequirement};
use datafusion_physical_expr_common::physical_expr::proto_decode::PhysicalExprDecodeCtx;
use datafusion_physical_expr_common::physical_expr::proto_encode::PhysicalExprEncodeCtx;
use datafusion_physical_plan::aggregates::{
@@ -80,9 +79,8 @@ use datafusion_physical_plan::empty::EmptyExec;
use datafusion_physical_plan::explain::ExplainExec;
use datafusion_physical_plan::expressions::PhysicalSortExpr;
use datafusion_physical_plan::filter::FilterExec;
-use datafusion_physical_plan::joins::utils::{ColumnIndex, JoinFilter};
use datafusion_physical_plan::joins::{
- CrossJoinExec, HashJoinExec, NestedLoopJoinExec, PartitionMode, SortMergeJoinExec,
+ CrossJoinExec, HashJoinExec, NestedLoopJoinExec, SortMergeJoinExec,
SymmetricHashJoinExec,
};
use datafusion_physical_plan::limit::{GlobalLimitExec, LocalLimitExec};
@@ -799,8 +797,8 @@ pub trait PhysicalPlanNodeExt: Sized {
PhysicalPlanType::Aggregate(hash_agg) => {
self.try_into_aggregate_physical_plan(hash_agg, ctx, proto_converter)
}
- PhysicalPlanType::HashJoin(hashjoin) => {
- self.try_into_hash_join_physical_plan(hashjoin, ctx, proto_converter)
+ PhysicalPlanType::HashJoin(_) => {
+ HashJoinExec::try_from_proto(self.node(), &decode_ctx)
}
PhysicalPlanType::SymmetricHashJoin(_) => {
SymmetricHashJoinExec::try_from_proto(self.node(), &decode_ctx)
@@ -909,14 +907,6 @@ pub trait PhysicalPlanNodeExt: Sized {
);
}
- if let Some(exec) = plan.downcast_ref::() {
- return protobuf::PhysicalPlanNode::try_from_hash_join_exec(
- exec,
- codec,
- proto_converter,
- );
- }
-
if let Some(exec) = plan.downcast_ref::() {
return protobuf::PhysicalPlanNode::try_from_aggregate_exec(
exec,
@@ -1703,137 +1693,27 @@ pub trait PhysicalPlanNodeExt: Sized {
Ok(Arc::new(agg))
}
+ #[deprecated(
+ since = "55.0.0",
+ note = "unused by DataFusion; `HashJoinExec` deserializes itself via `HashJoinExec::try_from_proto`"
+ )]
fn try_into_hash_join_physical_plan(
&self,
hashjoin: &protobuf::HashJoinExecNode,
ctx: &PhysicalPlanDecodeContext<'_>,
proto_converter: &dyn PhysicalProtoConverterExtension,
) -> Result> {
- let left: Arc =
- into_physical_plan(&hashjoin.left, ctx, proto_converter)?;
- let right: Arc =
- into_physical_plan(&hashjoin.right, ctx, proto_converter)?;
- let left_schema = left.schema();
- let right_schema = right.schema();
- let on: Vec<(PhysicalExprRef, PhysicalExprRef)> = hashjoin
- .on
- .iter()
- .map(|col| {
- let left = proto_converter.proto_to_physical_expr(
- &col.left.clone().unwrap(),
- left_schema.as_ref(),
- ctx,
- )?;
- let right = proto_converter.proto_to_physical_expr(
- &col.right.clone().unwrap(),
- right_schema.as_ref(),
- ctx,
- )?;
- Ok((left, right))
- })
- .collect::>()?;
- let join_type =
- protobuf::JoinType::try_from(hashjoin.join_type).map_err(|_| {
- proto_error(format!(
- "Received a HashJoinNode message with unknown JoinType {}",
- hashjoin.join_type
- ))
- })?;
- let null_equality = protobuf::NullEquality::try_from(hashjoin.null_equality)
- .map_err(|_| {
- proto_error(format!(
- "Received a HashJoinNode message with unknown NullEquality {}",
- hashjoin.null_equality
- ))
- })?;
- let filter = hashjoin
- .filter
- .as_ref()
- .map(|f| {
- let schema = f
- .schema
- .as_ref()
- .ok_or_else(|| proto_error("Missing JoinFilter schema"))?
- .try_into()?;
-
- let expression = proto_converter.proto_to_physical_expr(
- f.expression.as_ref().ok_or_else(|| {
- proto_error("Unexpected empty filter expression")
- })?,
- &schema,
- ctx,
- )?;
- let column_indices = f.column_indices
- .iter()
- .map(|i| {
- let side = protobuf::JoinSide::try_from(i.side)
- .map_err(|_| proto_error(format!(
- "Received a HashJoinNode message with JoinSide in Filter {}",
- i.side))
- )?;
-
- Ok(ColumnIndex {
- index: i.index as usize,
- side: side.into(),
- })
- })
- .collect::>>()?;
-
- Ok(JoinFilter::new(expression, column_indices, Arc::new(schema)))
- })
- .map_or(Ok(None), |v: Result| v.map(Some))?;
-
- let partition_mode = protobuf::PartitionMode::try_from(hashjoin.partition_mode)
- .map_err(|_| {
- proto_error(format!(
- "Received a HashJoinNode message with unknown PartitionMode {}",
- hashjoin.partition_mode
- ))
- })?;
- let partition_mode = match partition_mode {
- protobuf::PartitionMode::CollectLeft => PartitionMode::CollectLeft,
- protobuf::PartitionMode::Partitioned => PartitionMode::Partitioned,
- protobuf::PartitionMode::Auto => PartitionMode::Auto,
+ let node = protobuf::PhysicalPlanNode {
+ physical_plan_type: Some(PhysicalPlanType::HashJoin(Box::new(
+ hashjoin.clone(),
+ ))),
};
- // Proto3 `repeated` cannot distinguish `None` from `Some(vec![])`. The latter
- // is reachable via `try_embed_projection` for `SELECT count(1) … JOIN …` and
- // changes the join's output schema, so the encoder reserves the single-element
- // sentinel `[u32::MAX]` (never a valid column index) to mean "explicitly empty";
- // every other state is sent as-is. See `try_from_hash_join_exec`.
- let projection = match hashjoin.projection.as_slice() {
- [] => None,
- [u32::MAX] => Some(Vec::new()),
- indices => Some(indices.iter().map(|i| *i as usize).collect()),
+ let decoder = ConverterPlanDecoder {
+ ctx,
+ proto_converter,
};
- let mut hash_join = HashJoinExec::try_new(
- left,
- right,
- on,
- filter,
- &JoinType::from_proto(join_type),
- projection,
- partition_mode,
- NullEquality::from_proto(null_equality),
- hashjoin.null_aware,
- )?;
-
- if let Some(dynamic_filter_proto) = &hashjoin.dynamic_filter {
- let dynamic_filter_expr = proto_converter.proto_to_physical_expr(
- dynamic_filter_proto,
- right_schema.as_ref(),
- ctx,
- )?;
- let df = (dynamic_filter_expr as Arc)
- .downcast::()
- .map_err(|_| {
- internal_datafusion_err!(
- "HashJoinExec dynamic_filter did not decode to a DynamicFilterPhysicalExpr"
- )
- })?;
- hash_join = hash_join.with_dynamic_filter_expr(df)?;
- }
-
- Ok(Arc::new(hash_join))
+ let decode_ctx = ExecutionPlanDecodeCtx::new(&decoder);
+ HashJoinExec::try_from_proto(&node, &decode_ctx)
}
#[deprecated(
@@ -2585,99 +2465,22 @@ pub trait PhysicalPlanNodeExt: Sized {
.ok_or_else(|| internal_datafusion_err!("LocalLimitExec is not serializable"))
}
+ #[deprecated(
+ since = "55.0.0",
+ note = "unused by DataFusion; `HashJoinExec` serializes itself via `ExecutionPlan::try_to_proto`"
+ )]
fn try_from_hash_join_exec(
exec: &HashJoinExec,
codec: &dyn PhysicalExtensionCodec,
proto_converter: &dyn PhysicalProtoConverterExtension,
) -> Result {
- let left = protobuf::PhysicalPlanNode::try_from_physical_plan_with_converter(
- exec.left().to_owned(),
- codec,
- proto_converter,
- )?;
- let right = protobuf::PhysicalPlanNode::try_from_physical_plan_with_converter(
- exec.right().to_owned(),
+ let encoder = ConverterPlanEncoder {
codec,
proto_converter,
- )?;
- let on: Vec = exec
- .on()
- .iter()
- .map(|tuple| {
- let l = proto_converter.physical_expr_to_proto(&tuple.0, codec)?;
- let r = proto_converter.physical_expr_to_proto(&tuple.1, codec)?;
- Ok::<_, DataFusionError>(protobuf::JoinOn {
- left: Some(l),
- right: Some(r),
- })
- })
- .collect::>()?;
- let join_type = protobuf::JoinType::from_proto(exec.join_type().to_owned());
- let null_equality = protobuf::NullEquality::from_proto(exec.null_equality());
- let filter = exec
- .filter()
- .as_ref()
- .map(|f| {
- let expression =
- proto_converter.physical_expr_to_proto(f.expression(), codec)?;
- let column_indices = f
- .column_indices()
- .iter()
- .map(|i| {
- let side: protobuf::JoinSide = i.side.to_owned().into();
- protobuf::ColumnIndex {
- index: i.index as u32,
- side: side.into(),
- }
- })
- .collect();
- let schema = f.schema().as_ref().try_into()?;
- Ok(protobuf::JoinFilter {
- expression: Some(expression),
- column_indices,
- schema: Some(schema),
- })
- })
- .map_or(Ok(None), |v: Result| v.map(Some))?;
-
- let partition_mode = match exec.partition_mode() {
- PartitionMode::CollectLeft => protobuf::PartitionMode::CollectLeft,
- PartitionMode::Partitioned => protobuf::PartitionMode::Partitioned,
- PartitionMode::Auto => protobuf::PartitionMode::Auto,
};
-
- let dynamic_filter = exec
- .dynamic_filter_expr()
- .map(|df| {
- let df_expr: Arc =
- Arc::clone(df) as Arc;
- proto_converter.physical_expr_to_proto(&df_expr, codec)
- })
- .transpose()?;
-
- Ok(protobuf::PhysicalPlanNode {
- physical_plan_type: Some(PhysicalPlanType::HashJoin(Box::new(
- protobuf::HashJoinExecNode {
- left: Some(Box::new(left)),
- right: Some(Box::new(right)),
- on,
- join_type: join_type.into(),
- partition_mode: partition_mode.into(),
- null_equality: null_equality.into(),
- filter,
- // Send `Some(vec![])` as `[u32::MAX]` (never a valid index) so the
- // wire format can distinguish it from `None` (which stays empty).
- // See `try_into_hash_join_physical_plan` for the matching decoder.
- projection: match exec.projection.as_ref() {
- None => Vec::new(),
- Some(v) if v.is_empty() => vec![u32::MAX],
- Some(v) => v.iter().map(|x| *x as u32).collect(),
- },
- null_aware: exec.null_aware,
- dynamic_filter,
- },
- ))),
- })
+ let encode_ctx = ExecutionPlanEncodeCtx::new(&encoder);
+ exec.try_to_proto(&encode_ctx)?
+ .ok_or_else(|| internal_datafusion_err!("HashJoinExec is not serializable"))
}
#[deprecated(
From 16471eeb91635fcb874c91249f9b57128a20a746 Mon Sep 17 00:00:00 2001
From: Matthew Patton
Date: Fri, 24 Jul 2026 18:10:00 -0400
Subject: [PATCH 017/109] refactor(proto): migrate AsyncFuncExec to
self-serializing proto (#23825)
Closes #23514. Part of #23494.
Migrate `AsyncFuncExec` proto encode/decode into the plan itself via
`try_to_proto` / `try_from_proto`, removing its central-arm handling in
`physical_plan/mod.rs`.
## Rationale
`datafusion-proto` currently downcasts `AsyncFuncExec` in the central
encode match and rebuilds it inline on decode. #23495 introduced
self-serializing hooks so each plan owns its own wire format. This moves
`AsyncFuncExec` onto that pattern.
## Changes
- Added `try_to_proto` / `AsyncFuncExec::try_from_proto`, wired into the
decode dispatch
- Removed the central encode downcast branch
- Old helper methods kept as `#[deprecated]` stubs, per existing
convention
- Wire format unchanged
## Testing
Existing `roundtrip_async_func_exec` integration test now exercises the
new hooks (old path deleted, so it's the only path left). `cargo fmt` +
`cargo clippy --all-targets --features proto -- -D warnings` clean.
## User-facing changes
No
Co-authored-by: Matthew Patton
---
datafusion/physical-plan/src/async_func.rs | 77 ++++++++++++++++++
datafusion/proto/src/physical_plan/mod.rs | 92 +++++++---------------
2 files changed, 105 insertions(+), 64 deletions(-)
diff --git a/datafusion/physical-plan/src/async_func.rs b/datafusion/physical-plan/src/async_func.rs
index 5a65c9aedc2f1..e13a5b986aa2c 100644
--- a/datafusion/physical-plan/src/async_func.rs
+++ b/datafusion/physical-plan/src/async_func.rs
@@ -246,6 +246,83 @@ impl ExecutionPlan for AsyncFuncExec {
fn metrics(&self) -> Option {
Some(self.metrics.clone_inner())
}
+
+ #[cfg(feature = "proto")]
+ fn try_to_proto(
+ &self,
+ ctx: &crate::proto::ExecutionPlanEncodeCtx<'_>,
+ ) -> Result> {
+ use datafusion_proto_models::protobuf;
+ let input = ctx.encode_child(self.input())?;
+ let async_exprs =
+ ctx.encode_expressions(self.async_exprs.iter().map(|e| &e.func))?;
+ let async_expr_names = self
+ .async_exprs
+ .iter()
+ .map(|e| e.name().to_string())
+ .collect();
+ Ok(Some(protobuf::PhysicalPlanNode {
+ physical_plan_type: Some(
+ protobuf::physical_plan_node::PhysicalPlanType::AsyncFunc(Box::new(
+ protobuf::AsyncFuncExecNode {
+ input: Some(Box::new(input)),
+ async_exprs,
+ async_expr_names,
+ },
+ )),
+ ),
+ }))
+ }
+}
+
+#[cfg(feature = "proto")]
+impl AsyncFuncExec {
+ /// Reconstruct an [`AsyncFuncExec`] from its protobuf representation.
+ ///
+ /// The exact inverse of [`ExecutionPlan::try_to_proto`]: it takes the whole
+ /// [`PhysicalPlanNode`] so every plan's `try_from_proto` shares one
+ /// signature. Child plans and expressions are decoded recursively via the
+ /// [`ExecutionPlanDecodeCtx`].
+ ///
+ /// [`PhysicalPlanNode`]: datafusion_proto_models::protobuf::PhysicalPlanNode
+ /// [`ExecutionPlan::try_to_proto`]: crate::ExecutionPlan::try_to_proto
+ /// [`ExecutionPlanDecodeCtx`]: crate::proto::ExecutionPlanDecodeCtx
+ pub fn try_from_proto(
+ node: &datafusion_proto_models::protobuf::PhysicalPlanNode,
+ ctx: &crate::proto::ExecutionPlanDecodeCtx<'_>,
+ ) -> Result> {
+ use datafusion_proto_models::protobuf;
+ let async_func = crate::expect_plan_variant!(
+ node,
+ protobuf::physical_plan_node::PhysicalPlanType::AsyncFunc,
+ "AsyncFuncExec",
+ );
+ let input = ctx.decode_required_child(
+ async_func.input.as_deref(),
+ "AsyncFuncExec",
+ "input",
+ )?;
+ let input_schema = input.schema();
+ assert_eq_or_internal_err!(
+ async_func.async_exprs.len(),
+ async_func.async_expr_names.len(),
+ "AsyncFuncExecNode async_exprs length does not match async_expr_names"
+ );
+ let async_exprs = async_func
+ .async_exprs
+ .iter()
+ .zip(async_func.async_expr_names.iter())
+ .map(|(expr, name)| {
+ let physical_expr = ctx.decode_expr(expr, input_schema.as_ref())?;
+ Ok(Arc::new(AsyncFuncExpr::try_new(
+ name.clone(),
+ physical_expr,
+ input_schema.as_ref(),
+ )?))
+ })
+ .collect::>>()?;
+ Ok(Arc::new(AsyncFuncExec::try_new(async_exprs, input)?))
+ }
}
struct CoalesceInputStream {
diff --git a/datafusion/proto/src/physical_plan/mod.rs b/datafusion/proto/src/physical_plan/mod.rs
index 345ab87bf5d5b..5cb9a922a8826 100644
--- a/datafusion/proto/src/physical_plan/mod.rs
+++ b/datafusion/proto/src/physical_plan/mod.rs
@@ -57,7 +57,6 @@ use datafusion_functions_table::generate_series::{
Empty, GenSeriesArgs, GenerateSeriesTable, GenericSeriesState, TimestampValue,
};
use datafusion_physical_expr::aggregate::{AggregateExprBuilder, AggregateFunctionExpr};
-use datafusion_physical_expr::async_scalar_function::AsyncFuncExpr;
use datafusion_physical_expr::expressions::DynamicFilterPhysicalExpr;
use datafusion_physical_expr::{LexOrdering, LexRequirement};
use datafusion_physical_expr_common::physical_expr::proto_decode::PhysicalExprDecodeCtx;
@@ -855,8 +854,8 @@ pub trait PhysicalPlanNodeExt: Sized {
PhysicalPlanType::SortMergeJoin(_) => {
SortMergeJoinExec::try_from_proto(self.node(), &decode_ctx)
}
- PhysicalPlanType::AsyncFunc(async_func) => {
- self.try_into_async_func_physical_plan(async_func, ctx, proto_converter)
+ PhysicalPlanType::AsyncFunc(_) => {
+ AsyncFuncExec::try_from_proto(self.node(), &decode_ctx)
}
PhysicalPlanType::Buffer(_) => {
BufferExec::try_from_proto(self.node(), &decode_ctx)
@@ -958,14 +957,6 @@ pub trait PhysicalPlanNodeExt: Sized {
return Ok(node);
}
- if let Some(exec) = plan.downcast_ref::() {
- return protobuf::PhysicalPlanNode::try_from_async_func_exec(
- exec,
- codec,
- proto_converter,
- );
- }
-
if let Some(exec) = plan.downcast_ref::() {
return protobuf::PhysicalPlanNode::try_from_scalar_subquery_exec(
exec,
@@ -2244,41 +2235,27 @@ pub trait PhysicalPlanNodeExt: Sized {
CooperativeExec::try_from_proto(&node, &decode_ctx)
}
+ #[deprecated(
+ since = "55.0.0",
+ note = "unused by DataFusion; `AsyncFuncExec` deserializes itself via `AsyncFuncExec::try_from_proto`"
+ )]
fn try_into_async_func_physical_plan(
&self,
async_func: &protobuf::AsyncFuncExecNode,
ctx: &PhysicalPlanDecodeContext<'_>,
proto_converter: &dyn PhysicalProtoConverterExtension,
) -> Result> {
- let input: Arc =
- into_physical_plan(&async_func.input, ctx, proto_converter)?;
-
- if async_func.async_exprs.len() != async_func.async_expr_names.len() {
- return internal_err!(
- "AsyncFuncExecNode async_exprs length does not match async_expr_names"
- );
- }
-
- let async_exprs = async_func
- .async_exprs
- .iter()
- .zip(async_func.async_expr_names.iter())
- .map(|(expr, name)| {
- let physical_expr = proto_converter.proto_to_physical_expr(
- expr,
- input.schema().as_ref(),
- ctx,
- )?;
-
- Ok(Arc::new(AsyncFuncExpr::try_new(
- name.clone(),
- physical_expr,
- input.schema().as_ref(),
- )?))
- })
- .collect::>>()?;
-
- Ok(Arc::new(AsyncFuncExec::try_new(async_exprs, input)?))
+ let node = protobuf::PhysicalPlanNode {
+ physical_plan_type: Some(PhysicalPlanType::AsyncFunc(Box::new(
+ async_func.clone(),
+ ))),
+ };
+ let decoder = ConverterPlanDecoder {
+ ctx,
+ proto_converter,
+ };
+ let decode_ctx = ExecutionPlanDecodeCtx::new(&decoder);
+ AsyncFuncExec::try_from_proto(&node, &decode_ctx)
}
#[deprecated(
@@ -3333,35 +3310,22 @@ pub trait PhysicalPlanNodeExt: Sized {
Ok(None)
}
+ #[deprecated(
+ since = "55.0.0",
+ note = "unused by DataFusion; `AsyncFuncExec` serializes itself via `ExecutionPlan::try_to_proto`"
+ )]
fn try_from_async_func_exec(
exec: &AsyncFuncExec,
- codec: &dyn PhysicalExtensionCodec,
+ extension_codec: &dyn PhysicalExtensionCodec,
proto_converter: &dyn PhysicalProtoConverterExtension,
) -> Result {
- let input = protobuf::PhysicalPlanNode::try_from_physical_plan_with_converter(
- Arc::clone(exec.input()),
- codec,
+ let encoder = ConverterPlanEncoder {
+ codec: extension_codec,
proto_converter,
- )?;
-
- let mut async_exprs = vec![];
- let mut async_expr_names = vec![];
-
- for async_expr in exec.async_exprs() {
- async_exprs
- .push(proto_converter.physical_expr_to_proto(&async_expr.func, codec)?);
- async_expr_names.push(async_expr.name.clone())
- }
-
- Ok(protobuf::PhysicalPlanNode {
- physical_plan_type: Some(PhysicalPlanType::AsyncFunc(Box::new(
- protobuf::AsyncFuncExecNode {
- input: Some(Box::new(input)),
- async_exprs,
- async_expr_names,
- },
- ))),
- })
+ };
+ let encode_ctx = ExecutionPlanEncodeCtx::new(&encoder);
+ exec.try_to_proto(&encode_ctx)?
+ .ok_or_else(|| internal_datafusion_err!("AsyncFuncExec is not serializable"))
}
#[deprecated(
From 9808e83a9c3d3c7b2ef297e41b4532ee30cb8860 Mon Sep 17 00:00:00 2001
From: Phoenix
Date: Sat, 25 Jul 2026 10:18:33 +0800
Subject: [PATCH 018/109] refactor(proto): migrate window serde (#23780)
## Which issue does this PR close?
- Closes #23513.
## Rationale for this change
Window plans still relied on centralized protobuf dispatch, keeping
serialization separate from the execution plans that own their state.
Both window executors share one protobuf variant. Decoding must inspect
`input_order_mode` before selecting the concrete plan.
Add plan-local encoders for both executors and a decoder for the shared
node. Keep the legacy helpers as deprecated delegates while preserving
window frames and UDF payloads on the existing wire format.
## What changes are included in this PR?
## Are these changes tested?
Yes
## Are there any user-facing changes?
---------
Signed-off-by: Jiawei Zhao
---
.../src/windows/bounded_window_agg_exec.rs | 49 +++
datafusion/physical-plan/src/windows/mod.rs | 2 +
datafusion/physical-plan/src/windows/proto.rs | 288 ++++++++++++++++++
.../src/windows/window_agg_exec.rs | 102 +++++++
datafusion/proto/src/physical_plan/mod.rs | 177 +++--------
5 files changed, 480 insertions(+), 138 deletions(-)
create mode 100644 datafusion/physical-plan/src/windows/proto.rs
diff --git a/datafusion/physical-plan/src/windows/bounded_window_agg_exec.rs b/datafusion/physical-plan/src/windows/bounded_window_agg_exec.rs
index 3ca612bbdb775..d5863080895f6 100644
--- a/datafusion/physical-plan/src/windows/bounded_window_agg_exec.rs
+++ b/datafusion/physical-plan/src/windows/bounded_window_agg_exec.rs
@@ -401,6 +401,55 @@ impl ExecutionPlan for BoundedWindowAggExec {
fn cardinality_effect(&self) -> CardinalityEffect {
CardinalityEffect::Equal
}
+
+ #[cfg(feature = "proto")]
+ fn try_to_proto(
+ &self,
+ ctx: &crate::proto::ExecutionPlanEncodeCtx<'_>,
+ ) -> Result> {
+ use super::proto::encode_physical_window_expr;
+ use datafusion_proto_common::protobuf_common::EmptyMessage;
+ use datafusion_proto_models::protobuf;
+ use protobuf::window_agg_exec_node::InputOrderMode as ProtoInputOrderMode;
+
+ let input = ctx.encode_child(self.input())?;
+ let window_expr = self
+ .window_expr()
+ .iter()
+ .map(|expr| encode_physical_window_expr(expr, ctx))
+ .collect::>>()?;
+ let partition_keys = self
+ .partition_keys()
+ .iter()
+ .map(|expr| ctx.encode_expr(expr))
+ .collect::>>()?;
+ // A `Some(input_order_mode)` is what tells the shared `Window` decode
+ // arm to rebuild a `BoundedWindowAggExec` rather than a `WindowAggExec`.
+ let input_order_mode = match &self.input_order_mode {
+ InputOrderMode::Linear => ProtoInputOrderMode::Linear(EmptyMessage {}),
+ InputOrderMode::PartiallySorted(columns) => {
+ ProtoInputOrderMode::PartiallySorted(
+ protobuf::PartiallySortedInputOrderMode {
+ columns: columns.iter().map(|column| *column as u64).collect(),
+ },
+ )
+ }
+ InputOrderMode::Sorted => ProtoInputOrderMode::Sorted(EmptyMessage {}),
+ };
+
+ Ok(Some(protobuf::PhysicalPlanNode {
+ physical_plan_type: Some(
+ protobuf::physical_plan_node::PhysicalPlanType::Window(Box::new(
+ protobuf::WindowAggExecNode {
+ input: Some(Box::new(input)),
+ window_expr,
+ partition_keys,
+ input_order_mode: Some(input_order_mode),
+ },
+ )),
+ ),
+ }))
+ }
}
/// Trait that specifies how we search for (or calculate) partitions. It has two
diff --git a/datafusion/physical-plan/src/windows/mod.rs b/datafusion/physical-plan/src/windows/mod.rs
index b72a65cf996be..baa6abd839175 100644
--- a/datafusion/physical-plan/src/windows/mod.rs
+++ b/datafusion/physical-plan/src/windows/mod.rs
@@ -18,6 +18,8 @@
//! Physical expressions for window functions
mod bounded_window_agg_exec;
+#[cfg(feature = "proto")]
+mod proto;
mod utils;
mod window_agg_exec;
diff --git a/datafusion/physical-plan/src/windows/proto.rs b/datafusion/physical-plan/src/windows/proto.rs
new file mode 100644
index 0000000000000..aa62158d18fa0
--- /dev/null
+++ b/datafusion/physical-plan/src/windows/proto.rs
@@ -0,0 +1,288 @@
+// 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.
+
+//! Protobuf conversions shared by window execution plans.
+
+use std::sync::Arc;
+
+use arrow::compute::SortOptions;
+use arrow::datatypes::Schema;
+use datafusion_common::{
+ Result, ScalarValue, internal_datafusion_err, internal_err, not_impl_err,
+};
+use datafusion_expr::{
+ WindowFrame, WindowFrameBound, WindowFrameUnits, WindowFunctionDefinition,
+};
+use datafusion_physical_expr::window::SlidingAggregateWindowExpr;
+use datafusion_physical_expr_common::sort_expr::PhysicalSortExpr;
+use datafusion_proto_common::protobuf_common;
+use datafusion_proto_models::protobuf::{self, physical_window_expr_node};
+
+use super::{
+ PlainAggregateWindowExpr, StandardWindowExpr, WindowExpr, WindowUDFExpr,
+ create_window_expr, schema_add_window_field,
+};
+
+pub(super) fn encode_physical_window_expr(
+ window_expr: &Arc,
+ ctx: &crate::proto::ExecutionPlanEncodeCtx<'_>,
+) -> Result {
+ let expr = window_expr.as_any();
+ let mut args = window_expr.expressions().to_vec();
+ let window_frame = window_expr.get_window_frame();
+ let (window_function, fun_definition, ignore_nulls, distinct) =
+ if let Some(plain) = expr.downcast_ref::() {
+ let aggregate_expr = plain.get_aggregate_expr();
+ (
+ physical_window_expr_node::WindowFunction::UserDefinedAggrFunction(
+ aggregate_expr.fun().name().to_string(),
+ ),
+ ctx.encode_udaf(aggregate_expr.fun())?,
+ aggregate_expr.ignore_nulls(),
+ aggregate_expr.is_distinct(),
+ )
+ } else if let Some(sliding) = expr.downcast_ref::() {
+ let aggregate_expr = sliding.get_aggregate_expr();
+ (
+ physical_window_expr_node::WindowFunction::UserDefinedAggrFunction(
+ aggregate_expr.fun().name().to_string(),
+ ),
+ ctx.encode_udaf(aggregate_expr.fun())?,
+ aggregate_expr.ignore_nulls(),
+ aggregate_expr.is_distinct(),
+ )
+ } else if let Some(standard) = expr.downcast_ref::() {
+ if let Some(window_udf) = standard
+ .get_standard_func_expr()
+ .as_any()
+ .downcast_ref::()
+ {
+ // `WindowUDFExpr::args` returns the full, unfiltered argument list so
+ // every argument survives the round-trip.
+ args = window_udf.args().to_vec();
+ (
+ physical_window_expr_node::WindowFunction::UserDefinedWindowFunction(
+ window_udf.fun().name().to_string(),
+ ),
+ ctx.encode_udwf(window_udf.fun().as_ref())?,
+ false,
+ false,
+ )
+ } else {
+ return not_impl_err!(
+ "User-defined window function not supported: {window_expr:?}"
+ );
+ }
+ } else {
+ return not_impl_err!("WindowExpr not supported: {window_expr:?}");
+ };
+
+ let args = ctx.encode_expressions(&args)?;
+ let partition_by = ctx.encode_expressions(window_expr.partition_by())?;
+ let order_by = window_expr
+ .order_by()
+ .iter()
+ .map(|sort_expr| {
+ Ok(protobuf::PhysicalSortExprNode {
+ expr: Some(Box::new(ctx.encode_expr(&sort_expr.expr)?)),
+ asc: !sort_expr.options.descending,
+ nulls_first: sort_expr.options.nulls_first,
+ })
+ })
+ .collect::>>()?;
+
+ Ok(protobuf::PhysicalWindowExprNode {
+ args,
+ partition_by,
+ order_by,
+ window_frame: Some(encode_window_frame(window_frame.as_ref())?),
+ window_function: Some(window_function),
+ name: window_expr.name().to_string(),
+ fun_definition,
+ ignore_nulls,
+ distinct,
+ })
+}
+
+pub(super) fn decode_physical_window_expr(
+ proto: &protobuf::PhysicalWindowExprNode,
+ ctx: &crate::proto::ExecutionPlanDecodeCtx<'_>,
+ input_schema: &Schema,
+) -> Result> {
+ let args = proto
+ .args
+ .iter()
+ .map(|expr| ctx.decode_expr(expr, input_schema))
+ .collect::>>()?;
+ let partition_by = proto
+ .partition_by
+ .iter()
+ .map(|expr| ctx.decode_expr(expr, input_schema))
+ .collect::>>()?;
+ let order_by = proto
+ .order_by
+ .iter()
+ .map(|sort_expr| {
+ let expr = sort_expr.expr.as_ref().ok_or_else(|| {
+ internal_datafusion_err!(
+ "Missing expr in window order_by sort expression"
+ )
+ })?;
+ Ok(PhysicalSortExpr {
+ expr: ctx.decode_expr(expr, input_schema)?,
+ options: SortOptions {
+ descending: !sort_expr.asc,
+ nulls_first: sort_expr.nulls_first,
+ },
+ })
+ })
+ .collect::>>()?;
+ let window_frame = proto
+ .window_frame
+ .as_ref()
+ .map(decode_window_frame)
+ .transpose()?
+ .ok_or_else(|| {
+ internal_datafusion_err!("Missing required field 'window_frame' in protobuf")
+ })?;
+ let function = match proto.window_function.as_ref() {
+ Some(physical_window_expr_node::WindowFunction::UserDefinedAggrFunction(
+ name,
+ )) => WindowFunctionDefinition::AggregateUDF(
+ ctx.decode_udaf(name, proto.fun_definition.as_deref())?,
+ ),
+ Some(physical_window_expr_node::WindowFunction::UserDefinedWindowFunction(
+ name,
+ )) => WindowFunctionDefinition::WindowUDF(
+ ctx.decode_udwf(name, proto.fun_definition.as_deref())?,
+ ),
+ None => {
+ return internal_err!("Missing required field 'window_function' in protobuf");
+ }
+ };
+
+ let name = proto.name.clone();
+ // TODO: Remove extended_schema if functions are all UDAF
+ let extended_schema = schema_add_window_field(&args, input_schema, &function, &name)?;
+ create_window_expr(
+ &function,
+ name,
+ &args,
+ &partition_by,
+ &order_by,
+ Arc::new(window_frame),
+ extended_schema,
+ proto.ignore_nulls,
+ proto.distinct,
+ None,
+ )
+}
+
+fn encode_window_frame(window_frame: &WindowFrame) -> Result {
+ let units = match window_frame.units {
+ WindowFrameUnits::Rows => protobuf::WindowFrameUnits::Rows,
+ WindowFrameUnits::Range => protobuf::WindowFrameUnits::Range,
+ WindowFrameUnits::Groups => protobuf::WindowFrameUnits::Groups,
+ };
+ Ok(protobuf::WindowFrame {
+ window_frame_units: units.into(),
+ start_bound: Some(encode_window_frame_bound(&window_frame.start_bound)?),
+ end_bound: Some(protobuf::window_frame::EndBound::Bound(
+ encode_window_frame_bound(&window_frame.end_bound)?,
+ )),
+ })
+}
+
+fn encode_window_frame_bound(
+ bound: &WindowFrameBound,
+) -> Result {
+ let encode_value = |value: &ScalarValue| -> Result {
+ Ok(value.try_into()?)
+ };
+ Ok(match bound {
+ WindowFrameBound::CurrentRow => protobuf::WindowFrameBound {
+ window_frame_bound_type: protobuf::WindowFrameBoundType::CurrentRow.into(),
+ bound_value: None,
+ },
+ WindowFrameBound::Preceding(value) => protobuf::WindowFrameBound {
+ window_frame_bound_type: protobuf::WindowFrameBoundType::Preceding.into(),
+ bound_value: Some(encode_value(value)?),
+ },
+ WindowFrameBound::Following(value) => protobuf::WindowFrameBound {
+ window_frame_bound_type: protobuf::WindowFrameBoundType::Following.into(),
+ bound_value: Some(encode_value(value)?),
+ },
+ })
+}
+
+fn decode_window_frame(window_frame: &protobuf::WindowFrame) -> Result {
+ let units = protobuf::WindowFrameUnits::try_from(window_frame.window_frame_units)
+ .map_err(|_| {
+ internal_datafusion_err!(
+ "Received a WindowFrame message with unknown WindowFrameUnits {}",
+ window_frame.window_frame_units
+ )
+ })?;
+ let units = match units {
+ protobuf::WindowFrameUnits::Rows => WindowFrameUnits::Rows,
+ protobuf::WindowFrameUnits::Range => WindowFrameUnits::Range,
+ protobuf::WindowFrameUnits::Groups => WindowFrameUnits::Groups,
+ };
+ let start_bound =
+ decode_window_frame_bound(window_frame.start_bound.as_ref().ok_or_else(
+ || internal_datafusion_err!("Missing start_bound in WindowFrame"),
+ )?)?;
+ let end_bound = window_frame
+ .end_bound
+ .as_ref()
+ .map(|end_bound| match end_bound {
+ protobuf::window_frame::EndBound::Bound(bound) => {
+ decode_window_frame_bound(bound)
+ }
+ })
+ .transpose()?
+ .unwrap_or(WindowFrameBound::CurrentRow);
+ Ok(WindowFrame::new_bounds(units, start_bound, end_bound))
+}
+
+fn decode_window_frame_bound(
+ bound: &protobuf::WindowFrameBound,
+) -> Result {
+ let decode_value = |value: &protobuf_common::ScalarValue| -> Result {
+ Ok(ScalarValue::try_from(value)?)
+ };
+ let bound_type = protobuf::WindowFrameBoundType::try_from(
+ bound.window_frame_bound_type,
+ )
+ .map_err(|_| {
+ internal_datafusion_err!(
+ "Received a WindowFrameBound message with unknown WindowFrameBoundType {}",
+ bound.window_frame_bound_type
+ )
+ })?;
+ match bound_type {
+ protobuf::WindowFrameBoundType::CurrentRow => Ok(WindowFrameBound::CurrentRow),
+ protobuf::WindowFrameBoundType::Preceding => match &bound.bound_value {
+ Some(value) => Ok(WindowFrameBound::Preceding(decode_value(value)?)),
+ None => Ok(WindowFrameBound::Preceding(ScalarValue::UInt64(None))),
+ },
+ protobuf::WindowFrameBoundType::Following => match &bound.bound_value {
+ Some(value) => Ok(WindowFrameBound::Following(decode_value(value)?)),
+ None => Ok(WindowFrameBound::Following(ScalarValue::UInt64(None))),
+ },
+ }
+}
diff --git a/datafusion/physical-plan/src/windows/window_agg_exec.rs b/datafusion/physical-plan/src/windows/window_agg_exec.rs
index 3eb8edd298901..81838300cf5c7 100644
--- a/datafusion/physical-plan/src/windows/window_agg_exec.rs
+++ b/datafusion/physical-plan/src/windows/window_agg_exec.rs
@@ -21,6 +21,8 @@ use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};
+#[cfg(feature = "proto")]
+use super::proto::{decode_physical_window_expr, encode_physical_window_expr};
use super::utils::create_schema;
use crate::execution_plan::{CardinalityEffect, EmissionType};
use crate::metrics::{BaselineMetrics, ExecutionPlanMetricsSet, MetricsSet};
@@ -317,6 +319,106 @@ impl ExecutionPlan for WindowAggExec {
fn cardinality_effect(&self) -> CardinalityEffect {
CardinalityEffect::Equal
}
+
+ #[cfg(feature = "proto")]
+ fn try_to_proto(
+ &self,
+ ctx: &crate::proto::ExecutionPlanEncodeCtx<'_>,
+ ) -> Result> {
+ use datafusion_proto_models::protobuf;
+
+ let input = ctx.encode_child(self.input())?;
+ let window_expr = self
+ .window_expr()
+ .iter()
+ .map(|expr| encode_physical_window_expr(expr, ctx))
+ .collect::>>()?;
+ let partition_keys = self
+ .partition_keys()
+ .iter()
+ .map(|expr| ctx.encode_expr(expr))
+ .collect::>>()?;
+
+ Ok(Some(protobuf::PhysicalPlanNode {
+ physical_plan_type: Some(
+ protobuf::physical_plan_node::PhysicalPlanType::Window(Box::new(
+ protobuf::WindowAggExecNode {
+ input: Some(Box::new(input)),
+ window_expr,
+ partition_keys,
+ // `None` distinguishes a `WindowAggExec` from a
+ // `BoundedWindowAggExec` on the shared `Window` variant.
+ input_order_mode: None,
+ },
+ )),
+ ),
+ }))
+ }
+}
+
+#[cfg(feature = "proto")]
+impl WindowAggExec {
+ /// Reconstruct a window plan from its protobuf representation.
+ ///
+ /// This returns a [`WindowAggExec`] when `input_order_mode` is absent and a
+ /// [`BoundedWindowAggExec`] when it is present.
+ ///
+ /// [`BoundedWindowAggExec`]: crate::windows::BoundedWindowAggExec
+ pub fn try_from_proto(
+ node: &datafusion_proto_models::protobuf::PhysicalPlanNode,
+ ctx: &crate::proto::ExecutionPlanDecodeCtx<'_>,
+ ) -> Result> {
+ use super::BoundedWindowAggExec;
+ use crate::InputOrderMode;
+ use datafusion_proto_models::protobuf;
+ use protobuf::window_agg_exec_node::InputOrderMode as ProtoInputOrderMode;
+
+ let window_agg = crate::expect_plan_variant!(
+ node,
+ protobuf::physical_plan_node::PhysicalPlanType::Window,
+ "WindowAggExec",
+ );
+ let input = ctx.decode_required_child(
+ window_agg.input.as_deref(),
+ "WindowAggExec",
+ "input",
+ )?;
+ let input_schema = input.schema();
+ let window_expr = window_agg
+ .window_expr
+ .iter()
+ .map(|expr| decode_physical_window_expr(expr, ctx, input_schema.as_ref()))
+ .collect::>>()?;
+ let partition_keys = window_agg
+ .partition_keys
+ .iter()
+ .map(|expr| ctx.decode_expr(expr, input_schema.as_ref()))
+ .collect::>>()?;
+
+ if let Some(input_order_mode) = window_agg.input_order_mode.as_ref() {
+ let input_order_mode = match input_order_mode {
+ ProtoInputOrderMode::Linear(_) => InputOrderMode::Linear,
+ ProtoInputOrderMode::PartiallySorted(
+ protobuf::PartiallySortedInputOrderMode { columns },
+ ) => InputOrderMode::PartiallySorted(
+ columns.iter().map(|column| *column as usize).collect(),
+ ),
+ ProtoInputOrderMode::Sorted(_) => InputOrderMode::Sorted,
+ };
+ Ok(Arc::new(BoundedWindowAggExec::try_new(
+ window_expr,
+ input,
+ input_order_mode,
+ !partition_keys.is_empty(),
+ )?))
+ } else {
+ Ok(Arc::new(WindowAggExec::try_new(
+ window_expr,
+ input,
+ !partition_keys.is_empty(),
+ )?))
+ }
+ }
}
/// Compute the window aggregate columns
diff --git a/datafusion/proto/src/physical_plan/mod.rs b/datafusion/proto/src/physical_plan/mod.rs
index 5cb9a922a8826..e1801f66ef64f 100644
--- a/datafusion/proto/src/physical_plan/mod.rs
+++ b/datafusion/proto/src/physical_plan/mod.rs
@@ -98,7 +98,7 @@ use datafusion_physical_plan::sorts::sort_preserving_merge::SortPreservingMergeE
use datafusion_physical_plan::union::{InterleaveExec, UnionExec};
use datafusion_physical_plan::unnest::UnnestExec;
use datafusion_physical_plan::windows::{BoundedWindowAggExec, WindowAggExec};
-use datafusion_physical_plan::{ExecutionPlan, InputOrderMode, PhysicalExpr, WindowExpr};
+use datafusion_physical_plan::{ExecutionPlan, PhysicalExpr};
use prost::Message;
use prost::bytes::BufMut;
@@ -107,18 +107,18 @@ use crate::convert::{FromProto, TryFromProto};
use crate::convert_required;
use crate::physical_plan::from_proto::{
parse_physical_expr_with_converter, parse_physical_sort_expr,
- parse_physical_sort_exprs, parse_physical_window_expr,
- parse_protobuf_file_scan_config, parse_record_batches, parse_table_schema_from_proto,
+ parse_physical_sort_exprs, parse_protobuf_file_scan_config, parse_record_batches,
+ parse_table_schema_from_proto,
};
use crate::physical_plan::to_proto::{
serialize_file_scan_config, serialize_maybe_filter, serialize_physical_aggr_expr,
serialize_physical_expr_with_converter, serialize_physical_sort_exprs,
- serialize_physical_window_expr, serialize_record_batches,
+ serialize_record_batches,
};
use crate::protobuf::physical_aggregate_expr_node::AggregateFunction;
use crate::protobuf::physical_expr_node::ExprType;
use crate::protobuf::physical_plan_node::PhysicalPlanType;
-use crate::protobuf::{self, SortMergeJoinExecNode, proto_error, window_agg_exec_node};
+use crate::protobuf::{self, SortMergeJoinExecNode, proto_error};
pub mod from_proto;
pub mod to_proto;
@@ -790,8 +790,8 @@ pub trait PhysicalPlanNodeExt: Sized {
PhysicalPlanType::LocalLimit(_) => {
LocalLimitExec::try_from_proto(self.node(), &decode_ctx)
}
- PhysicalPlanType::Window(window_agg) => {
- self.try_into_window_physical_plan(window_agg, ctx, proto_converter)
+ PhysicalPlanType::Window(_) => {
+ WindowAggExec::try_from_proto(self.node(), &decode_ctx)
}
PhysicalPlanType::Aggregate(hash_agg) => {
self.try_into_aggregate_physical_plan(hash_agg, ctx, proto_converter)
@@ -924,22 +924,6 @@ pub trait PhysicalPlanNodeExt: Sized {
return Ok(node);
}
- if let Some(exec) = plan.downcast_ref::() {
- return protobuf::PhysicalPlanNode::try_from_window_agg_exec(
- exec,
- codec,
- proto_converter,
- );
- }
-
- if let Some(exec) = plan.downcast_ref::() {
- return protobuf::PhysicalPlanNode::try_from_bounded_window_agg_exec(
- exec,
- codec,
- proto_converter,
- );
- }
-
if let Some(exec) = plan.downcast_ref::()
&& let Some(node) = protobuf::PhysicalPlanNode::try_from_data_sink_exec(
exec,
@@ -1419,61 +1403,27 @@ pub trait PhysicalPlanNodeExt: Sized {
LocalLimitExec::try_from_proto(&node, &decode_ctx)
}
+ #[deprecated(
+ since = "55.0.0",
+ note = "unused by DataFusion; window plans deserialize via `WindowAggExec::try_from_proto`"
+ )]
fn try_into_window_physical_plan(
&self,
window_agg: &protobuf::WindowAggExecNode,
ctx: &PhysicalPlanDecodeContext<'_>,
proto_converter: &dyn PhysicalProtoConverterExtension,
) -> Result> {
- let input: Arc =
- into_physical_plan(&window_agg.input, ctx, proto_converter)?;
- let input_schema = input.schema();
-
- let physical_window_expr: Vec> = window_agg
- .window_expr
- .iter()
- .map(|window_expr| {
- parse_physical_window_expr(
- window_expr,
- ctx,
- input_schema.as_ref(),
- proto_converter,
- )
- })
- .collect::, _>>()?;
-
- let partition_keys = window_agg
- .partition_keys
- .iter()
- .map(|expr| {
- proto_converter.proto_to_physical_expr(expr, input.schema().as_ref(), ctx)
- })
- .collect::>>>()?;
-
- if let Some(input_order_mode) = window_agg.input_order_mode.as_ref() {
- let input_order_mode = match input_order_mode {
- window_agg_exec_node::InputOrderMode::Linear(_) => InputOrderMode::Linear,
- window_agg_exec_node::InputOrderMode::PartiallySorted(
- protobuf::PartiallySortedInputOrderMode { columns },
- ) => InputOrderMode::PartiallySorted(
- columns.iter().map(|c| *c as usize).collect(),
- ),
- window_agg_exec_node::InputOrderMode::Sorted(_) => InputOrderMode::Sorted,
- };
-
- Ok(Arc::new(BoundedWindowAggExec::try_new(
- physical_window_expr,
- input,
- input_order_mode,
- !partition_keys.is_empty(),
- )?))
- } else {
- Ok(Arc::new(WindowAggExec::try_new(
- physical_window_expr,
- input,
- !partition_keys.is_empty(),
- )?))
- }
+ let node = protobuf::PhysicalPlanNode {
+ physical_plan_type: Some(PhysicalPlanType::Window(Box::new(
+ window_agg.clone(),
+ ))),
+ };
+ let decoder = ConverterPlanDecoder {
+ ctx,
+ proto_converter,
+ };
+ let decode_ctx = ExecutionPlanDecodeCtx::new(&decoder);
+ WindowAggExec::try_from_proto(&node, &decode_ctx)
}
fn try_into_aggregate_physical_plan(
@@ -2985,89 +2935,40 @@ pub trait PhysicalPlanNodeExt: Sized {
})
}
+ #[deprecated(
+ since = "55.0.0",
+ note = "unused by DataFusion; `WindowAggExec` serializes itself via `ExecutionPlan::try_to_proto`"
+ )]
fn try_from_window_agg_exec(
exec: &WindowAggExec,
codec: &dyn PhysicalExtensionCodec,
proto_converter: &dyn PhysicalProtoConverterExtension,
) -> Result {
- let input = protobuf::PhysicalPlanNode::try_from_physical_plan_with_converter(
- exec.input().to_owned(),
+ let encoder = ConverterPlanEncoder {
codec,
proto_converter,
- )?;
-
- let window_expr = exec
- .window_expr()
- .iter()
- .map(|e| serialize_physical_window_expr(e, codec, proto_converter))
- .collect::>>()?;
-
- let partition_keys = exec
- .partition_keys()
- .iter()
- .map(|e| proto_converter.physical_expr_to_proto(e, codec))
- .collect::>>()?;
-
- Ok(protobuf::PhysicalPlanNode {
- physical_plan_type: Some(PhysicalPlanType::Window(Box::new(
- protobuf::WindowAggExecNode {
- input: Some(Box::new(input)),
- window_expr,
- partition_keys,
- input_order_mode: None,
- },
- ))),
- })
+ };
+ let encode_ctx = ExecutionPlanEncodeCtx::new(&encoder);
+ exec.try_to_proto(&encode_ctx)?
+ .ok_or_else(|| internal_datafusion_err!("WindowAggExec is not serializable"))
}
+ #[deprecated(
+ since = "55.0.0",
+ note = "unused by DataFusion; `BoundedWindowAggExec` serializes itself via `ExecutionPlan::try_to_proto`"
+ )]
fn try_from_bounded_window_agg_exec(
exec: &BoundedWindowAggExec,
codec: &dyn PhysicalExtensionCodec,
proto_converter: &dyn PhysicalProtoConverterExtension,
) -> Result {
- let input = protobuf::PhysicalPlanNode::try_from_physical_plan_with_converter(
- exec.input().to_owned(),
+ let encoder = ConverterPlanEncoder {
codec,
proto_converter,
- )?;
-
- let window_expr = exec
- .window_expr()
- .iter()
- .map(|e| serialize_physical_window_expr(e, codec, proto_converter))
- .collect::>>()?;
-
- let partition_keys = exec
- .partition_keys()
- .iter()
- .map(|e| proto_converter.physical_expr_to_proto(e, codec))
- .collect::>>()?;
-
- let input_order_mode = match &exec.input_order_mode {
- InputOrderMode::Linear => {
- window_agg_exec_node::InputOrderMode::Linear(protobuf::EmptyMessage {})
- }
- InputOrderMode::PartiallySorted(columns) => {
- window_agg_exec_node::InputOrderMode::PartiallySorted(
- protobuf::PartiallySortedInputOrderMode {
- columns: columns.iter().map(|c| *c as u64).collect(),
- },
- )
- }
- InputOrderMode::Sorted => {
- window_agg_exec_node::InputOrderMode::Sorted(protobuf::EmptyMessage {})
- }
};
-
- Ok(protobuf::PhysicalPlanNode {
- physical_plan_type: Some(PhysicalPlanType::Window(Box::new(
- protobuf::WindowAggExecNode {
- input: Some(Box::new(input)),
- window_expr,
- partition_keys,
- input_order_mode: Some(input_order_mode),
- },
- ))),
+ let encode_ctx = ExecutionPlanEncodeCtx::new(&encoder);
+ exec.try_to_proto(&encode_ctx)?.ok_or_else(|| {
+ internal_datafusion_err!("BoundedWindowAggExec is not serializable")
})
}
From 4a40101c69174ca68ef89ea5732d133ebf3730be Mon Sep 17 00:00:00 2001
From: Phoenix
Date: Sat, 25 Jul 2026 10:27:34 +0800
Subject: [PATCH 019/109] Migrate ExplainExec and AnalyzeExec protobuf serde
(#23742)
## Which issue does this PR close?
- Closes #23511.
## Rationale for this change
Physical plan protobuf serialization is being moved from the central
dispatch module into each `ExecutionPlan` implementation. Co-locating
this logic with the corresponding execution plan makes the serialization
code easier to maintain and incrementally reduces the central downcast
chain.
## What changes are included in this PR?
- Implement plan-local protobuf serialization and deserialization for
`ExplainExec`.
- Implement plan-local protobuf serialization and deserialization for
`AnalyzeExec`.
- Remove both plans from the live central serialization dispatch.
- Retain the deprecated compatibility methods as delegates to the new
implementations.
- Preserve the existing protobuf wire format.
- Strengthen roundtrip tests to cover all stored fields, including every
`StringifiedPlan` variant, metric categories, and explain formats.
Each execution plan migration is kept in a separate commit.
## Are these changes tested?
Yes. The following checks pass:
- `cargo fmt --all`
- `cargo clippy --all-targets --all-features -- -D warnings`
- Full extended workspace test suite
- `cargo test -p datafusion-proto --test proto_integration`
The proto integration suite passes all 209 tests after rebasing onto the
latest `main`.
## Are there any user-facing changes?
No. This is an internal refactor and does not change the protobuf wire
format or user-facing behavior.
---------
Signed-off-by: Jiawei Zhao
---
datafusion/physical-plan/src/analyze.rs | 96 +++++++++
datafusion/physical-plan/src/explain.rs | 182 ++++++++++++++++++
datafusion/proto/src/physical_plan/mod.rs | 156 +++++----------
.../tests/cases/roundtrip_physical_plan.rs | 116 ++++++++++-
4 files changed, 437 insertions(+), 113 deletions(-)
diff --git a/datafusion/physical-plan/src/analyze.rs b/datafusion/physical-plan/src/analyze.rs
index 72cd24ef95673..31e0a27410ff9 100644
--- a/datafusion/physical-plan/src/analyze.rs
+++ b/datafusion/physical-plan/src/analyze.rs
@@ -303,6 +303,102 @@ impl ExecutionPlan for AnalyzeExec {
futures::stream::once(output),
)))
}
+
+ #[cfg(feature = "proto")]
+ fn try_to_proto(
+ &self,
+ ctx: &crate::proto::ExecutionPlanEncodeCtx<'_>,
+ ) -> Result> {
+ use datafusion_proto_models::protobuf;
+
+ let input = ctx.encode_child(self.input())?;
+ let (has_metric_categories, metric_categories) = match self.metric_categories() {
+ Some(categories) => {
+ (true, categories.iter().map(ToString::to_string).collect())
+ }
+ None => (false, vec![]),
+ };
+ let format = match self.format() {
+ ExplainFormat::Indent => protobuf::ExplainFormat::Indent,
+ ExplainFormat::Tree => protobuf::ExplainFormat::Tree,
+ ExplainFormat::PostgresJSON => protobuf::ExplainFormat::Pgjson,
+ ExplainFormat::Graphviz => protobuf::ExplainFormat::Graphviz,
+ } as i32;
+ Ok(Some(protobuf::PhysicalPlanNode {
+ physical_plan_type: Some(
+ protobuf::physical_plan_node::PhysicalPlanType::Analyze(Box::new(
+ protobuf::AnalyzeExecNode {
+ verbose: self.verbose(),
+ show_statistics: self.show_statistics(),
+ input: Some(Box::new(input)),
+ schema: Some(self.schema().as_ref().try_into()?),
+ has_metric_categories,
+ metric_categories,
+ format,
+ },
+ )),
+ ),
+ }))
+ }
+}
+
+#[cfg(feature = "proto")]
+impl AnalyzeExec {
+ /// Reconstruct an [`AnalyzeExec`] from its protobuf representation.
+ pub fn try_from_proto(
+ node: &datafusion_proto_models::protobuf::PhysicalPlanNode,
+ ctx: &crate::proto::ExecutionPlanDecodeCtx<'_>,
+ ) -> Result> {
+ use datafusion_proto_models::protobuf;
+
+ let analyze = crate::expect_plan_variant!(
+ node,
+ protobuf::physical_plan_node::PhysicalPlanType::Analyze,
+ "AnalyzeExec",
+ );
+ let input =
+ ctx.decode_required_child(analyze.input.as_deref(), "AnalyzeExec", "input")?;
+ let metric_categories = if analyze.has_metric_categories {
+ Some(
+ analyze
+ .metric_categories
+ .iter()
+ .map(|category| category.parse::())
+ .collect::>>()?,
+ )
+ } else {
+ None
+ };
+ let proto_format =
+ protobuf::ExplainFormat::try_from(analyze.format).map_err(|_| {
+ DataFusionError::Internal(format!(
+ "Received an AnalyzeExecNode message with unknown ExplainFormat {}",
+ analyze.format
+ ))
+ })?;
+ let format = match proto_format {
+ protobuf::ExplainFormat::Indent => ExplainFormat::Indent,
+ protobuf::ExplainFormat::Tree => ExplainFormat::Tree,
+ protobuf::ExplainFormat::Pgjson => ExplainFormat::PostgresJSON,
+ protobuf::ExplainFormat::Graphviz => ExplainFormat::Graphviz,
+ };
+ let schema = analyze.schema.as_ref().ok_or_else(|| {
+ datafusion_common::internal_datafusion_err!(
+ "AnalyzeExec is missing required field 'schema'"
+ )
+ })?;
+ Ok(Arc::new(
+ AnalyzeExec::builder(
+ analyze.verbose,
+ analyze.show_statistics,
+ input,
+ Arc::new(arrow::datatypes::Schema::try_from(schema)?),
+ )
+ .with_metric_categories(metric_categories)
+ .with_format(format)
+ .build(),
+ ))
+ }
}
/// Creates the output of AnalyzeExec as a RecordBatch
diff --git a/datafusion/physical-plan/src/explain.rs b/datafusion/physical-plan/src/explain.rs
index 98eac3d28b5df..a270a003eba17 100644
--- a/datafusion/physical-plan/src/explain.rs
+++ b/datafusion/physical-plan/src/explain.rs
@@ -185,6 +185,188 @@ impl ExecutionPlan for ExplainExec {
futures::stream::iter(vec![Ok(record_batch)]),
)))
}
+
+ #[cfg(feature = "proto")]
+ fn try_to_proto(
+ &self,
+ _ctx: &crate::proto::ExecutionPlanEncodeCtx<'_>,
+ ) -> Result> {
+ use datafusion_proto_models::protobuf;
+
+ Ok(Some(protobuf::PhysicalPlanNode {
+ physical_plan_type: Some(
+ protobuf::physical_plan_node::PhysicalPlanType::Explain(
+ protobuf::ExplainExecNode {
+ schema: Some(self.schema().as_ref().try_into()?),
+ stringified_plans: self
+ .stringified_plans()
+ .iter()
+ .map(stringified_plan_to_proto)
+ .collect(),
+ verbose: self.verbose(),
+ },
+ ),
+ ),
+ }))
+ }
+}
+
+#[cfg(feature = "proto")]
+impl ExplainExec {
+ /// Reconstruct an [`ExplainExec`] from its protobuf representation.
+ pub fn try_from_proto(
+ node: &datafusion_proto_models::protobuf::PhysicalPlanNode,
+ _ctx: &crate::proto::ExecutionPlanDecodeCtx<'_>,
+ ) -> Result> {
+ use datafusion_proto_models::protobuf;
+
+ let explain = crate::expect_plan_variant!(
+ node,
+ protobuf::physical_plan_node::PhysicalPlanType::Explain,
+ "ExplainExec",
+ );
+ let schema = explain.schema.as_ref().ok_or_else(|| {
+ datafusion_common::internal_datafusion_err!(
+ "ExplainExec is missing required field 'schema'"
+ )
+ })?;
+ Ok(Arc::new(ExplainExec::new(
+ Arc::new(arrow::datatypes::Schema::try_from(schema)?),
+ explain
+ .stringified_plans
+ .iter()
+ .map(stringified_plan_from_proto)
+ .collect(),
+ explain.verbose,
+ )))
+ }
+}
+
+#[cfg(feature = "proto")]
+fn stringified_plan_to_proto(
+ stringified_plan: &StringifiedPlan,
+) -> datafusion_proto_models::protobuf::StringifiedPlan {
+ use datafusion_common::display::PlanType;
+ use datafusion_proto_models::datafusion_common::EmptyMessage;
+ use datafusion_proto_models::protobuf;
+ use protobuf::plan_type::PlanTypeEnum::{
+ AnalyzedLogicalPlan, FinalAnalyzedLogicalPlan, FinalLogicalPlan,
+ FinalPhysicalPlan, FinalPhysicalPlanWithSchema, FinalPhysicalPlanWithStats,
+ InitialLogicalPlan, InitialPhysicalPlan, InitialPhysicalPlanWithSchema,
+ InitialPhysicalPlanWithStats, OptimizedLogicalPlan, OptimizedPhysicalPlan,
+ PhysicalPlanError,
+ };
+
+ protobuf::StringifiedPlan {
+ plan_type: match stringified_plan.clone().plan_type {
+ PlanType::InitialLogicalPlan => Some(protobuf::PlanType {
+ plan_type_enum: Some(InitialLogicalPlan(EmptyMessage {})),
+ }),
+ PlanType::AnalyzedLogicalPlan { analyzer_name } => Some(protobuf::PlanType {
+ plan_type_enum: Some(AnalyzedLogicalPlan(
+ protobuf::AnalyzedLogicalPlanType { analyzer_name },
+ )),
+ }),
+ PlanType::FinalAnalyzedLogicalPlan => Some(protobuf::PlanType {
+ plan_type_enum: Some(FinalAnalyzedLogicalPlan(EmptyMessage {})),
+ }),
+ PlanType::OptimizedLogicalPlan { optimizer_name } => {
+ Some(protobuf::PlanType {
+ plan_type_enum: Some(OptimizedLogicalPlan(
+ protobuf::OptimizedLogicalPlanType { optimizer_name },
+ )),
+ })
+ }
+ PlanType::FinalLogicalPlan => Some(protobuf::PlanType {
+ plan_type_enum: Some(FinalLogicalPlan(EmptyMessage {})),
+ }),
+ PlanType::InitialPhysicalPlan => Some(protobuf::PlanType {
+ plan_type_enum: Some(InitialPhysicalPlan(EmptyMessage {})),
+ }),
+ PlanType::OptimizedPhysicalPlan { optimizer_name } => {
+ Some(protobuf::PlanType {
+ plan_type_enum: Some(OptimizedPhysicalPlan(
+ protobuf::OptimizedPhysicalPlanType { optimizer_name },
+ )),
+ })
+ }
+ PlanType::FinalPhysicalPlan => Some(protobuf::PlanType {
+ plan_type_enum: Some(FinalPhysicalPlan(EmptyMessage {})),
+ }),
+ PlanType::InitialPhysicalPlanWithStats => Some(protobuf::PlanType {
+ plan_type_enum: Some(InitialPhysicalPlanWithStats(EmptyMessage {})),
+ }),
+ PlanType::InitialPhysicalPlanWithSchema => Some(protobuf::PlanType {
+ plan_type_enum: Some(InitialPhysicalPlanWithSchema(EmptyMessage {})),
+ }),
+ PlanType::FinalPhysicalPlanWithStats => Some(protobuf::PlanType {
+ plan_type_enum: Some(FinalPhysicalPlanWithStats(EmptyMessage {})),
+ }),
+ PlanType::FinalPhysicalPlanWithSchema => Some(protobuf::PlanType {
+ plan_type_enum: Some(FinalPhysicalPlanWithSchema(EmptyMessage {})),
+ }),
+ PlanType::PhysicalPlanError => Some(protobuf::PlanType {
+ plan_type_enum: Some(PhysicalPlanError(EmptyMessage {})),
+ }),
+ },
+ plan: stringified_plan.plan.to_string(),
+ }
+}
+
+#[cfg(feature = "proto")]
+fn stringified_plan_from_proto(
+ stringified_plan: &datafusion_proto_models::protobuf::StringifiedPlan,
+) -> StringifiedPlan {
+ use datafusion_common::display::PlanType;
+ use datafusion_proto_models::protobuf::plan_type::PlanTypeEnum::{
+ AnalyzedLogicalPlan, FinalAnalyzedLogicalPlan, FinalLogicalPlan,
+ FinalPhysicalPlan, FinalPhysicalPlanWithSchema, FinalPhysicalPlanWithStats,
+ InitialLogicalPlan, InitialPhysicalPlan, InitialPhysicalPlanWithSchema,
+ InitialPhysicalPlanWithStats, OptimizedLogicalPlan, OptimizedPhysicalPlan,
+ PhysicalPlanError,
+ };
+ use datafusion_proto_models::protobuf::{
+ AnalyzedLogicalPlanType, OptimizedLogicalPlanType, OptimizedPhysicalPlanType,
+ };
+
+ StringifiedPlan {
+ plan_type: match stringified_plan
+ .plan_type
+ .as_ref()
+ .and_then(|plan_type| plan_type.plan_type_enum.as_ref())
+ .unwrap_or_else(|| {
+ panic!(
+ "Cannot create protobuf::StringifiedPlan from {stringified_plan:?}"
+ )
+ }) {
+ InitialLogicalPlan(_) => PlanType::InitialLogicalPlan,
+ AnalyzedLogicalPlan(AnalyzedLogicalPlanType { analyzer_name }) => {
+ PlanType::AnalyzedLogicalPlan {
+ analyzer_name: analyzer_name.clone(),
+ }
+ }
+ FinalAnalyzedLogicalPlan(_) => PlanType::FinalAnalyzedLogicalPlan,
+ OptimizedLogicalPlan(OptimizedLogicalPlanType { optimizer_name }) => {
+ PlanType::OptimizedLogicalPlan {
+ optimizer_name: optimizer_name.clone(),
+ }
+ }
+ FinalLogicalPlan(_) => PlanType::FinalLogicalPlan,
+ InitialPhysicalPlan(_) => PlanType::InitialPhysicalPlan,
+ InitialPhysicalPlanWithStats(_) => PlanType::InitialPhysicalPlanWithStats,
+ InitialPhysicalPlanWithSchema(_) => PlanType::InitialPhysicalPlanWithSchema,
+ OptimizedPhysicalPlan(OptimizedPhysicalPlanType { optimizer_name }) => {
+ PlanType::OptimizedPhysicalPlan {
+ optimizer_name: optimizer_name.clone(),
+ }
+ }
+ FinalPhysicalPlan(_) => PlanType::FinalPhysicalPlan,
+ FinalPhysicalPlanWithStats(_) => PlanType::FinalPhysicalPlanWithStats,
+ FinalPhysicalPlanWithSchema(_) => PlanType::FinalPhysicalPlanWithSchema,
+ PhysicalPlanError(_) => PlanType::PhysicalPlanError,
+ },
+ plan: Arc::new(stringified_plan.plan.clone()),
+ }
}
/// If this plan should be shown, given the previous plan that was
diff --git a/datafusion/proto/src/physical_plan/mod.rs b/datafusion/proto/src/physical_plan/mod.rs
index e1801f66ef64f..748ca53505c4d 100644
--- a/datafusion/proto/src/physical_plan/mod.rs
+++ b/datafusion/proto/src/physical_plan/mod.rs
@@ -24,8 +24,6 @@ use std::sync::Arc;
use arrow::datatypes::{IntervalMonthDayNanoType, Schema, SchemaRef};
use datafusion_catalog::memory::MemorySourceConfig;
use datafusion_common::config::CsvOptions;
-use datafusion_common::display::StringifiedPlan;
-use datafusion_common::format::ExplainFormat;
use datafusion_common::{
DataFusionError, Result, internal_datafusion_err, internal_err, not_impl_err,
};
@@ -84,7 +82,6 @@ use datafusion_physical_plan::joins::{
};
use datafusion_physical_plan::limit::{GlobalLimitExec, LocalLimitExec};
use datafusion_physical_plan::memory::LazyMemoryExec;
-use datafusion_physical_plan::metrics::MetricCategory;
use datafusion_physical_plan::placeholder_row::PlaceholderRowExec;
use datafusion_physical_plan::projection::ProjectionExec;
use datafusion_physical_plan::proto::{
@@ -103,7 +100,7 @@ use prost::Message;
use prost::bytes::BufMut;
use crate::common::{byte_to_string, str_to_byte};
-use crate::convert::{FromProto, TryFromProto};
+use crate::convert::TryFromProto;
use crate::convert_required;
use crate::physical_plan::from_proto::{
parse_physical_expr_with_converter, parse_physical_sort_expr,
@@ -744,8 +741,8 @@ pub trait PhysicalPlanNodeExt: Sized {
};
let decode_ctx = ExecutionPlanDecodeCtx::new(&plan_decoder);
match plan {
- PhysicalPlanType::Explain(explain) => {
- self.try_into_explain_physical_plan(explain, ctx, proto_converter)
+ PhysicalPlanType::Explain(_) => {
+ ExplainExec::try_from_proto(self.node(), &decode_ctx)
}
PhysicalPlanType::Projection(_) => {
ProjectionExec::try_from_proto(self.node(), &decode_ctx)
@@ -829,8 +826,8 @@ pub trait PhysicalPlanNodeExt: Sized {
PhysicalPlanType::NestedLoopJoin(_) => {
NestedLoopJoinExec::try_from_proto(self.node(), &decode_ctx)
}
- PhysicalPlanType::Analyze(analyze) => {
- self.try_into_analyze_physical_plan(analyze, ctx, proto_converter)
+ PhysicalPlanType::Analyze(_) => {
+ AnalyzeExec::try_from_proto(self.node(), &decode_ctx)
}
PhysicalPlanType::JsonSink(sink) => {
self.try_into_json_sink_physical_plan(sink, ctx, proto_converter)
@@ -894,18 +891,6 @@ pub trait PhysicalPlanNodeExt: Sized {
return Ok(node);
}
- if let Some(exec) = plan.downcast_ref::() {
- return protobuf::PhysicalPlanNode::try_from_explain_exec(exec, codec);
- }
-
- if let Some(exec) = plan.downcast_ref::() {
- return protobuf::PhysicalPlanNode::try_from_analyze_exec(
- exec,
- codec,
- proto_converter,
- );
- }
-
if let Some(exec) = plan.downcast_ref::() {
return protobuf::PhysicalPlanNode::try_from_aggregate_exec(
exec,
@@ -977,21 +962,22 @@ pub trait PhysicalPlanNodeExt: Sized {
}
}
+ #[deprecated(
+ since = "55.0.0",
+ note = "unused by DataFusion; `ExplainExec` deserializes itself via `ExplainExec::try_from_proto`"
+ )]
fn try_into_explain_physical_plan(
&self,
- explain: &protobuf::ExplainExecNode,
- _ctx: &PhysicalPlanDecodeContext<'_>,
- _proto_converter: &dyn PhysicalProtoConverterExtension,
+ _explain: &protobuf::ExplainExecNode,
+ ctx: &PhysicalPlanDecodeContext<'_>,
+ proto_converter: &dyn PhysicalProtoConverterExtension,
) -> Result> {
- Ok(Arc::new(ExplainExec::new(
- Arc::new(explain.schema.as_ref().unwrap().try_into()?),
- explain
- .stringified_plans
- .iter()
- .map(StringifiedPlan::from_proto)
- .collect(),
- explain.verbose,
- )))
+ let plan_decoder = ConverterPlanDecoder {
+ ctx,
+ proto_converter,
+ };
+ let decode_ctx = ExecutionPlanDecodeCtx::new(&plan_decoder);
+ ExplainExec::try_from_proto(self.node(), &decode_ctx)
}
#[deprecated(
@@ -1878,48 +1864,22 @@ pub trait PhysicalPlanNodeExt: Sized {
NestedLoopJoinExec::try_from_proto(&node, &decode_ctx)
}
+ #[deprecated(
+ since = "55.0.0",
+ note = "unused by DataFusion; `AnalyzeExec` deserializes itself via `AnalyzeExec::try_from_proto`"
+ )]
fn try_into_analyze_physical_plan(
&self,
- analyze: &protobuf::AnalyzeExecNode,
+ _analyze: &protobuf::AnalyzeExecNode,
ctx: &PhysicalPlanDecodeContext<'_>,
proto_converter: &dyn PhysicalProtoConverterExtension,
) -> Result> {
- let input: Arc =
- into_physical_plan(&analyze.input, ctx, proto_converter)?;
- let metric_categories = if analyze.has_metric_categories {
- let cats: Result> = analyze
- .metric_categories
- .iter()
- .map(|s| s.parse::())
- .collect();
- Some(cats?)
- } else {
- None
+ let plan_decoder = ConverterPlanDecoder {
+ ctx,
+ proto_converter,
};
- let pb_format =
- protobuf::ExplainFormat::try_from(analyze.format).map_err(|_| {
- DataFusionError::Internal(format!(
- "Received an AnalyzeExecNode message with unknown ExplainFormat {}",
- analyze.format
- ))
- })?;
- let format = match pb_format {
- protobuf::ExplainFormat::Indent => ExplainFormat::Indent,
- protobuf::ExplainFormat::Tree => ExplainFormat::Tree,
- protobuf::ExplainFormat::Pgjson => ExplainFormat::PostgresJSON,
- protobuf::ExplainFormat::Graphviz => ExplainFormat::Graphviz,
- };
- Ok(Arc::new(
- AnalyzeExec::builder(
- analyze.verbose,
- analyze.show_statistics,
- input,
- Arc::new(convert_required!(analyze.schema)?),
- )
- .with_metric_categories(metric_categories)
- .with_format(format)
- .build(),
- ))
+ let decode_ctx = ExecutionPlanDecodeCtx::new(&plan_decoder);
+ AnalyzeExec::try_from_proto(self.node(), &decode_ctx)
}
fn try_into_json_sink_physical_plan(
@@ -2263,22 +2223,22 @@ pub trait PhysicalPlanNodeExt: Sized {
)))
}
+ #[deprecated(
+ since = "55.0.0",
+ note = "unused by DataFusion; `ExplainExec` serializes itself via `ExecutionPlan::try_to_proto`"
+ )]
fn try_from_explain_exec(
exec: &ExplainExec,
- _codec: &dyn PhysicalExtensionCodec,
+ codec: &dyn PhysicalExtensionCodec,
) -> Result {
- Ok(protobuf::PhysicalPlanNode {
- physical_plan_type: Some(PhysicalPlanType::Explain(
- protobuf::ExplainExecNode {
- schema: Some(exec.schema().as_ref().try_into()?),
- stringified_plans: exec
- .stringified_plans()
- .iter()
- .map(protobuf::StringifiedPlan::from_proto)
- .collect(),
- verbose: exec.verbose(),
- },
- )),
+ let proto_converter = DefaultPhysicalProtoConverter {};
+ let plan_encoder = ConverterPlanEncoder {
+ codec,
+ proto_converter: &proto_converter,
+ };
+ let encode_ctx = ExecutionPlanEncodeCtx::new(&plan_encoder);
+ exec.try_to_proto(&encode_ctx)?.ok_or_else(|| {
+ internal_datafusion_err!("ExplainExec did not serialize itself")
})
}
@@ -2301,38 +2261,22 @@ pub trait PhysicalPlanNodeExt: Sized {
})
}
+ #[deprecated(
+ since = "55.0.0",
+ note = "unused by DataFusion; `AnalyzeExec` serializes itself via `ExecutionPlan::try_to_proto`"
+ )]
fn try_from_analyze_exec(
exec: &AnalyzeExec,
codec: &dyn PhysicalExtensionCodec,
proto_converter: &dyn PhysicalProtoConverterExtension,
) -> Result {
- let input = protobuf::PhysicalPlanNode::try_from_physical_plan_with_converter(
- exec.input().to_owned(),
+ let plan_encoder = ConverterPlanEncoder {
codec,
proto_converter,
- )?;
- let (has_metric_categories, metric_categories) = match exec.metric_categories() {
- Some(cats) => (true, cats.iter().map(|c| c.to_string()).collect()),
- None => (false, vec![]),
- };
- let format = match exec.format() {
- ExplainFormat::Indent => protobuf::ExplainFormat::Indent,
- ExplainFormat::Tree => protobuf::ExplainFormat::Tree,
- ExplainFormat::PostgresJSON => protobuf::ExplainFormat::Pgjson,
- ExplainFormat::Graphviz => protobuf::ExplainFormat::Graphviz,
- } as i32;
- Ok(protobuf::PhysicalPlanNode {
- physical_plan_type: Some(PhysicalPlanType::Analyze(Box::new(
- protobuf::AnalyzeExecNode {
- verbose: exec.verbose(),
- show_statistics: exec.show_statistics(),
- input: Some(Box::new(input)),
- schema: Some(exec.schema().as_ref().try_into()?),
- has_metric_categories,
- metric_categories,
- format,
- },
- ))),
+ };
+ let encode_ctx = ExecutionPlanEncodeCtx::new(&plan_encoder);
+ exec.try_to_proto(&encode_ctx)?.ok_or_else(|| {
+ internal_datafusion_err!("AnalyzeExec did not serialize itself")
})
}
diff --git a/datafusion/proto/tests/cases/roundtrip_physical_plan.rs b/datafusion/proto/tests/cases/roundtrip_physical_plan.rs
index 889d42df40e0e..3d13ffe16e8b9 100644
--- a/datafusion/proto/tests/cases/roundtrip_physical_plan.rs
+++ b/datafusion/proto/tests/cases/roundtrip_physical_plan.rs
@@ -66,6 +66,7 @@ use datafusion::physical_plan::coalesce_batches::CoalesceBatchesExec;
use datafusion::physical_plan::coalesce_partitions::CoalescePartitionsExec;
use datafusion::physical_plan::coop::CooperativeExec;
use datafusion::physical_plan::empty::EmptyExec;
+use datafusion::physical_plan::explain::ExplainExec;
use datafusion::physical_plan::expressions::{
BinaryExpr, Column, DynamicFilterPhysicalExpr, NotExpr, PhysicalSortExpr, binary,
cast, col, in_list, like, lit,
@@ -77,6 +78,7 @@ use datafusion::physical_plan::joins::{
StreamJoinPartitionMode, SymmetricHashJoinExec,
};
use datafusion::physical_plan::limit::{GlobalLimitExec, LocalLimitExec};
+use datafusion::physical_plan::metrics::MetricCategory;
use datafusion::physical_plan::placeholder_row::PlaceholderRowExec;
use datafusion::physical_plan::projection::{ProjectionExec, ProjectionExpr};
use datafusion::physical_plan::repartition::RepartitionExec;
@@ -98,8 +100,10 @@ use datafusion::physical_plan::{
use datafusion::prelude::{ParquetReadOptions, SessionContext};
use datafusion::scalar::ScalarValue;
use datafusion_common::config::{ConfigOptions, TableParquetOptions};
+use datafusion_common::display::{PlanType, StringifiedPlan};
use datafusion_common::file_options::csv_writer::CsvWriterOptions;
use datafusion_common::file_options::json_writer::JsonWriterOptions;
+use datafusion_common::format::ExplainFormat;
use datafusion_common::parsers::CompressionTypeVariant;
use datafusion_common::stats::Precision;
use datafusion_common::{
@@ -1947,14 +1951,112 @@ fn roundtrip_like() -> Result<()> {
#[test]
fn roundtrip_analyze() -> Result<()> {
- let field_a = Field::new("plan_type", DataType::Utf8, false);
- let field_b = Field::new("plan", DataType::Utf8, false);
- let schema = Schema::new(vec![field_a, field_b]);
- let input = Arc::new(PlaceholderRowExec::new(Arc::new(schema.clone())));
+ let schema = Arc::new(Schema::new(vec![
+ Field::new("plan_type", DataType::Utf8, false),
+ Field::new("plan", DataType::Utf8, false),
+ ]));
+ let input = Arc::new(PlaceholderRowExec::new(Arc::clone(&schema)));
+ let metric_categories = vec![MetricCategory::Rows, MetricCategory::Timing];
+ let analyze = Arc::new(
+ AnalyzeExec::builder(true, true, input, Arc::clone(&schema))
+ .with_metric_categories(Some(metric_categories.clone()))
+ .with_format(ExplainFormat::Tree)
+ .build(),
+ );
- roundtrip_test(Arc::new(
- AnalyzeExec::builder(false, false, input, Arc::new(schema)).build(),
- ))
+ let ctx = SessionContext::new();
+ let roundtripped = roundtrip_test_and_return(
+ analyze,
+ &ctx,
+ &DefaultPhysicalExtensionCodec {},
+ &DefaultPhysicalProtoConverter {},
+ )?;
+ let roundtripped = roundtripped.downcast_ref::().unwrap();
+
+ assert_eq!(roundtripped.schema(), schema);
+ assert!(roundtripped.verbose());
+ assert!(roundtripped.show_statistics());
+ assert_eq!(
+ roundtripped.metric_categories(),
+ Some(metric_categories.as_slice())
+ );
+ assert_eq!(roundtripped.format(), &ExplainFormat::Tree);
+ assert!(
+ roundtripped
+ .input()
+ .downcast_ref::()
+ .is_some()
+ );
+ Ok(())
+}
+
+#[test]
+fn roundtrip_explain() -> Result<()> {
+ let schema = Arc::new(Schema::new(vec![
+ Field::new("plan_type", DataType::Utf8, false),
+ Field::new("plan", DataType::Utf8, false),
+ ]));
+ let stringified_plans = vec![
+ StringifiedPlan::new(PlanType::InitialLogicalPlan, "initial logical"),
+ StringifiedPlan::new(
+ PlanType::AnalyzedLogicalPlan {
+ analyzer_name: "analyzer".to_string(),
+ },
+ "analyzed logical",
+ ),
+ StringifiedPlan::new(PlanType::FinalAnalyzedLogicalPlan, "final analyzed"),
+ StringifiedPlan::new(
+ PlanType::OptimizedLogicalPlan {
+ optimizer_name: "logical optimizer".to_string(),
+ },
+ "optimized logical",
+ ),
+ StringifiedPlan::new(PlanType::FinalLogicalPlan, "final logical"),
+ StringifiedPlan::new(PlanType::InitialPhysicalPlan, "initial physical"),
+ StringifiedPlan::new(
+ PlanType::InitialPhysicalPlanWithStats,
+ "initial physical with stats",
+ ),
+ StringifiedPlan::new(
+ PlanType::InitialPhysicalPlanWithSchema,
+ "initial physical with schema",
+ ),
+ StringifiedPlan::new(
+ PlanType::OptimizedPhysicalPlan {
+ optimizer_name: "physical optimizer".to_string(),
+ },
+ "optimized physical",
+ ),
+ StringifiedPlan::new(PlanType::FinalPhysicalPlan, "final physical"),
+ StringifiedPlan::new(
+ PlanType::FinalPhysicalPlanWithStats,
+ "final physical with stats",
+ ),
+ StringifiedPlan::new(
+ PlanType::FinalPhysicalPlanWithSchema,
+ "final physical with schema",
+ ),
+ StringifiedPlan::new(PlanType::PhysicalPlanError, "physical plan error"),
+ ];
+ let explain = Arc::new(ExplainExec::new(
+ Arc::clone(&schema),
+ stringified_plans.clone(),
+ true,
+ ));
+
+ let ctx = SessionContext::new();
+ let roundtripped = roundtrip_test_and_return(
+ explain,
+ &ctx,
+ &DefaultPhysicalExtensionCodec {},
+ &DefaultPhysicalProtoConverter {},
+ )?;
+ let roundtripped = roundtripped.downcast_ref::().unwrap();
+
+ assert_eq!(roundtripped.schema(), schema);
+ assert_eq!(roundtripped.stringified_plans(), stringified_plans);
+ assert!(roundtripped.verbose());
+ Ok(())
}
#[tokio::test]
From 9e3c71fea170a6c95f9bc4723725decf2c301a9f Mon Sep 17 00:00:00 2001
From: Phoenix
Date: Sat, 25 Jul 2026 12:31:33 +0800
Subject: [PATCH 020/109] refactor(proto): migrate aggregate exec serde
(#23779)
## Which issue does this PR close?
- Closes #23512.
## Rationale for this change
Aggregate protobuf conversion lived in the central proto crate, which
prevented AggregateExec from owning its function-codec serialization.
Move encoding and decoding into AggregateExec and keep the deprecated
helpers as delegates so existing callers retain wire-compatible
behavior.
## What changes are included in this PR?
## Are these changes tested?
Yes
## Are there any user-facing changes?
---------
Signed-off-by: Jiawei Zhao
---
.../physical-plan/src/aggregates/mod.rs | 401 ++++++++++++++++++
datafusion/proto/src/physical_plan/mod.rs | 383 ++---------------
2 files changed, 430 insertions(+), 354 deletions(-)
diff --git a/datafusion/physical-plan/src/aggregates/mod.rs b/datafusion/physical-plan/src/aggregates/mod.rs
index e3cf1c4568009..787cd4f03ff6c 100644
--- a/datafusion/physical-plan/src/aggregates/mod.rs
+++ b/datafusion/physical-plan/src/aggregates/mod.rs
@@ -2168,6 +2168,385 @@ impl ExecutionPlan for AggregateExec {
Ok(result)
}
+
+ #[cfg(feature = "proto")]
+ fn try_to_proto(
+ &self,
+ ctx: &crate::proto::ExecutionPlanEncodeCtx<'_>,
+ ) -> Result> {
+ use datafusion_proto_models::protobuf;
+
+ let input = ctx.encode_child(self.input())?;
+ let group_by = self.group_expr();
+ let group_expr =
+ ctx.encode_expressions(group_by.expr().iter().map(|(expr, _)| expr))?;
+ let group_expr_name = group_by
+ .expr()
+ .iter()
+ .map(|(_, name)| name.to_owned())
+ .collect();
+ let null_expr =
+ ctx.encode_expressions(group_by.null_expr().iter().map(|(expr, _)| expr))?;
+ let groups = group_by.groups().iter().flatten().copied().collect();
+ let aggr_expr = self
+ .aggr_expr()
+ .iter()
+ .map(|expr| encode_aggregate_expr(expr, ctx))
+ .collect::>>()?;
+ let aggr_expr_name = self
+ .aggr_expr()
+ .iter()
+ .map(|expr| expr.name().to_string())
+ .collect();
+ let filter_expr = self
+ .filter_expr()
+ .iter()
+ .map(|filter| {
+ Ok(protobuf::MaybeFilter {
+ expr: filter
+ .as_ref()
+ .map(|expr| ctx.encode_expr(expr))
+ .transpose()?,
+ })
+ })
+ .collect::>>()?;
+ // Match by name because the protobuf and execution enums use different
+ // discriminants, so a numeric cast would corrupt the wire format.
+ let mode = match self.mode() {
+ AggregateMode::Partial => protobuf::AggregateMode::Partial,
+ AggregateMode::Final => protobuf::AggregateMode::Final,
+ AggregateMode::FinalPartitioned => protobuf::AggregateMode::FinalPartitioned,
+ AggregateMode::Single => protobuf::AggregateMode::Single,
+ AggregateMode::SinglePartitioned => {
+ protobuf::AggregateMode::SinglePartitioned
+ }
+ AggregateMode::PartialReduce => protobuf::AggregateMode::PartialReduce,
+ };
+ let limit = self.limit_options().map(|options| protobuf::AggLimit {
+ limit: options.limit() as u64,
+ descending: options.descending(),
+ });
+ let dynamic_filter = match self.dynamic_filter_expr() {
+ Some(filter) => {
+ let expr: Arc =
+ Arc::clone(filter) as Arc;
+ Some(ctx.encode_expr(&expr)?)
+ }
+ None => None,
+ };
+
+ Ok(Some(protobuf::PhysicalPlanNode {
+ physical_plan_type: Some(
+ protobuf::physical_plan_node::PhysicalPlanType::Aggregate(Box::new(
+ protobuf::AggregateExecNode {
+ group_expr,
+ group_expr_name,
+ aggr_expr,
+ filter_expr,
+ aggr_expr_name,
+ mode: mode as i32,
+ input: Some(Box::new(input)),
+ input_schema: Some(self.input_schema().as_ref().try_into()?),
+ null_expr,
+ groups,
+ limit,
+ has_grouping_set: group_by.has_grouping_set(),
+ dynamic_filter,
+ },
+ )),
+ ),
+ }))
+ }
+}
+
+/// Keep this marker byte-identical to the copy used by the deprecated
+/// aggregate serializer in `datafusion-proto` until that path is removed.
+#[cfg(feature = "proto")]
+const HUMAN_DISPLAY_ALIAS_PREFIX: &str = "\u{1f}datafusion_human_display_alias_v1:";
+
+#[cfg(feature = "proto")]
+fn encode_human_display_alias(human_display: &str, alias: &str) -> String {
+ format!(
+ "{HUMAN_DISPLAY_ALIAS_PREFIX}{}:{alias}{human_display}",
+ alias.len()
+ )
+}
+
+#[cfg(feature = "proto")]
+fn split_human_display_alias<'a>(
+ human_display: &'a str,
+ name: &'a str,
+) -> (&'a str, Option<&'a str>) {
+ if let Some(encoded) = human_display.strip_prefix(HUMAN_DISPLAY_ALIAS_PREFIX)
+ && let Some((alias_len, encoded)) = encoded.split_once(':')
+ && let Ok(alias_len) = alias_len.parse::()
+ && let Some(alias) = encoded.get(..alias_len)
+ && let Some(human_display) = encoded.get(alias_len..)
+ && alias == name
+ && !human_display.is_empty()
+ {
+ return (human_display, Some(alias));
+ }
+
+ (human_display, None)
+}
+
+#[cfg(feature = "proto")]
+fn encode_aggregate_expr(
+ aggr_expr: &Arc,
+ ctx: &crate::proto::ExecutionPlanEncodeCtx<'_>,
+) -> Result {
+ use datafusion_proto_models::protobuf;
+
+ let expressions = aggr_expr.expressions();
+ let expr = ctx.encode_expressions(expressions.iter())?;
+ let ordering_req = aggr_expr
+ .order_bys()
+ .iter()
+ .map(|sort_expr| {
+ Ok(protobuf::PhysicalSortExprNode {
+ expr: Some(Box::new(ctx.encode_expr(&sort_expr.expr)?)),
+ asc: !sort_expr.options.descending,
+ nulls_first: sort_expr.options.nulls_first,
+ })
+ })
+ .collect::>>()?;
+ let name = aggr_expr.fun().name().to_string();
+ // The context already applies `(!buf.is_empty()).then_some(buf)`.
+ let fun_definition = ctx.encode_udaf(aggr_expr.fun())?;
+ let human_display = match (aggr_expr.human_display(), aggr_expr.human_display_alias())
+ {
+ (Some(display), Some(alias)) => encode_human_display_alias(display, alias),
+ (Some(display), None) => display.to_string(),
+ (None, _) => String::new(),
+ };
+
+ Ok(protobuf::PhysicalExprNode {
+ expr_id: None,
+ expr_type: Some(protobuf::physical_expr_node::ExprType::AggregateExpr(
+ protobuf::PhysicalAggregateExprNode {
+ aggregate_function: Some(
+ protobuf::physical_aggregate_expr_node::AggregateFunction::UserDefinedAggrFunction(name),
+ ),
+ expr,
+ ordering_req,
+ distinct: aggr_expr.is_distinct(),
+ ignore_nulls: aggr_expr.ignore_nulls(),
+ fun_definition,
+ human_display,
+ },
+ )),
+ })
+}
+
+#[cfg(feature = "proto")]
+impl AggregateExec {
+ /// Reconstruct an [`AggregateExec`] from its protobuf representation.
+ ///
+ /// Grouping expressions are decoded against the child schema. Aggregate
+ /// arguments, ordering, filters, and the dynamic filter are decoded against
+ /// the aggregate input schema carried in the protobuf node.
+ pub fn try_from_proto(
+ node: &datafusion_proto_models::protobuf::PhysicalPlanNode,
+ ctx: &crate::proto::ExecutionPlanDecodeCtx<'_>,
+ ) -> Result> {
+ use datafusion_physical_expr::PhysicalSortExpr;
+ use datafusion_physical_expr::aggregate::AggregateExprBuilder;
+ use datafusion_proto_models::protobuf;
+ use protobuf::physical_aggregate_expr_node::AggregateFunction;
+ use protobuf::physical_expr_node::ExprType;
+
+ let hash_agg = crate::expect_plan_variant!(
+ node,
+ protobuf::physical_plan_node::PhysicalPlanType::Aggregate,
+ "AggregateExec",
+ );
+ let input = ctx.decode_required_child(
+ hash_agg.input.as_deref(),
+ "AggregateExec",
+ "input",
+ )?;
+ // Match by name because the protobuf and execution enums use different
+ // discriminants, so a numeric cast would corrupt the wire format.
+ let mode = protobuf::AggregateMode::try_from(hash_agg.mode).map_err(|_| {
+ datafusion_common::internal_datafusion_err!(
+ "Received an AggregateNode message with unknown AggregateMode {}",
+ hash_agg.mode
+ )
+ })?;
+ let mode = match mode {
+ protobuf::AggregateMode::Partial => AggregateMode::Partial,
+ protobuf::AggregateMode::Final => AggregateMode::Final,
+ protobuf::AggregateMode::FinalPartitioned => AggregateMode::FinalPartitioned,
+ protobuf::AggregateMode::Single => AggregateMode::Single,
+ protobuf::AggregateMode::SinglePartitioned => {
+ AggregateMode::SinglePartitioned
+ }
+ protobuf::AggregateMode::PartialReduce => AggregateMode::PartialReduce,
+ };
+ let num_expr = hash_agg.group_expr.len();
+ // Grouping expressions refer to the child plan's output schema.
+ let child_schema = input.schema();
+ let group_expr = hash_agg
+ .group_expr
+ .iter()
+ .zip(hash_agg.group_expr_name.iter())
+ .map(|(expr, name)| {
+ Ok((
+ ctx.decode_expr(expr, child_schema.as_ref())?,
+ name.to_string(),
+ ))
+ })
+ .collect::>>()?;
+ let null_expr = hash_agg
+ .null_expr
+ .iter()
+ .zip(hash_agg.group_expr_name.iter())
+ .map(|(expr, name)| {
+ Ok((
+ ctx.decode_expr(expr, child_schema.as_ref())?,
+ name.to_string(),
+ ))
+ })
+ .collect::>>()?;
+ let groups = if hash_agg.groups.is_empty() {
+ vec![]
+ } else {
+ hash_agg
+ .groups
+ .chunks(num_expr)
+ .map(|group| group.to_vec())
+ .collect()
+ };
+ // Aggregate arguments, ordering, filters, and dynamic filters refer to
+ // the aggregate input schema carried in the protobuf node.
+ let input_schema = hash_agg.input_schema.as_ref().ok_or_else(|| {
+ datafusion_common::internal_datafusion_err!(
+ "input_schema in AggregateNode is missing."
+ )
+ })?;
+ let input_schema: SchemaRef = SchemaRef::new(input_schema.try_into()?);
+ let filter_expr = hash_agg
+ .filter_expr
+ .iter()
+ .map(|filter| {
+ filter
+ .expr
+ .as_ref()
+ .map(|expr| ctx.decode_expr(expr, input_schema.as_ref()))
+ .transpose()
+ })
+ .collect::>>()?;
+ let aggr_expr = hash_agg
+ .aggr_expr
+ .iter()
+ .zip(hash_agg.aggr_expr_name.iter())
+ .map(|(expr, name)| {
+ let expr_type = expr.expr_type.as_ref().ok_or_else(|| {
+ datafusion_common::internal_datafusion_err!(
+ "Unexpected empty aggregate physical expression"
+ )
+ })?;
+ let ExprType::AggregateExpr(aggregate) = expr_type else {
+ return internal_err!(
+ "Invalid aggregate expression for AggregateExec"
+ );
+ };
+ let args = aggregate
+ .expr
+ .iter()
+ .map(|expr| ctx.decode_expr(expr, input_schema.as_ref()))
+ .collect::>>()?;
+ let order_by = aggregate
+ .ordering_req
+ .iter()
+ .map(|sort_expr| {
+ let expr = sort_expr.expr.as_deref().ok_or_else(|| {
+ datafusion_common::internal_datafusion_err!(
+ "AggregateExec ordering expression is missing its inner expr"
+ )
+ })?;
+ Ok(PhysicalSortExpr {
+ expr: ctx.decode_expr(expr, input_schema.as_ref())?,
+ options: arrow::compute::SortOptions {
+ descending: !sort_expr.asc,
+ nulls_first: sort_expr.nulls_first,
+ },
+ })
+ })
+ .collect::>>()?;
+ let Some(AggregateFunction::UserDefinedAggrFunction(udaf_name)) =
+ aggregate.aggregate_function.as_ref()
+ else {
+ return internal_err!(
+ "Invalid AggregateExpr, missing aggregate_function"
+ );
+ };
+ // The context owns the payload-to-codec and
+ // registry-to-codec fallback order.
+ let udaf = ctx.decode_udaf(
+ udaf_name,
+ aggregate.fun_definition.as_deref(),
+ )?;
+ let (human_display, human_display_alias) =
+ split_human_display_alias(&aggregate.human_display, name);
+ let builder = AggregateExprBuilder::new(udaf, args)
+ .schema(Arc::clone(&input_schema))
+ .alias(name)
+ .with_ignore_nulls(aggregate.ignore_nulls)
+ .with_distinct(aggregate.distinct)
+ .order_by(order_by)
+ .human_display(human_display);
+ let builder = if let Some(alias) = human_display_alias {
+ builder.human_display_alias(alias)
+ } else {
+ builder
+ };
+ builder.build().map(Arc::new)
+ })
+ .collect::>>()?;
+ let aggregate = AggregateExec::try_new(
+ mode,
+ PhysicalGroupBy::new(
+ group_expr,
+ null_expr,
+ groups,
+ hash_agg.has_grouping_set,
+ ),
+ aggr_expr,
+ filter_expr,
+ input,
+ Arc::clone(&input_schema),
+ )?;
+ let aggregate = if let Some(limit) = &hash_agg.limit {
+ let options = match limit.descending {
+ Some(descending) => {
+ LimitOptions::new_with_order(limit.limit as usize, descending)
+ }
+ None => LimitOptions::new(limit.limit as usize),
+ };
+ aggregate.with_limit_options(Some(options))
+ } else {
+ aggregate
+ };
+ let aggregate = if let Some(dynamic_filter) = &hash_agg.dynamic_filter {
+ let dynamic_filter =
+ ctx.decode_expr(dynamic_filter, input_schema.as_ref())?;
+ let dynamic_filter = (dynamic_filter
+ as Arc)
+ .downcast::()
+ .map_err(|_| {
+ datafusion_common::internal_datafusion_err!(
+ "AggregateExec dynamic_filter did not decode to a DynamicFilterPhysicalExpr"
+ )
+ })?;
+ aggregate.with_dynamic_filter_expr(dynamic_filter)?
+ } else {
+ aggregate
+ };
+
+ Ok(Arc::new(aggregate))
+ }
}
/// Creates the output schema for an [`AggregateExec`] containing the group by columns followed
@@ -2708,6 +3087,28 @@ mod tests {
use futures::{FutureExt, Stream, StreamExt};
use insta::{allow_duplicates, assert_snapshot};
+ #[cfg(feature = "proto")]
+ #[test]
+ fn split_human_display_alias_ignores_mismatched_alias() {
+ let encoded = encode_human_display_alias("sum(value)", "revenue");
+
+ assert_eq!(
+ split_human_display_alias(&encoded, "other"),
+ (encoded.as_str(), None)
+ );
+ }
+
+ #[cfg(feature = "proto")]
+ #[test]
+ fn split_human_display_alias_keeps_malformed_prefix_literal() {
+ let display = format!("{HUMAN_DISPLAY_ALIAS_PREFIX}not-an-encoding");
+
+ assert_eq!(
+ split_human_display_alias(&display, "agg"),
+ (display.as_str(), None)
+ );
+ }
+
// Generate a schema which consists of 5 columns (a, b, c, d, e)
fn create_test_schema() -> Result {
let a = Field::new("a", DataType::Int32, true);
diff --git a/datafusion/proto/src/physical_plan/mod.rs b/datafusion/proto/src/physical_plan/mod.rs
index 748ca53505c4d..7ee173cb36868 100644
--- a/datafusion/proto/src/physical_plan/mod.rs
+++ b/datafusion/proto/src/physical_plan/mod.rs
@@ -54,14 +54,10 @@ use datafusion_expr::{AggregateUDF, HigherOrderUDF, ScalarUDF, WindowUDF};
use datafusion_functions_table::generate_series::{
Empty, GenSeriesArgs, GenerateSeriesTable, GenericSeriesState, TimestampValue,
};
-use datafusion_physical_expr::aggregate::{AggregateExprBuilder, AggregateFunctionExpr};
-use datafusion_physical_expr::expressions::DynamicFilterPhysicalExpr;
use datafusion_physical_expr::{LexOrdering, LexRequirement};
use datafusion_physical_expr_common::physical_expr::proto_decode::PhysicalExprDecodeCtx;
use datafusion_physical_expr_common::physical_expr::proto_encode::PhysicalExprEncodeCtx;
-use datafusion_physical_plan::aggregates::{
- AggregateExec, AggregateMode, LimitOptions, PhysicalGroupBy,
-};
+use datafusion_physical_plan::aggregates::AggregateExec;
use datafusion_physical_plan::analyze::AnalyzeExec;
use datafusion_physical_plan::async_func::AsyncFuncExec;
use datafusion_physical_plan::buffer::BufferExec;
@@ -103,17 +99,13 @@ use crate::common::{byte_to_string, str_to_byte};
use crate::convert::TryFromProto;
use crate::convert_required;
use crate::physical_plan::from_proto::{
- parse_physical_expr_with_converter, parse_physical_sort_expr,
- parse_physical_sort_exprs, parse_protobuf_file_scan_config, parse_record_batches,
- parse_table_schema_from_proto,
+ parse_physical_expr_with_converter, parse_physical_sort_exprs,
+ parse_protobuf_file_scan_config, parse_record_batches, parse_table_schema_from_proto,
};
use crate::physical_plan::to_proto::{
- serialize_file_scan_config, serialize_maybe_filter, serialize_physical_aggr_expr,
- serialize_physical_expr_with_converter, serialize_physical_sort_exprs,
- serialize_record_batches,
+ serialize_file_scan_config, serialize_physical_expr_with_converter,
+ serialize_physical_sort_exprs, serialize_record_batches,
};
-use crate::protobuf::physical_aggregate_expr_node::AggregateFunction;
-use crate::protobuf::physical_expr_node::ExprType;
use crate::protobuf::physical_plan_node::PhysicalPlanType;
use crate::protobuf::{self, SortMergeJoinExecNode, proto_error};
@@ -129,48 +121,10 @@ fn encode_human_display_alias(human_display: &str, alias: &str) -> String {
)
}
-fn split_human_display_alias<'a>(
- human_display: &'a str,
- name: &'a str,
-) -> (&'a str, Option<&'a str>) {
- if let Some(encoded) = human_display.strip_prefix(HUMAN_DISPLAY_ALIAS_PREFIX)
- && let Some((alias_len, encoded)) = encoded.split_once(':')
- && let Ok(alias_len) = alias_len.parse::()
- && let Some(alias) = encoded.get(..alias_len)
- && let Some(human_display) = encoded.get(alias_len..)
- && alias == name
- && !human_display.is_empty()
- {
- return (human_display, Some(alias));
- }
-
- (human_display, None)
-}
-
#[cfg(test)]
mod tests {
use super::*;
- #[test]
- fn split_human_display_alias_ignores_mismatched_alias() {
- let encoded = encode_human_display_alias("sum(value)", "revenue");
-
- assert_eq!(
- split_human_display_alias(&encoded, "other"),
- (encoded.as_str(), None)
- );
- }
-
- #[test]
- fn split_human_display_alias_keeps_malformed_prefix_literal() {
- let display = format!("{HUMAN_DISPLAY_ALIAS_PREFIX}not-an-encoding");
-
- assert_eq!(
- split_human_display_alias(&display, "agg"),
- (display.as_str(), None)
- );
- }
-
/// Unit tests for the bytes-only function serde exposed on
/// [`ExecutionPlanEncodeCtx`] / [`ExecutionPlanDecodeCtx`] and backed by
/// [`ConverterPlanEncoder`] / [`ConverterPlanDecoder`]. Function-carrying
@@ -790,8 +744,8 @@ pub trait PhysicalPlanNodeExt: Sized {
PhysicalPlanType::Window(_) => {
WindowAggExec::try_from_proto(self.node(), &decode_ctx)
}
- PhysicalPlanType::Aggregate(hash_agg) => {
- self.try_into_aggregate_physical_plan(hash_agg, ctx, proto_converter)
+ PhysicalPlanType::Aggregate(_) => {
+ AggregateExec::try_from_proto(self.node(), &decode_ctx)
}
PhysicalPlanType::HashJoin(_) => {
HashJoinExec::try_from_proto(self.node(), &decode_ctx)
@@ -891,14 +845,6 @@ pub trait PhysicalPlanNodeExt: Sized {
return Ok(node);
}
- if let Some(exec) = plan.downcast_ref::() {
- return protobuf::PhysicalPlanNode::try_from_aggregate_exec(
- exec,
- codec,
- proto_converter,
- );
- }
-
if let Some(data_source_exec) = plan.downcast_ref::()
&& let Some(node) = protobuf::PhysicalPlanNode::try_from_data_source_exec(
data_source_exec,
@@ -1412,212 +1358,27 @@ pub trait PhysicalPlanNodeExt: Sized {
WindowAggExec::try_from_proto(&node, &decode_ctx)
}
+ #[deprecated(
+ since = "55.0.0",
+ note = "unused by DataFusion; `AggregateExec` deserializes itself via `AggregateExec::try_from_proto`"
+ )]
fn try_into_aggregate_physical_plan(
&self,
hash_agg: &protobuf::AggregateExecNode,
ctx: &PhysicalPlanDecodeContext<'_>,
proto_converter: &dyn PhysicalProtoConverterExtension,
) -> Result> {
- let input: Arc =
- into_physical_plan(&hash_agg.input, ctx, proto_converter)?;
- let mode = protobuf::AggregateMode::try_from(hash_agg.mode).map_err(|_| {
- proto_error(format!(
- "Received a AggregateNode message with unknown AggregateMode {}",
- hash_agg.mode
- ))
- })?;
- let agg_mode: AggregateMode = match mode {
- protobuf::AggregateMode::Partial => AggregateMode::Partial,
- protobuf::AggregateMode::Final => AggregateMode::Final,
- protobuf::AggregateMode::FinalPartitioned => AggregateMode::FinalPartitioned,
- protobuf::AggregateMode::Single => AggregateMode::Single,
- protobuf::AggregateMode::SinglePartitioned => {
- AggregateMode::SinglePartitioned
- }
- protobuf::AggregateMode::PartialReduce => AggregateMode::PartialReduce,
- };
-
- let num_expr = hash_agg.group_expr.len();
-
- let group_expr = hash_agg
- .group_expr
- .iter()
- .zip(hash_agg.group_expr_name.iter())
- .map(|(expr, name)| {
- proto_converter
- .proto_to_physical_expr(expr, input.schema().as_ref(), ctx)
- .map(|expr| (expr, name.to_string()))
- })
- .collect::, _>>()?;
-
- let null_expr = hash_agg
- .null_expr
- .iter()
- .zip(hash_agg.group_expr_name.iter())
- .map(|(expr, name)| {
- proto_converter
- .proto_to_physical_expr(expr, input.schema().as_ref(), ctx)
- .map(|expr| (expr, name.to_string()))
- })
- .collect::, _>>()?;
-
- let groups: Vec> = if !hash_agg.groups.is_empty() {
- hash_agg
- .groups
- .chunks(num_expr)
- .map(|g| g.to_vec())
- .collect::>>()
- } else {
- vec![]
- };
-
- let has_grouping_set = hash_agg.has_grouping_set;
-
- let input_schema = hash_agg.input_schema.as_ref().ok_or_else(|| {
- internal_datafusion_err!("input_schema in AggregateNode is missing.")
- })?;
- let physical_schema: SchemaRef = SchemaRef::new(input_schema.try_into()?);
-
- let physical_filter_expr = hash_agg
- .filter_expr
- .iter()
- .map(|expr| {
- expr.expr
- .as_ref()
- .map(|e| {
- proto_converter.proto_to_physical_expr(e, &physical_schema, ctx)
- })
- .transpose()
- })
- .collect::, _>>()?;
-
- let physical_aggr_expr: Vec> = hash_agg
- .aggr_expr
- .iter()
- .zip(hash_agg.aggr_expr_name.iter())
- .map(|(expr, name)| {
- let expr_type = expr.expr_type.as_ref().ok_or_else(|| {
- proto_error("Unexpected empty aggregate physical expression")
- })?;
-
- match expr_type {
- ExprType::AggregateExpr(agg_node) => {
- let input_phy_expr: Vec> = agg_node
- .expr
- .iter()
- .map(|e| {
- proto_converter.proto_to_physical_expr(
- e,
- &physical_schema,
- ctx,
- )
- })
- .collect::>>()?;
- let order_bys = agg_node
- .ordering_req
- .iter()
- .map(|e| {
- parse_physical_sort_expr(
- e,
- ctx,
- &physical_schema,
- proto_converter,
- )
- })
- .collect::>()?;
- agg_node
- .aggregate_function
- .as_ref()
- .map(|func| match func {
- AggregateFunction::UserDefinedAggrFunction(udaf_name) => {
- let agg_udf = match &agg_node.fun_definition {
- Some(buf) => {
- ctx.codec().try_decode_udaf(udaf_name, buf)?
- }
- None => ctx.task_ctx().udaf(udaf_name).or_else(
- |_| {
- ctx.codec()
- .try_decode_udaf(udaf_name, &[])
- },
- )?,
- };
-
- let (human_display, human_display_alias) =
- split_human_display_alias(
- &agg_node.human_display,
- name,
- );
- let builder = AggregateExprBuilder::new(
- agg_udf,
- input_phy_expr,
- )
- .schema(Arc::clone(&physical_schema))
- .alias(name)
- .with_ignore_nulls(agg_node.ignore_nulls)
- .with_distinct(agg_node.distinct)
- .order_by(order_bys)
- .human_display(human_display);
- let builder = if let Some(alias) = human_display_alias
- {
- builder.human_display_alias(alias)
- } else {
- builder
- };
- builder.build().map(Arc::new)
- }
- })
- .transpose()?
- .ok_or_else(|| {
- proto_error(
- "Invalid AggregateExpr, missing aggregate_function",
- )
- })
- }
- _ => internal_err!("Invalid aggregate expression for AggregateExec"),
- }
- })
- .collect::, _>>()?;
-
- let physical_schema_ref = Arc::clone(&physical_schema);
- let agg = AggregateExec::try_new(
- agg_mode,
- PhysicalGroupBy::new(group_expr, null_expr, groups, has_grouping_set),
- physical_aggr_expr,
- physical_filter_expr,
- input,
- physical_schema,
- )?;
-
- let agg = if let Some(limit_proto) = &hash_agg.limit {
- let limit = limit_proto.limit as usize;
- let limit_options = match limit_proto.descending {
- Some(descending) => LimitOptions::new_with_order(limit, descending),
- None => LimitOptions::new(limit),
- };
- agg.with_limit_options(Some(limit_options))
- } else {
- agg
+ let node = protobuf::PhysicalPlanNode {
+ physical_plan_type: Some(PhysicalPlanType::Aggregate(Box::new(
+ hash_agg.clone(),
+ ))),
};
-
- let agg = if let Some(dynamic_filter_proto) = &hash_agg.dynamic_filter {
- let dynamic_filter_expr = proto_converter.proto_to_physical_expr(
- dynamic_filter_proto,
- physical_schema_ref.as_ref(),
- ctx,
- )?;
- let df = (dynamic_filter_expr as Arc)
- .downcast::()
- .map_err(|_| {
- internal_datafusion_err!(
- "AggregateExec dynamic_filter did not decode to a DynamicFilterPhysicalExpr"
- )
- })?;
- agg.with_dynamic_filter_expr(df)?
- } else {
- agg
+ let decoder = ConverterPlanDecoder {
+ ctx,
+ proto_converter,
};
-
- Ok(Arc::new(agg))
+ let decode_ctx = ExecutionPlanDecodeCtx::new(&decoder);
+ AggregateExec::try_from_proto(&node, &decode_ctx)
}
#[deprecated(
@@ -2410,108 +2171,22 @@ pub trait PhysicalPlanNodeExt: Sized {
.ok_or_else(|| internal_datafusion_err!("CrossJoinExec is not serializable"))
}
+ #[deprecated(
+ since = "55.0.0",
+ note = "unused by DataFusion; `AggregateExec` serializes itself via `ExecutionPlan::try_to_proto`"
+ )]
fn try_from_aggregate_exec(
exec: &AggregateExec,
codec: &dyn PhysicalExtensionCodec,
proto_converter: &dyn PhysicalProtoConverterExtension,
) -> Result {
- let groups: Vec = exec
- .group_expr()
- .groups()
- .iter()
- .flatten()
- .copied()
- .collect();
-
- let group_names = exec
- .group_expr()
- .expr()
- .iter()
- .map(|expr| expr.1.to_owned())
- .collect();
-
- let filter = exec
- .filter_expr()
- .iter()
- .map(|expr| serialize_maybe_filter(expr.to_owned(), codec, proto_converter))
- .collect::>>()?;
-
- let agg = exec
- .aggr_expr()
- .iter()
- .map(|expr| {
- serialize_physical_aggr_expr(expr.to_owned(), codec, proto_converter)
- })
- .collect::>>()?;
-
- let agg_names = exec
- .aggr_expr()
- .iter()
- .map(|expr| expr.name().to_string())
- .collect::>();
-
- let agg_mode = match exec.mode() {
- AggregateMode::Partial => protobuf::AggregateMode::Partial,
- AggregateMode::Final => protobuf::AggregateMode::Final,
- AggregateMode::FinalPartitioned => protobuf::AggregateMode::FinalPartitioned,
- AggregateMode::Single => protobuf::AggregateMode::Single,
- AggregateMode::SinglePartitioned => {
- protobuf::AggregateMode::SinglePartitioned
- }
- AggregateMode::PartialReduce => protobuf::AggregateMode::PartialReduce,
- };
- let input_schema = exec.input_schema();
- let input = protobuf::PhysicalPlanNode::try_from_physical_plan_with_converter(
- exec.input().to_owned(),
+ let encoder = ConverterPlanEncoder {
codec,
proto_converter,
- )?;
-
- let null_expr = exec
- .group_expr()
- .null_expr()
- .iter()
- .map(|expr| proto_converter.physical_expr_to_proto(&expr.0, codec))
- .collect::>>()?;
-
- let group_expr = exec
- .group_expr()
- .expr()
- .iter()
- .map(|expr| proto_converter.physical_expr_to_proto(&expr.0, codec))
- .collect::>>()?;
-
- let limit = exec.limit_options().map(|config| protobuf::AggLimit {
- limit: config.limit() as u64,
- descending: config.descending(),
- });
-
- Ok(protobuf::PhysicalPlanNode {
- physical_plan_type: Some(PhysicalPlanType::Aggregate(Box::new(
- protobuf::AggregateExecNode {
- group_expr,
- group_expr_name: group_names,
- aggr_expr: agg,
- filter_expr: filter,
- aggr_expr_name: agg_names,
- mode: agg_mode as i32,
- input: Some(Box::new(input)),
- input_schema: Some(input_schema.as_ref().try_into()?),
- null_expr,
- groups,
- limit,
- has_grouping_set: exec.group_expr().has_grouping_set(),
- dynamic_filter: exec
- .dynamic_filter_expr()
- .map(|df| {
- let df_expr: Arc =
- Arc::clone(df) as Arc;
- proto_converter.physical_expr_to_proto(&df_expr, codec)
- })
- .transpose()?,
- },
- ))),
- })
+ };
+ let encode_ctx = ExecutionPlanEncodeCtx::new(&encoder);
+ exec.try_to_proto(&encode_ctx)?
+ .ok_or_else(|| internal_datafusion_err!("AggregateExec is not serializable"))
}
#[deprecated(
From abec3115b47d2c8ec89a20949f5cbcfe3efec8d4 Mon Sep 17 00:00:00 2001
From: Naman Modi
Date: Sat, 25 Jul 2026 16:08:23 +0530
Subject: [PATCH 021/109] refactor(unparser): centralize aggregate-scope
rendering in the SQL unparser (#23789)
## Which issue does this PR close?
- Closes #23668.
## Rationale for this change
- When printing a GROUP BY query, the unparser sometimes wraps the
aggregate's input in an inner subquery (`... FROM (SELECT ...)`).
- Table aliases like `cs` only exist inside that subquery. Any clause
outside it (SELECT, GROUP BY, HAVING, QUALIFY, ORDER BY) must use the
subquery's output columns, not the aliases, so the unparser drops the
alias.
- Each clause does that dropping on its own, so it is easy to miss one.
- ORDER BY on an aggregate that is not in the SELECT list was missed: it
printed `ORDER BY round(sum("cs"."total_revenue"), 2)`, while the SELECT
list in the same query correctly printed `sum("total_revenue")`.
- DataFusion reads that SQL back fine, but stricter databases reject it
because `cs` is out of scope there.
## What changes are included in this PR?
I moved the rule into one helper, `UnparserAggScope`, that checks once
per aggregate whether the input is an inner subquery and then prepares
expressions for each clause. SELECT, GROUP BY, HAVING, QUALIFY, and both
ORDER BY paths now go through it, which fixes the ORDER BY case. The
window-over-aggregate path is left as-is with a comment: it is only
reachable from hand-built plans, since a window in SQL always sits
inside a SELECT that already drops the alias. Only ORDER BY output
changes; every already-correct case stays the same.
## Are these changes tested?
Yes. New tests in `datafusion/core/tests/sql/unparser.rs` cover the
inner-subquery shape for a window sorting by an aggregate, ORDER BY on
an unselected aggregate (the fixed case), and a top-level ORDER BY (the
second sort path). Existing unparser tests and the TPC-H/Clickbench
roundtrips still pass.
## Are there any user-facing changes?
ORDER BY over an aggregate is now printed without an out-of-scope table
qualifier, so the generated SQL is valid for stricter databases. No API
changes.
---
datafusion/core/tests/sql/unparser.rs | 135 +++++++++++++++++++++++
datafusion/sql/src/unparser/plan.rs | 151 ++++++++++++++++----------
2 files changed, 231 insertions(+), 55 deletions(-)
diff --git a/datafusion/core/tests/sql/unparser.rs b/datafusion/core/tests/sql/unparser.rs
index d689fb1496a2b..355a58fd6f45b 100644
--- a/datafusion/core/tests/sql/unparser.rs
+++ b/datafusion/core/tests/sql/unparser.rs
@@ -468,6 +468,59 @@ QUALIFY
rn = 1 AND count(DISTINCT cs.customer_id) > 0
"#;
+// https://github.com/apache/datafusion/issues/23668
+//
+// Extends the #23317 aggregate-scope fix to the window and ORDER BY clauses.
+// Reuses issue_23317_context() (same derived-projection shape).
+
+// Window sorting by an aggregate, over a derived-projection input. Already
+// correct today; this locks the OVER clause against keeping the out-of-scope
+// `cs` qualifier across the refactor.
+const ISSUE_23668_WINDOW_QUERY: &str = r#"
+SELECT
+ date_part('year', c.signup_date) AS signup_year,
+ count(DISTINCT cs.customer_id) AS customers,
+ row_number() OVER (ORDER BY count(DISTINCT cs.customer_id) DESC) AS rn
+FROM
+ "warehouse"."main"."sales" cs
+ JOIN "warehouse"."main"."customers" c USING (customer_id)
+GROUP BY
+ 1
+"#;
+
+// ORDER BY an aggregate that is NOT selected, so it can't use a select alias
+// and is unprojected through the Aggregate. It must be normalized like the
+// SELECT list, not keep the out-of-scope `cs` qualifier.
+const ISSUE_23668_ORDER_BY_QUERY: &str = r#"
+SELECT
+ date_part('year', c.signup_date) AS signup_year,
+ count(DISTINCT cs.customer_id) AS customers
+FROM
+ "warehouse"."main"."sales" cs
+ JOIN "warehouse"."main"."customers" c USING (customer_id)
+GROUP BY
+ 1
+ORDER BY
+ round(sum(cs.total_revenue), 2) DESC
+"#;
+
+// ORDER BY a selected aggregate keeps a top-level Sort (the direct `Sort` arm,
+// vs the projection-absorbed one above). It resolves to the select alias, so
+// this covers routing only -- the normalization in that arm isn't reachable
+// from SQL (an unselected aggregate takes the absorbed path above instead).
+const ISSUE_23668_TOP_LEVEL_SORT_QUERY: &str = r#"
+SELECT
+ date_part('year', c.signup_date) AS signup_year,
+ count(DISTINCT cs.customer_id) AS customers
+FROM
+ "warehouse"."main"."sales" cs
+ JOIN "warehouse"."main"."customers" c USING (customer_id)
+GROUP BY
+ 1
+ORDER BY
+ customers DESC
+"#;
+
fn issue_23317_context() -> Result {
let ctx = SessionContext::new();
@@ -612,6 +665,88 @@ async fn optimized_duckdb_unparse_qualify_unqualifies_agg_input() -> Result<()>
Ok(())
}
+#[tokio::test]
+async fn optimized_duckdb_unparse_window_over_agg_unqualifies_input() -> Result<()> {
+ let ctx = issue_23317_context()?;
+ assert!(ctx.remove_optimizer_rule(SingleDistinctToGroupBy::new().name()));
+
+ let plan = ctx
+ .sql(ISSUE_23668_WINDOW_QUERY)
+ .await?
+ .into_optimized_plan()?;
+ let dialect = DuckDBDialect::new();
+ let unparser = Unparser::new(&dialect);
+ let sql = unparser.plan_to_sql(&plan)?.to_string();
+
+ assert_issue_23317_unparsed_sql_plans(&ctx, &sql).await?;
+
+ assert!(
+ sql.contains(r#"OVER (ORDER BY count(DISTINCT "customer_id")"#),
+ "window ORDER BY aggregate should resolve against the derived projection output: {sql}",
+ );
+ assert!(
+ !sql.contains(r#"count(DISTINCT "cs"."customer_id")"#),
+ "window OVER clause must not reference out-of-scope alias cs: {sql}",
+ );
+
+ Ok(())
+}
+
+#[tokio::test]
+async fn optimized_duckdb_unparse_order_by_unqualifies_agg_input() -> Result<()> {
+ let ctx = issue_23317_context()?;
+ assert!(ctx.remove_optimizer_rule(SingleDistinctToGroupBy::new().name()));
+
+ let plan = ctx
+ .sql(ISSUE_23668_ORDER_BY_QUERY)
+ .await?
+ .into_optimized_plan()?;
+ let dialect = DuckDBDialect::new();
+ let unparser = Unparser::new(&dialect);
+ let sql = unparser.plan_to_sql(&plan)?.to_string();
+
+ assert_issue_23317_unparsed_sql_plans(&ctx, &sql).await?;
+
+ assert!(
+ sql.contains(r#"ORDER BY round(sum("total_revenue"), 2)"#),
+ "ORDER BY aggregate should resolve against the derived projection output: {sql}",
+ );
+ assert!(
+ !sql.contains(r#"sum("cs"."total_revenue")"#),
+ "ORDER BY must not reference out-of-scope alias cs: {sql}",
+ );
+
+ Ok(())
+}
+
+#[tokio::test]
+async fn optimized_duckdb_unparse_top_level_sort_over_agg_uses_select_alias() -> Result<()>
+{
+ let ctx = issue_23317_context()?;
+ assert!(ctx.remove_optimizer_rule(SingleDistinctToGroupBy::new().name()));
+
+ let plan = ctx
+ .sql(ISSUE_23668_TOP_LEVEL_SORT_QUERY)
+ .await?
+ .into_optimized_plan()?;
+ let dialect = DuckDBDialect::new();
+ let unparser = Unparser::new(&dialect);
+ let sql = unparser.plan_to_sql(&plan)?.to_string();
+
+ assert_issue_23317_unparsed_sql_plans(&ctx, &sql).await?;
+
+ assert!(
+ sql.contains(r#"ORDER BY "customers""#),
+ "top-level ORDER BY should resolve to the select alias: {sql}",
+ );
+ assert!(
+ !sql.contains(r#""cs"."customer_id") AS "customers""#),
+ "aggregate output must not reference out-of-scope alias cs: {sql}",
+ );
+
+ Ok(())
+}
+
/// The outcome of running a single roundtrip test.
///
/// A successful test produces [`TestCaseResult::Success`].
diff --git a/datafusion/sql/src/unparser/plan.rs b/datafusion/sql/src/unparser/plan.rs
index 5eef9b82d975e..9fe97a8291b6a 100644
--- a/datafusion/sql/src/unparser/plan.rs
+++ b/datafusion/sql/src/unparser/plan.rs
@@ -101,6 +101,69 @@ pub fn plan_to_sql(plan: &LogicalPlan) -> Result {
unparser.plan_to_sql(plan)
}
+/// Aggregate-expression scope for one rendered SELECT block.
+///
+/// When an aggregate's input is itself emitted as a derived subquery (a
+/// projection sits between the aggregate and its relation), the input columns
+/// are only reachable by that derived table's output names. Base-table
+/// qualifiers like `t.col` name a relation that is out of scope above the
+/// boundary, so emitting them produces SQL a strict engine rejects.
+///
+/// Every clause that renders an aggregate expression (SELECT / GROUP BY /
+/// HAVING / QUALIFY / ORDER BY) has to apply the same rule. Detect the
+/// boundary once here and reuse it, so the clauses can't drift apart (which is
+/// how earlier fixes left some clauses correct and others not).
+struct UnparserAggScope<'a> {
+ agg: &'a Aggregate,
+ /// `agg.input` renders as a derived projection, so out-of-scope qualifiers
+ /// must be stripped from expressions in this scope.
+ input_is_derived_projection: bool,
+}
+
+impl<'a> UnparserAggScope<'a> {
+ fn new(agg: &'a Aggregate) -> Self {
+ Self {
+ agg,
+ input_is_derived_projection: Unparser::contains_projection_before_relation(
+ agg.input.as_ref(),
+ ),
+ }
+ }
+
+ /// Prepare a projected column or predicate that still references the
+ /// aggregate by its output columns: unproject it back onto the aggregate
+ /// (and `windows`) expressions, then normalize it for this scope.
+ fn prepare(&self, expr: Expr, windows: Option<&[&Window]>) -> Result {
+ self.normalize(unproject_agg_exprs(expr, self.agg, windows)?)
+ }
+
+ /// Normalize an expression that is already in aggregate form (group / aggr
+ /// exprs, or an unprojected sort expr): strip the qualifiers that fall out
+ /// of scope once the input is a derived projection. No-op otherwise.
+ fn normalize(&self, expr: Expr) -> Result {
+ if self.input_is_derived_projection {
+ Unparser::strip_column_qualifiers_for_schema(
+ expr,
+ self.agg.input.schema().as_ref(),
+ )
+ } else {
+ Ok(expr)
+ }
+ }
+
+ /// Unproject a sort expression onto this aggregate, then normalize it so
+ /// ORDER BY uses the same scope as the other clauses.
+ fn prepare_sort_expr(
+ &self,
+ sort_expr: SortExpr,
+ input: &LogicalPlan,
+ ) -> Result {
+ let mut sort_expr = unproject_sort_expr(sort_expr, Some(self.agg), input)?;
+ sort_expr.expr = self.normalize(sort_expr.expr)?;
+ Ok(sort_expr)
+ }
+}
+
impl Unparser<'_> {
pub fn plan_to_sql(&self, plan: &LogicalPlan) -> Result {
let mut plan = normalize_union_schema(plan)?;
@@ -312,17 +375,12 @@ impl Unparser<'_> {
match (agg, window) {
(Some(agg), window) => {
let window_option = window.as_deref();
- let agg_input_has_derived_projection =
- Self::contains_projection_before_relation(agg.input.as_ref());
+ let unparser_agg_scope = UnparserAggScope::new(agg);
let items = exprs
.into_iter()
.map(|proj_expr| {
- let unproj = unproject_agg_exprs(proj_expr, agg, window_option)?;
- let unproj = Self::normalize_agg_input_columns(
- unproj,
- agg,
- agg_input_has_derived_projection,
- )?;
+ let unproj =
+ unparser_agg_scope.prepare(proj_expr, window_option)?;
self.select_item_to_sql(&unproj)
})
.collect::>>()?;
@@ -333,12 +391,7 @@ impl Unparser<'_> {
.iter()
.cloned()
.map(|expr| {
- let expr = Self::normalize_agg_input_columns(
- expr,
- agg,
- agg_input_has_derived_projection,
- )?;
- self.expr_to_sql(&expr)
+ self.expr_to_sql(&unparser_agg_scope.normalize(expr)?)
})
.collect::>>()?,
vec![],
@@ -379,18 +432,6 @@ impl Unparser<'_> {
}
}
- fn normalize_agg_input_columns(
- expr: Expr,
- agg: &Aggregate,
- input_has_derived_projection: bool,
- ) -> Result {
- if input_has_derived_projection {
- Self::strip_column_qualifiers_for_schema(expr, agg.input.schema().as_ref())
- } else {
- Ok(expr)
- }
- }
-
fn contains_projection_before_relation(plan: &LogicalPlan) -> bool {
match plan {
LogicalPlan::Projection(_) => true,
@@ -429,6 +470,19 @@ impl Unparser<'_> {
}
}
+ /// Unproject a sort expression; normalize it when the sort is above an
+ /// aggregate, otherwise just unproject (no scope to normalize against).
+ fn unproject_sort_expr_in_scope(
+ sort_expr: SortExpr,
+ agg: Option<&Aggregate>,
+ input: &LogicalPlan,
+ ) -> Result {
+ match agg {
+ Some(agg) => UnparserAggScope::new(agg).prepare_sort_expr(sort_expr, input),
+ None => unproject_sort_expr(sort_expr, None, input),
+ }
+ }
+
fn derive(
&self,
plan: &LogicalPlan,
@@ -592,6 +646,9 @@ impl Unparser<'_> {
window_expr
.iter()
.map(|expr| {
+ // No normalization: this agg branch is only reachable from a
+ // hand-built plan. SQL wraps windows in a projection, which
+ // reconstruct_select_statement handles (and normalizes).
let expr = if let Some(agg) = agg {
unproject_agg_exprs(expr.clone(), agg, None)?
} else {
@@ -977,7 +1034,7 @@ impl Unparser<'_> {
sort.expr
.iter()
.map(|sort_expr| {
- unproject_sort_expr(
+ Self::unproject_sort_expr_in_scope(
sort_expr.clone(),
agg,
sort.input.as_ref(),
@@ -1028,23 +1085,14 @@ impl Unparser<'_> {
let mut unprojected =
unproject_window_exprs(filter.predicate.clone(), window)?;
if let Some(agg) = agg {
- unprojected = unproject_agg_exprs(unprojected, agg, None)?;
- unprojected = Self::normalize_agg_input_columns(
- unprojected,
- agg,
- Self::contains_projection_before_relation(agg.input.as_ref()),
- )?;
+ unprojected =
+ UnparserAggScope::new(agg).prepare(unprojected, None)?;
}
let filter_expr = self.expr_to_sql(&unprojected)?;
select.qualify(Some(filter_expr));
} else if let Some(agg) = agg {
- let unprojected =
- unproject_agg_exprs(filter.predicate.clone(), agg, None)?;
- let unprojected = Self::normalize_agg_input_columns(
- unprojected,
- agg,
- Self::contains_projection_before_relation(agg.input.as_ref()),
- )?;
+ let unprojected = UnparserAggScope::new(agg)
+ .prepare(filter.predicate.clone(), None)?;
let filter_expr = self.expr_to_sql(&unprojected)?;
select.having(Some(filter_expr));
} else {
@@ -1130,7 +1178,11 @@ impl Unparser<'_> {
.expr
.iter()
.map(|sort_expr| {
- unproject_sort_expr(sort_expr.clone(), agg, sort.input.as_ref())
+ Self::unproject_sort_expr_in_scope(
+ sort_expr.clone(),
+ agg,
+ sort.input.as_ref(),
+ )
})
.collect::>>()?;
@@ -1146,8 +1198,7 @@ impl Unparser<'_> {
LogicalPlan::Aggregate(agg) => {
// Aggregation can be already handled in the projection case
if !select.already_projected() {
- let agg_input_has_derived_projection =
- Self::contains_projection_before_relation(agg.input.as_ref());
+ let unparser_agg_scope = UnparserAggScope::new(agg);
// The query returns aggregate and group expressions. If that weren't the case,
// the aggregate would have been placed inside a projection, making the check above^ false
let exprs: Vec<_> = agg
@@ -1156,12 +1207,7 @@ impl Unparser<'_> {
.chain(agg.group_expr.iter())
.cloned()
.map(|expr| {
- let expr = Self::normalize_agg_input_columns(
- expr,
- agg,
- agg_input_has_derived_projection,
- )?;
- self.select_item_to_sql(&expr)
+ self.select_item_to_sql(&unparser_agg_scope.normalize(expr)?)
})
.collect::>>()?;
select.projection(exprs);
@@ -1171,12 +1217,7 @@ impl Unparser<'_> {
.iter()
.cloned()
.map(|expr| {
- let expr = Self::normalize_agg_input_columns(
- expr,
- agg,
- agg_input_has_derived_projection,
- )?;
- self.expr_to_sql(&expr)
+ self.expr_to_sql(&unparser_agg_scope.normalize(expr)?)
})
.collect::>>()?,
vec![],
From f1ab86dad406a189e43ac19d24965b3fbf9dbba9 Mon Sep 17 00:00:00 2001
From: kosiew
Date: Sat, 25 Jul 2026 18:39:02 +0800
Subject: [PATCH 022/109] Add FixedSizeList support for recursive struct schema
adaptation (#22980)
## Which issue does this PR close?
* Part of #20835
## Rationale for this change
`FixedSizeList` containing `Struct` values was not handled by the
existing recursive nested adaptation logic used for schema evolution. As
a result, planner-time compatibility checks, nested cast detection, and
runtime casting did not support additive struct evolution within
`FixedSizeList` containers.
This change adds `FixedSizeList` support and verifies planner/runtime
parity so that planning allows exactly the cases runtime can adapt while
continuing to reject incompatible schema changes.
## What changes are included in this PR?
* Extend `cast_column` to support recursive casting of `FixedSizeList`
values when source and target list sizes match.
* Add `FixedSizeList` handling to:
* `requires_nested_struct_cast`
* `validate_data_type_compatibility`
* Implement recursive casting of nested `Struct` values contained in
`FixedSizeList`.
* Preserve planner/runtime parity by validating child type compatibility
before runtime fallback logic is applied.
* Add handling for null-parent `FixedSizeList` entries by masking hidden
child values before retrying casts, avoiding failures caused by
semantically inaccessible child data.
* Refactor list and list-view casting helpers to use Arrow `AsArray`
accessors.
## Are these changes tested?
Yes.
The following tests were added:
* `test_cast_fixed_size_list_struct`
* `test_validate_fixed_size_list_struct_compatibility`
*
`test_validate_fixed_size_list_struct_missing_non_nullable_field_rejected`
* `test_validate_fixed_size_list_struct_size_mismatch_rejected`
* `test_cast_fixed_size_list_struct_all_null`
*
`test_fixed_size_list_struct_planner_runtime_parity_on_incompatible_type`
*
`test_cast_fixed_size_list_struct_missing_non_nullable_field_runtime_rejected`
*
`test_cast_fixed_size_list_struct_ignores_hidden_child_values_for_null_parent`
Existing coverage in `test_requires_nested_struct_cast` was also
extended to include `FixedSizeList` cases.
These tests cover:
* Additive nullable nested-field evolution
* All-null and partially null list cases
* Incompatible nested type changes
* Non-nullable field addition rejection
* Planner/runtime parity validation
## Are there any user-facing changes?
No user-facing changes. This is an internal enhancement to nested schema
adaptation and casting behavior for `FixedSizeList` types.
## LLM-generated code disclosure
This PR includes LLM-generated code and comments. All LLM-generated
content has been manually reviewed.
---------
Co-authored-by: Andrew Lamb