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