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