Skip to content

Commit 11e5623

Browse files
authored
Implements ORDER BY (#337)
1 parent 75185dd commit 11e5623

File tree

5 files changed

+210
-15
lines changed

5 files changed

+210
-15
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1818
- Expose `partiql_value::parse_ion` as a public API.
1919
- Implements `GROUP BY` operator in evaluator
2020
- Implements `HAVING` operator in evaluator
21+
- Implements `ORDER BY` operator in evaluator
2122

2223
### Fixes
2324
- Fixes Tuple value duplicate equality and hashing

partiql-eval/src/eval/evaluable.rs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use partiql_value::Value::{Boolean, Missing, Null};
66
use partiql_value::{partiql_bag, partiql_tuple, Bag, List, Tuple, Value};
77
use std::borrow::{Borrow, Cow};
88
use std::cell::RefCell;
9+
use std::cmp::Ordering;
910
use std::collections::HashMap;
1011
use std::fmt::Debug;
1112
use std::rc::Rc;
@@ -548,6 +549,84 @@ impl Evaluable for EvalHaving {
548549
}
549550
}
550551

552+
#[derive(Debug)]
553+
pub struct EvalOrderBySortCondition {
554+
pub expr: Box<dyn EvalExpr>,
555+
pub spec: EvalOrderBySortSpec,
556+
}
557+
558+
#[derive(Debug)]
559+
pub enum EvalOrderBySortSpec {
560+
AscNullsFirst,
561+
AscNullsLast,
562+
DescNullsFirst,
563+
DescNullsLast,
564+
}
565+
566+
/// Represents an evaluation `Order By` operator; e.g. `ORDER BY a DESC NULLS LAST` in `SELECT a FROM t ORDER BY a DESC NULLS LAST`.
567+
#[derive(Debug)]
568+
pub struct EvalOrderBy {
569+
pub cmp: Vec<EvalOrderBySortCondition>,
570+
pub input: Option<Value>,
571+
}
572+
573+
impl EvalOrderBy {
574+
#[inline]
575+
fn compare(&self, l: &Value, r: &Value, ctx: &dyn EvalContext) -> Ordering {
576+
let l = l.as_tuple_ref();
577+
let r = r.as_tuple_ref();
578+
self.cmp
579+
.iter()
580+
.map(|spec| {
581+
let l = spec.expr.evaluate(&l, ctx);
582+
let r = spec.expr.evaluate(&r, ctx);
583+
584+
match spec.spec {
585+
EvalOrderBySortSpec::AscNullsFirst => l.as_ref().cmp(r.as_ref()),
586+
EvalOrderBySortSpec::AscNullsLast => match (l.as_ref(), r.as_ref()) {
587+
(Null, Null) => Ordering::Equal,
588+
(Null, Missing) => Ordering::Less,
589+
(Missing, Missing) => Ordering::Equal,
590+
(Missing, Null) => Ordering::Greater,
591+
(Null, _) => Ordering::Greater,
592+
(Missing, _) => Ordering::Greater,
593+
(_, Null) => Ordering::Less,
594+
(_, Missing) => Ordering::Less,
595+
(l, r) => l.cmp(r),
596+
},
597+
EvalOrderBySortSpec::DescNullsFirst => match (l.as_ref(), r.as_ref()) {
598+
(Null, Null) => Ordering::Equal,
599+
(Null, Missing) => Ordering::Less,
600+
(Missing, Missing) => Ordering::Equal,
601+
(Missing, Null) => Ordering::Greater,
602+
(Null, _) => Ordering::Less,
603+
(Missing, _) => Ordering::Less,
604+
(_, Null) => Ordering::Greater,
605+
(_, Missing) => Ordering::Greater,
606+
(l, r) => r.cmp(l),
607+
},
608+
EvalOrderBySortSpec::DescNullsLast => r.as_ref().cmp(l.as_ref()),
609+
}
610+
})
611+
.find_or_last(|o| o != &Ordering::Equal)
612+
.unwrap_or(Ordering::Equal)
613+
}
614+
}
615+
616+
impl Evaluable for EvalOrderBy {
617+
fn evaluate(&mut self, ctx: &dyn EvalContext) -> Option<Value> {
618+
let input_value = self.input.take().expect("Error in retrieving input value");
619+
620+
let mut values = input_value.into_iter().collect_vec();
621+
values.sort_by(|l, r| self.compare(l, r, ctx));
622+
Some(Value::from(List::from(values)))
623+
}
624+
625+
fn update_input(&mut self, input: Value, _branch_num: u8) {
626+
self.input = Some(input);
627+
}
628+
}
629+
551630
/// Represents an evaluation `LIMIT` and/or `OFFSET` operator.
552631
#[derive(Debug)]
553632
pub struct EvalLimitOffset {

partiql-eval/src/plan.rs

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,15 @@ use partiql_logical as logical;
66

77
use partiql_logical::{
88
BinaryOp, BindingsOp, CallName, GroupingStrategy, IsTypeExpr, JoinKind, LogicalPlan, OpId,
9-
PathComponent, Pattern, PatternMatchExpr, SearchedCase, Type, UnaryOp, ValueExpr,
9+
PathComponent, Pattern, PatternMatchExpr, SearchedCase, SortSpecNullOrder, SortSpecOrder, Type,
10+
UnaryOp, ValueExpr,
1011
};
1112

1213
use crate::eval;
13-
use crate::eval::evaluable::{EvalGroupingStrategy, EvalJoinKind, EvalSubQueryExpr, Evaluable};
14+
use crate::eval::evaluable::{
15+
EvalGroupingStrategy, EvalJoinKind, EvalOrderBy, EvalOrderBySortCondition, EvalOrderBySortSpec,
16+
EvalSubQueryExpr, Evaluable,
17+
};
1418
use crate::eval::expr::pattern_match::like_to_re_pattern;
1519
use crate::eval::expr::{
1620
EvalBagExpr, EvalBetweenExpr, EvalBinOp, EvalBinOpExpr, EvalDynamicLookup, EvalExpr, EvalFnAbs,
@@ -158,7 +162,30 @@ impl EvaluatorPlanner {
158162
let expr = self.plan_values(expr);
159163
Box::new(eval::evaluable::EvalExprQuery::new(expr))
160164
}
161-
BindingsOp::OrderBy => todo!("OrderBy"),
165+
BindingsOp::OrderBy(logical::OrderBy { specs }) => {
166+
let cmp = specs
167+
.iter()
168+
.map(|spec| {
169+
let expr = self.plan_values(&spec.expr);
170+
let spec = match (&spec.order, &spec.null_order) {
171+
(SortSpecOrder::Asc, SortSpecNullOrder::First) => {
172+
EvalOrderBySortSpec::AscNullsFirst
173+
}
174+
(SortSpecOrder::Asc, SortSpecNullOrder::Last) => {
175+
EvalOrderBySortSpec::AscNullsLast
176+
}
177+
(SortSpecOrder::Desc, SortSpecNullOrder::First) => {
178+
EvalOrderBySortSpec::DescNullsFirst
179+
}
180+
(SortSpecOrder::Desc, SortSpecNullOrder::Last) => {
181+
EvalOrderBySortSpec::DescNullsFirst
182+
}
183+
};
184+
EvalOrderBySortCondition { expr, spec }
185+
})
186+
.collect_vec();
187+
Box::new(EvalOrderBy { cmp, input: None })
188+
}
162189
BindingsOp::LimitOffset(logical::LimitOffset { limit, offset }) => {
163190
Box::new(eval::evaluable::EvalLimitOffset {
164191
limit: limit.as_ref().map(|e| self.plan_values(e)),

partiql-logical-planner/src/lower.rs

Lines changed: 66 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,17 @@ use partiql_ast::ast::{
77
Assignment, Bag, Between, BinOp, BinOpKind, Call, CallAgg, CallArg, CallArgNamed,
88
CaseSensitivity, CreateIndex, CreateTable, Ddl, DdlOp, Delete, Dml, DmlOp, DropIndex,
99
DropTable, FromClause, FromLet, FromLetKind, GroupByExpr, GroupKey, GroupingStrategy, Insert,
10-
InsertValue, Item, Join, JoinKind, JoinSpec, Like, List, Lit, NodeId, OnConflict, OrderByExpr,
11-
Path, PathStep, ProjectExpr, Projection, ProjectionKind, Query, QuerySet, Remove, SearchedCase,
12-
Select, Set, SetExpr, SetQuantifier, Sexp, SimpleCase, Struct, SymbolPrimitive, UniOp,
13-
UniOpKind, VarRef,
10+
InsertValue, Item, Join, JoinKind, JoinSpec, Like, List, Lit, NodeId, NullOrderingSpec,
11+
OnConflict, OrderByExpr, OrderingSpec, Path, PathStep, ProjectExpr, Projection, ProjectionKind,
12+
Query, QuerySet, Remove, SearchedCase, Select, Set, SetExpr, SetQuantifier, Sexp, SimpleCase,
13+
SortSpec, Struct, SymbolPrimitive, UniOp, UniOpKind, VarRef,
1414
};
1515
use partiql_ast::visit::{Visit, Visitor};
1616
use partiql_logical as logical;
1717
use partiql_logical::{
1818
BagExpr, BetweenExpr, BindingsOp, IsTypeExpr, LikeMatch, LikeNonStringNonLiteralMatch,
19-
ListExpr, LogicalPlan, OpId, PathComponent, Pattern, PatternMatchExpr, TupleExpr, ValueExpr,
19+
ListExpr, LogicalPlan, OpId, PathComponent, Pattern, PatternMatchExpr, SortSpecOrder,
20+
TupleExpr, ValueExpr,
2021
};
2122

2223
use partiql_value::{BindingsName, Value};
@@ -26,6 +27,7 @@ use std::collections::{HashMap, HashSet};
2627
use crate::call_defs::{CallArgument, FnSymTab, FN_SYM_TAB};
2728
use crate::name_resolver;
2829
use itertools::Itertools;
30+
2931
use std::sync::atomic::{AtomicU32, Ordering};
3032

3133
type FnvIndexMap<K, V> = IndexMap<K, V, FnvBuildHasher>;
@@ -34,6 +36,7 @@ type FnvIndexMap<K, V> = IndexMap<K, V, FnvBuildHasher>;
3436
enum QueryContext {
3537
FromLet,
3638
Path,
39+
Order,
3740
Query,
3841
}
3942

@@ -104,6 +107,7 @@ pub struct AstToLogical {
104107
vexpr_stack: Vec<Vec<ValueExpr>>,
105108
arg_stack: Vec<Vec<CallArgument>>,
106109
path_stack: Vec<Vec<PathComponent>>,
110+
sort_stack: Vec<Vec<logical::SortSpec>>,
107111

108112
from_lets: HashSet<ast::NodeId>,
109113

@@ -166,6 +170,7 @@ impl AstToLogical {
166170
vexpr_stack: Default::default(),
167171
arg_stack: Default::default(),
168172
path_stack: Default::default(),
173+
sort_stack: Default::default(),
169174

170175
from_lets: Default::default(),
171176

@@ -406,6 +411,23 @@ impl AstToLogical {
406411
fn push_path_step(&mut self, step: PathComponent) {
407412
self.path_stack.last_mut().unwrap().push(step);
408413
}
414+
415+
#[inline]
416+
fn enter_sort(&mut self) {
417+
self.sort_stack.push(vec![]);
418+
self.ctx_stack.push(QueryContext::Order);
419+
}
420+
421+
#[inline]
422+
fn exit_sort(&mut self) -> Vec<logical::SortSpec> {
423+
self.ctx_stack.pop();
424+
self.sort_stack.pop().expect("sort specs")
425+
}
426+
427+
#[inline]
428+
fn push_sort_spec(&mut self, spec: logical::SortSpec) {
429+
self.sort_stack.last_mut().unwrap().push(spec);
430+
}
409431
}
410432

411433
// SQL (and therefore PartiQL) text (and therefore AST) is not lexically-scoped as is the
@@ -1158,12 +1180,48 @@ impl<'ast> Visitor<'ast> for AstToLogical {
11581180
}
11591181

11601182
fn enter_order_by_expr(&mut self, _order_by_expr: &'ast OrderByExpr) {
1161-
self.enter_env();
1183+
self.enter_sort();
11621184
}
11631185

11641186
fn exit_order_by_expr(&mut self, _order_by_expr: &'ast OrderByExpr) {
1165-
let _env = self.exit_env();
1166-
todo!("order by clause");
1187+
let specs = self.exit_sort();
1188+
let order_by = logical::BindingsOp::OrderBy(logical::OrderBy { specs });
1189+
let id = self.plan.add_operator(order_by);
1190+
self.current_clauses_mut().order_by_clause.replace(id);
1191+
}
1192+
1193+
fn enter_sort_spec(&mut self, _sort_spec: &'ast SortSpec) {
1194+
self.enter_env();
1195+
}
1196+
1197+
fn exit_sort_spec(&mut self, sort_spec: &'ast SortSpec) {
1198+
let mut env = self.exit_env();
1199+
assert_eq!(env.len(), 1);
1200+
1201+
let expr = env.pop().unwrap();
1202+
let order = match sort_spec
1203+
.ordering_spec
1204+
.as_ref()
1205+
.unwrap_or(&OrderingSpec::Asc)
1206+
{
1207+
OrderingSpec::Asc => logical::SortSpecOrder::Asc,
1208+
OrderingSpec::Desc => logical::SortSpecOrder::Desc,
1209+
};
1210+
1211+
let null_order = match sort_spec.null_ordering_spec {
1212+
None => match order {
1213+
SortSpecOrder::Asc => logical::SortSpecNullOrder::Last,
1214+
SortSpecOrder::Desc => logical::SortSpecNullOrder::First,
1215+
},
1216+
Some(NullOrderingSpec::First) => logical::SortSpecNullOrder::First,
1217+
Some(NullOrderingSpec::Last) => logical::SortSpecNullOrder::Last,
1218+
};
1219+
1220+
self.push_sort_spec(logical::SortSpec {
1221+
expr,
1222+
order,
1223+
null_order,
1224+
});
11671225
}
11681226

11691227
fn enter_limit_offset_clause(&mut self, _limit_offset: &'ast ast::LimitOffsetClause) {

partiql-logical/src/lib.rs

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -108,10 +108,10 @@ where
108108
/// Extends the logical plan with the given data flows.
109109
/// #Examples:
110110
/// ```
111-
/// use partiql_logical::{BindingsOp, GroupBy, GroupingStrategy, LimitOffset, LogicalPlan};
111+
/// use partiql_logical::{BindingsOp, GroupBy, GroupingStrategy, LimitOffset, LogicalPlan, OrderBy};
112112
/// let mut p: LogicalPlan<BindingsOp> = LogicalPlan::new();
113113
///
114-
/// let a = p.add_operator(BindingsOp::OrderBy);
114+
/// let a = p.add_operator(BindingsOp::OrderBy(OrderBy{specs: vec![]}));
115115
/// let b = p.add_operator(BindingsOp::Sink);
116116
/// let c = p.add_operator(BindingsOp::LimitOffset(LimitOffset{limit:None, offset:None}));
117117
/// let d = p.add_operator(BindingsOp::GroupBy(GroupBy {
@@ -191,7 +191,7 @@ pub enum BindingsOp {
191191
Pivot(Pivot),
192192
Unpivot(Unpivot),
193193
Filter(Filter),
194-
OrderBy,
194+
OrderBy(OrderBy),
195195
LimitOffset(LimitOffset),
196196
Join(Join),
197197
SetOp,
@@ -249,6 +249,36 @@ pub struct Having {
249249
pub expr: ValueExpr,
250250
}
251251

252+
/// [`OrderBy`] represents a sort operatyion, e.g. `ORDER BY a DESC NULLS LAST` in `SELECT a FROM t ORDER BY a DESC NULLS LAST`.
253+
#[derive(Debug, Clone, Eq, PartialEq)]
254+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
255+
pub struct OrderBy {
256+
pub specs: Vec<SortSpec>,
257+
}
258+
259+
#[derive(Clone, Debug, PartialEq, Eq)]
260+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
261+
pub enum SortSpecOrder {
262+
Asc,
263+
Desc,
264+
}
265+
266+
#[derive(Clone, Debug, PartialEq, Eq)]
267+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
268+
pub enum SortSpecNullOrder {
269+
First,
270+
Last,
271+
}
272+
273+
/// Represents a PartiQL sort specification.
274+
#[derive(Debug, Clone, Eq, PartialEq)]
275+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
276+
pub struct SortSpec {
277+
pub expr: ValueExpr,
278+
pub order: SortSpecOrder,
279+
pub null_order: SortSpecNullOrder,
280+
}
281+
252282
/// [`LimitOffset`] represents a possible limit and/or offset operator, e.g. `LIMIT 10 OFFSET 5` in `SELECT a FROM t LIMIT 10 OFFSET 5`.
253283
#[derive(Debug, Clone, Eq, PartialEq)]
254284
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
@@ -597,7 +627,7 @@ mod tests {
597627
#[test]
598628
fn test_plan() {
599629
let mut p: LogicalPlan<BindingsOp> = LogicalPlan::new();
600-
let a = p.add_operator(BindingsOp::OrderBy);
630+
let a = p.add_operator(BindingsOp::OrderBy(OrderBy { specs: vec![] }));
601631
let b = p.add_operator(BindingsOp::Sink);
602632
let c = p.add_operator(BindingsOp::LimitOffset(LimitOffset {
603633
limit: None,

0 commit comments

Comments
 (0)