1#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
9#[repr(u8)]
10pub enum TypeId {
11 Null = 0x1F,
14 Int1 = 0x30,
16 Bit = 0x32,
18 Int2 = 0x34,
20 Int4 = 0x38,
22 Int8 = 0x7F,
24 DateTimeN = 0x6F,
26 Float4 = 0x3B,
28 Float8 = 0x3E,
30 Money = 0x3C,
32 Money4 = 0x7A,
34 DateTime = 0x3D,
36 DateTime4 = 0x3A,
38
39 Guid = 0x24,
42 IntN = 0x26,
44 Decimal = 0x37,
46 Numeric = 0x3F,
48 BitN = 0x68,
50 DecimalN = 0x6A,
52 NumericN = 0x6C,
54 FloatN = 0x6D,
56 MoneyN = 0x6E,
58
59 Char = 0x2F,
62 VarChar = 0x27,
64 Binary = 0x2D,
66 VarBinary = 0x25,
68
69 BigVarChar = 0xA7,
72 BigVarBinary = 0xA5,
74 BigChar = 0xAF,
76 BigBinary = 0xAD,
78
79 NChar = 0xEF,
82 NVarChar = 0xE7,
84
85 Text = 0x23,
88 Image = 0x22,
90 NText = 0x63,
92
93 Date = 0x28,
96 Time = 0x29,
98 DateTime2 = 0x2A,
100 DateTimeOffset = 0x2B,
102
103 Variant = 0x62,
106 Udt = 0xF0,
108 Xml = 0xF1,
110 Tvp = 0xF3,
112}
113
114impl TypeId {
115 pub fn from_u8(value: u8) -> Option<Self> {
117 match value {
118 0x1F => Some(Self::Null),
119 0x30 => Some(Self::Int1),
120 0x32 => Some(Self::Bit),
121 0x34 => Some(Self::Int2),
122 0x38 => Some(Self::Int4),
123 0x7F => Some(Self::Int8),
124 0x6F => Some(Self::DateTimeN),
125 0x3B => Some(Self::Float4),
126 0x3E => Some(Self::Float8),
127 0x3C => Some(Self::Money),
128 0x7A => Some(Self::Money4),
129 0x3D => Some(Self::DateTime),
130 0x3A => Some(Self::DateTime4),
131 0x24 => Some(Self::Guid),
132 0x26 => Some(Self::IntN),
133 0x37 => Some(Self::Decimal),
134 0x3F => Some(Self::Numeric),
135 0x68 => Some(Self::BitN),
136 0x6A => Some(Self::DecimalN),
137 0x6C => Some(Self::NumericN),
138 0x6D => Some(Self::FloatN),
139 0x6E => Some(Self::MoneyN),
140 0x2F => Some(Self::Char),
141 0x27 => Some(Self::VarChar),
142 0x2D => Some(Self::Binary),
143 0x25 => Some(Self::VarBinary),
144 0xA7 => Some(Self::BigVarChar),
145 0xA5 => Some(Self::BigVarBinary),
146 0xAF => Some(Self::BigChar),
147 0xAD => Some(Self::BigBinary),
148 0xEF => Some(Self::NChar),
149 0xE7 => Some(Self::NVarChar),
150 0x23 => Some(Self::Text),
151 0x22 => Some(Self::Image),
152 0x63 => Some(Self::NText),
153 0x28 => Some(Self::Date),
154 0x29 => Some(Self::Time),
155 0x2A => Some(Self::DateTime2),
156 0x2B => Some(Self::DateTimeOffset),
157 0x62 => Some(Self::Variant),
158 0xF0 => Some(Self::Udt),
159 0xF1 => Some(Self::Xml),
160 0xF3 => Some(Self::Tvp),
161 _ => None,
162 }
163 }
164
165 #[must_use]
167 pub const fn is_fixed_length(&self) -> bool {
168 matches!(
169 self,
170 Self::Null
171 | Self::Int1
172 | Self::Bit
173 | Self::Int2
174 | Self::Int4
175 | Self::Int8
176 | Self::Float4
177 | Self::Float8
178 | Self::Money
179 | Self::Money4
180 | Self::DateTime
181 | Self::DateTime4
182 )
183 }
184
185 #[must_use]
187 pub const fn is_variable_length(&self) -> bool {
188 !self.is_fixed_length()
189 }
190
191 #[must_use]
197 pub const fn is_plp(&self) -> bool {
198 matches!(self, Self::Text | Self::Image | Self::NText | Self::Xml)
199 }
200
201 #[must_use]
208 pub const fn can_be_max(&self) -> bool {
209 matches!(self, Self::NVarChar | Self::BigVarChar | Self::BigVarBinary)
210 }
211
212 #[must_use]
214 pub const fn is_unicode(&self) -> bool {
215 matches!(self, Self::NChar | Self::NVarChar | Self::NText)
216 }
217
218 #[must_use]
220 pub const fn is_datetime(&self) -> bool {
221 matches!(
222 self,
223 Self::DateTime
224 | Self::DateTime4
225 | Self::DateTimeN
226 | Self::Date
227 | Self::Time
228 | Self::DateTime2
229 | Self::DateTimeOffset
230 )
231 }
232
233 #[must_use]
235 pub const fn fixed_size(&self) -> Option<usize> {
236 match self {
237 Self::Null => Some(0),
238 Self::Int1 => Some(1),
239 Self::Bit => Some(1),
240 Self::Int2 => Some(2),
241 Self::Int4 => Some(4),
242 Self::Int8 => Some(8),
243 Self::Float4 => Some(4),
244 Self::Float8 => Some(8),
245 Self::Money => Some(8),
246 Self::Money4 => Some(4),
247 Self::DateTime => Some(8),
248 Self::DateTime4 => Some(4),
249 Self::Date => Some(3),
250 _ => None,
251 }
252 }
253}
254
255#[derive(Debug, Clone, Copy, Default)]
257pub struct ColumnFlags {
258 pub nullable: bool,
260 pub case_sensitive: bool,
262 pub updateable: Updateable,
264 pub identity: bool,
266 pub computed: bool,
268 pub fixed_len_clr_type: bool,
270 pub sparse_column_set: bool,
272 pub encrypted: bool,
274 pub hidden: bool,
276 pub key: bool,
278 pub nullable_unknown: bool,
280}
281
282#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
284pub enum Updateable {
285 #[default]
287 ReadOnly,
288 ReadWrite,
290 Unknown,
292}
293
294impl ColumnFlags {
295 #[must_use]
297 pub fn from_bits(flags: u16) -> Self {
298 Self {
299 nullable: (flags & 0x0001) != 0,
300 case_sensitive: (flags & 0x0002) != 0,
301 updateable: match (flags >> 2) & 0x03 {
302 0 => Updateable::ReadOnly,
303 1 => Updateable::ReadWrite,
304 _ => Updateable::Unknown,
305 },
306 identity: (flags & 0x0010) != 0,
307 computed: (flags & 0x0020) != 0,
308 fixed_len_clr_type: (flags & 0x0100) != 0,
309 sparse_column_set: (flags & 0x0200) != 0,
310 encrypted: (flags & 0x0400) != 0,
311 hidden: (flags & 0x2000) != 0,
312 key: (flags & 0x4000) != 0,
313 nullable_unknown: (flags & 0x8000) != 0,
314 }
315 }
316
317 #[must_use]
319 pub fn to_bits(&self) -> u16 {
320 let mut flags = 0u16;
321 if self.nullable {
322 flags |= 0x0001;
323 }
324 if self.case_sensitive {
325 flags |= 0x0002;
326 }
327 flags |= match self.updateable {
328 Updateable::ReadOnly => 0,
329 Updateable::ReadWrite => 1 << 2,
330 Updateable::Unknown => 2 << 2,
331 };
332 if self.identity {
333 flags |= 0x0010;
334 }
335 if self.computed {
336 flags |= 0x0020;
337 }
338 if self.fixed_len_clr_type {
339 flags |= 0x0100;
340 }
341 if self.sparse_column_set {
342 flags |= 0x0200;
343 }
344 if self.encrypted {
345 flags |= 0x0400;
346 }
347 if self.hidden {
348 flags |= 0x2000;
349 }
350 if self.key {
351 flags |= 0x4000;
352 }
353 if self.nullable_unknown {
354 flags |= 0x8000;
355 }
356 flags
357 }
358}
359
360#[cfg(test)]
361#[allow(clippy::unwrap_used)]
362mod tests {
363 use super::*;
364
365 #[test]
366 fn test_type_id_from_u8() {
367 assert_eq!(TypeId::from_u8(0x38), Some(TypeId::Int4));
368 assert_eq!(TypeId::from_u8(0xE7), Some(TypeId::NVarChar));
369 assert_eq!(TypeId::from_u8(0x99), None);
370 }
371
372 #[test]
373 fn test_fixed_length_detection() {
374 assert!(TypeId::Int4.is_fixed_length());
375 assert!(TypeId::Float8.is_fixed_length());
376 assert!(!TypeId::NVarChar.is_fixed_length());
377 }
378
379 #[test]
380 fn test_column_flags_roundtrip() {
381 let flags = ColumnFlags {
382 nullable: true,
383 identity: true,
384 key: true,
385 ..Default::default()
386 };
387 let bits = flags.to_bits();
388 let restored = ColumnFlags::from_bits(bits);
389 assert_eq!(flags.nullable, restored.nullable);
390 assert_eq!(flags.identity, restored.identity);
391 assert_eq!(flags.key, restored.key);
392 }
393}