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}