1#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
9#[repr(u8)]
10#[non_exhaustive]
11pub enum TypeId {
12 Null = 0x1F,
15 Int1 = 0x30,
17 Bit = 0x32,
19 Int2 = 0x34,
21 Int4 = 0x38,
23 Int8 = 0x7F,
25 DateTimeN = 0x6F,
27 Float4 = 0x3B,
29 Float8 = 0x3E,
31 Money = 0x3C,
33 Money4 = 0x7A,
35 DateTime = 0x3D,
37 DateTime4 = 0x3A,
39
40 Guid = 0x24,
43 IntN = 0x26,
45 Decimal = 0x37,
47 Numeric = 0x3F,
49 BitN = 0x68,
51 DecimalN = 0x6A,
53 NumericN = 0x6C,
55 FloatN = 0x6D,
57 MoneyN = 0x6E,
59
60 Char = 0x2F,
63 VarChar = 0x27,
65 Binary = 0x2D,
67 VarBinary = 0x25,
69
70 BigVarChar = 0xA7,
73 BigVarBinary = 0xA5,
75 BigChar = 0xAF,
77 BigBinary = 0xAD,
79
80 NChar = 0xEF,
83 NVarChar = 0xE7,
85
86 Text = 0x23,
89 Image = 0x22,
91 NText = 0x63,
93
94 Date = 0x28,
97 Time = 0x29,
99 DateTime2 = 0x2A,
101 DateTimeOffset = 0x2B,
103
104 Variant = 0x62,
107 Udt = 0xF0,
109 Xml = 0xF1,
111 Tvp = 0xF3,
113}
114
115impl TypeId {
116 pub fn from_u8(value: u8) -> Option<Self> {
118 match value {
119 0x1F => Some(Self::Null),
120 0x30 => Some(Self::Int1),
121 0x32 => Some(Self::Bit),
122 0x34 => Some(Self::Int2),
123 0x38 => Some(Self::Int4),
124 0x7F => Some(Self::Int8),
125 0x6F => Some(Self::DateTimeN),
126 0x3B => Some(Self::Float4),
127 0x3E => Some(Self::Float8),
128 0x3C => Some(Self::Money),
129 0x7A => Some(Self::Money4),
130 0x3D => Some(Self::DateTime),
131 0x3A => Some(Self::DateTime4),
132 0x24 => Some(Self::Guid),
133 0x26 => Some(Self::IntN),
134 0x37 => Some(Self::Decimal),
135 0x3F => Some(Self::Numeric),
136 0x68 => Some(Self::BitN),
137 0x6A => Some(Self::DecimalN),
138 0x6C => Some(Self::NumericN),
139 0x6D => Some(Self::FloatN),
140 0x6E => Some(Self::MoneyN),
141 0x2F => Some(Self::Char),
142 0x27 => Some(Self::VarChar),
143 0x2D => Some(Self::Binary),
144 0x25 => Some(Self::VarBinary),
145 0xA7 => Some(Self::BigVarChar),
146 0xA5 => Some(Self::BigVarBinary),
147 0xAF => Some(Self::BigChar),
148 0xAD => Some(Self::BigBinary),
149 0xEF => Some(Self::NChar),
150 0xE7 => Some(Self::NVarChar),
151 0x23 => Some(Self::Text),
152 0x22 => Some(Self::Image),
153 0x63 => Some(Self::NText),
154 0x28 => Some(Self::Date),
155 0x29 => Some(Self::Time),
156 0x2A => Some(Self::DateTime2),
157 0x2B => Some(Self::DateTimeOffset),
158 0x62 => Some(Self::Variant),
159 0xF0 => Some(Self::Udt),
160 0xF1 => Some(Self::Xml),
161 0xF3 => Some(Self::Tvp),
162 _ => None,
163 }
164 }
165
166 #[must_use]
168 pub const fn is_fixed_length(&self) -> bool {
169 matches!(
170 self,
171 Self::Null
172 | Self::Int1
173 | Self::Bit
174 | Self::Int2
175 | Self::Int4
176 | Self::Int8
177 | Self::Float4
178 | Self::Float8
179 | Self::Money
180 | Self::Money4
181 | Self::DateTime
182 | Self::DateTime4
183 )
184 }
185
186 #[must_use]
188 pub const fn is_variable_length(&self) -> bool {
189 !self.is_fixed_length()
190 }
191
192 #[must_use]
198 pub const fn is_plp(&self) -> bool {
199 matches!(self, Self::Text | Self::Image | Self::NText | Self::Xml)
200 }
201
202 #[must_use]
209 pub const fn can_be_max(&self) -> bool {
210 matches!(self, Self::NVarChar | Self::BigVarChar | Self::BigVarBinary)
211 }
212
213 #[must_use]
215 pub const fn is_unicode(&self) -> bool {
216 matches!(self, Self::NChar | Self::NVarChar | Self::NText)
217 }
218
219 #[must_use]
221 pub const fn is_datetime(&self) -> bool {
222 matches!(
223 self,
224 Self::DateTime
225 | Self::DateTime4
226 | Self::DateTimeN
227 | Self::Date
228 | Self::Time
229 | Self::DateTime2
230 | Self::DateTimeOffset
231 )
232 }
233
234 #[must_use]
236 pub const fn fixed_size(&self) -> Option<usize> {
237 match self {
238 Self::Null => Some(0),
239 Self::Int1 => Some(1),
240 Self::Bit => Some(1),
241 Self::Int2 => Some(2),
242 Self::Int4 => Some(4),
243 Self::Int8 => Some(8),
244 Self::Float4 => Some(4),
245 Self::Float8 => Some(8),
246 Self::Money => Some(8),
247 Self::Money4 => Some(4),
248 Self::DateTime => Some(8),
249 Self::DateTime4 => Some(4),
250 Self::Date => Some(3),
251 _ => None,
252 }
253 }
254}
255
256#[derive(Debug, Clone, Copy, Default)]
258#[non_exhaustive]
259pub struct ColumnFlags {
260 pub nullable: bool,
262 pub case_sensitive: bool,
264 pub updateable: Updateable,
266 pub identity: bool,
268 pub computed: bool,
270 pub fixed_len_clr_type: bool,
272 pub sparse_column_set: bool,
274 pub encrypted: bool,
276 pub hidden: bool,
278 pub key: bool,
280 pub nullable_unknown: bool,
282}
283
284#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
286#[non_exhaustive]
287pub enum Updateable {
288 #[default]
290 ReadOnly,
291 ReadWrite,
293 Unknown,
295}
296
297impl ColumnFlags {
298 #[must_use]
300 pub fn from_bits(flags: u16) -> Self {
301 Self {
302 nullable: (flags & 0x0001) != 0,
303 case_sensitive: (flags & 0x0002) != 0,
304 updateable: match (flags >> 2) & 0x03 {
305 0 => Updateable::ReadOnly,
306 1 => Updateable::ReadWrite,
307 _ => Updateable::Unknown,
308 },
309 identity: (flags & 0x0010) != 0,
310 computed: (flags & 0x0020) != 0,
311 fixed_len_clr_type: (flags & 0x0100) != 0,
312 sparse_column_set: (flags & 0x0400) != 0,
315 encrypted: (flags & 0x0800) != 0,
316 hidden: (flags & 0x2000) != 0,
317 key: (flags & 0x4000) != 0,
318 nullable_unknown: (flags & 0x8000) != 0,
319 }
320 }
321
322 #[must_use]
324 pub fn to_bits(&self) -> u16 {
325 let mut flags = 0u16;
326 if self.nullable {
327 flags |= 0x0001;
328 }
329 if self.case_sensitive {
330 flags |= 0x0002;
331 }
332 flags |= match self.updateable {
333 Updateable::ReadOnly => 0,
334 Updateable::ReadWrite => 1 << 2,
335 Updateable::Unknown => 2 << 2,
336 };
337 if self.identity {
338 flags |= 0x0010;
339 }
340 if self.computed {
341 flags |= 0x0020;
342 }
343 if self.fixed_len_clr_type {
344 flags |= 0x0100;
345 }
346 if self.sparse_column_set {
347 flags |= 0x0400;
348 }
349 if self.encrypted {
350 flags |= 0x0800;
351 }
352 if self.hidden {
353 flags |= 0x2000;
354 }
355 if self.key {
356 flags |= 0x4000;
357 }
358 if self.nullable_unknown {
359 flags |= 0x8000;
360 }
361 flags
362 }
363}
364
365#[cfg(test)]
366#[allow(clippy::unwrap_used)]
367mod tests {
368 use super::*;
369
370 #[test]
371 fn test_type_id_from_u8() {
372 assert_eq!(TypeId::from_u8(0x38), Some(TypeId::Int4));
373 assert_eq!(TypeId::from_u8(0xE7), Some(TypeId::NVarChar));
374 assert_eq!(TypeId::from_u8(0x99), None);
375 }
376
377 #[test]
378 fn test_fixed_length_detection() {
379 assert!(TypeId::Int4.is_fixed_length());
380 assert!(TypeId::Float8.is_fixed_length());
381 assert!(!TypeId::NVarChar.is_fixed_length());
382 }
383
384 #[test]
385 fn test_column_flags_roundtrip() {
386 let flags = ColumnFlags {
387 nullable: true,
388 identity: true,
389 key: true,
390 ..Default::default()
391 };
392 let bits = flags.to_bits();
393 let restored = ColumnFlags::from_bits(bits);
394 assert_eq!(flags.nullable, restored.nullable);
395 assert_eq!(flags.identity, restored.identity);
396 assert_eq!(flags.key, restored.key);
397 }
398
399 #[test]
400 fn test_column_flags_spec_bit_positions() {
401 assert_eq!(crate::crypto::COLUMN_FLAG_ENCRYPTED, 0x0800);
405
406 let sparse = ColumnFlags::from_bits(0x0400);
407 assert!(sparse.sparse_column_set);
408 assert!(!sparse.encrypted);
409
410 let encrypted = ColumnFlags::from_bits(0x0800);
411 assert!(encrypted.encrypted);
412 assert!(!encrypted.sparse_column_set);
413
414 let reserved = ColumnFlags::from_bits(0x0200);
416 assert!(!reserved.sparse_column_set);
417 assert!(!reserved.encrypted);
418
419 assert_eq!(
421 ColumnFlags {
422 sparse_column_set: true,
423 ..Default::default()
424 }
425 .to_bits(),
426 0x0400
427 );
428 assert_eq!(
429 ColumnFlags {
430 encrypted: true,
431 ..Default::default()
432 }
433 .to_bits(),
434 0x0800
435 );
436 }
437}