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