Skip to main content

oxigdal_query/executor/
sort.rs

1//! Sort executor.
2
3use crate::error::{QueryError, Result};
4use crate::executor::filter::{Value, evaluate_expr_for_row};
5use crate::executor::scan::{ColumnData, RecordBatch};
6use crate::parser::ast::OrderByExpr;
7use std::cmp::Ordering;
8
9/// Sort operator.
10pub struct Sort {
11    /// ORDER BY expressions.
12    pub order_by: Vec<OrderByExpr>,
13}
14
15impl Sort {
16    /// Create a new sort operator.
17    pub fn new(order_by: Vec<OrderByExpr>) -> Self {
18        Self { order_by }
19    }
20
21    /// Execute the sort.
22    pub fn execute(&self, batch: &RecordBatch) -> Result<RecordBatch> {
23        if self.order_by.is_empty() {
24            return Ok(batch.clone());
25        }
26
27        // Pre-evaluate every ORDER BY key for every row. Unlike the previous
28        // implementation, this routes *all* expressions (arithmetic, function
29        // calls, ...) through the shared expression evaluator instead of only
30        // handling bare column references and silently no-opping everything
31        // else. Evaluation errors (e.g. unknown column) are propagated rather
32        // than swallowed.
33        let mut keys: Vec<Vec<Value>> = Vec::with_capacity(self.order_by.len());
34        for order in &self.order_by {
35            let mut column_keys = Vec::with_capacity(batch.num_rows);
36            for row in 0..batch.num_rows {
37                let value = evaluate_expr_for_row(&order.expr, batch, row)?;
38                // Values with no defined total ordering (e.g. geometry) cannot
39                // be sorted; surface an error instead of returning misordered
40                // rows.
41                if matches!(value, Value::Geometry(_)) {
42                    return Err(QueryError::unsupported(
43                        "ORDER BY expression produced a value with no defined ordering (geometry)",
44                    ));
45                }
46                column_keys.push(value);
47            }
48            keys.push(column_keys);
49        }
50
51        // Create index array for indirect sorting.
52        let mut indices: Vec<usize> = (0..batch.num_rows).collect();
53        indices.sort_by(|&a, &b| self.compare_rows(&keys, a, b));
54
55        // Reorder columns based on sorted indices.
56        let mut sorted_columns = Vec::new();
57        for column in &batch.columns {
58            sorted_columns.push(self.reorder_column(column, &indices));
59        }
60
61        RecordBatch::new(batch.schema.clone(), sorted_columns, batch.num_rows)
62    }
63
64    /// Compare two rows based on the pre-evaluated ORDER BY keys.
65    fn compare_rows(&self, keys: &[Vec<Value>], a: usize, b: usize) -> Ordering {
66        for (idx, order) in self.order_by.iter().enumerate() {
67            let ordering = compare_value(&keys[idx][a], &keys[idx][b], order.nulls_first);
68            let ordering = if order.asc {
69                ordering
70            } else {
71                ordering.reverse()
72            };
73
74            if ordering != Ordering::Equal {
75                return ordering;
76            }
77        }
78        Ordering::Equal
79    }
80
81    /// Reorder a column based on indices.
82    fn reorder_column(&self, column: &ColumnData, indices: &[usize]) -> ColumnData {
83        match column {
84            ColumnData::Boolean(data) => {
85                let reordered = indices.iter().map(|&i| data[i]).collect();
86                ColumnData::Boolean(reordered)
87            }
88            ColumnData::Int32(data) => {
89                let reordered = indices.iter().map(|&i| data[i]).collect();
90                ColumnData::Int32(reordered)
91            }
92            ColumnData::Int64(data) => {
93                let reordered = indices.iter().map(|&i| data[i]).collect();
94                ColumnData::Int64(reordered)
95            }
96            ColumnData::Float32(data) => {
97                let reordered = indices.iter().map(|&i| data[i]).collect();
98                ColumnData::Float32(reordered)
99            }
100            ColumnData::Float64(data) => {
101                let reordered = indices.iter().map(|&i| data[i]).collect();
102                ColumnData::Float64(reordered)
103            }
104            ColumnData::String(data) => {
105                let reordered = indices.iter().map(|&i| data[i].clone()).collect();
106                ColumnData::String(reordered)
107            }
108            ColumnData::Binary(data) => {
109                let reordered = indices.iter().map(|&i| data[i].clone()).collect();
110                ColumnData::Binary(reordered)
111            }
112        }
113    }
114}
115
116/// Compare two runtime [`Value`]s for ORDER BY, honoring NULL placement.
117///
118/// `nulls_first` places NULLs before non-NULL values when `true` (SQL
119/// `NULLS FIRST`) and after them when `false` (`NULLS LAST`). As in the
120/// parallel sort implementation, the caller applies `.reverse()` afterwards for
121/// descending order.
122fn compare_value(a: &Value, b: &Value, nulls_first: bool) -> Ordering {
123    use Value::*;
124
125    // NULL handling first, so it is uniform across all value types.
126    match (a, b) {
127        (Null, Null) => return Ordering::Equal,
128        (Null, _) => {
129            return if nulls_first {
130                Ordering::Less
131            } else {
132                Ordering::Greater
133            };
134        }
135        (_, Null) => {
136            return if nulls_first {
137                Ordering::Greater
138            } else {
139                Ordering::Less
140            };
141        }
142        _ => {}
143    }
144
145    match (a, b) {
146        (Boolean(x), Boolean(y)) => x.cmp(y),
147        (Int32(x), Int32(y)) => x.cmp(y),
148        (Int64(x), Int64(y)) => x.cmp(y),
149        (Int32(x), Int64(y)) => (*x as i64).cmp(y),
150        (Int64(x), Int32(y)) => x.cmp(&(*y as i64)),
151        (Float32(x), Float32(y)) => x.partial_cmp(y).unwrap_or(Ordering::Equal),
152        (Float64(x), Float64(y)) => x.partial_cmp(y).unwrap_or(Ordering::Equal),
153        (Float32(x), Float64(y)) => (*x as f64).partial_cmp(y).unwrap_or(Ordering::Equal),
154        (Float64(x), Float32(y)) => x.partial_cmp(&(*y as f64)).unwrap_or(Ordering::Equal),
155        (Int32(x), Float64(y)) => (*x as f64).partial_cmp(y).unwrap_or(Ordering::Equal),
156        (Int64(x), Float64(y)) => (*x as f64).partial_cmp(y).unwrap_or(Ordering::Equal),
157        (Float64(x), Int32(y)) => x.partial_cmp(&(*y as f64)).unwrap_or(Ordering::Equal),
158        (Float64(x), Int64(y)) => x.partial_cmp(&(*y as f64)).unwrap_or(Ordering::Equal),
159        (Int32(x), Float32(y)) => (*x as f32).partial_cmp(y).unwrap_or(Ordering::Equal),
160        (Int64(x), Float32(y)) => (*x as f64)
161            .partial_cmp(&(*y as f64))
162            .unwrap_or(Ordering::Equal),
163        (Float32(x), Int32(y)) => x.partial_cmp(&(*y as f32)).unwrap_or(Ordering::Equal),
164        (Float32(x), Int64(y)) => (*x as f64)
165            .partial_cmp(&(*y as f64))
166            .unwrap_or(Ordering::Equal),
167        (String(x), String(y)) => x.cmp(y),
168        // Incomparable / mismatched types: treat as equal so the sort remains
169        // total without reordering rows arbitrarily.
170        _ => Ordering::Equal,
171    }
172}
173
174#[cfg(test)]
175#[allow(clippy::panic)]
176mod tests {
177    use super::*;
178    use crate::executor::scan::{DataType, Field, Schema};
179    use crate::parser::ast::{BinaryOperator, Expr};
180    use std::sync::Arc;
181
182    fn col(name: &str) -> Expr {
183        Expr::Column {
184            table: None,
185            name: name.to_string(),
186        }
187    }
188
189    #[test]
190    fn test_sort_execution() -> Result<()> {
191        let schema = Arc::new(Schema::new(vec![
192            Field::new("id".to_string(), DataType::Int64, false),
193            Field::new("value".to_string(), DataType::Int64, false),
194        ]));
195
196        let columns = vec![
197            ColumnData::Int64(vec![Some(3), Some(1), Some(4), Some(1), Some(5)]),
198            ColumnData::Int64(vec![Some(30), Some(10), Some(40), Some(15), Some(50)]),
199        ];
200
201        let batch = RecordBatch::new(schema, columns, 5)?;
202
203        // Sort by id ASC
204        let order_by = vec![OrderByExpr {
205            expr: Expr::Column {
206                table: None,
207                name: "id".to_string(),
208            },
209            asc: true,
210            nulls_first: false,
211        }];
212
213        let sort = Sort::new(order_by);
214        let sorted = sort.execute(&batch)?;
215
216        // Verify sorted order
217        let ColumnData::Int64(data) = &sorted.columns[0] else {
218            panic!("Expected Int64 column");
219        };
220        assert_eq!(data[0], Some(1));
221        assert_eq!(data[1], Some(1));
222        assert_eq!(data[2], Some(3));
223        assert_eq!(data[3], Some(4));
224        assert_eq!(data[4], Some(5));
225
226        Ok(())
227    }
228
229    #[test]
230    fn test_sort_by_arithmetic_expression() -> Result<()> {
231        // ORDER BY a * b should now actually sort (previously a silent no-op).
232        let schema = Arc::new(Schema::new(vec![
233            Field::new("a".to_string(), DataType::Int64, false),
234            Field::new("b".to_string(), DataType::Int64, false),
235        ]));
236        let columns = vec![
237            ColumnData::Int64(vec![Some(2), Some(1), Some(3)]),
238            ColumnData::Int64(vec![Some(5), Some(4), Some(1)]),
239        ];
240        let batch = RecordBatch::new(schema, columns, 3)?;
241        // products: row0=10, row1=4, row2=3  -> ascending order: row2, row1, row0
242        let order_by = vec![OrderByExpr {
243            expr: Expr::BinaryOp {
244                left: Box::new(col("a")),
245                op: BinaryOperator::Multiply,
246                right: Box::new(col("b")),
247            },
248            asc: true,
249            nulls_first: false,
250        }];
251        let sorted = Sort::new(order_by).execute(&batch)?;
252        let ColumnData::Int64(a) = &sorted.columns[0] else {
253            panic!("Expected Int64 column");
254        };
255        assert_eq!(a[0], Some(3)); // product 3
256        assert_eq!(a[1], Some(1)); // product 4
257        assert_eq!(a[2], Some(2)); // product 10
258        Ok(())
259    }
260
261    #[test]
262    fn test_sort_nulls_first() -> Result<()> {
263        let schema = Arc::new(Schema::new(vec![Field::new(
264            "v".to_string(),
265            DataType::Int64,
266            true,
267        )]));
268        let columns = vec![ColumnData::Int64(vec![
269            Some(3),
270            None,
271            Some(1),
272            None,
273            Some(2),
274        ])];
275        let batch = RecordBatch::new(schema, columns, 5)?;
276
277        // NULLS FIRST, ascending
278        let order_by = vec![OrderByExpr {
279            expr: col("v"),
280            asc: true,
281            nulls_first: true,
282        }];
283        let sorted = Sort::new(order_by).execute(&batch)?;
284        let ColumnData::Int64(v) = &sorted.columns[0] else {
285            panic!("Expected Int64 column");
286        };
287        assert_eq!(v[0], None);
288        assert_eq!(v[1], None);
289        assert_eq!(v[2], Some(1));
290        assert_eq!(v[3], Some(2));
291        assert_eq!(v[4], Some(3));
292
293        // NULLS LAST, ascending (default) puts NULLs at the end.
294        let order_by = vec![OrderByExpr {
295            expr: col("v"),
296            asc: true,
297            nulls_first: false,
298        }];
299        let sorted = Sort::new(order_by).execute(&batch)?;
300        let ColumnData::Int64(v) = &sorted.columns[0] else {
301            panic!("Expected Int64 column");
302        };
303        assert_eq!(v[0], Some(1));
304        assert_eq!(v[1], Some(2));
305        assert_eq!(v[2], Some(3));
306        assert_eq!(v[3], None);
307        assert_eq!(v[4], None);
308        Ok(())
309    }
310
311    #[test]
312    fn test_sort_unknown_column_errors() -> Result<()> {
313        let schema = Arc::new(Schema::new(vec![Field::new(
314            "v".to_string(),
315            DataType::Int64,
316            false,
317        )]));
318        let columns = vec![ColumnData::Int64(vec![Some(1), Some(2)])];
319        let batch = RecordBatch::new(schema, columns, 2)?;
320        let order_by = vec![OrderByExpr {
321            expr: col("does_not_exist"),
322            asc: true,
323            nulls_first: false,
324        }];
325        // Previously this silently returned rows in input order; now it errors.
326        assert!(Sort::new(order_by).execute(&batch).is_err());
327        Ok(())
328    }
329}