Skip to main content

odbc_api/buffers/
description.rs

1use std::mem::size_of;
2
3use odbc_sys::{Date, Numeric, Time, Timestamp};
4
5use crate::{Bit, DataType};
6
7/// Describes a column of a [`crate::buffers::ColumnarBuffer`].
8///
9/// While related to the [`crate::DataType`] of the column this is bound to, the Buffer type is
10/// different as it does not describe the type of the data source but the format the data is going
11/// to be represented in memory. While the data source is often considered to choose the buffer
12/// type, the kind of processing which is supposed to be applied to the data may be even more
13/// important when choosing a buffer for the cursor type. E.g. if you intend to print a date to
14/// standard output, it may be more reasonable to bind it as `Text` rather than `Date`.
15#[derive(Clone, Copy, Debug, PartialEq, Eq)]
16pub enum BufferDesc {
17    /// Variable sized binary buffer, holding up to `max_bytes` bytes per value.
18    Binary {
19        /// Maximum number of bytes per value.
20        max_bytes: usize,
21    },
22    /// Text buffer holding strings with binary length of up to `max_str_len`.
23    ///
24    /// Consider an upper bound choosing this based on the information in a [`DataType::Varchar`]
25    /// column. E.g. PostgreSQL may return a field size of several GiB for individual values if a
26    /// column is specified as `TEXT`, or Microsoft SQL Server may return `0` for a column of type
27    /// `VARCHAR(max)`. In such situations, if values are truly that large, bulk fetching data is
28    /// not recommended, but streaming individual fields one by one. Usually though, the actual
29    /// cells of the table in the database contain much shorter values. The best thing todo is to
30    /// adapt the database schema to better reflect the actual size of the values. Lacking control
31    /// over the database schema, you can always choose a smaller buffer size than initializing the
32    /// buffer in disagreement with the database schema.
33    Text {
34        /// Maximum string length. Terminating zero is excluded, i.e. memory for it will be
35        /// implicitly allocated if required.
36        max_str_len: usize,
37    },
38    /// UTF-16 encoded text buffer holding strings with length of up to `max_str_len`. Length is in
39    /// terms of 2-Byte characters.
40    WText {
41        /// Maximum string length. Terminating zero is excluded, i.e. memory for it will be
42        /// implicitly allocated if required.
43        max_str_len: usize,
44    },
45    /// 64 bit floating point
46    F64 {
47        /// This indicates whether or not the buffer will be able to represent NULL values. This
48        /// will cause an indicator buffer to be bound.
49        nullable: bool,
50    },
51    /// 32 bit floating point
52    F32 {
53        /// This indicates whether or not the buffer will be able to represent NULL values. This
54        /// will cause an indicator buffer to be bound.
55        nullable: bool,
56    },
57    /// Describes a buffer holding [`crate::sys::Date`] values.
58    Date {
59        /// This indicates whether or not the buffer will be able to represent NULL values. This
60        /// will cause an indicator buffer to be bound.
61        nullable: bool,
62    },
63    /// Describes a buffer holding [`crate::sys::Time`] values.
64    Time {
65        /// This indicates whether or not the buffer will be able to represent NULL values. This
66        /// will cause an indicator buffer to be bound.
67        nullable: bool,
68    },
69    /// Describes a buffer holding [`crate::sys::Timestamp`] values.
70    Timestamp {
71        /// This indicates whether or not the buffer will be able to represent NULL values. This
72        /// will cause an indicator buffer to be bound.
73        nullable: bool,
74    },
75    /// Signed 8 Bit integer
76    I8 {
77        /// This indicates whether or not the buffer will be able to represent NULL values. This
78        /// will cause an indicator buffer to be bound.
79        nullable: bool,
80    },
81    /// Signed 16 Bit integer
82    I16 {
83        /// This indicates whether or not the buffer will be able to represent NULL values. This
84        /// will cause an indicator buffer to be bound.
85        nullable: bool,
86    },
87    /// Signed 32 Bit integer
88    I32 {
89        /// This indicates whether or not the buffer will be able to represent NULL values. This
90        /// will cause an indicator buffer to be bound.
91        nullable: bool,
92    },
93    /// Signed 64 Bit integer
94    I64 {
95        /// This indicates whether or not the buffer will be able to represent NULL values. This
96        /// will cause an indicator buffer to be bound.
97        nullable: bool,
98    },
99    /// Unsigned 8 Bit integer
100    U8 {
101        /// This indicates whether or not the buffer will be able to represent NULL values. This
102        /// will cause an indicator buffer to be bound.
103        nullable: bool,
104    },
105    /// Can either be zero or one
106    Bit {
107        /// This indicates whether or not the buffer will be able to represent NULL values. This
108        /// will cause an indicator buffer to be bound.
109        nullable: bool,
110    },
111    /// Use [`crate::sys::Numeric`] to represent Numeric values. Note that not all driver support
112    /// Numeric types. Even if they do, they may not respect the `scale` and `precision` values
113    /// unless they are explicit set in the Applicatior Parameter Descriptor (APD) for inserting or
114    /// the Application Row Descriptor (ARD). This currently would require unsafe code. Using text
115    /// buffers to insert or fetch Numeric values works more reliable.
116    ///
117    /// In my tests so far using Numeric buffers with PostgreSQL works for both inserting and
118    /// fetching values. With Microsoft SQL Server, it defaults to scale `0` and can not even be
119    /// changed by manipulating the ARD / APD via unsafe code for insertion.
120    ///
121    /// With MariaDB inserting works out of the box, yet fetching does default to scale `0` and
122    /// would require manipulating the ARD.
123    Numeric,
124}
125
126impl BufferDesc {
127    pub fn from_data_type(data_type: DataType, nullable: bool) -> Option<Self> {
128        let buffer_desc = match data_type {
129            DataType::Numeric { precision, scale }
130            | DataType::Decimal { precision, scale } if scale == 0 && precision < 3 => BufferDesc::I8 { nullable },
131            DataType::Numeric { precision, scale }
132            | DataType::Decimal { precision, scale } if scale == 0 && precision < 10 => BufferDesc::I32 { nullable },
133            DataType::Numeric { precision, scale }
134            | DataType::Decimal { precision, scale } if scale == 0 && precision < 19 => BufferDesc::I64 { nullable },
135            DataType::Integer => BufferDesc::I32 { nullable },
136            DataType::SmallInt => BufferDesc::I16 { nullable },
137            DataType::Float { precision: 0..=24 } | DataType::Real => BufferDesc::F32 { nullable },
138            DataType::Float { precision: 25..=53 } |DataType::Double => BufferDesc::F64 { nullable },
139            DataType::Date => BufferDesc::Date { nullable },
140            DataType::Time { precision: 0 } => BufferDesc::Time { nullable },
141            DataType::Timestamp { precision: _ } => BufferDesc::Timestamp { nullable },
142            DataType::BigInt => BufferDesc::I64 { nullable },
143            DataType::TinyInt => BufferDesc::I8 { nullable },
144            DataType::Bit => BufferDesc::Bit { nullable },
145            DataType::Varbinary { length }
146            | DataType::Binary { length  }
147            | DataType::LongVarbinary { length } => length.map(|l| BufferDesc::Binary { max_bytes: l.get() })?,
148            DataType::Varchar { length }
149            | DataType::WVarchar { length }
150            // Currently no special buffers for fixed lengths text implemented.
151            | DataType::WChar {length }
152            | DataType::Char { length }
153            | DataType::WLongVarchar { length }
154            | DataType::LongVarchar { length } => {
155                length.map(|length| BufferDesc::Text { max_str_len : length.get() } )?
156            },
157            // Specialized buffers for Numeric and decimal are not yet supported.
158            | DataType::Numeric { precision: _, scale: _ }
159            | DataType::Decimal { precision: _, scale: _ }
160            | DataType::Time { precision: _ } => BufferDesc::Text { max_str_len: data_type.display_size().unwrap().get() },
161            DataType::Unknown
162            | DataType::Float { precision: _ }
163            | DataType::Other { data_type: _, column_size: _, decimal_digits: _ } => return None,
164        };
165        Some(buffer_desc)
166    }
167
168    /// Element size of buffer if bound as a columnar row. Can be used to estimate memory for
169    /// columnar bindings.
170    pub fn bytes_per_row(&self) -> usize {
171        let size_indicator = |nullable: bool| if nullable { size_of::<isize>() } else { 0 };
172        match *self {
173            BufferDesc::Binary { max_bytes: length } => length + size_indicator(true),
174            BufferDesc::Text { max_str_len } => max_str_len + 1 + size_indicator(true),
175            BufferDesc::WText { max_str_len } => (max_str_len + 1) * 2 + size_indicator(true),
176            BufferDesc::F64 { nullable } => size_of::<f64>() + size_indicator(nullable),
177            BufferDesc::F32 { nullable } => size_of::<f32>() + size_indicator(nullable),
178            BufferDesc::Date { nullable } => size_of::<Date>() + size_indicator(nullable),
179            BufferDesc::Time { nullable } => size_of::<Time>() + size_indicator(nullable),
180            BufferDesc::Timestamp { nullable } => size_of::<Timestamp>() + size_indicator(nullable),
181            BufferDesc::I8 { nullable } => size_of::<i8>() + size_indicator(nullable),
182            BufferDesc::I16 { nullable } => size_of::<i16>() + size_indicator(nullable),
183            BufferDesc::I32 { nullable } => size_of::<i32>() + size_indicator(nullable),
184            BufferDesc::I64 { nullable } => size_of::<i64>() + size_indicator(nullable),
185            BufferDesc::U8 { nullable } => size_of::<u8>() + size_indicator(nullable),
186            BufferDesc::Bit { nullable } => size_of::<Bit>() + size_indicator(nullable),
187            BufferDesc::Numeric => size_of::<Numeric>(),
188        }
189    }
190}
191
192#[cfg(test)]
193mod tests {
194
195    use super::*;
196
197    #[test]
198    #[cfg(target_pointer_width = "64")] // Indicator size is platform dependent.
199    fn bytes_per_row() {
200        assert_eq!(5 + 8, BufferDesc::Binary { max_bytes: 5 }.bytes_per_row());
201        assert_eq!(
202            5 + 1 + 8,
203            BufferDesc::Text { max_str_len: 5 }.bytes_per_row()
204        );
205        assert_eq!(
206            10 + 2 + 8,
207            BufferDesc::WText { max_str_len: 5 }.bytes_per_row()
208        );
209        assert_eq!(6, BufferDesc::Date { nullable: false }.bytes_per_row());
210        assert_eq!(6, BufferDesc::Time { nullable: false }.bytes_per_row());
211        assert_eq!(
212            16,
213            BufferDesc::Timestamp { nullable: false }.bytes_per_row()
214        );
215        assert_eq!(1, BufferDesc::Bit { nullable: false }.bytes_per_row());
216        assert_eq!(1 + 8, BufferDesc::Bit { nullable: true }.bytes_per_row());
217        assert_eq!(4, BufferDesc::F32 { nullable: false }.bytes_per_row());
218        assert_eq!(8, BufferDesc::F64 { nullable: false }.bytes_per_row());
219        assert_eq!(1, BufferDesc::I8 { nullable: false }.bytes_per_row());
220        assert_eq!(2, BufferDesc::I16 { nullable: false }.bytes_per_row());
221        assert_eq!(4, BufferDesc::I32 { nullable: false }.bytes_per_row());
222        assert_eq!(8, BufferDesc::I64 { nullable: false }.bytes_per_row());
223        assert_eq!(1, BufferDesc::U8 { nullable: false }.bytes_per_row());
224        assert_eq!(19, BufferDesc::Numeric.bytes_per_row());
225    }
226}