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