1use std::fmt::{Arguments, Display};
2
3use arrayvec::ArrayString;
4use serde::{Deserialize, Serialize};
5use sqlx_core::type_info::TypeInfo;
6
7#[derive(Debug, Clone, Copy, Deserialize)]
10#[serde(from = "ExaDataType")]
11pub struct ExaTypeInfo {
12 pub(crate) name: DataTypeName,
13 pub(crate) data_type: ExaDataType,
14}
15
16impl ExaTypeInfo {
17 #[doc(hidden)]
18 #[allow(clippy::must_use_candidate)]
19 pub fn __type_feature_gate(&self) -> Option<&'static str> {
20 match self.data_type {
21 ExaDataType::Date
22 | ExaDataType::Timestamp
23 | ExaDataType::TimestampWithLocalTimeZone => Some("time"),
24 ExaDataType::Decimal(decimal)
25 if decimal.scale > 0 || decimal.precision > Some(Decimal::MAX_64BIT_PRECISION) =>
26 {
27 Some("bigdecimal")
28 }
29 _ => None,
30 }
31 }
32}
33
34impl Serialize for ExaTypeInfo {
38 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
39 where
40 S: serde::Serializer,
41 {
42 self.data_type.serialize(serializer)
43 }
44}
45
46impl From<ExaDataType> for ExaTypeInfo {
47 fn from(data_type: ExaDataType) -> Self {
48 let name = data_type.full_name();
49 Self { name, data_type }
50 }
51}
52
53impl PartialEq for ExaTypeInfo {
54 fn eq(&self, other: &Self) -> bool {
55 self.data_type == other.data_type
56 }
57}
58
59impl Display for ExaTypeInfo {
60 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61 write!(f, "{}", self.name)
62 }
63}
64
65impl TypeInfo for ExaTypeInfo {
66 fn is_null(&self) -> bool {
67 false
68 }
69
70 fn name(&self) -> &str {
79 self.name.as_ref()
80 }
81
82 fn type_compatible(&self, other: &Self) -> bool
86 where
87 Self: Sized,
88 {
89 self.data_type.compatible(&other.data_type)
90 }
91}
92
93#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq)]
100#[serde(rename_all = "UPPERCASE")]
101#[serde(tag = "type")]
102pub enum ExaDataType {
103 Boolean,
105 #[serde(rename_all = "camelCase")]
107 Char { size: u32, character_set: Charset },
108 Date,
110 Decimal(Decimal),
112 Double,
114 #[serde(rename_all = "camelCase")]
116 Geometry { srid: u16 },
117 #[serde(rename = "INTERVAL DAY TO SECOND")]
119 #[serde(rename_all = "camelCase")]
120 IntervalDayToSecond { precision: u32, fraction: u32 },
121 #[serde(rename = "INTERVAL YEAR TO MONTH")]
123 #[serde(rename_all = "camelCase")]
124 IntervalYearToMonth { precision: u32 },
125 Timestamp,
127 #[serde(rename = "TIMESTAMP WITH LOCAL TIME ZONE")]
129 TimestampWithLocalTimeZone,
130 #[serde(rename_all = "camelCase")]
132 Varchar { size: u32, character_set: Charset },
133 HashType { size: Option<u16> },
146}
147
148impl ExaDataType {
149 const BOOLEAN: &'static str = "BOOLEAN";
151 const CHAR: &'static str = "CHAR";
152 const DATE: &'static str = "DATE";
153 const DECIMAL: &'static str = "DECIMAL";
154 const DOUBLE: &'static str = "DOUBLE PRECISION";
155 const GEOMETRY: &'static str = "GEOMETRY";
156 const INTERVAL_DAY_TO_SECOND: &'static str = "INTERVAL DAY TO SECOND";
157 const INTERVAL_YEAR_TO_MONTH: &'static str = "INTERVAL YEAR TO MONTH";
158 const TIMESTAMP: &'static str = "TIMESTAMP";
159 const TIMESTAMP_WITH_LOCAL_TIME_ZONE: &'static str = "TIMESTAMP WITH LOCAL TIME ZONE";
160 const VARCHAR: &'static str = "VARCHAR";
161 const HASHTYPE: &'static str = "HASHTYPE";
162
163 #[allow(dead_code, reason = "used by optional dependency")]
175 pub(crate) const INTERVAL_DTS_MAX_FRACTION: u32 = 3;
176 #[allow(dead_code, reason = "used by optional dependency")]
177 pub(crate) const INTERVAL_DTS_MAX_PRECISION: u32 = 9;
178 pub(crate) const INTERVAL_YTM_MAX_PRECISION: u32 = 9;
179 pub(crate) const VARCHAR_MAX_LEN: u32 = 2_000_000;
180 #[cfg_attr(not(test), expect(dead_code))]
181 pub(crate) const CHAR_MAX_LEN: u32 = 2_000;
182 #[cfg_attr(not(test), expect(dead_code))]
184 pub(crate) const HASHTYPE_MAX_LEN: u16 = 2048;
185
186 pub fn compatible(&self, other: &Self) -> bool {
191 match (self, other) {
192 (Self::HashType { size: Some(s1) }, Self::HashType { size: Some(s2) }) => s1 == s2,
193 (Self::Boolean, Self::Boolean)
194 | (
195 Self::Char { .. } | Self::Varchar { .. },
196 Self::Char { .. } | Self::Varchar { .. },
197 )
198 | (Self::Date, Self::Date)
199 | (Self::Double, Self::Double)
200 | (Self::Geometry { .. }, Self::Geometry { .. })
201 | (Self::IntervalDayToSecond { .. }, Self::IntervalDayToSecond { .. })
202 | (Self::IntervalYearToMonth { .. }, Self::IntervalYearToMonth { .. })
203 | (Self::Timestamp, Self::Timestamp)
204 | (Self::TimestampWithLocalTimeZone, Self::TimestampWithLocalTimeZone)
205 | (Self::HashType { .. }, Self::HashType { .. }) => true,
206 (Self::Decimal(d1), Self::Decimal(d2)) => d1.compatible(*d2),
207 _ => false,
208 }
209 }
210
211 fn full_name(&self) -> DataTypeName {
212 match self {
213 Self::Boolean => Self::BOOLEAN.into(),
214 Self::Date => Self::DATE.into(),
215 Self::Double => Self::DOUBLE.into(),
216 Self::Timestamp => Self::TIMESTAMP.into(),
217 Self::TimestampWithLocalTimeZone => Self::TIMESTAMP_WITH_LOCAL_TIME_ZONE.into(),
218 Self::Char {
219 size,
220 character_set,
221 }
222 | Self::Varchar {
223 size,
224 character_set,
225 } => format_args!("{}({}) {}", self.as_ref(), size, character_set).into(),
226 Self::Decimal(d) => match d.precision {
227 Some(p) => format_args!("{}({}, {})", self.as_ref(), p, d.scale).into(),
228 None => format_args!("{}(*, {})", self.as_ref(), d.scale).into(),
229 },
230 Self::Geometry { srid } => format_args!("{}({srid})", self.as_ref()).into(),
231 Self::IntervalDayToSecond {
232 precision,
233 fraction,
234 } => format_args!("INTERVAL DAY({precision}) TO SECOND({fraction})").into(),
235 Self::IntervalYearToMonth { precision } => {
236 format_args!("INTERVAL YEAR({precision}) TO MONTH").into()
237 }
238 Self::HashType { size } => match size {
239 Some(s) => format_args!("{}({} BYTE)", self.as_ref(), s / 2).into(),
241 None => format_args!("{}", self.as_ref()).into(),
242 },
243 }
244 }
245}
246
247impl AsRef<str> for ExaDataType {
248 fn as_ref(&self) -> &str {
249 match self {
250 Self::Boolean => Self::BOOLEAN,
251 Self::Char { .. } => Self::CHAR,
252 Self::Date => Self::DATE,
253 Self::Decimal(_) => Self::DECIMAL,
254 Self::Double => Self::DOUBLE,
255 Self::Geometry { .. } => Self::GEOMETRY,
256 Self::IntervalDayToSecond { .. } => Self::INTERVAL_DAY_TO_SECOND,
257 Self::IntervalYearToMonth { .. } => Self::INTERVAL_YEAR_TO_MONTH,
258 Self::Timestamp => Self::TIMESTAMP,
259 Self::TimestampWithLocalTimeZone => Self::TIMESTAMP_WITH_LOCAL_TIME_ZONE,
260 Self::Varchar { .. } => Self::VARCHAR,
261 Self::HashType { .. } => Self::HASHTYPE,
262 }
263 }
264}
265
266#[derive(Debug, Clone, Copy)]
272pub enum DataTypeName {
273 Static(&'static str),
274 Inline(ArrayString<30>),
275}
276
277impl AsRef<str> for DataTypeName {
278 fn as_ref(&self) -> &str {
279 match self {
280 DataTypeName::Static(s) => s,
281 DataTypeName::Inline(s) => s.as_str(),
282 }
283 }
284}
285
286impl Display for DataTypeName {
287 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
288 write!(f, "{}", self.as_ref())
289 }
290}
291
292impl From<&'static str> for DataTypeName {
293 fn from(value: &'static str) -> Self {
294 Self::Static(value)
295 }
296}
297
298impl From<Arguments<'_>> for DataTypeName {
299 fn from(value: Arguments<'_>) -> Self {
300 Self::Inline(ArrayString::try_from(value).expect("inline data type name too large"))
301 }
302}
303
304#[derive(Debug, Copy, Clone, Deserialize, Serialize, PartialEq)]
306#[serde(rename_all = "camelCase")]
307pub struct Decimal {
308 pub(crate) precision: Option<u8>,
310 pub(crate) scale: u8,
311}
312
313impl Decimal {
314 pub(crate) const MAX_8BIT_PRECISION: u8 = 3;
316 pub(crate) const MAX_16BIT_PRECISION: u8 = 5;
317 pub(crate) const MAX_32BIT_PRECISION: u8 = 10;
318 pub(crate) const MAX_64BIT_PRECISION: u8 = 20;
319
320 pub(crate) const MAX_PRECISION: u8 = 36;
322 #[allow(dead_code)]
323 pub(crate) const MAX_SCALE: u8 = 36;
324
325 #[rustfmt::skip] fn compatible(self, dec: Decimal) -> bool {
345 let (precision, scale) = match dec.precision {
346 Some(precision) => (precision, dec.scale),
347 None => return true,
349 };
350
351 let self_diff = self.precision.map_or(Decimal::MAX_PRECISION, |p| p - self.scale);
353 let other_diff = precision - scale;
354
355 self.scale >= scale && self_diff >= other_diff
356 }
357}
358
359#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq)]
361#[serde(rename_all = "UPPERCASE")]
362pub enum Charset {
363 Utf8,
364 Ascii,
365}
366
367impl AsRef<str> for Charset {
368 fn as_ref(&self) -> &str {
369 match self {
370 Charset::Utf8 => "UTF8",
371 Charset::Ascii => "ASCII",
372 }
373 }
374}
375
376impl Display for Charset {
377 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
378 write!(f, "{}", self.as_ref())
379 }
380}
381
382#[cfg(test)]
387mod tests {
388 use super::*;
389
390 #[test]
391 fn test_boolean_name() {
392 let data_type = ExaDataType::Boolean;
393 assert_eq!(data_type.full_name().as_ref(), "BOOLEAN");
394 }
395
396 #[test]
397 fn test_max_char_name() {
398 let data_type = ExaDataType::Char {
399 size: ExaDataType::CHAR_MAX_LEN,
400 character_set: Charset::Ascii,
401 };
402 assert_eq!(
403 data_type.full_name().as_ref(),
404 format!("CHAR({}) ASCII", ExaDataType::CHAR_MAX_LEN)
405 );
406 }
407
408 #[test]
409 fn test_date_name() {
410 let data_type = ExaDataType::Date;
411 assert_eq!(data_type.full_name().as_ref(), "DATE");
412 }
413
414 #[test]
415 fn test_max_decimal_name() {
416 let decimal = Decimal {
417 precision: Some(Decimal::MAX_PRECISION),
418 scale: Decimal::MAX_SCALE,
419 };
420 let data_type = ExaDataType::Decimal(decimal);
421 assert_eq!(
422 data_type.full_name().as_ref(),
423 format!(
424 "DECIMAL({}, {})",
425 Decimal::MAX_PRECISION,
426 Decimal::MAX_SCALE
427 )
428 );
429 }
430
431 #[test]
432 fn test_double_name() {
433 let data_type = ExaDataType::Double;
434 assert_eq!(data_type.full_name().as_ref(), "DOUBLE PRECISION");
435 }
436
437 #[test]
438 fn test_max_geometry_name() {
439 let data_type = ExaDataType::Geometry { srid: u16::MAX };
440 assert_eq!(
441 data_type.full_name().as_ref(),
442 format!("GEOMETRY({})", u16::MAX)
443 );
444 }
445
446 #[test]
447 fn test_max_interval_day_name() {
448 let data_type = ExaDataType::IntervalDayToSecond {
449 precision: ExaDataType::INTERVAL_DTS_MAX_PRECISION,
450 fraction: ExaDataType::INTERVAL_DTS_MAX_FRACTION,
451 };
452 assert_eq!(
453 data_type.full_name().as_ref(),
454 format!(
455 "INTERVAL DAY({}) TO SECOND({})",
456 ExaDataType::INTERVAL_DTS_MAX_PRECISION,
457 ExaDataType::INTERVAL_DTS_MAX_FRACTION
458 )
459 );
460 }
461
462 #[test]
463 fn test_max_interval_year_name() {
464 let data_type = ExaDataType::IntervalYearToMonth {
465 precision: ExaDataType::INTERVAL_YTM_MAX_PRECISION,
466 };
467 assert_eq!(
468 data_type.full_name().as_ref(),
469 format!(
470 "INTERVAL YEAR({}) TO MONTH",
471 ExaDataType::INTERVAL_YTM_MAX_PRECISION,
472 )
473 );
474 }
475
476 #[test]
477 fn test_timestamp_name() {
478 let data_type = ExaDataType::Timestamp;
479 assert_eq!(data_type.full_name().as_ref(), "TIMESTAMP");
480 }
481
482 #[test]
483 fn test_timestamp_with_tz_name() {
484 let data_type = ExaDataType::TimestampWithLocalTimeZone;
485 assert_eq!(
486 data_type.full_name().as_ref(),
487 "TIMESTAMP WITH LOCAL TIME ZONE"
488 );
489 }
490
491 #[test]
492 fn test_max_varchar_name() {
493 let data_type = ExaDataType::Varchar {
494 size: ExaDataType::VARCHAR_MAX_LEN,
495 character_set: Charset::Ascii,
496 };
497 assert_eq!(
498 data_type.full_name().as_ref(),
499 format!("VARCHAR({}) ASCII", ExaDataType::VARCHAR_MAX_LEN)
500 );
501 }
502
503 #[test]
504 fn test_max_hashbyte_name() {
505 let data_type = ExaDataType::HashType {
506 size: Some(ExaDataType::HASHTYPE_MAX_LEN),
507 };
508 assert_eq!(
509 data_type.full_name().as_ref(),
510 format!("HASHTYPE({} BYTE)", ExaDataType::HASHTYPE_MAX_LEN / 2)
511 );
512 }
513}