Skip to main content

exarrow_rs/types/
schema.rs

1//! Schema building utilities for converting Exasol result metadata to Arrow schemas.
2
3use crate::error::ConversionError;
4use crate::types::{ExasolType, TypeMapper};
5use arrow::datatypes::{Field, Schema};
6
7/// Column metadata from Exasol result set.
8#[derive(Debug, Clone)]
9pub struct ColumnMetadata {
10    /// Column name
11    pub name: String,
12    /// Exasol data type
13    pub data_type: ExasolType,
14    /// Whether the column is nullable
15    pub nullable: bool,
16}
17
18/// Builder for constructing Arrow schemas from Exasol metadata.
19pub struct SchemaBuilder {
20    columns: Vec<ColumnMetadata>,
21}
22
23impl SchemaBuilder {
24    /// Create a new schema builder.
25    pub fn new() -> Self {
26        Self {
27            columns: Vec::new(),
28        }
29    }
30
31    /// Add a column to the schema.
32    pub fn add_column(mut self, column: ColumnMetadata) -> Self {
33        self.columns.push(column);
34        self
35    }
36
37    /// Add multiple columns to the schema.
38    pub fn add_columns(mut self, columns: Vec<ColumnMetadata>) -> Self {
39        self.columns.extend(columns);
40        self
41    }
42
43    /// Build the Arrow schema.
44    ///
45    /// # Returns
46    /// An Arrow Schema with all columns converted to Arrow types
47    ///
48    /// # Errors
49    /// Returns `ConversionError` if any column type cannot be mapped
50    pub fn build(self) -> Result<Schema, ConversionError> {
51        let fields: Result<Vec<Field>, ConversionError> = self
52            .columns
53            .iter()
54            .map(|col| {
55                let arrow_type = TypeMapper::exasol_to_arrow(&col.data_type, col.nullable)?;
56                let metadata = TypeMapper::create_field_metadata(&col.data_type);
57
58                Ok(Field::new(&col.name, arrow_type, col.nullable).with_metadata(metadata))
59            })
60            .collect();
61
62        Ok(Schema::new(fields?))
63    }
64
65    /// Build the Arrow schema with additional schema-level metadata.
66    pub fn build_with_metadata(
67        self,
68        metadata: std::collections::HashMap<String, String>,
69    ) -> Result<Schema, ConversionError> {
70        let schema = self.build()?;
71        Ok(schema.with_metadata(metadata))
72    }
73}
74
75impl Default for SchemaBuilder {
76    fn default() -> Self {
77        Self::new()
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84
85    #[test]
86    fn test_schema_builder() {
87        let schema = SchemaBuilder::new()
88            .add_column(ColumnMetadata {
89                name: "id".to_string(),
90                data_type: ExasolType::Decimal {
91                    precision: 18,
92                    scale: 0,
93                },
94                nullable: false,
95            })
96            .add_column(ColumnMetadata {
97                name: "name".to_string(),
98                data_type: ExasolType::Varchar { size: 100 },
99                nullable: true,
100            })
101            .add_column(ColumnMetadata {
102                name: "price".to_string(),
103                data_type: ExasolType::Decimal {
104                    precision: 10,
105                    scale: 2,
106                },
107                nullable: true,
108            })
109            .build()
110            .unwrap();
111
112        assert_eq!(schema.fields().len(), 3);
113        assert_eq!(schema.field(0).name(), "id");
114        assert_eq!(schema.field(1).name(), "name");
115        assert_eq!(schema.field(2).name(), "price");
116
117        assert!(!schema.field(0).is_nullable());
118        assert!(schema.field(1).is_nullable());
119    }
120
121    #[test]
122    fn test_schema_with_metadata() {
123        let mut metadata = std::collections::HashMap::new();
124        metadata.insert("source".to_string(), "exasol".to_string());
125
126        let schema = SchemaBuilder::new()
127            .add_column(ColumnMetadata {
128                name: "id".to_string(),
129                data_type: ExasolType::Decimal {
130                    precision: 18,
131                    scale: 0,
132                },
133                nullable: false,
134            })
135            .build_with_metadata(metadata.clone())
136            .unwrap();
137
138        assert_eq!(schema.metadata().get("source"), Some(&"exasol".to_string()));
139    }
140}