This is an automated email from the ASF dual-hosted git repository.
github-merge-queue[bot] pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datafusion.git
The following commit(s) were added to refs/heads/main by this push:
new 8332cfafb3 test: add range-partitioned sorted time-bin aggregation
coverage (#24500)
8332cfafb3 is described below
commit 8332cfafb37aa9209eacdbb098afc3e4bcd7f59a
Author: Nga Tran <[email protected]>
AuthorDate: Thu Aug 20 19:21:58 2026 +0000
test: add range-partitioned sorted time-bin aggregation coverage (#24500)
## Which issue does this PR close?
- Related to #23569 (skip hash shuffle for `GROUP BY key,
date_bin(timestamp)` on `Range([timestamp])`).
- Related to #24438.
This PR does not close those issues; it only pins today's plan. A
follow-up will update the expected plan once the shuffle is removed.
## Rationale for this change
Adds a table that matches a common metrics layout:
- columns `key, zone, host, pod, service, timestamp, value`
- range-partitioned on `timestamp` into two 60-minute file groups
- sorted within each file on `(key, timestamp)`
and pins today's physical plan for:
```sql
SELECT key, date_bin(INTERVAL '60 seconds', timestamp) AS time_bin,
sum(value)
FROM metrics_range_sorted
WHERE service = 'a'
GROUP BY key, time_bin
```
The scan already advertises `Range([timestamp])`, `output_ordering=[key,
timestamp]`, and two parallel file groups. Time bins do not straddle the
hour split, so `(key, time_bin)` is partition-disjoint.
**Today DataFusion still inserts a hash `RepartitionExec` between
Partial and Final**, even though `ordering_mode=Sorted` is already
recognized. This PR documents that plan so a follow-up can remove the
shuffle and the expected plan will shrink to a single
`SinglePartitioned` aggregate.
## What changes are included in this PR?
- Register `metrics_range_sorted` for sqllogictests.
- EXPLAIN + result coverage for the filtered and unfiltered queries.
## Are these changes tested?
This PR is tests only.
## Are there any user-facing changes?
No.
## Test plan
- [x] `cargo test --test sqllogictests -- range_sorted_time_bin_agg.slt`
- [ ] CI sqllogictests
---------
Co-authored-by: Cursor <[email protected]>
---
datafusion/sqllogictest/src/test_context.rs | 8 +-
.../src/test_context/range_partitioning.rs | 166 +++++++++++++++++--
.../test_files/range_sorted_time_bin_agg.slt | 183 +++++++++++++++++++++
3 files changed, 345 insertions(+), 12 deletions(-)
diff --git a/datafusion/sqllogictest/src/test_context.rs
b/datafusion/sqllogictest/src/test_context.rs
index 39aa2b09e5..9b97d3f59d 100644
--- a/datafusion/sqllogictest/src/test_context.rs
+++ b/datafusion/sqllogictest/src/test_context.rs
@@ -54,7 +54,9 @@ use datafusion::{
use datafusion_spark::SessionStateBuilderSpark;
use crate::is_spark_path;
-use range_partitioning::register_range_partitioned_table;
+use range_partitioning::{
+ register_range_partitioned_table, register_range_sorted_time_bin_table,
+};
use async_trait::async_trait;
use datafusion::common::cast::as_float64_array;
@@ -179,6 +181,10 @@ impl TestContext {
info!("Registering range partitioned table");
register_range_partitioned_table(test_ctx.session_ctx());
}
+ "range_sorted_time_bin_agg.slt" => {
+ info!("Registering range-sorted time-bin table");
+ register_range_sorted_time_bin_table(test_ctx.session_ctx());
+ }
"metadata.slt" | "arrow_field.slt" => {
info!("Registering metadata table tables");
register_metadata_tables(test_ctx.session_ctx());
diff --git a/datafusion/sqllogictest/src/test_context/range_partitioning.rs
b/datafusion/sqllogictest/src/test_context/range_partitioning.rs
index 3cde3939f0..becde0f328 100644
--- a/datafusion/sqllogictest/src/test_context/range_partitioning.rs
+++ b/datafusion/sqllogictest/src/test_context/range_partitioning.rs
@@ -19,9 +19,11 @@ use std::fs::{File, create_dir_all, remove_dir_all};
use std::path::Path;
use std::sync::Arc;
-use arrow::array::{ArrayRef, Int32Array};
+use arrow::array::{
+ ArrayRef, Int32Array, Int64Array, StringArray, TimestampNanosecondArray,
+};
use arrow::compute::SortOptions;
-use arrow::datatypes::{DataType, Field, Schema, SchemaRef};
+use arrow::datatypes::{DataType, Field, Schema, SchemaRef, TimeUnit};
use arrow::record_batch::RecordBatch;
use datafusion::catalog::streaming::StreamingTable;
use datafusion::common::{ScalarValue, SplitPoint};
@@ -29,7 +31,7 @@ use
datafusion::datasource::file_format::parquet::ParquetFormat;
use datafusion::datasource::listing::{
ListingOptions, ListingTable, ListingTableConfig, ListingTableUrl,
};
-use datafusion::logical_expr::{Partitioning, RangePartitioning, col};
+use datafusion::logical_expr::{Partitioning, RangePartitioning, SortExpr, col};
use datafusion::parquet::arrow::ArrowWriter;
use datafusion::physical_expr::{
Partitioning as PhysicalPartitioning, PhysicalSortExpr,
@@ -95,8 +97,9 @@ pub(super) fn register_range_partitioned_table(ctx:
&SessionContext) {
"range_partitioned",
&range_table_dir,
Arc::clone(&schema),
- RANGE_PARTITIONS,
+ range_batches(&schema, RANGE_PARTITIONS),
output_partitioning,
+ None,
);
register_unbounded_range_stream_table(
@@ -132,8 +135,9 @@ pub(super) fn register_range_partitioned_table(ctx:
&SessionContext) {
Path::new(env!("CARGO_MANIFEST_DIR"))
.join("test_files/scratch_range_partitioning/range_partitioned_shifted"),
Arc::clone(&schema),
- SHIFTED_RANGE_PARTITIONS,
+ range_batches(&schema, SHIFTED_RANGE_PARTITIONS),
shifted_output_partitioning,
+ None,
);
// Same rows as `range_partitioned` but split into only three range
@@ -156,8 +160,9 @@ pub(super) fn register_range_partitioned_table(ctx:
&SessionContext) {
Path::new(env!("CARGO_MANIFEST_DIR"))
.join("test_files/scratch_range_partitioning/range_partitioned_narrow"),
Arc::clone(&schema),
- NARROW_RANGE_PARTITIONS,
+ range_batches(&schema, NARROW_RANGE_PARTITIONS),
narrow_output_partitioning,
+ None,
);
let sparse_output_partitioning = Partitioning::Range(
@@ -178,8 +183,9 @@ pub(super) fn register_range_partitioned_table(ctx:
&SessionContext) {
Path::new(env!("CARGO_MANIFEST_DIR"))
.join("test_files/scratch_range_partitioning/range_partitioned_sparse"),
Arc::clone(&schema),
- SPARSE_RANGE_PARTITIONS,
+ range_batches(&schema, SPARSE_RANGE_PARTITIONS),
sparse_output_partitioning,
+ None,
);
}
@@ -188,16 +194,16 @@ fn register_parquet_listing_table(
name: &str,
table_dir: impl AsRef<Path>,
schema: SchemaRef,
- partitions: impl IntoIterator<Item = &'static [(i32, i32, i32)]>,
+ batches: Vec<RecordBatch>,
output_partitioning: Partitioning,
+ file_sort_order: Option<Vec<Vec<SortExpr>>>,
) {
let table_dir = table_dir.as_ref();
if table_dir.exists() {
remove_dir_all(table_dir).expect("test table dir should be removable");
}
create_dir_all(table_dir).expect("test table dir should be created");
- for (idx, rows) in partitions.into_iter().enumerate() {
- let batch = range_batch(Arc::clone(&schema), rows);
+ for (idx, batch) in batches.into_iter().enumerate() {
let file = File::create(table_dir.join(format!("part-{idx}.parquet")))
.expect("test table parquet partition should be created");
let mut writer = ArrowWriter::try_new(file, Arc::clone(&schema), None)
@@ -218,8 +224,11 @@ fn register_parquet_listing_table(
);
let table_url =
ListingTableUrl::parse(&table_path).expect("test table url should
parse");
- let options = ListingOptions::new(Arc::new(ParquetFormat::default()))
+ let mut options = ListingOptions::new(Arc::new(ParquetFormat::default()))
.with_output_partitioning(Some(output_partitioning));
+ if let Some(file_sort_order) = file_sort_order {
+ options = options.with_file_sort_order(file_sort_order);
+ }
let config = ListingTableConfig::new(table_url)
.with_listing_options(options)
.with_schema(schema);
@@ -278,6 +287,16 @@ fn range_stream_partition(
)]))
}
+fn range_batches(
+ schema: &SchemaRef,
+ partitions: impl IntoIterator<Item = &'static [(i32, i32, i32)]>,
+) -> Vec<RecordBatch> {
+ partitions
+ .into_iter()
+ .map(|rows| range_batch(Arc::clone(schema), rows))
+ .collect()
+}
+
fn range_batch(schema: SchemaRef, rows: &[(i32, i32, i32)]) -> RecordBatch {
RecordBatch::try_new(
schema,
@@ -292,3 +311,128 @@ fn range_batch(schema: SchemaRef, rows: &[(i32, i32,
i32)]) -> RecordBatch {
)
.expect("range batch should be valid")
}
+
+//
==============================================================================
+// Time-bin table: range-partitioned on timestamp, sorted on (key, timestamp)
+//
==============================================================================
+
+/// Unix nanoseconds for `2024-01-01 00:00:00 UTC`.
+const TIME_BIN_EPOCH_NS: i64 = 1_704_067_200_000_000_000;
+const NANOS_PER_SECOND: i64 = 1_000_000_000;
+const NANOS_PER_MINUTE: i64 = 60 * NANOS_PER_SECOND;
+
+/// Timestamp helper: minutes and seconds after `2024-01-01 00:00:00 UTC`.
+fn time_bin_ts(minutes: i64, seconds: i64) -> i64 {
+ TIME_BIN_EPOCH_NS + minutes * NANOS_PER_MINUTE + seconds * NANOS_PER_SECOND
+}
+
+/// Row: (key, col1, col2, col3, col4, timestamp_ns, value)
+type TimeBinRow = (
+ &'static str,
+ &'static str,
+ &'static str,
+ &'static str,
+ &'static str,
+ i64,
+ i64,
+);
+
+/// Registers `range_sorted_time_bin` for time-bin aggregation plan tests.
+///
+/// Two file groups, each covering a 60-minute timestamp range:
+/// - partition 0: `[2024-01-01 00:00, 01:00)`
+/// - partition 1: `[2024-01-01 01:00, 02:00)`
+///
+/// Files are range-partitioned on `timestamp` and sorted on `(key,
timestamp)`.
+/// Because `date_bin(60 seconds, timestamp)` does not straddle the hour split,
+/// grouping by `(key, time_bin)` is partition-disjoint. Today's planner still
+/// inserts a hash shuffle; the test pins that plan so a follow-up can remove
it.
+pub(super) fn register_range_sorted_time_bin_table(ctx: &SessionContext) {
+ let schema = Arc::new(Schema::new(vec![
+ Field::new("key", DataType::Utf8, false),
+ Field::new("col1", DataType::Utf8, false),
+ Field::new("col2", DataType::Utf8, false),
+ Field::new("col3", DataType::Utf8, false),
+ Field::new("col4", DataType::Utf8, false),
+ Field::new(
+ "timestamp",
+ DataType::Timestamp(TimeUnit::Nanosecond, None),
+ false,
+ ),
+ Field::new("value", DataType::Int64, false),
+ ]));
+
+ // Each partition covers 60 minutes. The split is aligned to the 60-second
+ // `date_bin` used by the test query, so time bins do not straddle files.
+ let hour_split = time_bin_ts(60, 0);
+ let output_partitioning = Partitioning::Range(
+ RangePartitioning::try_new(
+ vec![col("timestamp").sort(true, true)],
+ vec![SplitPoint::new(vec![ScalarValue::TimestampNanosecond(
+ Some(hour_split),
+ None,
+ )])],
+ )
+ .expect("time-bin range partitioning should be valid"),
+ );
+
+ // Within each 60-minute file, rows are sorted by (key, timestamp).
+ let partitions = [
+ vec![
+ ("k1", "x", "y", "z", "a", time_bin_ts(0, 10), 1),
+ ("k1", "x", "y", "z", "a", time_bin_ts(0, 40), 2),
+ ("k1", "x", "y", "z", "b", time_bin_ts(1, 10), 99),
+ ("k2", "x", "y", "z", "a", time_bin_ts(30, 0), 3),
+ ("k2", "x", "y", "z", "a", time_bin_ts(30, 30), 4),
+ ],
+ vec![
+ ("k1", "x", "y", "z", "a", time_bin_ts(60, 10), 10),
+ ("k1", "x", "y", "z", "a", time_bin_ts(60, 40), 20),
+ ("k2", "x", "y", "z", "a", time_bin_ts(90, 0), 30),
+ ("k2", "x", "y", "z", "a", time_bin_ts(105, 0), 5),
+ ],
+ ];
+
+ let table_dir = Path::new(env!("CARGO_MANIFEST_DIR"))
+ .join("test_files/scratch_range_partitioning/range_sorted_time_bin");
+ let batches = partitions
+ .iter()
+ .map(|rows| time_bin_batch(Arc::clone(&schema), rows))
+ .collect();
+ register_parquet_listing_table(
+ ctx,
+ "range_sorted_time_bin",
+ &table_dir,
+ Arc::clone(&schema),
+ batches,
+ output_partitioning,
+ Some(vec![vec![
+ col("key").sort(true, true),
+ col("timestamp").sort(true, true),
+ ]]),
+ );
+}
+
+fn time_bin_batch(schema: SchemaRef, rows: &[TimeBinRow]) -> RecordBatch {
+ RecordBatch::try_new(
+ schema,
+ vec![
+ Arc::new(StringArray::from_iter_values(rows.iter().map(|row|
row.0)))
+ as ArrayRef,
+ Arc::new(StringArray::from_iter_values(rows.iter().map(|row|
row.1)))
+ as ArrayRef,
+ Arc::new(StringArray::from_iter_values(rows.iter().map(|row|
row.2)))
+ as ArrayRef,
+ Arc::new(StringArray::from_iter_values(rows.iter().map(|row|
row.3)))
+ as ArrayRef,
+ Arc::new(StringArray::from_iter_values(rows.iter().map(|row|
row.4)))
+ as ArrayRef,
+ Arc::new(TimestampNanosecondArray::from_iter_values(
+ rows.iter().map(|row| row.5),
+ )) as ArrayRef,
+ Arc::new(Int64Array::from_iter_values(rows.iter().map(|row|
row.6)))
+ as ArrayRef,
+ ],
+ )
+ .expect("time-bin batch should be valid")
+}
diff --git a/datafusion/sqllogictest/test_files/range_sorted_time_bin_agg.slt
b/datafusion/sqllogictest/test_files/range_sorted_time_bin_agg.slt
new file mode 100644
index 0000000000..18123a492d
--- /dev/null
+++ b/datafusion/sqllogictest/test_files/range_sorted_time_bin_agg.slt
@@ -0,0 +1,183 @@
+# 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.
+
+# GROUP BY on a table that is:
+# - range partitioned on timestamp into two 60-minute file groups
+# - sorted within each file on (key, timestamp)
+#
+# Query:
+# SELECT key, date_bin(INTERVAL '60 seconds', timestamp) AS time_bin,
sum(value)
+# FROM range_sorted_time_bin
+# WHERE col4 = 'a'
+# GROUP BY key, time_bin
+#
+# Scan metadata already advertises:
+# 1. Range([timestamp]) and output_ordering=[key, timestamp]
+# 2. Two file_groups, so the two 60-minute streams run in parallel
+#
+# Improvement opportunity:
+# date_bin(60s) is monotonic in timestamp and the hour split is aligned to bin
+# boundaries, so (key, time_bin) is partition-disjoint. Aggregation could be a
+# single streaming SinglePartitioned step with no hash shuffle.
+#
+# Today's plan still hash-repartitions:
+# Partial AggregateExec (ordering_mode=Sorted)
+# -> RepartitionExec Hash([key, date_bin(...)])
+# -> FinalPartitioned AggregateExec (ordering_mode=Sorted)
+
+statement ok
+set datafusion.explain.physical_plan_only = true;
+
+statement ok
+set datafusion.execution.collect_statistics = false;
+
+statement ok
+set datafusion.execution.target_partitions = 2;
+
+statement ok
+set datafusion.optimizer.subset_repartition_threshold = 2;
+
+statement ok
+set datafusion.optimizer.preserve_file_partitions = 1;
+
+statement ok
+set datafusion.optimizer.enable_round_robin_repartition = false;
+
+statement ok
+set datafusion.optimizer.enable_join_dynamic_filter_pushdown = false;
+
+statement ok
+set datafusion.optimizer.enable_topk_dynamic_filter_pushdown = false;
+
+statement ok
+set datafusion.optimizer.enable_aggregate_dynamic_filter_pushdown = false;
+
+statement ok
+set datafusion.execution.parquet.pushdown_filters = false;
+
+##########
+# TEST 1: Scan metadata — range partitioned on timestamp, sorted on (key,
timestamp),
+# two parallel file groups covering 60-minute intervals.
+##########
+
+query TT
+EXPLAIN SELECT key, timestamp, value FROM range_sorted_time_bin;
+----
+physical_plan DataSourceExec: file_groups={2 groups:
[[WORKSPACE_ROOT/datafusion/sqllogictest/test_files/scratch_range_partitioning/range_sorted_time_bin/part-0.parquet],
[WORKSPACE_ROOT/datafusion/sqllogictest/test_files/scratch_range_partitioning/range_sorted_time_bin/part-1.parquet]]},
projection=[key, timestamp, value], output_ordering=[key@0 ASC, timestamp@1
ASC], output_partitioning=Range([timestamp@1 ASC], [(1704070800000000000)], 2),
file_type=parquet
+
+##########
+# TEST 2: Filtered time-bin aggregation.
+# GROUP BY keys are (key, date_bin(timestamp)). Input is sorted on those keys
+# (date_bin is monotonic in timestamp) and range-partitioned so bins do not
+# overlap across the two 60-minute streams.
+#
+# Today this is still Partial + hash RepartitionExec + Final, even though
+# ordering_mode=Sorted is already recognized.
+##########
+
+query TT
+EXPLAIN SELECT key, date_bin(INTERVAL '60 seconds', timestamp) AS time_bin,
sum(value)
+FROM range_sorted_time_bin
+WHERE col4 = 'a'
+GROUP BY key, time_bin;
+----
+physical_plan
+01)ProjectionExec: expr=[key@0 as key,
date_bin(IntervalMonthDayNano("IntervalMonthDayNano { months: 0, days: 0,
nanoseconds: 60000000000 }"),range_sorted_time_bin.timestamp)@1 as time_bin,
sum(range_sorted_time_bin.value)@2 as sum(range_sorted_time_bin.value)]
+02)--AggregateExec: mode=FinalPartitioned, gby=[key@0 as key,
date_bin(IntervalMonthDayNano("IntervalMonthDayNano { months: 0, days: 0,
nanoseconds: 60000000000 }"),range_sorted_time_bin.timestamp)@1 as
date_bin(IntervalMonthDayNano("IntervalMonthDayNano { months: 0, days: 0,
nanoseconds: 60000000000 }"),range_sorted_time_bin.timestamp)],
aggr=[sum(range_sorted_time_bin.value)], ordering_mode=Sorted
+03)----RepartitionExec: partitioning=Hash([key@0,
date_bin(IntervalMonthDayNano("IntervalMonthDayNano { months: 0, days: 0,
nanoseconds: 60000000000 }"),range_sorted_time_bin.timestamp)@1], 2),
input_partitions=2, preserve_order=true, sort_exprs=key@0 ASC,
date_bin(IntervalMonthDayNano("IntervalMonthDayNano { months: 0, days: 0,
nanoseconds: 60000000000 }"),range_sorted_time_bin.timestamp)@1 ASC
+04)------AggregateExec: mode=Partial, gby=[key@0 as key,
date_bin(IntervalMonthDayNano { months: 0, days: 0, nanoseconds: 60000000000 },
timestamp@1) as date_bin(IntervalMonthDayNano("IntervalMonthDayNano { months:
0, days: 0, nanoseconds: 60000000000 }"),range_sorted_time_bin.timestamp)],
aggr=[sum(range_sorted_time_bin.value)], ordering_mode=Sorted
+05)--------FilterExec: col4@1 = a, projection=[key@0, timestamp@2, value@3]
+06)----------DataSourceExec: file_groups={2 groups:
[[WORKSPACE_ROOT/datafusion/sqllogictest/test_files/scratch_range_partitioning/range_sorted_time_bin/part-0.parquet],
[WORKSPACE_ROOT/datafusion/sqllogictest/test_files/scratch_range_partitioning/range_sorted_time_bin/part-1.parquet]]},
projection=[key, col4, timestamp, value], output_ordering=[key@0 ASC,
timestamp@2 ASC], output_partitioning=Range([timestamp@2 ASC],
[(1704070800000000000)], 2), file_type=parquet, predicate=col4@4 = a, [...]
+
+query TPI
+SELECT key, date_bin(INTERVAL '60 seconds', timestamp) AS time_bin, sum(value)
+FROM range_sorted_time_bin
+WHERE col4 = 'a'
+GROUP BY key, time_bin
+ORDER BY key, time_bin;
+----
+k1 2024-01-01T00:00:00 3
+k1 2024-01-01T01:00:00 30
+k2 2024-01-01T00:30:00 7
+k2 2024-01-01T01:30:00 30
+k2 2024-01-01T01:45:00 5
+
+##########
+# TEST 3: Same aggregation without the col4 filter. The scan still has two
+# 60-minute file groups, and today's plan still hash-repartitions.
+##########
+
+query TT
+EXPLAIN SELECT key, date_bin(INTERVAL '60 seconds', timestamp) AS time_bin,
sum(value)
+FROM range_sorted_time_bin
+GROUP BY key, time_bin;
+----
+physical_plan
+01)ProjectionExec: expr=[key@0 as key,
date_bin(IntervalMonthDayNano("IntervalMonthDayNano { months: 0, days: 0,
nanoseconds: 60000000000 }"),range_sorted_time_bin.timestamp)@1 as time_bin,
sum(range_sorted_time_bin.value)@2 as sum(range_sorted_time_bin.value)]
+02)--AggregateExec: mode=FinalPartitioned, gby=[key@0 as key,
date_bin(IntervalMonthDayNano("IntervalMonthDayNano { months: 0, days: 0,
nanoseconds: 60000000000 }"),range_sorted_time_bin.timestamp)@1 as
date_bin(IntervalMonthDayNano("IntervalMonthDayNano { months: 0, days: 0,
nanoseconds: 60000000000 }"),range_sorted_time_bin.timestamp)],
aggr=[sum(range_sorted_time_bin.value)], ordering_mode=Sorted
+03)----RepartitionExec: partitioning=Hash([key@0,
date_bin(IntervalMonthDayNano("IntervalMonthDayNano { months: 0, days: 0,
nanoseconds: 60000000000 }"),range_sorted_time_bin.timestamp)@1], 2),
input_partitions=2, preserve_order=true, sort_exprs=key@0 ASC,
date_bin(IntervalMonthDayNano("IntervalMonthDayNano { months: 0, days: 0,
nanoseconds: 60000000000 }"),range_sorted_time_bin.timestamp)@1 ASC
+04)------AggregateExec: mode=Partial, gby=[key@0 as key,
date_bin(IntervalMonthDayNano { months: 0, days: 0, nanoseconds: 60000000000 },
timestamp@1) as date_bin(IntervalMonthDayNano("IntervalMonthDayNano { months:
0, days: 0, nanoseconds: 60000000000 }"),range_sorted_time_bin.timestamp)],
aggr=[sum(range_sorted_time_bin.value)], ordering_mode=Sorted
+05)--------DataSourceExec: file_groups={2 groups:
[[WORKSPACE_ROOT/datafusion/sqllogictest/test_files/scratch_range_partitioning/range_sorted_time_bin/part-0.parquet],
[WORKSPACE_ROOT/datafusion/sqllogictest/test_files/scratch_range_partitioning/range_sorted_time_bin/part-1.parquet]]},
projection=[key, timestamp, value], output_ordering=[key@0 ASC, timestamp@1
ASC], output_partitioning=Range([timestamp@1 ASC], [(1704070800000000000)], 2),
file_type=parquet
+
+query TPI
+SELECT key, date_bin(INTERVAL '60 seconds', timestamp) AS time_bin, sum(value)
+FROM range_sorted_time_bin
+GROUP BY key, time_bin
+ORDER BY key, time_bin;
+----
+k1 2024-01-01T00:00:00 3
+k1 2024-01-01T00:01:00 99
+k1 2024-01-01T01:00:00 30
+k2 2024-01-01T00:30:00 7
+k2 2024-01-01T01:30:00 30
+k2 2024-01-01T01:45:00 5
+
+##########
+# CLEANUP
+##########
+
+# The SLT runner sets `target_partitions` to 4 instead of using the default, so
+# reset it explicitly.
+statement ok
+set datafusion.execution.target_partitions = 4;
+
+statement ok
+reset datafusion.explain.physical_plan_only;
+
+statement ok
+reset datafusion.execution.collect_statistics;
+
+statement ok
+reset datafusion.optimizer.subset_repartition_threshold;
+
+statement ok
+reset datafusion.optimizer.preserve_file_partitions;
+
+statement ok
+reset datafusion.optimizer.enable_round_robin_repartition;
+
+statement ok
+reset datafusion.optimizer.enable_join_dynamic_filter_pushdown;
+
+statement ok
+reset datafusion.optimizer.enable_topk_dynamic_filter_pushdown;
+
+statement ok
+reset datafusion.optimizer.enable_aggregate_dynamic_filter_pushdown;
+
+statement ok
+reset datafusion.execution.parquet.pushdown_filters;
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]