You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@auron.apache.org by sl...@apache.org on 2026/09/05 07:48:32 UTC

(auron) branch master updated: [AURON #2497] Fix SortExec projected schema and sort key mapping (#2498)

This is an automated email from the ASF dual-hosted git repository.

slfan1989 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/auron.git


The following commit(s) were added to refs/heads/master by this push:
     new 04eeb90d2 [AURON #2497] Fix SortExec projected schema and sort key mapping (#2498)
04eeb90d2 is described below

commit 04eeb90d2911465eb350253eacc281a16bb5f37f
Author: linfeng <33...@users.noreply.github.com>
AuthorDate: Sat Sep 5 15:48:29 2026 +0800

    [AURON #2497] Fix SortExec projected schema and sort key mapping (#2498)
    
    # Which issue does this PR close?
    
    Closes #2497
    
    # Rationale for this change
    
    `SortExec` used the full input schema during projected execution and
    consumed original sort-key indices against the projected schema. Subset
    or reordered projections could therefore produce an incorrect schema or
    incorrectly mapped columns.
    
    # What changes are included in this PR?
    
    - Use the projected output schema in both projected execution paths.
    - Remap sort-key indices against every matching position in the
    projection.
    - Add regression tests for reordered SortMergeJoin projections and
    strict single-column SortExec projections.
    
    # Are there any user-facing changes?
    
    No API changes. Affected projected sort and sort-merge join plans now
    return columns with the correct schema
    
    # How was this patch tested?
    
    Passed unit tests
    
    # Was this patch authored or co-authored using generative AI tooling?
    - [x] Yes
    - [ ] No
    
    If yes, include: `Generated-by: gpt5`
    
    ASF guidance: https://www.apache.org/legal/generative-tooling.html
    
    Co-authored-by: Shilun Fan <sl...@apache.org>
    Signed-off-by: Shilun Fan <sl...@apache.org>
---
 .../datafusion-ext-plans/src/joins/test.rs         | 68 +++++++++++++++++++++-
 .../datafusion-ext-plans/src/sort_exec.rs          | 53 ++++++++++++++++-
 2 files changed, 117 insertions(+), 4 deletions(-)

diff --git a/native-engine/datafusion-ext-plans/src/joins/test.rs b/native-engine/datafusion-ext-plans/src/joins/test.rs
index 70d0d2fca..9b7e56db1 100644
--- a/native-engine/datafusion-ext-plans/src/joins/test.rs
+++ b/native-engine/datafusion-ext-plans/src/joins/test.rs
@@ -30,7 +30,10 @@ mod tests {
         assert_batches_sorted_eq,
         common::{JoinSide, Result},
         logical_expr::Operator,
-        physical_expr::expressions::{BinaryExpr, Column},
+        physical_expr::{
+            PhysicalSortExpr,
+            expressions::{BinaryExpr, Column},
+        },
         physical_plan::{ExecutionPlan, common, joins::utils::*, test::TestMemoryExec},
         prelude::{SessionConfig, SessionContext},
     };
@@ -38,10 +41,12 @@ mod tests {
     use crate::{
         broadcast_join_build_hash_map_exec::BroadcastJoinBuildHashMapExec,
         broadcast_join_exec::BroadcastJoinExec,
+        common::column_pruning::ExecuteWithColumnPruning,
         joins::{
             ColumnIndex, JoinFilter,
             join_utils::{JoinType, JoinType::*},
         },
+        sort_exec::SortExec,
         sort_merge_join_exec::SortMergeJoinExec,
     };
 
@@ -483,6 +488,67 @@ mod tests {
         Ok(())
     }
 
+    #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
+    async fn sort_merge_join_projects_non_key_columns_from_sorted_inputs() -> Result<()> {
+        MemManager::init(1000000);
+        let session_ctx = SessionContext::new();
+        let task_ctx = session_ctx.task_ctx();
+        let left_input = build_table(
+            ("left_key", &vec![2, 1]),
+            ("left_unused", &vec![200, 100]),
+            ("left_value", &vec![20, 10]),
+        )?;
+        let right_input = build_table(
+            ("right_key", &vec![1, 2]),
+            ("right_unused", &vec![1000, 2000]),
+            ("right_value", &vec![100, 200]),
+        )?;
+        let left_key = Arc::new(Column::new("left_key", 0));
+        let right_key = Arc::new(Column::new("right_key", 0));
+        let left: Arc<dyn ExecutionPlan> = Arc::new(SortExec::new(
+            left_input,
+            vec![PhysicalSortExpr {
+                expr: left_key.clone(),
+                options: SortOptions::default(),
+            }],
+            None,
+            0,
+        ));
+        let right: Arc<dyn ExecutionPlan> = Arc::new(SortExec::new(
+            right_input,
+            vec![PhysicalSortExpr {
+                expr: right_key.clone(),
+                options: SortOptions::default(),
+            }],
+            None,
+            0,
+        ));
+        let schema = build_join_schema_for_test(&left.schema(), &right.schema(), Inner)?;
+        let join = SortMergeJoinExec::try_new(
+            schema,
+            left,
+            right,
+            vec![(left_key, right_key)],
+            Inner,
+            None,
+            vec![SortOptions::default()],
+        )?;
+
+        let stream = join.execute_projected(0, task_ctx, &[2, 1, 0, 5, 4, 3])?;
+        let batches = common::collect(stream).await?;
+        let expected = vec![
+            "+------------+-------------+----------+-------------+--------------+-----------+",
+            "| left_value | left_unused | left_key | right_value | right_unused | right_key |",
+            "+------------+-------------+----------+-------------+--------------+-----------+",
+            "| 10         | 100         | 1        | 100         | 1000         | 1         |",
+            "| 20         | 200         | 2        | 200         | 2000         | 2         |",
+            "+------------+-------------+----------+-------------+--------------+-----------+",
+        ];
+        assert_batches_sorted_eq!(expected, &batches);
+
+        Ok(())
+    }
+
     #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
     async fn join_inner_two() -> Result<()> {
         for test_type in ALL_TEST_TYPE {
diff --git a/native-engine/datafusion-ext-plans/src/sort_exec.rs b/native-engine/datafusion-ext-plans/src/sort_exec.rs
index 48a8b9237..6b7bb2b80 100644
--- a/native-engine/datafusion-ext-plans/src/sort_exec.rs
+++ b/native-engine/datafusion-ext-plans/src/sort_exec.rs
@@ -261,8 +261,9 @@ impl SortExec {
         context: Arc<TaskContext>,
         projection: &[usize],
     ) -> Result<SendableRecordBatchStream> {
+        let output_schema = Arc::new(self.schema().project(projection)?);
         let exec_ctx =
-            ExecutionContext::new(context.clone(), partition, self.schema(), &self.metrics);
+            ExecutionContext::new(context.clone(), partition, output_schema, &self.metrics);
         let sorter = self.init_sorter(exec_ctx.clone(), projection)?;
 
         let elapsed_compute = exec_ctx.baseline_metrics().elapsed_compute().clone();
@@ -307,8 +308,9 @@ impl SortExec {
         context: Arc<TaskContext>,
         projection: &[usize],
     ) -> Result<SendableRecordBatchWithKeyRowsStream> {
+        let output_schema = Arc::new(self.schema().project(projection)?);
         let exec_ctx =
-            ExecutionContext::new(context.clone(), partition, self.schema(), &self.metrics);
+            ExecutionContext::new(context.clone(), partition, output_schema, &self.metrics);
         let sorter = self.init_sorter(exec_ctx.clone(), projection)?;
 
         let elapsed_compute = exec_ctx.baseline_metrics().elapsed_compute().clone();
@@ -1212,7 +1214,12 @@ impl PruneSortKeysFromBatch {
         let mut relation = vec![];
         for (expr_idx, expr) in exprs.iter().enumerate() {
             if let Some(col) = expr.expr.as_any().downcast_ref::<Column>() {
-                relation.push((expr_idx, col.index()));
+                // Relation indices are consumed against the projected schema below.
+                relation.extend(input_projection.iter().enumerate().filter_map(
+                    |(projected_idx, &input_idx)| {
+                        (input_idx == col.index()).then_some((expr_idx, projected_idx))
+                    },
+                ));
             }
         }
 
@@ -1574,6 +1581,7 @@ mod test {
         physical_plan::{ExecutionPlan, common, test::TestMemoryExec},
         prelude::SessionContext,
     };
+    use futures::TryStreamExt;
 
     use super::common_prefix_len;
     use crate::sort_exec::SortExec;
@@ -1674,6 +1682,45 @@ mod test {
         Ok(())
     }
 
+    #[tokio::test]
+    async fn test_sort_projected_with_key_rows_excludes_sort_key() -> Result<()> {
+        MemManager::init(100);
+        let task_ctx = SessionContext::new().task_ctx();
+        let input = build_table(
+            ("key", &vec![3, 1, 2]),
+            ("value", &vec![30, 10, 20]),
+            ("unused", &vec![300, 100, 200]),
+        )?;
+        let sort = SortExec::new(
+            input,
+            vec![PhysicalSortExpr {
+                expr: Arc::new(Column::new("key", 0)),
+                options: SortOptions::default(),
+            }],
+            None,
+            0,
+        );
+
+        let output = sort.execute_projected_with_key_rows(0, task_ctx, &[1])?;
+        assert_eq!(output.schema().fields().len(), 1);
+        assert_eq!(output.schema().field(0).name(), "value");
+        let batches = output
+            .map_ok(|batch| batch.batch)
+            .try_collect::<Vec<_>>()
+            .await?;
+        let expected = vec![
+            "+-------+",
+            "| value |",
+            "+-------+",
+            "| 10    |",
+            "| 20    |",
+            "| 30    |",
+            "+-------+",
+        ];
+        assert_batches_eq!(expected, &batches);
+        Ok(())
+    }
+
     #[tokio::test]
     async fn test_top_k_with_only_sort_column() -> Result<()> {
         MemManager::init(100);