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}