iceberg_rust_spec/spec/
sort.rs

1/*!
2 * Sort order specification for Iceberg tables
3 *
4 * This module defines the sort ordering capabilities of Iceberg tables, including:
5 * - Sort direction (ascending/descending)
6 * - Null ordering (nulls first/last)
7 * - Sort fields that specify which columns to sort by
8 * - Sort order specifications that combine multiple sort fields
9 *
10 * Sort orders are used to:
11 * - Optimize data layout for efficient querying
12 * - Support range predicates and partition pruning
13 * - Enable merge-on-read operations
14 */
15
16use std::{fmt, str};
17
18use derive_builder::Builder;
19use serde::{Deserialize, Serialize};
20
21use crate::error::Error;
22
23use super::partition::Transform;
24
25pub static DEFAULT_SORT_ORDER_ID: i32 = 0;
26
27#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
28/// Sort direction in a partition, either ascending or descending
29pub enum SortDirection {
30    /// Ascending
31    #[serde(rename = "asc")]
32    Ascending,
33    /// Descending
34    #[serde(rename = "desc")]
35    Descending,
36}
37
38#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
39/// Describes the order of null values when sorted.
40pub enum NullOrder {
41    #[serde(rename = "nulls-first")]
42    /// Nulls are stored first
43    First,
44    #[serde(rename = "nulls-last")]
45    /// Nulls are stored last
46    Last,
47}
48
49#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
50#[serde(rename_all = "kebab-case")]
51/// Entry for every column that is to be sorted
52pub struct SortField {
53    /// A source column id from the table’s schema
54    pub source_id: i32,
55    /// A transform that is used to produce values to be sorted on from the source column.
56    pub transform: Transform,
57    /// A sort direction, that can only be either asc or desc
58    pub direction: SortDirection,
59    /// A null order that describes the order of null values when sorted.
60    pub null_order: NullOrder,
61}
62
63#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Default, Builder)]
64#[serde(rename_all = "kebab-case")]
65#[builder(setter(prefix = "with"))]
66/// A sort order is defined by a sort order id and a list of sort fields.
67/// The order of the sort fields within the list defines the order in which the sort is applied to the data.
68pub struct SortOrder {
69    /// Identifier for SortOrder, order_id `0` is no sort order.
70    #[builder(default = "DEFAULT_SORT_ORDER_ID")]
71    pub order_id: i32,
72    #[builder(setter(each(name = "with_sort_field")))]
73    /// Details of the sort
74    pub fields: Vec<SortField>,
75}
76
77impl fmt::Display for SortOrder {
78    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79        write!(
80            f,
81            "{}",
82            &serde_json::to_string(self).map_err(|_| fmt::Error)?,
83        )
84    }
85}
86
87impl str::FromStr for SortOrder {
88    type Err = Error;
89    fn from_str(s: &str) -> Result<Self, Self::Err> {
90        serde_json::from_str(s).map_err(Error::from)
91    }
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97
98    #[test]
99    fn sort_field() {
100        let sort_field = r#"
101        {
102            "transform": "bucket[4]",
103            "source-id": 3,
104            "direction": "desc",
105            "null-order": "nulls-last"
106         }
107        "#;
108
109        let field: SortField = serde_json::from_str(sort_field).unwrap();
110        assert_eq!(Transform::Bucket(4), field.transform);
111        assert_eq!(3, field.source_id);
112        assert_eq!(SortDirection::Descending, field.direction);
113        assert_eq!(NullOrder::Last, field.null_order);
114    }
115
116    #[test]
117    fn sort_order() {
118        let sort_order = r#"
119        {
120        "order-id": 1,
121        "fields": [ {
122            "transform": "identity",
123            "source-id": 2,
124            "direction": "asc",
125            "null-order": "nulls-first"
126         }, {
127            "transform": "bucket[4]",
128            "source-id": 3,
129            "direction": "desc",
130            "null-order": "nulls-last"
131         } ]
132        }
133        "#;
134
135        let order: SortOrder = serde_json::from_str(sort_order).unwrap();
136        assert_eq!(Transform::Identity, order.fields[0].transform);
137        assert_eq!(2, order.fields[0].source_id);
138        assert_eq!(SortDirection::Ascending, order.fields[0].direction);
139        assert_eq!(NullOrder::First, order.fields[0].null_order);
140
141        assert_eq!(Transform::Bucket(4), order.fields[1].transform);
142        assert_eq!(3, order.fields[1].source_id);
143        assert_eq!(SortDirection::Descending, order.fields[1].direction);
144        assert_eq!(NullOrder::Last, order.fields[1].null_order);
145    }
146}