Try to optimize the scan to produce data in the requested sort order. Inputs: 1. The query's required ordering (`order` parameter) 2. The source's equivalence properties (`eq_properties`) # Returns - `Exact`: the source's natural ordering already satisfies the request. The surrounding `SortExec` can be eliminated provided files within each group are non-overlapping (verified by `FileScanConfig`)
(
&self,
order: &[PhysicalSortExpr],
eq_properties: &EquivalenceProperties,
)
| 828 | /// the request (function-wrapped case), this is always `true` |
| 829 | /// because we're flipping the file's natural order. |
| 830 | fn try_pushdown_sort( |
| 831 | &self, |
| 832 | order: &[PhysicalSortExpr], |
| 833 | eq_properties: &EquivalenceProperties, |
| 834 | ) -> datafusion_common::Result<SortOrderPushdownResult<Arc<dyn FileSource>>> { |
| 835 | if order.is_empty() { |
| 836 | return Ok(SortOrderPushdownResult::Unsupported); |
| 837 | } |
| 838 | |
| 839 | // Check if the natural (non-reversed) ordering already satisfies the request. |
| 840 | // Parquet metadata guarantees within-file ordering, so if the ordering matches |
| 841 | // we can return Exact. FileScanConfig will verify that files within each group |
| 842 | // are non-overlapping before declaring the entire scan as Exact. |
| 843 | if eq_properties.ordering_satisfy(order.iter().cloned())? { |
| 844 | return Ok(SortOrderPushdownResult::Exact { |
| 845 | inner: Arc::new(self.clone()) as Arc<dyn FileSource>, |
| 846 | }); |
| 847 | } |
| 848 | |
| 849 | // If the source's declared ordering is a non-empty *proper* prefix |
| 850 | // of the request (e.g. source `[a DESC, b ASC]`, request |
| 851 | // `[a DESC, b ASC, c DESC]`), decline pushdown so the outer |
| 852 | // `SortExec`'s `sort_prefix` optimisation — prefix-aware early |
| 853 | // termination in `TopK` — can still fire. Firing the Inexact |
| 854 | // pipeline below would invalidate the source's `output_ordering` |
| 855 | // (the runtime row-group reorder is approximate, so we can't |
| 856 | // honour the declared ordering anymore), which is exactly what |
| 857 | // `EnforceSorting` needs to derive `sort_prefix`. On data that |
| 858 | // is already in prefix order the stats-based reorder is mostly |
| 859 | // a no-op anyway, so the trade-off is plainly bad. |
| 860 | for prefix_len in 1..order.len() { |
| 861 | let prefix = order[..prefix_len].to_vec(); |
| 862 | if eq_properties.ordering_satisfy(prefix.iter().cloned())? { |
| 863 | return Ok(SortOrderPushdownResult::Unsupported); |
| 864 | } |
| 865 | } |
| 866 | |
| 867 | // Inexact pushdown. Two independent signals; either is enough |
| 868 | // to produce an approximate ordering, and they compose when |
| 869 | // both apply: |
| 870 | // |
| 871 | // 1. `column_in_file_schema`: the request's leading sort key is |
| 872 | // a plain `Column` present in the file schema. The opener |
| 873 | // can sort row groups by that column's `min` via parquet |
| 874 | // statistics. Drives `sort_order_for_reorder`'s actual use. |
| 875 | // |
| 876 | // 2. `reversed_satisfies`: the source's declared ordering, |
| 877 | // when reversed, satisfies the request. This is strictly |
| 878 | // more powerful than the column-in-schema check because it |
| 879 | // runs the request through `EquivalenceProperties`'s full |
| 880 | // reasoning machinery: |
| 881 | // |
| 882 | // - Function monotonicity: e.g. file declares |
| 883 | // `[extract_year_month(ws) DESC, ws DESC]`, request is |
| 884 | // `[ws ASC]`; the reversed ordering still satisfies the |
| 885 | // request via `extract_year_month`'s monotonicity even |
| 886 | // though parquet has no stats keyed by the function |
| 887 | // expression itself. |