1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
use std::convert::TryInto;

use arrow::datatypes::{DataType as ArrowDataType, Field, Schema, TimeUnit};
use odbc_api::{ColumnDescription, DataType as OdbcDataType, ResultSetMetadata};

use crate::Error;

/// Query the metadata to create an arrow schema. This method is invoked automatically for you by
/// [`crate::OdbcReader::new`]. You may want to call this method in situtation ther you want to
/// create an arrow schema without creating the reader yet.
///
/// # Example
///
/// ```
/// use anyhow::Error;
///
/// use arrow_odbc::{arrow_schema_from, arrow::datatypes::Schema, odbc_api::Connection};
///
/// fn fetch_schema_for_table(
///     table_name: &str,
///     connection: &Connection<'_>
/// ) -> Result<Schema, Error> {
///     // Query column with values to get a cursor
///     let sql = format!("SELECT * FROM {}", table_name);
///     let prepared = connection.prepare(&sql)?;
///     
///     // Now that we have prepared statement, we want to use it to query metadata.
///     let schema = arrow_schema_from(&prepared)?;
///     Ok(schema)
/// }
/// ```
pub fn arrow_schema_from(resut_set_metadata: &impl ResultSetMetadata) -> Result<Schema, Error> {
    let num_cols: u16 = resut_set_metadata
        .num_result_cols()
        .map_err(Error::UnableToRetrieveNumCols)?
        .try_into()
        .unwrap();
    let mut fields = Vec::new();
    for index in 0..num_cols {
        let mut column_description = ColumnDescription::default();
        resut_set_metadata
            .describe_col(index + 1, &mut column_description)
            .map_err(Error::FailedToDescribeColumn)?;

        let field = Field::new(
            &column_description
                .name_to_string()
                .expect("Column name must be representable in utf8"),
            match column_description.data_type {
                OdbcDataType::Numeric {
                    precision: p @ 0..=38,
                    scale,
                }
                | OdbcDataType::Decimal {
                    precision: p @ 0..=38,
                    scale,
                } => ArrowDataType::Decimal(p, scale.try_into().unwrap()),
                OdbcDataType::Integer => ArrowDataType::Int32,
                OdbcDataType::SmallInt => ArrowDataType::Int16,
                OdbcDataType::Real | OdbcDataType::Float { precision: 0..=24 } => {
                    ArrowDataType::Float32
                }
                OdbcDataType::Float { precision: _ } | OdbcDataType::Double => {
                    ArrowDataType::Float64
                }
                OdbcDataType::Date => ArrowDataType::Date32,
                OdbcDataType::Timestamp { precision: 0 } => {
                    ArrowDataType::Timestamp(TimeUnit::Second, None)
                }
                OdbcDataType::Timestamp { precision: 1..=3 } => {
                    ArrowDataType::Timestamp(TimeUnit::Millisecond, None)
                }
                OdbcDataType::Timestamp { precision: 4..=6 } => {
                    ArrowDataType::Timestamp(TimeUnit::Microsecond, None)
                }
                OdbcDataType::Timestamp { precision: _ } => {
                    ArrowDataType::Timestamp(TimeUnit::Nanosecond, None)
                }
                OdbcDataType::BigInt => ArrowDataType::Int64,
                OdbcDataType::TinyInt => ArrowDataType::Int8,
                OdbcDataType::Bit => ArrowDataType::Boolean,
                OdbcDataType::Binary { length } => {
                    ArrowDataType::FixedSizeBinary(length.try_into().unwrap())
                }
                OdbcDataType::LongVarbinary { length: _ }
                | OdbcDataType::Varbinary { length: _ } => ArrowDataType::Binary,
                OdbcDataType::Unknown
                | OdbcDataType::Time { precision: _ }
                | OdbcDataType::Numeric { .. }
                | OdbcDataType::Decimal { .. }
                | OdbcDataType::Other {
                    data_type: _,
                    column_size: _,
                    decimal_digits: _,
                }
                | OdbcDataType::WChar { length: _ }
                | OdbcDataType::Char { length: _ }
                | OdbcDataType::WVarchar { length: _ }
                | OdbcDataType::LongVarchar { length: _ }
                | OdbcDataType::Varchar { length: _ } => ArrowDataType::Utf8,
            },
            column_description.could_be_nullable(),
        );

        fields.push(field)
    }
    Ok(Schema::new(fields))
}