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::Schema;
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                    let field_transform = sort_field.transform;
198                    field_transform.result_type(source_type)?;
199                }
200            }
201        }
202
203        Ok(sort_order)
204    }
205}
206
207#[cfg(test)]
208mod tests {
209    use super::*;
210    use crate::spec::{ListType, NestedField, PrimitiveType, Type};
211
212    #[test]
213    fn test_sort_field() {
214        let spec = r#"
215        {
216            "transform": "bucket[4]",
217            "source-id": 3,
218            "direction": "desc",
219            "null-order": "nulls-last"
220         }
221        "#;
222
223        let field: SortField = serde_json::from_str(spec).unwrap();
224        assert_eq!(Transform::Bucket(4), field.transform);
225        assert_eq!(3, field.source_id);
226        assert_eq!(SortDirection::Descending, field.direction);
227        assert_eq!(NullOrder::Last, field.null_order);
228    }
229
230    #[test]
231    fn test_sort_order() {
232        let spec = r#"
233        {
234        "order-id": 1,
235        "fields": [ {
236            "transform": "identity",
237            "source-id": 2,
238            "direction": "asc",
239            "null-order": "nulls-first"
240         }, {
241            "transform": "bucket[4]",
242            "source-id": 3,
243            "direction": "desc",
244            "null-order": "nulls-last"
245         } ]
246        }
247        "#;
248
249        let order: SortOrder = serde_json::from_str(spec).unwrap();
250        assert_eq!(Transform::Identity, order.fields[0].transform);
251        assert_eq!(2, order.fields[0].source_id);
252        assert_eq!(SortDirection::Ascending, order.fields[0].direction);
253        assert_eq!(NullOrder::First, order.fields[0].null_order);
254
255        assert_eq!(Transform::Bucket(4), order.fields[1].transform);
256        assert_eq!(3, order.fields[1].source_id);
257        assert_eq!(SortDirection::Descending, order.fields[1].direction);
258        assert_eq!(NullOrder::Last, order.fields[1].null_order);
259    }
260
261    #[test]
262    fn test_build_unbound_should_return_err_if_unsorted_order_does_not_have_an_order_id_of_zero() {
263        assert_eq!(
264            SortOrder::builder()
265                .with_order_id(1)
266                .build_unbound()
267                .expect_err("Expected an Err value")
268                .message(),
269            "Unsorted order ID must be 0"
270        )
271    }
272
273    #[test]
274    fn test_build_unbound_should_return_err_if_order_id_equals_zero_is_used_for_anything_other_than_unsorted_order()
275     {
276        assert_eq!(
277            SortOrder::builder()
278                .with_order_id(SortOrder::UNSORTED_ORDER_ID)
279                .with_sort_field(
280                    SortField::builder()
281                        .source_id(2)
282                        .direction(SortDirection::Ascending)
283                        .null_order(NullOrder::First)
284                        .transform(Transform::Identity)
285                        .build()
286                )
287                .build_unbound()
288                .expect_err("Expected an Err value")
289                .message(),
290            "Sort order ID 0 is reserved for unsorted order"
291        )
292    }
293
294    #[test]
295    fn test_build_unbound_returns_correct_default_order_id_for_no_fields() {
296        assert_eq!(
297            SortOrder::builder()
298                .build_unbound()
299                .expect("Expected an Ok value")
300                .order_id,
301            SortOrder::UNSORTED_ORDER_ID
302        )
303    }
304
305    #[test]
306    fn test_build_unbound_returns_correct_default_order_id_for_fields() {
307        let sort_field = SortField::builder()
308            .source_id(2)
309            .direction(SortDirection::Ascending)
310            .null_order(NullOrder::First)
311            .transform(Transform::Identity)
312            .build();
313        assert_ne!(
314            SortOrder::builder()
315                .with_sort_field(sort_field.clone())
316                .build_unbound()
317                .expect("Expected an Ok value")
318                .order_id,
319            SortOrder::UNSORTED_ORDER_ID
320        )
321    }
322
323    #[test]
324    fn test_build_unbound_should_return_unsorted_sort_order() {
325        assert_eq!(
326            SortOrder::builder()
327                .with_order_id(SortOrder::UNSORTED_ORDER_ID)
328                .build_unbound()
329                .expect("Expected an Ok value"),
330            SortOrder::unsorted_order()
331        )
332    }
333
334    #[test]
335    fn test_build_unbound_should_return_sort_order_with_given_order_id_and_sort_fields() {
336        let sort_field = SortField::builder()
337            .source_id(2)
338            .direction(SortDirection::Ascending)
339            .null_order(NullOrder::First)
340            .transform(Transform::Identity)
341            .build();
342
343        assert_eq!(
344            SortOrder::builder()
345                .with_order_id(2)
346                .with_sort_field(sort_field.clone())
347                .build_unbound()
348                .expect("Expected an Ok value"),
349            SortOrder {
350                order_id: 2,
351                fields: vec![sort_field]
352            }
353        )
354    }
355
356    #[test]
357    fn test_build_unbound_should_return_sort_order_with_given_sort_fields_and_defaults_to_1_if_missing_an_order_id()
358     {
359        let sort_field = SortField::builder()
360            .source_id(2)
361            .direction(SortDirection::Ascending)
362            .null_order(NullOrder::First)
363            .transform(Transform::Identity)
364            .build();
365
366        assert_eq!(
367            SortOrder::builder()
368                .with_sort_field(sort_field.clone())
369                .build_unbound()
370                .expect("Expected an Ok value"),
371            SortOrder {
372                order_id: 1,
373                fields: vec![sort_field]
374            }
375        )
376    }
377
378    #[test]
379    fn test_build_should_return_err_if_sort_order_field_is_not_present_in_schema() {
380        let schema = Schema::builder()
381            .with_schema_id(1)
382            .with_fields(vec![
383                NestedField::required(1, "foo", Type::Primitive(PrimitiveType::Int)).into(),
384            ])
385            .build()
386            .unwrap();
387
388        let sort_order_builder_result = SortOrder::builder()
389            .with_sort_field(
390                SortField::builder()
391                    .source_id(2)
392                    .direction(SortDirection::Ascending)
393                    .null_order(NullOrder::First)
394                    .transform(Transform::Identity)
395                    .build(),
396            )
397            .build(&schema);
398
399        let err = sort_order_builder_result.expect_err("Expected an Err value");
400        assert_eq!(err.kind(), ErrorKind::DataInvalid);
401        assert_eq!(
402            err.message(),
403            "Cannot find source column for sort field: SortField { source_id: 2, transform: identity, direction: ascending, null_order: first }"
404        );
405    }
406
407    #[test]
408    fn test_build_should_return_err_if_source_field_is_not_a_primitive_type() {
409        let schema = Schema::builder()
410            .with_schema_id(1)
411            .with_fields(vec![
412                NestedField::required(
413                    1,
414                    "foo",
415                    Type::List(ListType {
416                        element_field: NestedField::list_element(
417                            2,
418                            Type::Primitive(PrimitiveType::String),
419                            true,
420                        )
421                        .into(),
422                    }),
423                )
424                .into(),
425            ])
426            .build()
427            .unwrap();
428
429        let sort_order_builder_result = SortOrder::builder()
430            .with_sort_field(
431                SortField::builder()
432                    .source_id(1)
433                    .direction(SortDirection::Ascending)
434                    .null_order(NullOrder::First)
435                    .transform(Transform::Identity)
436                    .build(),
437            )
438            .build(&schema);
439
440        let err = sort_order_builder_result.expect_err("Expected an Err value");
441        assert_eq!(err.kind(), ErrorKind::DataInvalid);
442        assert_eq!(
443            err.message(),
444            "Cannot sort by non-primitive source field: list"
445        );
446    }
447
448    #[test]
449    fn test_build_should_return_err_if_source_field_is_variant() {
450        let schema = Schema::builder()
451            .with_schema_id(1)
452            .with_fields(vec![
453                NestedField::optional(1, "v", Type::Variant(crate::spec::VariantType)).into(),
454            ])
455            .build()
456            .unwrap();
457
458        let sort_order_builder_result = SortOrder::builder()
459            .with_sort_field(
460                SortField::builder()
461                    .source_id(1)
462                    .direction(SortDirection::Ascending)
463                    .null_order(NullOrder::First)
464                    .transform(Transform::Identity)
465                    .build(),
466            )
467            .build(&schema);
468
469        let err = sort_order_builder_result.expect_err("Expected an Err value");
470        assert_eq!(err.kind(), ErrorKind::DataInvalid);
471        assert_eq!(
472            err.message(),
473            "Cannot sort by non-primitive source field: variant"
474        );
475    }
476
477    #[test]
478    fn test_build_should_return_err_if_source_field_type_is_not_supported_by_transform() {
479        let schema = Schema::builder()
480            .with_schema_id(1)
481            .with_fields(vec![
482                NestedField::required(1, "foo", Type::Primitive(PrimitiveType::Int)).into(),
483            ])
484            .build()
485            .unwrap();
486
487        let sort_order_builder_result = SortOrder::builder()
488            .with_sort_field(
489                SortField::builder()
490                    .source_id(1)
491                    .direction(SortDirection::Ascending)
492                    .null_order(NullOrder::First)
493                    .transform(Transform::Year)
494                    .build(),
495            )
496            .build(&schema);
497
498        let err = sort_order_builder_result.expect_err("Expected an Err value");
499        assert_eq!(err.kind(), ErrorKind::DataInvalid);
500        assert_eq!(
501            err.message(),
502            "int is not a valid input type of year transform"
503        );
504    }
505
506    #[test]
507    fn test_build_should_return_valid_sort_order() {
508        let schema = Schema::builder()
509            .with_schema_id(1)
510            .with_fields(vec![
511                NestedField::required(1, "foo", Type::Primitive(PrimitiveType::String)).into(),
512                NestedField::required(2, "bar", Type::Primitive(PrimitiveType::Int)).into(),
513            ])
514            .build()
515            .unwrap();
516
517        let sort_field = SortField::builder()
518            .source_id(2)
519            .direction(SortDirection::Ascending)
520            .null_order(NullOrder::First)
521            .transform(Transform::Identity)
522            .build();
523
524        let sort_order_builder_result = SortOrder::builder()
525            .with_sort_field(sort_field.clone())
526            .build(&schema);
527
528        assert_eq!(
529            sort_order_builder_result.expect("Expected an Ok value"),
530            SortOrder {
531                order_id: 1,
532                fields: vec![sort_field],
533            }
534        )
535    }
536}