odbc_api/handles/data_type.rs
1use std::num::NonZeroUsize;
2
3use odbc_sys::SqlDataType;
4
5/// The relational type of the column. Think of it as the type used in the `CREATE TABLE` statement
6/// then creating the database.
7///
8/// There might be a mismatch between the types supported by your database and the types defined in
9/// ODBC. E.g. ODBC does not have a timestamp with timezone type, theras Postgersql and Microsoft
10/// SQL Server both have one. In such cases it is up to the specific ODBC driver what happens.
11/// Microsoft SQL Server return a custom type, with its meaning specific to that driver. PostgreSQL
12/// identifies that column as an ordinary ODBC timestamp.
13#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
14/// Enumeration over valid SQL Data Types supported by ODBC
15pub enum DataType {
16 /// The type is not known.
17 #[default]
18 Unknown,
19 /// `Char(n)`. Character string of fixed length.
20 Char {
21 /// Column size in characters (excluding terminating zero).
22 length: Option<NonZeroUsize>,
23 },
24 /// `NChar(n)`. Character string of fixed length.
25 WChar {
26 /// Column size in characters (excluding terminating zero).
27 length: Option<NonZeroUsize>,
28 },
29 /// `Numeric(p,s). Signed, exact, numeric value with a precision p and scale s (1 <= p <= 15; s
30 /// <= p)
31 Numeric {
32 /// Total number of digits.
33 precision: usize,
34 /// Number of decimal digits.
35 scale: i16,
36 },
37 /// `Decimal(p,s)`. Signed, exact, numeric value with a precision of at least p and scale s.
38 /// The maximum precision is driver-defined. (1 <= p <= 15; s <= p)
39 Decimal {
40 /// Total number of digits.
41 precision: usize,
42 /// Number of decimal digits.
43 scale: i16,
44 },
45 /// `Integer`. 32 Bit Integer
46 Integer,
47 /// `Smallint`. 16 Bit Integer
48 SmallInt,
49 /// `Float(p)`. Signed, approximate, numeric value with a binary precision of at least p. The
50 /// maximum precision is driver-defined.
51 ///
52 /// Depending on the implementation binary precision is either 24 (`f32`) or 53 (`f64`).
53 Float { precision: usize },
54 /// `Real`. Signed, approximate, numeric value with a binary precision 24 (zero or absolute
55 /// value 10^-38] to 10^38).
56 Real,
57 /// `Double Precision`. Signed, approximate, numeric value with a binary precision 53 (zero or
58 /// absolute value 10^-308 to 10^308).
59 Double,
60 /// `Varchar(n)`. Variable length character string.
61 Varchar {
62 /// Maximum length of the character string (excluding terminating zero). Whether this length
63 /// is to be interpreted as bytes or Codepoints is ambigious and depends on the datasource.
64 ///
65 /// E.g. For Microsoft SQL Server this is the binary length, theras for a MariaDB this
66 /// refers to codepoints in case of UTF-8 encoding. If you need the binary size query the
67 /// octet length for that column instead.
68 ///
69 /// To find out how to interpret this value for a particular datasource you can use the
70 /// `odbcsv` command line tool `list-columns` subcommand and query a Varchar column. If the
71 /// buffer/octet length matches the column size, you can interpret this as the byte length.
72 length: Option<NonZeroUsize>,
73 },
74 /// `NVARCHAR(n)`. Variable length character string. Indicates the use of wide character strings
75 /// and use of UCS2 encoding on the side of the database.
76 WVarchar {
77 /// Maximum length of the character string (excluding terminating zero).
78 length: Option<NonZeroUsize>,
79 },
80 /// `TEXT`. Variable length characeter string for long text objects.
81 LongVarchar {
82 /// Maximum length of the character string (excluding terminating zero). Maximum size
83 /// depends on the capabilities of the driver and datasource. E.g. its 2^31 - 1 for MSSQL.
84 length: Option<NonZeroUsize>,
85 },
86 /// `BLOB`. Variable length data for long binary objects.
87 LongVarbinary {
88 /// Maximum length of the binary data. Maximum size depends on the capabilities of the
89 /// driver and datasource.
90 length: Option<NonZeroUsize>,
91 },
92 /// `Date`. Year, month, and day fields, conforming to the rules of the Gregorian calendar.
93 Date,
94 /// `Time`. Hour, minute, and second fields, with valid values for hours of 00 to 23, valid
95 /// values for minutes of 00 to 59, and valid values for seconds of 00 to 61. Precision p
96 /// indicates the seconds precision.
97 Time {
98 /// Number of radix ten digits used to represent the timestamp after the decimal points.
99 /// E.g. Milliseconds would be represented by precision 3, Microseconds by 6 and Nanoseconds
100 /// by 9.
101 precision: i16,
102 },
103 /// `Timestamp`. Year, month, day, hour, minute, and second fields, with valid values as
104 /// defined for the Date and Time variants.
105 Timestamp {
106 /// Number of radix ten digits used to represent the timestamp after the decimal points.
107 /// E.g. Milliseconds would be represented by precision 3, Microseconds by 6 and Nanoseconds
108 /// by 9.
109 precision: i16,
110 },
111 /// `BIGINT`. Exact numeric value with precision 19 (if signed) or 20 (if unsigned) and scale 0
112 /// (signed: -2^63 <= n <= 2^63 - 1, unsigned: 0 <= n <= 2^64 - 1). Has no corresponding
113 /// type in SQL-92.
114 BigInt,
115 /// `TINYINT`. Exact numeric value with precision 3 and scale 0 (signed: -128 <= n <= 127,
116 /// unsigned: 0 <= n <= 255)
117 TinyInt,
118 /// `BIT`. Single bit binary data.
119 Bit,
120 /// `VARBINARY(n)`. Type for variable sized binary data.
121 Varbinary { length: Option<NonZeroUsize> },
122 /// `BINARY(n)`. Type for fixed sized binary data.
123 Binary { length: Option<NonZeroUsize> },
124 /// The driver returned a type, but it is not among the other types of these enumeration. This
125 /// is a catchall, in case the library is incomplete, or the data source supports custom or
126 /// non-standard types.
127 Other {
128 /// Type of the column
129 data_type: SqlDataType,
130 /// Size of column element
131 column_size: Option<NonZeroUsize>,
132 /// Decimal digits returned for the column element. Exact meaning if any depends on the
133 /// `data_type` field.
134 decimal_digits: i16,
135 },
136}
137
138impl DataType {
139 /// This constructor is useful to create an instance of the enumeration using values returned by
140 /// ODBC Api calls like `SQLDescribeCol`, rather than just initializing a variant directly.
141 pub fn new(data_type: SqlDataType, column_size: usize, decimal_digits: i16) -> Self {
142 match data_type {
143 SqlDataType::UNKNOWN_TYPE => DataType::Unknown,
144 SqlDataType::EXT_LONG_VARCHAR => DataType::LongVarchar {
145 length: NonZeroUsize::new(column_size),
146 },
147 SqlDataType::EXT_BINARY => DataType::Binary {
148 length: NonZeroUsize::new(column_size),
149 },
150 SqlDataType::EXT_VAR_BINARY => DataType::Varbinary {
151 length: NonZeroUsize::new(column_size),
152 },
153 SqlDataType::EXT_LONG_VAR_BINARY => DataType::LongVarbinary {
154 length: NonZeroUsize::new(column_size),
155 },
156 SqlDataType::CHAR => DataType::Char {
157 length: NonZeroUsize::new(column_size),
158 },
159 SqlDataType::VARCHAR => DataType::Varchar {
160 length: NonZeroUsize::new(column_size),
161 },
162 SqlDataType::NUMERIC => DataType::Numeric {
163 precision: column_size,
164 scale: decimal_digits,
165 },
166 SqlDataType::DECIMAL => DataType::Decimal {
167 precision: column_size,
168 scale: decimal_digits,
169 },
170 SqlDataType::INTEGER => DataType::Integer,
171 SqlDataType::SMALLINT => DataType::SmallInt,
172 SqlDataType::FLOAT => DataType::Float {
173 precision: column_size,
174 },
175 SqlDataType::REAL => DataType::Real,
176 SqlDataType::DOUBLE => DataType::Double,
177 SqlDataType::DATE => DataType::Date,
178 SqlDataType::TIME => DataType::Time {
179 precision: decimal_digits,
180 },
181 SqlDataType::TIMESTAMP => DataType::Timestamp {
182 precision: decimal_digits,
183 },
184 SqlDataType::EXT_BIG_INT => DataType::BigInt,
185 SqlDataType::EXT_TINY_INT => DataType::TinyInt,
186 SqlDataType::EXT_BIT => DataType::Bit,
187 SqlDataType::EXT_W_VARCHAR => DataType::WVarchar {
188 length: NonZeroUsize::new(column_size),
189 },
190 SqlDataType::EXT_W_CHAR => DataType::WChar {
191 length: NonZeroUsize::new(column_size),
192 },
193 other => DataType::Other {
194 data_type: other,
195 column_size: NonZeroUsize::new(column_size),
196 decimal_digits,
197 },
198 }
199 }
200
201 /// The associated `data_type` discriminator for this variant.
202 pub fn data_type(&self) -> SqlDataType {
203 match self {
204 DataType::Unknown => SqlDataType::UNKNOWN_TYPE,
205 DataType::Binary { .. } => SqlDataType::EXT_BINARY,
206 DataType::Varbinary { .. } => SqlDataType::EXT_VAR_BINARY,
207 DataType::LongVarbinary { .. } => SqlDataType::EXT_LONG_VAR_BINARY,
208 DataType::Char { .. } => SqlDataType::CHAR,
209 DataType::Numeric { .. } => SqlDataType::NUMERIC,
210 DataType::Decimal { .. } => SqlDataType::DECIMAL,
211 DataType::Integer => SqlDataType::INTEGER,
212 DataType::SmallInt => SqlDataType::SMALLINT,
213 DataType::Float { .. } => SqlDataType::FLOAT,
214 DataType::Real => SqlDataType::REAL,
215 DataType::Double => SqlDataType::DOUBLE,
216 DataType::Varchar { .. } => SqlDataType::VARCHAR,
217 DataType::LongVarchar { .. } => SqlDataType::EXT_LONG_VARCHAR,
218 DataType::Date => SqlDataType::DATE,
219 DataType::Time { .. } => SqlDataType::TIME,
220 DataType::Timestamp { .. } => SqlDataType::TIMESTAMP,
221 DataType::BigInt => SqlDataType::EXT_BIG_INT,
222 DataType::TinyInt => SqlDataType::EXT_TINY_INT,
223 DataType::Bit => SqlDataType::EXT_BIT,
224 DataType::WVarchar { .. } => SqlDataType::EXT_W_VARCHAR,
225 DataType::WChar { .. } => SqlDataType::EXT_W_CHAR,
226 DataType::Other { data_type, .. } => *data_type,
227 }
228 }
229
230 // Return the column size, as it is required to bind the data type as a parameter. Fixed sized
231 // types are mapped to `None` and should be bound using `0`. See also
232 // [crates::Cursor::describe_col]. Variadic types without upper bound are also mapped to `None`.
233 pub fn column_size(&self) -> Option<NonZeroUsize> {
234 match self {
235 DataType::Unknown
236 | DataType::Integer
237 | DataType::SmallInt
238 | DataType::Real
239 | DataType::Double
240 | DataType::Date
241 | DataType::Time { .. }
242 | DataType::Timestamp { .. }
243 | DataType::BigInt
244 | DataType::TinyInt
245 | DataType::Bit => None,
246 DataType::Char { length }
247 | DataType::Varchar { length }
248 | DataType::Varbinary { length }
249 | DataType::LongVarbinary { length }
250 | DataType::Binary { length }
251 | DataType::WChar { length }
252 | DataType::WVarchar { length }
253 | DataType::LongVarchar { length } => *length,
254 DataType::Float { precision, .. }
255 | DataType::Numeric { precision, .. }
256 | DataType::Decimal { precision, .. } => NonZeroUsize::new(*precision),
257 DataType::Other { column_size, .. } => *column_size,
258 }
259 }
260
261 /// Return the number of decimal digits as required to bind the data type as a parameter.
262 pub fn decimal_digits(&self) -> i16 {
263 match self {
264 DataType::Unknown
265 | DataType::Char { .. }
266 | DataType::Integer
267 | DataType::SmallInt
268 | DataType::Float { .. }
269 | DataType::Real
270 | DataType::Double
271 | DataType::Varchar { .. }
272 | DataType::WVarchar { .. }
273 | DataType::WChar { .. }
274 | DataType::Varbinary { .. }
275 | DataType::LongVarbinary { .. }
276 | DataType::Binary { .. }
277 | DataType::LongVarchar { .. }
278 | DataType::Date
279 | DataType::BigInt
280 | DataType::TinyInt
281 | DataType::Bit => 0,
282 DataType::Numeric { scale, .. } | DataType::Decimal { scale, .. } => *scale,
283 DataType::Time { precision } | DataType::Timestamp { precision } => *precision,
284 DataType::Other { decimal_digits, .. } => *decimal_digits,
285 }
286 }
287
288 /// The maximum number of characters needed to display data in character form.
289 ///
290 /// See: <https://docs.microsoft.com/en-us/sql/odbc/reference/appendixes/display-size>
291 pub fn display_size(&self) -> Option<NonZeroUsize> {
292 match self {
293 DataType::Unknown
294 | DataType::Other {
295 data_type: _,
296 column_size: _,
297 decimal_digits: _,
298 } => None,
299 // Each binary byte is represented by a 2-digit hexadecimal number.
300 DataType::Varbinary { length }
301 | DataType::Binary { length }
302 | DataType::LongVarbinary { length } => {
303 length.map(|l| l.get() * 2).and_then(NonZeroUsize::new)
304 }
305 // The defined (for fixed types) or maximum (for variable types) number of characters
306 // needed to display the data in character form.
307 DataType::Varchar { length }
308 | DataType::WVarchar { length }
309 | DataType::WChar { length }
310 | DataType::Char { length }
311 | DataType::LongVarchar { length } => *length,
312 // The precision of the column plus 2 (a sign, precision digits, and a decimal point).
313 // For example, the display size of a column defined as NUMERIC(10,3) is 12.
314 DataType::Numeric {
315 precision,
316 scale: _,
317 }
318 | DataType::Decimal {
319 precision,
320 scale: _,
321 } => NonZeroUsize::new(precision + 2),
322 // 11 if signed (a sign and 10 digits) or 10 if unsigned (10 digits).
323 DataType::Integer => NonZeroUsize::new(11),
324 // 6 if signed (a sign and 5 digits) or 5 if unsigned (5 digits).
325 DataType::SmallInt => NonZeroUsize::new(6),
326 // 24 (a sign, 15 digits, a decimal point, the letter E, a sign, and 3 digits).
327 DataType::Float { .. } | DataType::Double => NonZeroUsize::new(24),
328 // 14 (a sign, 7 digits, a decimal point, the letter E, a sign, and 2 digits).
329 DataType::Real => NonZeroUsize::new(14),
330 // 10 (a date in the format yyyy-mm-dd).
331 DataType::Date => NonZeroUsize::new(10),
332 // 8 (a time in the format hh:mm:ss)
333 // or
334 // 9 + s (a time in the format hh:mm:ss[.fff...], where s is the fractional seconds
335 // precision).
336 DataType::Time { precision } => NonZeroUsize::new(if *precision == 0 {
337 8
338 } else {
339 9 + *precision as usize
340 }),
341 // 19 (for a timestamp in the yyyy-mm-dd hh:mm:ss format)
342 // or
343 // 20 + s (for a timestamp in the yyyy-mm-dd hh:mm:ss[.fff...] format, where s is the
344 // fractional seconds precision).
345 DataType::Timestamp { precision } => NonZeroUsize::new(if *precision == 0 {
346 19
347 } else {
348 20 + *precision as usize
349 }),
350 // 20 (a sign and 19 digits if signed or 20 digits if unsigned).
351 DataType::BigInt => NonZeroUsize::new(20),
352 // 4 if signed (a sign and 3 digits) or 3 if unsigned (3 digits).
353 DataType::TinyInt => NonZeroUsize::new(4),
354 // 1 digit.
355 DataType::Bit => NonZeroUsize::new(1),
356 }
357 }
358
359 /// The maximum length of the UTF-8 representation in bytes.
360 ///
361 /// ```
362 /// use odbc_api::DataType;
363 /// use std::num::NonZeroUsize;
364 ///
365 /// let nz = NonZeroUsize::new;
366 /// // Character set data types length is multiplied by four.
367 /// assert_eq!(DataType::Varchar { length: nz(10) }.utf8_len(), nz(40));
368 /// assert_eq!(DataType::Char { length: nz(10) }.utf8_len(), nz(40));
369 /// assert_eq!(DataType::WVarchar { length: nz(10) }.utf8_len(), nz(40));
370 /// assert_eq!(DataType::WChar { length: nz(10) }.utf8_len(), nz(40));
371 /// // For other types return value is identical to display size as they are assumed to be
372 /// // entirely representable with ASCII characters.
373 /// assert_eq!(DataType::Numeric { precision: 10, scale: 3}.utf8_len(), nz(10 + 2));
374 /// ```
375 pub fn utf8_len(&self) -> Option<NonZeroUsize> {
376 match self {
377 // One character may need up to four bytes to be represented in utf-8.
378 DataType::Varchar { length }
379 | DataType::WVarchar { length }
380 | DataType::WChar { length }
381 | DataType::Char { length } => length.map(|l| l.get() * 4).and_then(NonZeroUsize::new),
382 other => other.display_size(),
383 }
384 }
385
386 /// The maximum length of the UTF-16 representation in 2-Byte characters.
387 ///
388 /// ```
389 /// use odbc_api::DataType;
390 /// use std::num::NonZeroUsize;
391 ///
392 /// let nz = NonZeroUsize::new;
393 ///
394 /// // Character set data types length is multiplied by two.
395 /// assert_eq!(DataType::Varchar { length: nz(10) }.utf16_len(), nz(20));
396 /// assert_eq!(DataType::Char { length: nz(10) }.utf16_len(), nz(20));
397 /// assert_eq!(DataType::WVarchar { length: nz(10) }.utf16_len(), nz(20));
398 /// assert_eq!(DataType::WChar { length: nz(10) }.utf16_len(), nz(20));
399 /// // For other types return value is identical to display size as they are assumed to be
400 /// // entirely representable with ASCII characters.
401 /// assert_eq!(DataType::Numeric { precision: 10, scale: 3}.utf16_len(), nz(10 + 2));
402 /// ```
403 pub fn utf16_len(&self) -> Option<NonZeroUsize> {
404 match self {
405 // One character may need up to two u16 to be represented in utf-16.
406 DataType::Varchar { length }
407 | DataType::WVarchar { length }
408 | DataType::WChar { length }
409 | DataType::Char { length } => length.map(|l| l.get() * 2).and_then(NonZeroUsize::new),
410 other => other.display_size(),
411 }
412 }
413}