Skip to main content

acta/
schema.rs

1//! Immutable public schema metadata reconstructed from a v0.2 schema frame.
2
3/// The logical unit of a `timestamp64` column.
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum TimeUnit {
6    Second,
7    Millisecond,
8    Microsecond,
9    Nanosecond,
10}
11
12/// The timezone annotation of a `timestamp64` column.
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub enum TimeZone {
15    Naive,
16    Utc,
17    Iana(String),
18}
19
20/// A v0.2 logical column type.
21#[derive(Debug, Clone, PartialEq, Eq)]
22pub enum LogicalType {
23    Bool,
24    Int8,
25    Int16,
26    Int32,
27    Int64,
28    UInt8,
29    UInt16,
30    UInt32,
31    UInt64,
32    Float32,
33    Float64,
34    Decimal { precision: u16, scale: i16 },
35    Timestamp { unit: TimeUnit, timezone: TimeZone },
36    Utf8,
37    Categorical { ordered: bool },
38    Binary,
39    FixedBinary { byte_width: u32 },
40    Date32,
41}
42
43/// One immutable column in a [`Schema`].
44#[derive(Debug, Clone, PartialEq, Eq)]
45pub struct Column {
46    id: u32,
47    name: String,
48    logical_type: LogicalType,
49    nullable: bool,
50}
51
52impl Column {
53    /// Construct a column descriptor.
54    ///
55    /// The writer checks the complete schema invariants, including nonzero
56    /// unique IDs, unique names, and primary-column compatibility, before it
57    /// creates a file.
58    pub fn new(
59        id: u32,
60        name: impl Into<String>,
61        logical_type: LogicalType,
62        nullable: bool,
63    ) -> Self {
64        Self {
65            id,
66            name: name.into(),
67            logical_type,
68            nullable,
69        }
70    }
71
72    /// The stable nonzero column ID stored in the file.
73    pub fn id(&self) -> u32 {
74        self.id
75    }
76
77    /// The UTF-8 column name.
78    pub fn name(&self) -> &str {
79        &self.name
80    }
81
82    /// The column's logical type.
83    pub fn logical_type(&self) -> &LogicalType {
84        &self.logical_type
85    }
86
87    /// Whether the column may contain null values.
88    pub fn is_nullable(&self) -> bool {
89        self.nullable
90    }
91}
92
93/// The fixed schema shared by every block in an Acta file.
94#[derive(Debug, Clone, PartialEq, Eq)]
95pub struct Schema {
96    schema_id: u64,
97    columns: Vec<Column>,
98    primary_column_id: Option<u32>,
99}
100
101impl Schema {
102    /// Construct immutable schema metadata.
103    ///
104    /// Schema validation that depends on the file format is performed by
105    /// [`crate::Writer::create`].
106    pub fn new(schema_id: u64, columns: Vec<Column>, primary_column_id: Option<u32>) -> Self {
107        Self {
108            schema_id,
109            columns,
110            primary_column_id,
111        }
112    }
113
114    /// The nonzero schema ID stored in the schema and data headers.
115    pub fn schema_id(&self) -> u64 {
116        self.schema_id
117    }
118
119    /// The columns in schema order, which is the order the file declares them
120    /// and the order a reader reports them.
121    ///
122    /// Section 7 does not require that order to be sorted by column ID. Only a
123    /// data frame's column table is sorted, and the writer sorts it there.
124    pub fn columns(&self) -> &[Column] {
125        &self.columns
126    }
127
128    /// The number of declared columns.
129    pub fn column_count(&self) -> usize {
130        self.columns.len()
131    }
132
133    /// The selected primary timestamp/date column, if the schema has one.
134    pub fn primary_column(&self) -> Option<&Column> {
135        self.primary_column_id.and_then(|id| self.column_by_id(id))
136    }
137
138    /// The selected primary timestamp/date column ID, if present.
139    pub fn primary_column_id(&self) -> Option<u32> {
140        self.primary_column_id
141    }
142
143    /// Find a column by its stable ID.
144    pub fn column_by_id(&self, id: u32) -> Option<&Column> {
145        self.columns.iter().find(|column| column.id == id)
146    }
147
148    /// Find a column by its exact UTF-8 name.
149    pub fn column_by_name(&self, name: &str) -> Option<&Column> {
150        self.columns.iter().find(|column| column.name == name)
151    }
152}