Skip to main content

uqa_execution/relational/
sort.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! External sort wrapper and SQL key comparison.
8
9use super::{
10    Batch, DefaultExpressionEvaluator, ExecResult, PhysicalOperator, SQLParam, ScalarExpr,
11    SharedExpressionEvaluator, Value,
12};
13
14#[derive(Debug, Clone)]
15pub struct SortKey {
16    pub expr: ScalarExpr,
17    pub descending: bool,
18    /// `Some(true)` forces NULLS FIRST, `Some(false)` forces NULLS
19    /// LAST. `None` falls back to the SQL-standard default - NULLS
20    /// LAST for ASC and NULLS FIRST for DESC.
21    pub nulls_first: Option<bool>,
22}
23
24/// Byte-bounded blocking sort backed by the external merge-sort implementation.
25/// Compatibility constructors use a 64 MiB budget; engine callers should pass
26/// the active session's `work_mem` through [`Self::with_evaluator_and_work_mem`].
27const DEFAULT_SORT_WORK_MEM_BYTES: usize = 64 * 1024 * 1024;
28
29pub struct Sort<'a> {
30    inner: crate::external_sort::ExternalSort<'a>,
31}
32
33impl Sort<'static> {
34    pub fn new(
35        child: Box<dyn PhysicalOperator>,
36        keys: Vec<SortKey>,
37        params: Vec<SQLParam>,
38    ) -> Self {
39        Self::with_evaluator(child, keys, DefaultExpressionEvaluator::shared(params))
40    }
41
42    /// Top-K variant: retain only the first `keep` rows of the sorted
43    /// order. Uses a partial selection, so the cost is `O(n + k log k)`
44    /// instead of `O(n log n)`.
45    pub fn with_keep(
46        child: Box<dyn PhysicalOperator>,
47        keys: Vec<SortKey>,
48        params: Vec<SQLParam>,
49        keep: usize,
50    ) -> Self {
51        Self::with_evaluator_and_keep(
52            child,
53            keys,
54            DefaultExpressionEvaluator::shared(params),
55            keep,
56        )
57    }
58}
59
60impl<'a> Sort<'a> {
61    pub fn with_evaluator(
62        child: Box<dyn PhysicalOperator + 'a>,
63        keys: Vec<SortKey>,
64        evaluator: SharedExpressionEvaluator<'a>,
65    ) -> Self {
66        Self::with_evaluator_and_work_mem(child, keys, evaluator, DEFAULT_SORT_WORK_MEM_BYTES)
67    }
68
69    pub fn with_evaluator_and_work_mem(
70        child: Box<dyn PhysicalOperator + 'a>,
71        mut keys: Vec<SortKey>,
72        evaluator: SharedExpressionEvaluator<'a>,
73        work_mem_bytes: usize,
74    ) -> Self {
75        for key in &mut keys {
76            let expression = std::mem::replace(&mut key.expr, ScalarExpr::Literal(Value::Null));
77            key.expr = crate::bind_type_introspection(
78                expression,
79                child.row_schema(),
80                evaluator.parameters(),
81            );
82        }
83        Self {
84            inner: crate::external_sort::ExternalSort::new(
85                child,
86                keys,
87                evaluator,
88                None,
89                work_mem_bytes,
90            ),
91        }
92    }
93
94    pub fn with_evaluator_and_keep(
95        child: Box<dyn PhysicalOperator + 'a>,
96        mut keys: Vec<SortKey>,
97        evaluator: SharedExpressionEvaluator<'a>,
98        keep: usize,
99    ) -> Self {
100        for key in &mut keys {
101            let expression = std::mem::replace(&mut key.expr, ScalarExpr::Literal(Value::Null));
102            key.expr = crate::bind_type_introspection(
103                expression,
104                child.row_schema(),
105                evaluator.parameters(),
106            );
107        }
108        Self {
109            inner: crate::external_sort::ExternalSort::new(
110                child,
111                keys,
112                evaluator,
113                Some(keep),
114                DEFAULT_SORT_WORK_MEM_BYTES,
115            ),
116        }
117    }
118}
119
120/// Compare two pre-computed sort-key vectors under `keys` semantics:
121/// per-key direction plus `PostgreSQL` NULLS placement (default NULLS
122/// LAST for ascending, NULLS FIRST for descending).
123pub fn compare_sort_key_values(keys: &[SortKey], av: &[Value], bv: &[Value]) -> std::cmp::Ordering {
124    compare_sort_key_values_by(keys, |index| (&av[index], &bv[index]))
125}
126
127pub(crate) fn compare_sort_key_values_by<'a>(
128    keys: &[SortKey],
129    mut values: impl FnMut(usize) -> (&'a Value, &'a Value),
130) -> std::cmp::Ordering {
131    use std::cmp::Ordering;
132    for (i, k) in keys.iter().enumerate() {
133        let (a, b) = values(i);
134        let a_null = matches!(a, Value::Null);
135        let b_null = matches!(b, Value::Null);
136        let nulls_first = k.nulls_first.unwrap_or(k.descending);
137        if a_null || b_null {
138            let null_cmp = if a_null == b_null {
139                Ordering::Equal
140            } else if a_null {
141                if nulls_first {
142                    Ordering::Less
143                } else {
144                    Ordering::Greater
145                }
146            } else if nulls_first {
147                Ordering::Greater
148            } else {
149                Ordering::Less
150            };
151            if null_cmp != Ordering::Equal {
152                return null_cmp;
153            }
154            continue;
155        }
156        let ord = compare_values(a, b);
157        let ord = if k.descending { ord.reverse() } else { ord };
158        if ord != Ordering::Equal {
159            return ord;
160        }
161    }
162    Ordering::Equal
163}
164
165pub(super) fn compare_values(a: &Value, b: &Value) -> std::cmp::Ordering {
166    use std::cmp::Ordering::*;
167    match (a, b) {
168        (Value::Null, Value::Null) => Equal,
169        (Value::Null, _) => Less,
170        (_, Value::Null) => Greater,
171        (Value::Temporal(x), Value::Str(y)) => x
172            .parse_same_kind(y)
173            .map_or_else(|| a.cmp(b), |parsed| x.cmp(&parsed)),
174        (Value::Str(x), Value::Temporal(y)) => y
175            .parse_same_kind(x)
176            .map_or_else(|| a.cmp(b), |parsed| parsed.cmp(y)),
177        _ => a.cmp(b),
178    }
179}
180
181impl PhysicalOperator for Sort<'_> {
182    fn row_schema(&self) -> &super::RowSchema {
183        self.inner.row_schema()
184    }
185
186    fn output_ordering(&self) -> &[crate::PhysicalOrder] {
187        self.inner.output_ordering()
188    }
189
190    fn open(&mut self) -> ExecResult<()> {
191        self.inner.open()
192    }
193
194    fn next(&mut self) -> ExecResult<Option<Batch>> {
195        self.inner.next()
196    }
197
198    fn close(&mut self) -> ExecResult<()> {
199        self.inner.close()
200    }
201}
202
203// -------------------------------------------------------------------------
204// Limit / Offset
205// -------------------------------------------------------------------------