Skip to main content

iceberg/spec/
sort.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18/*!
19 * Sorting
20 */
21use 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
33/// Reference to [`SortOrder`].
34pub type SortOrderRef = Arc<SortOrder>;
35#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Copy, Clone)]
36/// Sort direction in a partition, either ascending or descending
37pub enum SortDirection {
38    /// Ascending
39    #[serde(rename = "asc")]
40    Ascending,
41    /// Descending
42    #[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)]
56/// Describes the order of null values when sorted.
57pub enum NullOrder {
58    #[serde(rename = "nulls-first")]
59    /// Nulls are stored first
60    First,
61    #[serde(rename = "nulls-last")]
62    /// Nulls are stored last
63    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")]
77/// Entry for every column that is to be sorted
78pub struct SortField {
79    /// A source column id from the table’s schema
80    pub source_id: i32,
81    /// A transform that is used to produce values to be sorted on from the source column.
82    pub transform: Transform,
83    /// A sort direction, that can only be either asc or desc
84    pub direction: SortDirection,
85    /// A null order that describes the order of null values when sorted.
86    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))]
103/// A sort order is defined by a sort order id and a list of sort fields.
104/// The order of the sort fields within the list defines the order in which the sort is applied to the data.
105pub struct SortOrder {
106    /// Identifier for SortOrder, order_id `0` is no sort order.
107    #[builder(default)]
108    pub order_id: i64,
109    /// Details of the sort
110    #[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    /// Create sort order builder
118    pub fn builder() -> SortOrderBuilder {
119        SortOrderBuilder::default()
120    }
121
122    /// Create an unbound unsorted order
123    pub fn unsorted_order() -> SortOrder {
124        SortOrder {
125            order_id: SortOrder::UNSORTED_ORDER_ID,
126            fields: Vec::new(),
127        }
128    }
129
130    /// Returns true if the sort order is unsorted.
131    ///
132    /// A [`SortOrder`] is unsorted if it has no sort fields.
133    pub fn is_unsorted(&self) -> bool {
134        self.fields.is_empty()
135    }
136
137    /// Set the order id for the sort order
138    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    /// Creates a new unbound sort order.
148    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    /// Creates a new bound sort order.
171    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    /// Returns the given sort order if it is compatible with the given schema
177    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}