Skip to main content

hdbconnect_arrow/schema/
mapping.rs

1//! HANA to Arrow schema mapping.
2//!
3//! Converts HANA `ResultSet` metadata to Arrow Schema.
4
5use std::sync::Arc;
6
7use arrow_schema::{Field, Schema, SchemaRef};
8
9/// Schema mapper for converting HANA metadata to Arrow schema.
10///
11/// Provides utilities for building Arrow schemas from HANA `ResultSet` metadata.
12///
13/// # Example
14///
15/// ```rust,ignore
16/// use hdbconnect_arrow::schema::SchemaMapper;
17///
18/// let schema = SchemaMapper::from_result_set(&result_set);
19/// let fields = schema.fields();
20/// ```
21#[derive(Debug, Clone, Default)]
22pub struct SchemaMapper;
23
24impl SchemaMapper {
25    /// Create a new schema mapper.
26    #[must_use]
27    pub const fn new() -> Self {
28        Self
29    }
30
31    /// Build an Arrow schema from HANA `ResultSet` metadata.
32    ///
33    /// # Arguments
34    ///
35    /// * `result_set` - The HANA `ResultSet` to extract metadata from
36    #[must_use]
37    pub fn from_result_set(result_set: &hdbconnect::ResultSet) -> Schema {
38        // ResultSetMetadata derefs to Vec<FieldMetadata>
39        let metadata = result_set.metadata();
40        let fields: Vec<Field> = metadata
41            .iter()
42            .map(super::super::types::arrow::FieldMetadataExt::to_arrow_field)
43            .collect();
44
45        Schema::new(fields)
46    }
47
48    /// Build an Arrow schema from a slice of HANA `FieldMetadata`.
49    ///
50    /// # Arguments
51    ///
52    /// * `metadata` - Slice of HANA field metadata
53    #[must_use]
54    pub fn from_field_metadata(metadata: &[hdbconnect::FieldMetadata]) -> Schema {
55        let fields: Vec<Field> = metadata
56            .iter()
57            .map(super::super::types::arrow::FieldMetadataExt::to_arrow_field)
58            .collect();
59
60        Schema::new(fields)
61    }
62
63    /// Build an Arrow `SchemaRef` from HANA `ResultSet` metadata.
64    ///
65    /// Returns an `Arc<Schema>` for efficient sharing.
66    #[must_use]
67    pub fn schema_ref_from_result_set(result_set: &hdbconnect::ResultSet) -> SchemaRef {
68        Arc::new(Self::from_result_set(result_set))
69    }
70
71    /// Build an Arrow `SchemaRef` from HANA field metadata.
72    ///
73    /// Returns an `Arc<Schema>` for efficient sharing.
74    #[must_use]
75    pub fn schema_ref_from_field_metadata(metadata: &[hdbconnect::FieldMetadata]) -> SchemaRef {
76        Arc::new(Self::from_field_metadata(metadata))
77    }
78}
79
80/// Extension trait for building Arrow Schema from HANA metadata.
81pub trait SchemaFromHana {
82    /// Build an Arrow schema from HANA field metadata.
83    fn from_hana_metadata(metadata: &[hdbconnect::FieldMetadata]) -> Schema;
84}
85
86impl SchemaFromHana for Schema {
87    fn from_hana_metadata(metadata: &[hdbconnect::FieldMetadata]) -> Schema {
88        SchemaMapper::from_field_metadata(metadata)
89    }
90}
91
92#[cfg(test)]
93mod tests {
94    use std::mem::{size_of, size_of_val};
95
96    use super::*;
97
98    // ═══════════════════════════════════════════════════════════════════════════
99    // SchemaMapper Creation Tests
100    // ═══════════════════════════════════════════════════════════════════════════
101
102    #[test]
103    fn test_schema_mapper_new() {
104        let mapper = SchemaMapper::new();
105        assert!(size_of_val(&mapper) == 0);
106    }
107
108    #[test]
109    fn test_schema_mapper_default() {
110        let mapper = SchemaMapper::default();
111        assert!(size_of_val(&mapper) == 0);
112    }
113
114    #[test]
115    fn test_schema_mapper_is_zero_sized() {
116        assert_eq!(size_of::<SchemaMapper>(), 0);
117    }
118
119    #[test]
120    fn test_schema_mapper_clone() {
121        let mapper1 = SchemaMapper::new();
122        #[allow(clippy::clone_on_copy)]
123        let mapper2 = mapper1.clone();
124        assert!(size_of_val(&mapper2) == 0);
125    }
126
127    #[test]
128    fn test_schema_mapper_debug() {
129        let mapper = SchemaMapper::new();
130        let debug_str = format!("{:?}", mapper);
131        assert!(debug_str.contains("SchemaMapper"));
132    }
133
134    // ═══════════════════════════════════════════════════════════════════════════
135    // SchemaMapper Const Construction Tests
136    // ═══════════════════════════════════════════════════════════════════════════
137
138    #[test]
139    fn test_schema_mapper_const_new() {
140        const _MAPPER: SchemaMapper = SchemaMapper::new();
141    }
142
143    // ═══════════════════════════════════════════════════════════════════════════
144    // Arrow Field Helper Tests (using hana_field_to_arrow directly)
145    // ═══════════════════════════════════════════════════════════════════════════
146
147    #[test]
148    fn test_arrow_field_int32() {
149        use arrow_schema::DataType;
150        use hdbconnect::TypeId;
151
152        use crate::types::arrow::hana_field_to_arrow;
153
154        let field = hana_field_to_arrow("id", TypeId::INT, false, None, None);
155        assert_eq!(field.name(), "id");
156        assert!(!field.is_nullable());
157        assert_eq!(field.data_type(), &DataType::Int32);
158    }
159
160    #[test]
161    fn test_arrow_field_nullable() {
162        use arrow_schema::DataType;
163        use hdbconnect::TypeId;
164
165        use crate::types::arrow::hana_field_to_arrow;
166
167        let field = hana_field_to_arrow("value", TypeId::INT, true, None, None);
168        assert!(field.is_nullable());
169        assert_eq!(field.data_type(), &DataType::Int32);
170    }
171
172    #[test]
173    fn test_arrow_field_decimal() {
174        use arrow_schema::DataType;
175        use hdbconnect::TypeId;
176
177        use crate::types::arrow::hana_field_to_arrow;
178
179        let field = hana_field_to_arrow("amount", TypeId::DECIMAL, false, Some(18), Some(2));
180        assert_eq!(field.data_type(), &DataType::Decimal128(18, 2));
181    }
182
183    #[test]
184    fn test_arrow_field_varchar() {
185        use arrow_schema::DataType;
186        use hdbconnect::TypeId;
187
188        use crate::types::arrow::hana_field_to_arrow;
189
190        let field = hana_field_to_arrow("name", TypeId::VARCHAR, true, None, None);
191        assert_eq!(field.data_type(), &DataType::Utf8);
192    }
193
194    #[test]
195    fn test_arrow_field_clob() {
196        use arrow_schema::DataType;
197        use hdbconnect::TypeId;
198
199        use crate::types::arrow::hana_field_to_arrow;
200
201        let field = hana_field_to_arrow("content", TypeId::CLOB, true, None, None);
202        assert_eq!(field.data_type(), &DataType::LargeUtf8);
203    }
204
205    #[test]
206    fn test_arrow_field_blob() {
207        use arrow_schema::DataType;
208        use hdbconnect::TypeId;
209
210        use crate::types::arrow::hana_field_to_arrow;
211
212        let field = hana_field_to_arrow("data", TypeId::BLOB, true, None, None);
213        assert_eq!(field.data_type(), &DataType::LargeBinary);
214    }
215
216    #[test]
217    fn test_arrow_field_date() {
218        use arrow_schema::DataType;
219        use hdbconnect::TypeId;
220
221        use crate::types::arrow::hana_field_to_arrow;
222
223        let field = hana_field_to_arrow("created", TypeId::DAYDATE, true, None, None);
224        assert_eq!(field.data_type(), &DataType::Date32);
225    }
226
227    #[test]
228    fn test_arrow_field_timestamp() {
229        use arrow_schema::{DataType, TimeUnit};
230        use hdbconnect::TypeId;
231
232        use crate::types::arrow::hana_field_to_arrow;
233
234        let field = hana_field_to_arrow("updated", TypeId::LONGDATE, true, None, None);
235        assert_eq!(
236            field.data_type(),
237            &DataType::Timestamp(TimeUnit::Nanosecond, None)
238        );
239    }
240
241    #[test]
242    fn test_arrow_field_boolean() {
243        use arrow_schema::DataType;
244        use hdbconnect::TypeId;
245
246        use crate::types::arrow::hana_field_to_arrow;
247
248        let field = hana_field_to_arrow("active", TypeId::BOOLEAN, false, None, None);
249        assert_eq!(field.data_type(), &DataType::Boolean);
250    }
251
252    #[test]
253    fn test_arrow_field_tinyint() {
254        use arrow_schema::DataType;
255        use hdbconnect::TypeId;
256
257        use crate::types::arrow::hana_field_to_arrow;
258
259        let field = hana_field_to_arrow("tiny", TypeId::TINYINT, false, None, None);
260        assert_eq!(field.data_type(), &DataType::UInt8);
261    }
262
263    #[test]
264    fn test_arrow_field_smallint() {
265        use arrow_schema::DataType;
266        use hdbconnect::TypeId;
267
268        use crate::types::arrow::hana_field_to_arrow;
269
270        let field = hana_field_to_arrow("small", TypeId::SMALLINT, false, None, None);
271        assert_eq!(field.data_type(), &DataType::Int16);
272    }
273
274    #[test]
275    fn test_arrow_field_bigint() {
276        use arrow_schema::DataType;
277        use hdbconnect::TypeId;
278
279        use crate::types::arrow::hana_field_to_arrow;
280
281        let field = hana_field_to_arrow("big", TypeId::BIGINT, false, None, None);
282        assert_eq!(field.data_type(), &DataType::Int64);
283    }
284
285    #[test]
286    fn test_arrow_field_real() {
287        use arrow_schema::DataType;
288        use hdbconnect::TypeId;
289
290        use crate::types::arrow::hana_field_to_arrow;
291
292        let field = hana_field_to_arrow("real_val", TypeId::REAL, false, None, None);
293        assert_eq!(field.data_type(), &DataType::Float32);
294    }
295
296    #[test]
297    fn test_arrow_field_double() {
298        use arrow_schema::DataType;
299        use hdbconnect::TypeId;
300
301        use crate::types::arrow::hana_field_to_arrow;
302
303        let field = hana_field_to_arrow("double_val", TypeId::DOUBLE, false, None, None);
304        assert_eq!(field.data_type(), &DataType::Float64);
305    }
306
307    #[test]
308    fn test_arrow_field_binary() {
309        use arrow_schema::DataType;
310        use hdbconnect::TypeId;
311
312        use crate::types::arrow::hana_field_to_arrow;
313
314        let field = hana_field_to_arrow("bin", TypeId::BINARY, true, None, None);
315        assert_eq!(field.data_type(), &DataType::Binary);
316    }
317
318    #[test]
319    fn test_arrow_field_time() {
320        use arrow_schema::{DataType, TimeUnit};
321        use hdbconnect::TypeId;
322
323        use crate::types::arrow::hana_field_to_arrow;
324
325        let field = hana_field_to_arrow("time", TypeId::SECONDTIME, true, None, None);
326        assert_eq!(field.data_type(), &DataType::Time64(TimeUnit::Nanosecond));
327    }
328
329    #[test]
330    fn test_arrow_field_geometry() {
331        use arrow_schema::DataType;
332        use hdbconnect::TypeId;
333
334        use crate::types::arrow::hana_field_to_arrow;
335
336        let field = hana_field_to_arrow("geom", TypeId::GEOMETRY, true, None, None);
337        assert_eq!(field.data_type(), &DataType::Binary);
338    }
339
340    #[test]
341    fn test_arrow_field_point() {
342        use arrow_schema::DataType;
343        use hdbconnect::TypeId;
344
345        use crate::types::arrow::hana_field_to_arrow;
346
347        let field = hana_field_to_arrow("pt", TypeId::POINT, true, None, None);
348        assert_eq!(field.data_type(), &DataType::Binary);
349    }
350
351    // ═══════════════════════════════════════════════════════════════════════════
352    // Field Name Edge Cases
353    // ═══════════════════════════════════════════════════════════════════════════
354
355    #[test]
356    fn test_arrow_field_empty_name() {
357        use hdbconnect::TypeId;
358
359        use crate::types::arrow::hana_field_to_arrow;
360
361        let field = hana_field_to_arrow("", TypeId::INT, false, None, None);
362        assert_eq!(field.name(), "");
363    }
364
365    #[test]
366    fn test_arrow_field_special_chars_in_name() {
367        use hdbconnect::TypeId;
368
369        use crate::types::arrow::hana_field_to_arrow;
370
371        let field = hana_field_to_arrow("col-name_123", TypeId::INT, false, None, None);
372        assert_eq!(field.name(), "col-name_123");
373    }
374
375    #[test]
376    fn test_arrow_field_unicode_name() {
377        use hdbconnect::TypeId;
378
379        use crate::types::arrow::hana_field_to_arrow;
380
381        let field = hana_field_to_arrow("列名", TypeId::VARCHAR, true, None, None);
382        assert_eq!(field.name(), "列名");
383    }
384}