datafusion_physical_plan/sort_pushdown.rs
1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements. See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership. The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License. You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied. See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18//! Sort pushdown types for physical execution plans.
19//!
20//! This module provides types used for pushing sort ordering requirements
21//! down through the execution plan tree to data sources.
22
23/// Result of attempting to push down sort ordering to a node.
24///
25/// Used by [`ExecutionPlan::try_pushdown_sort`] to communicate
26/// whether and how sort ordering was successfully pushed down.
27///
28/// [`ExecutionPlan::try_pushdown_sort`]: crate::ExecutionPlan::try_pushdown_sort
29#[derive(Debug, Clone)]
30pub enum SortOrderPushdownResult<T> {
31 /// The source can guarantee exact ordering (data is perfectly sorted).
32 ///
33 /// When this is returned, the optimizer can safely remove the Sort operator
34 /// entirely since the data source guarantees the requested ordering.
35 Exact {
36 /// The optimized node that provides exact ordering
37 inner: T,
38 },
39 /// The source has optimized for the ordering but cannot guarantee perfect sorting.
40 ///
41 /// This indicates the data source has been optimized (e.g., reordered files/row groups
42 /// based on statistics, enabled reverse scanning) but the data may not be perfectly
43 /// sorted. The optimizer should keep the Sort operator but benefits from the
44 /// optimization (e.g., faster TopK queries due to early termination).
45 Inexact {
46 /// The optimized node that provides approximate ordering
47 inner: T,
48 },
49 /// The source cannot optimize for this ordering.
50 ///
51 /// The data source does not support the requested sort ordering and no
52 /// optimization was applied.
53 Unsupported,
54}
55
56impl<T> SortOrderPushdownResult<T> {
57 /// Extract the inner value if present
58 pub fn into_inner(self) -> Option<T> {
59 match self {
60 Self::Exact { inner } | Self::Inexact { inner } => Some(inner),
61 Self::Unsupported => None,
62 }
63 }
64
65 /// Map the inner value to a different type while preserving the variant.
66 pub fn map<U, F: FnOnce(T) -> U>(self, f: F) -> SortOrderPushdownResult<U> {
67 match self {
68 Self::Exact { inner } => SortOrderPushdownResult::Exact { inner: f(inner) },
69 Self::Inexact { inner } => {
70 SortOrderPushdownResult::Inexact { inner: f(inner) }
71 }
72 Self::Unsupported => SortOrderPushdownResult::Unsupported,
73 }
74 }
75
76 /// Try to map the inner value, returning an error if the function fails.
77 pub fn try_map<U, E, F: FnOnce(T) -> Result<U, E>>(
78 self,
79 f: F,
80 ) -> Result<SortOrderPushdownResult<U>, E> {
81 match self {
82 Self::Exact { inner } => {
83 Ok(SortOrderPushdownResult::Exact { inner: f(inner)? })
84 }
85 Self::Inexact { inner } => {
86 Ok(SortOrderPushdownResult::Inexact { inner: f(inner)? })
87 }
88 Self::Unsupported => Ok(SortOrderPushdownResult::Unsupported),
89 }
90 }
91
92 /// Convert this result to `Inexact`, downgrading `Exact` if present.
93 ///
94 /// This is useful when an operation (like merging multiple partitions)
95 /// cannot guarantee exact ordering even if the input provides it.
96 ///
97 /// # Examples
98 ///
99 /// ```
100 /// # use datafusion_physical_plan::SortOrderPushdownResult;
101 /// let exact = SortOrderPushdownResult::Exact { inner: 42 };
102 /// let inexact = exact.into_inexact();
103 /// assert!(matches!(inexact, SortOrderPushdownResult::Inexact { inner: 42 }));
104 ///
105 /// let already_inexact = SortOrderPushdownResult::Inexact { inner: 42 };
106 /// let still_inexact = already_inexact.into_inexact();
107 /// assert!(matches!(still_inexact, SortOrderPushdownResult::Inexact { inner: 42 }));
108 ///
109 /// let unsupported = SortOrderPushdownResult::<i32>::Unsupported;
110 /// let still_unsupported = unsupported.into_inexact();
111 /// assert!(matches!(still_unsupported, SortOrderPushdownResult::Unsupported));
112 /// ```
113 pub fn into_inexact(self) -> Self {
114 match self {
115 Self::Exact { inner } => Self::Inexact { inner },
116 Self::Inexact { inner } => Self::Inexact { inner },
117 Self::Unsupported => Self::Unsupported,
118 }
119 }
120}