1use core::fmt;
22use std::fmt::Formatter;
23use std::sync::Arc;
24
25use serde::{Deserialize, Serialize};
26use typed_builder::TypedBuilder;
27
28use super::transform::Transform;
29use crate::error::Result;
30use crate::spec::{PrimitiveType, Schema, Type};
31use crate::{Error, ErrorKind};
32
33pub type SortOrderRef = Arc<SortOrder>;
35#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Copy, Clone)]
36pub enum SortDirection {
38 #[serde(rename = "asc")]
40 Ascending,
41 #[serde(rename = "desc")]
43 Descending,
44}
45
46impl fmt::Display for SortDirection {
47 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
48 match *self {
49 SortDirection::Ascending => write!(f, "ascending"),
50 SortDirection::Descending => write!(f, "descending"),
51 }
52 }
53}
54
55#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Copy, Clone)]
56pub enum NullOrder {
58 #[serde(rename = "nulls-first")]
59 First,
61 #[serde(rename = "nulls-last")]
62 Last,
64}
65
66impl fmt::Display for NullOrder {
67 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
68 match *self {
69 NullOrder::First => write!(f, "first"),
70 NullOrder::Last => write!(f, "last"),
71 }
72 }
73}
74
75#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, TypedBuilder)]
76#[serde(rename_all = "kebab-case")]
77pub struct SortField {
79 pub source_id: i32,
81 pub transform: Transform,
83 pub direction: SortDirection,
85 pub null_order: NullOrder,
87}
88
89impl fmt::Display for SortField {
90 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
91 write!(
92 f,
93 "SortField {{ source_id: {}, transform: {}, direction: {}, null_order: {} }}",
94 self.source_id, self.transform, self.direction, self.null_order
95 )
96 }
97}
98
99#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Builder, Default)]
100#[serde(rename_all = "kebab-case")]
101#[builder(setter(prefix = "with"))]
102#[builder(build_fn(skip))]
103pub struct SortOrder {
106 #[builder(default)]
108 pub order_id: i64,
109 #[builder(setter(each(name = "with_sort_field")), default)]
111 pub fields: Vec<SortField>,
112}
113
114impl SortOrder {
115 pub(crate) const UNSORTED_ORDER_ID: i64 = 0;
116
117 pub fn builder() -> SortOrderBuilder {
119 SortOrderBuilder::default()
120 }
121
122 pub fn unsorted_order() -> SortOrder {
124 SortOrder {
125 order_id: SortOrder::UNSORTED_ORDER_ID,
126 fields: Vec::new(),
127 }
128 }
129
130 pub fn is_unsorted(&self) -> bool {
134 self.fields.is_empty()
135 }
136
137 pub fn with_order_id(self, order_id: i64) -> SortOrder {
139 SortOrder {
140 order_id,
141 fields: self.fields,
142 }
143 }
144}
145
146impl SortOrderBuilder {
147 pub fn build_unbound(&self) -> Result<SortOrder> {
149 let fields = self.fields.clone().unwrap_or_default();
150 match (self.order_id, fields.as_slice()) {
151 (Some(SortOrder::UNSORTED_ORDER_ID) | None, []) => Ok(SortOrder::unsorted_order()),
152 (_, []) => Err(Error::new(
153 ErrorKind::Unexpected,
154 format!("Unsorted order ID must be {}", SortOrder::UNSORTED_ORDER_ID),
155 )),
156 (Some(SortOrder::UNSORTED_ORDER_ID), [..]) => Err(Error::new(
157 ErrorKind::Unexpected,
158 format!(
159 "Sort order ID {} is reserved for unsorted order",
160 SortOrder::UNSORTED_ORDER_ID
161 ),
162 )),
163 (maybe_order_id, [..]) => Ok(SortOrder {
164 order_id: maybe_order_id.unwrap_or(1),
165 fields: fields.to_vec(),
166 }),
167 }
168 }
169
170 pub fn build(&self, schema: &Schema) -> Result<SortOrder> {
172 let unbound_sort_order = self.build_unbound()?;
173 SortOrderBuilder::check_compatibility(unbound_sort_order, schema)
174 }
175
176 fn check_compatibility(sort_order: SortOrder, schema: &Schema) -> Result<SortOrder> {
178 let sort_fields = &sort_order.fields;
179 for sort_field in sort_fields {
180 match schema.field_by_id(sort_field.source_id) {
181 None => {
182 return Err(Error::new(
183 ErrorKind::DataInvalid,
184 format!("Cannot find source column for sort field: {sort_field}"),
185 ));
186 }
187 Some(source_field) => {
188 let source_type = source_field.field_type.as_ref();
189
190 if !source_type.is_primitive() {
191 return Err(Error::new(
192 ErrorKind::DataInvalid,
193 format!("Cannot sort by non-primitive source field: {source_type}"),
194 ));
195 }
196
197 if matches!(
198 source_type,
199 Type::Primitive(PrimitiveType::Geometry(_) | PrimitiveType::Geography(_))
200 ) {
201 return Err(Error::new(
202 ErrorKind::DataInvalid,
203 format!(
204 "Cannot sort by geospatial source field: {}",
205 source_field.name
206 ),
207 ));
208 }
209
210 let field_transform = sort_field.transform;
211 field_transform.result_type(source_type)?;
212 }
213 }
214 }
215
216 Ok(sort_order)
217 }
218}
219
220#[cfg(test)]
221mod tests {
222 use super::*;
223 use crate::spec::{GeometryType, ListType, NestedField, PrimitiveType, Type};
224
225 #[test]
226 fn test_sort_field() {
227 let spec = r#"
228 {
229 "transform": "bucket[4]",
230 "source-id": 3,
231 "direction": "desc",
232 "null-order": "nulls-last"
233 }
234 "#;
235
236 let field: SortField = serde_json::from_str(spec).unwrap();
237 assert_eq!(Transform::Bucket(4), field.transform);
238 assert_eq!(3, field.source_id);
239 assert_eq!(SortDirection::Descending, field.direction);
240 assert_eq!(NullOrder::Last, field.null_order);
241 }
242
243 #[test]
244 fn test_sort_order() {
245 let spec = r#"
246 {
247 "order-id": 1,
248 "fields": [ {
249 "transform": "identity",
250 "source-id": 2,
251 "direction": "asc",
252 "null-order": "nulls-first"
253 }, {
254 "transform": "bucket[4]",
255 "source-id": 3,
256 "direction": "desc",
257 "null-order": "nulls-last"
258 } ]
259 }
260 "#;
261
262 let order: SortOrder = serde_json::from_str(spec).unwrap();
263 assert_eq!(Transform::Identity, order.fields[0].transform);
264 assert_eq!(2, order.fields[0].source_id);
265 assert_eq!(SortDirection::Ascending, order.fields[0].direction);
266 assert_eq!(NullOrder::First, order.fields[0].null_order);
267
268 assert_eq!(Transform::Bucket(4), order.fields[1].transform);
269 assert_eq!(3, order.fields[1].source_id);
270 assert_eq!(SortDirection::Descending, order.fields[1].direction);
271 assert_eq!(NullOrder::Last, order.fields[1].null_order);
272 }
273
274 #[test]
275 fn test_build_unbound_should_return_err_if_unsorted_order_does_not_have_an_order_id_of_zero() {
276 assert_eq!(
277 SortOrder::builder()
278 .with_order_id(1)
279 .build_unbound()
280 .expect_err("Expected an Err value")
281 .message(),
282 "Unsorted order ID must be 0"
283 )
284 }
285
286 #[test]
287 fn test_build_unbound_should_return_err_if_order_id_equals_zero_is_used_for_anything_other_than_unsorted_order()
288 {
289 assert_eq!(
290 SortOrder::builder()
291 .with_order_id(SortOrder::UNSORTED_ORDER_ID)
292 .with_sort_field(
293 SortField::builder()
294 .source_id(2)
295 .direction(SortDirection::Ascending)
296 .null_order(NullOrder::First)
297 .transform(Transform::Identity)
298 .build()
299 )
300 .build_unbound()
301 .expect_err("Expected an Err value")
302 .message(),
303 "Sort order ID 0 is reserved for unsorted order"
304 )
305 }
306
307 #[test]
308 fn test_build_unbound_returns_correct_default_order_id_for_no_fields() {
309 assert_eq!(
310 SortOrder::builder()
311 .build_unbound()
312 .expect("Expected an Ok value")
313 .order_id,
314 SortOrder::UNSORTED_ORDER_ID
315 )
316 }
317
318 #[test]
319 fn test_build_unbound_returns_correct_default_order_id_for_fields() {
320 let sort_field = SortField::builder()
321 .source_id(2)
322 .direction(SortDirection::Ascending)
323 .null_order(NullOrder::First)
324 .transform(Transform::Identity)
325 .build();
326 assert_ne!(
327 SortOrder::builder()
328 .with_sort_field(sort_field.clone())
329 .build_unbound()
330 .expect("Expected an Ok value")
331 .order_id,
332 SortOrder::UNSORTED_ORDER_ID
333 )
334 }
335
336 #[test]
337 fn test_build_unbound_should_return_unsorted_sort_order() {
338 assert_eq!(
339 SortOrder::builder()
340 .with_order_id(SortOrder::UNSORTED_ORDER_ID)
341 .build_unbound()
342 .expect("Expected an Ok value"),
343 SortOrder::unsorted_order()
344 )
345 }
346
347 #[test]
348 fn test_build_unbound_should_return_sort_order_with_given_order_id_and_sort_fields() {
349 let sort_field = SortField::builder()
350 .source_id(2)
351 .direction(SortDirection::Ascending)
352 .null_order(NullOrder::First)
353 .transform(Transform::Identity)
354 .build();
355
356 assert_eq!(
357 SortOrder::builder()
358 .with_order_id(2)
359 .with_sort_field(sort_field.clone())
360 .build_unbound()
361 .expect("Expected an Ok value"),
362 SortOrder {
363 order_id: 2,
364 fields: vec![sort_field]
365 }
366 )
367 }
368
369 #[test]
370 fn test_build_unbound_should_return_sort_order_with_given_sort_fields_and_defaults_to_1_if_missing_an_order_id()
371 {
372 let sort_field = SortField::builder()
373 .source_id(2)
374 .direction(SortDirection::Ascending)
375 .null_order(NullOrder::First)
376 .transform(Transform::Identity)
377 .build();
378
379 assert_eq!(
380 SortOrder::builder()
381 .with_sort_field(sort_field.clone())
382 .build_unbound()
383 .expect("Expected an Ok value"),
384 SortOrder {
385 order_id: 1,
386 fields: vec![sort_field]
387 }
388 )
389 }
390
391 #[test]
392 fn test_build_should_return_err_if_sort_order_field_is_not_present_in_schema() {
393 let schema = Schema::builder()
394 .with_schema_id(1)
395 .with_fields(vec![
396 NestedField::required(1, "foo", Type::Primitive(PrimitiveType::Int)).into(),
397 ])
398 .build()
399 .unwrap();
400
401 let sort_order_builder_result = SortOrder::builder()
402 .with_sort_field(
403 SortField::builder()
404 .source_id(2)
405 .direction(SortDirection::Ascending)
406 .null_order(NullOrder::First)
407 .transform(Transform::Identity)
408 .build(),
409 )
410 .build(&schema);
411
412 let err = sort_order_builder_result.expect_err("Expected an Err value");
413 assert_eq!(err.kind(), ErrorKind::DataInvalid);
414 assert_eq!(
415 err.message(),
416 "Cannot find source column for sort field: SortField { source_id: 2, transform: identity, direction: ascending, null_order: first }"
417 );
418 }
419
420 #[test]
421 fn test_build_should_return_err_if_source_field_is_not_a_primitive_type() {
422 let schema = Schema::builder()
423 .with_schema_id(1)
424 .with_fields(vec![
425 NestedField::required(
426 1,
427 "foo",
428 Type::List(ListType {
429 element_field: NestedField::list_element(
430 2,
431 Type::Primitive(PrimitiveType::String),
432 true,
433 )
434 .into(),
435 }),
436 )
437 .into(),
438 ])
439 .build()
440 .unwrap();
441
442 let sort_order_builder_result = SortOrder::builder()
443 .with_sort_field(
444 SortField::builder()
445 .source_id(1)
446 .direction(SortDirection::Ascending)
447 .null_order(NullOrder::First)
448 .transform(Transform::Identity)
449 .build(),
450 )
451 .build(&schema);
452
453 let err = sort_order_builder_result.expect_err("Expected an Err value");
454 assert_eq!(err.kind(), ErrorKind::DataInvalid);
455 assert_eq!(
456 err.message(),
457 "Cannot sort by non-primitive source field: list"
458 );
459 }
460
461 #[test]
462 fn test_build_should_return_err_if_source_field_is_variant() {
463 let schema = Schema::builder()
464 .with_schema_id(1)
465 .with_fields(vec![
466 NestedField::optional(1, "v", Type::Variant(crate::spec::VariantType)).into(),
467 ])
468 .build()
469 .unwrap();
470
471 let sort_order_builder_result = SortOrder::builder()
472 .with_sort_field(
473 SortField::builder()
474 .source_id(1)
475 .direction(SortDirection::Ascending)
476 .null_order(NullOrder::First)
477 .transform(Transform::Identity)
478 .build(),
479 )
480 .build(&schema);
481
482 let err = sort_order_builder_result.expect_err("Expected an Err value");
483 assert_eq!(err.kind(), ErrorKind::DataInvalid);
484 assert_eq!(
485 err.message(),
486 "Cannot sort by non-primitive source field: variant"
487 );
488 }
489
490 #[test]
491 fn test_build_should_reject_geospatial_source_fields() {
492 let schema = Schema::builder()
493 .with_schema_id(1)
494 .with_fields(vec![
495 NestedField::required(
496 1,
497 "geom",
498 Type::Primitive(PrimitiveType::Geometry(GeometryType::default())),
499 )
500 .into(),
501 ])
502 .build()
503 .unwrap();
504
505 let err = SortOrder::builder()
506 .with_sort_field(
507 SortField::builder()
508 .source_id(1)
509 .direction(SortDirection::Ascending)
510 .null_order(NullOrder::First)
511 .transform(Transform::Void)
512 .build(),
513 )
514 .build(&schema)
515 .unwrap_err();
516
517 assert_eq!(err.kind(), ErrorKind::DataInvalid);
518 assert_eq!(
519 err.message(),
520 "Cannot sort by geospatial source field: geom"
521 );
522 }
523
524 #[test]
525 fn test_build_should_return_err_if_source_field_type_is_not_supported_by_transform() {
526 let schema = Schema::builder()
527 .with_schema_id(1)
528 .with_fields(vec![
529 NestedField::required(1, "foo", Type::Primitive(PrimitiveType::Int)).into(),
530 ])
531 .build()
532 .unwrap();
533
534 let sort_order_builder_result = SortOrder::builder()
535 .with_sort_field(
536 SortField::builder()
537 .source_id(1)
538 .direction(SortDirection::Ascending)
539 .null_order(NullOrder::First)
540 .transform(Transform::Year)
541 .build(),
542 )
543 .build(&schema);
544
545 let err = sort_order_builder_result.expect_err("Expected an Err value");
546 assert_eq!(err.kind(), ErrorKind::DataInvalid);
547 assert_eq!(
548 err.message(),
549 "int is not a valid input type of year transform"
550 );
551 }
552
553 #[test]
554 fn test_build_should_return_valid_sort_order() {
555 let schema = Schema::builder()
556 .with_schema_id(1)
557 .with_fields(vec![
558 NestedField::required(1, "foo", Type::Primitive(PrimitiveType::String)).into(),
559 NestedField::required(2, "bar", Type::Primitive(PrimitiveType::Int)).into(),
560 ])
561 .build()
562 .unwrap();
563
564 let sort_field = SortField::builder()
565 .source_id(2)
566 .direction(SortDirection::Ascending)
567 .null_order(NullOrder::First)
568 .transform(Transform::Identity)
569 .build();
570
571 let sort_order_builder_result = SortOrder::builder()
572 .with_sort_field(sort_field.clone())
573 .build(&schema);
574
575 assert_eq!(
576 sort_order_builder_result.expect("Expected an Ok value"),
577 SortOrder {
578 order_id: 1,
579 fields: vec![sort_field],
580 }
581 )
582 }
583}