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 `length` bytes per value.
18    Binary {
19        /// Maximum number of bytes per value.
20        length: 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 [`sys::Numeric`] to represent Numeric values. Note that not all driver support Numeric
112    /// types. Even if they do, they may not respect the `scale` and `precision` values unless they
113    /// are explicit set in the Applicatior Parameter Descriptor (APD) for inserting or the
114    /// 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 only be changed
119    /// by manipulating the ARD / APD via unsafe code.
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        /// Total number of significant digits.
125        precision: usize,
126        /// Number of digits to the right of the decimal point.
127        scale: i16,
128    },
129}
130
131impl BufferDesc {
132    pub fn from_data_type(data_type: DataType, nullable: bool) -> Option<Self> {
133        let buffer_desc = match data_type {
134            DataType::Numeric { precision, scale }
135            | DataType::Decimal { precision, scale } if scale == 0 && precision < 3 => BufferDesc::I8 { nullable },
136            DataType::Numeric { precision, scale }
137            | DataType::Decimal { precision, scale } if scale == 0 && precision < 10 => BufferDesc::I32 { nullable },
138            DataType::Numeric { precision, scale }
139            | DataType::Decimal { precision, scale } if scale == 0 && precision < 19 => BufferDesc::I64 { nullable },
140            DataType::Integer => BufferDesc::I32 { nullable },
141            DataType::SmallInt => BufferDesc::I16 { nullable },
142            DataType::Float { precision: 0..=24 } | DataType::Real => BufferDesc::F32 { nullable },
143            DataType::Float { precision: 25..=53 } |DataType::Double => BufferDesc::F64 { nullable },
144            DataType::Date => BufferDesc::Date { nullable },
145            DataType::Time { precision: 0 } => BufferDesc::Time { nullable },
146            DataType::Timestamp { precision: _ } => BufferDesc::Timestamp { nullable },
147            DataType::BigInt => BufferDesc::I64 { nullable },
148            DataType::TinyInt => BufferDesc::I8 { nullable },
149            DataType::Bit => BufferDesc::Bit { nullable },
150            DataType::Varbinary { length }
151            | DataType::Binary { length  }
152            | DataType::LongVarbinary { length } => length.map(|l| BufferDesc::Binary { length: l.get() })?,
153            DataType::Varchar { length }
154            | DataType::WVarchar { length }
155            // Currently no special buffers for fixed lengths text implemented.
156            | DataType::WChar {length }
157            | DataType::Char { length }
158            | DataType::WLongVarchar { length }
159            | DataType::LongVarchar { length } => {
160                length.map(|length| BufferDesc::Text { max_str_len : length.get() } )?
161            },
162            // Specialized buffers for Numeric and decimal are not yet supported.
163            | DataType::Numeric { precision: _, scale: _ }
164            | DataType::Decimal { precision: _, scale: _ }
165            | DataType::Time { precision: _ } => BufferDesc::Text { max_str_len: data_type.display_size().unwrap().get() },
166            DataType::Unknown
167            | DataType::Float { precision: _ }
168            | DataType::Other { data_type: _, column_size: _, decimal_digits: _ } => return None,
169        };
170        Some(buffer_desc)
171    }
172
173    /// Element size of buffer if bound as a columnar row. Can be used to estimate memory for
174    /// columnar bindings.
175    pub fn bytes_per_row(&self) -> usize {
176        let size_indicator = |nullable: bool| if nullable { size_of::<isize>() } else { 0 };
177        match *self {
178            BufferDesc::Binary { length } => length + size_indicator(true),
179            BufferDesc::Text { max_str_len } => max_str_len + 1 + size_indicator(true),
180            BufferDesc::WText { max_str_len } => (max_str_len + 1) * 2 + size_indicator(true),
181            BufferDesc::F64 { nullable } => size_of::<f64>() + size_indicator(nullable),
182            BufferDesc::F32 { nullable } => size_of::<f32>() + size_indicator(nullable),
183            BufferDesc::Date { nullable } => size_of::<Date>() + size_indicator(nullable),
184            BufferDesc::Time { nullable } => size_of::<Time>() + size_indicator(nullable),
185            BufferDesc::Timestamp { nullable } => size_of::<Timestamp>() + size_indicator(nullable),
186            BufferDesc::I8 { nullable } => size_of::<i8>() + size_indicator(nullable),
187            BufferDesc::I16 { nullable } => size_of::<i16>() + size_indicator(nullable),
188            BufferDesc::I32 { nullable } => size_of::<i32>() + size_indicator(nullable),
189            BufferDesc::I64 { nullable } => size_of::<i64>() + size_indicator(nullable),
190            BufferDesc::U8 { nullable } => size_of::<u8>() + size_indicator(nullable),
191            BufferDesc::Bit { nullable } => size_of::<Bit>() + size_indicator(nullable),
192            BufferDesc::Numeric {
193                precision: _,
194                scale: _,
195            } => size_of::<Numeric>(),
196        }
197    }
198}
199
200#[cfg(test)]
201mod tests {
202
203    use super::*;
204
205    #[test]
206    #[cfg(target_pointer_width = "64")] // Indicator size is platform dependent.
207    fn bytes_per_row() {
208        assert_eq!(5 + 8, BufferDesc::Binary { length: 5 }.bytes_per_row());
209        assert_eq!(
210            5 + 1 + 8,
211            BufferDesc::Text { max_str_len: 5 }.bytes_per_row()
212        );
213        assert_eq!(
214            10 + 2 + 8,
215            BufferDesc::WText { max_str_len: 5 }.bytes_per_row()
216        );
217        assert_eq!(6, BufferDesc::Date { nullable: false }.bytes_per_row());
218        assert_eq!(6, BufferDesc::Time { nullable: false }.bytes_per_row());
219        assert_eq!(
220            16,
221            BufferDesc::Timestamp { nullable: false }.bytes_per_row()
222        );
223        assert_eq!(1, BufferDesc::Bit { nullable: false }.bytes_per_row());
224        assert_eq!(1 + 8, BufferDesc::Bit { nullable: true }.bytes_per_row());
225        assert_eq!(4, BufferDesc::F32 { nullable: false }.bytes_per_row());
226        assert_eq!(8, BufferDesc::F64 { nullable: false }.bytes_per_row());
227        assert_eq!(1, BufferDesc::I8 { nullable: false }.bytes_per_row());
228        assert_eq!(2, BufferDesc::I16 { nullable: false }.bytes_per_row());
229        assert_eq!(4, BufferDesc::I32 { nullable: false }.bytes_per_row());
230        assert_eq!(8, BufferDesc::I64 { nullable: false }.bytes_per_row());
231        assert_eq!(1, BufferDesc::U8 { nullable: false }.bytes_per_row());
232        assert_eq!(
233            19,
234            BufferDesc::Numeric {
235                precision: 5,
236                scale: 3,
237            }
238            .bytes_per_row()
239        );
240    }
241}