1use std::{
2 cmp::Ordering,
3 fmt::{Arguments, Display},
4};
5
6use arrayvec::ArrayString;
7use serde::{Deserialize, Serialize};
8use sqlx_core::type_info::TypeInfo;
9
10#[derive(Debug, Clone, Copy, Deserialize, Serialize)]
13#[serde(from = "ExaDataType")]
14#[serde(rename_all = "camelCase")]
15pub struct ExaTypeInfo {
16 #[serde(skip_serializing)]
17 pub(crate) name: DataTypeName,
18 data_type: ExaDataType,
19}
20
21impl ExaTypeInfo {
22 #[must_use]
27 pub fn compatible(&self, other: &Self) -> bool {
28 self.data_type.compatible(&other.data_type)
29 }
30}
31
32impl From<ExaDataType> for ExaTypeInfo {
33 fn from(data_type: ExaDataType) -> Self {
34 let name = data_type.full_name();
35 Self { name, data_type }
36 }
37}
38
39impl PartialEq for ExaTypeInfo {
40 fn eq(&self, other: &Self) -> bool {
41 self.data_type == other.data_type
42 }
43}
44
45impl Display for ExaTypeInfo {
46 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47 write!(f, "{}", self.name)
48 }
49}
50
51impl TypeInfo for ExaTypeInfo {
52 fn is_null(&self) -> bool {
53 matches!(self.data_type, ExaDataType::Null)
54 }
55
56 fn name(&self) -> &str {
63 self.name.as_ref()
64 }
65}
66
67#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq)]
74#[serde(rename_all = "UPPERCASE")]
75#[serde(tag = "type")]
76pub enum ExaDataType {
77 Null,
78 Boolean,
79 Char(StringLike),
80 Date,
81 Decimal(Decimal),
82 Double,
83 Geometry(Geometry),
84 #[serde(rename = "INTERVAL DAY TO SECOND")]
85 IntervalDayToSecond(IntervalDayToSecond),
86 #[serde(rename = "INTERVAL YEAR TO MONTH")]
87 IntervalYearToMonth(IntervalYearToMonth),
88 Timestamp,
89 #[serde(rename = "TIMESTAMP WITH LOCAL TIME ZONE")]
90 TimestampWithLocalTimeZone,
91 Varchar(StringLike),
92 HashType(HashType),
93}
94
95impl ExaDataType {
96 const NULL: &'static str = "NULL";
97 const BOOLEAN: &'static str = "BOOLEAN";
98 const CHAR: &'static str = "CHAR";
99 const DATE: &'static str = "DATE";
100 const DECIMAL: &'static str = "DECIMAL";
101 const DOUBLE: &'static str = "DOUBLE PRECISION";
102 const GEOMETRY: &'static str = "GEOMETRY";
103 const INTERVAL_DAY_TO_SECOND: &'static str = "INTERVAL DAY TO SECOND";
104 const INTERVAL_YEAR_TO_MONTH: &'static str = "INTERVAL YEAR TO MONTH";
105 const TIMESTAMP: &'static str = "TIMESTAMP";
106 const TIMESTAMP_WITH_LOCAL_TIME_ZONE: &'static str = "TIMESTAMP WITH LOCAL TIME ZONE";
107 const VARCHAR: &'static str = "VARCHAR";
108 const HASHTYPE: &'static str = "HASHTYPE";
109
110 pub fn compatible(&self, other: &Self) -> bool {
115 match self {
116 ExaDataType::Null => true,
117 ExaDataType::Boolean => matches!(other, ExaDataType::Boolean | ExaDataType::Null),
118 ExaDataType::Char(c) | ExaDataType::Varchar(c) => c.compatible(other),
119 ExaDataType::Date => matches!(
120 other,
121 ExaDataType::Date
122 | ExaDataType::Char(_)
123 | ExaDataType::Varchar(_)
124 | ExaDataType::Null
125 ),
126 ExaDataType::Decimal(d) => d.compatible(other),
127 ExaDataType::Double => match other {
128 ExaDataType::Double | ExaDataType::Null => true,
129 ExaDataType::Decimal(d) if d.scale > 0 => true,
130 _ => false,
131 },
132 ExaDataType::Geometry(g) => g.compatible(other),
133 ExaDataType::IntervalDayToSecond(ids) => ids.compatible(other),
134 ExaDataType::IntervalYearToMonth(iym) => iym.compatible(other),
135 ExaDataType::Timestamp => matches!(
136 other,
137 ExaDataType::Timestamp
138 | ExaDataType::TimestampWithLocalTimeZone
139 | ExaDataType::Char(_)
140 | ExaDataType::Varchar(_)
141 | ExaDataType::Null
142 ),
143 ExaDataType::TimestampWithLocalTimeZone => matches!(
144 other,
145 ExaDataType::TimestampWithLocalTimeZone
146 | ExaDataType::Timestamp
147 | ExaDataType::Char(_)
148 | ExaDataType::Varchar(_)
149 | ExaDataType::Null
150 ),
151 ExaDataType::HashType(_) => matches!(
152 other,
153 ExaDataType::HashType(_)
154 | ExaDataType::Varchar(_)
155 | ExaDataType::Char(_)
156 | ExaDataType::Null
157 ),
158 }
159 }
160
161 fn full_name(&self) -> DataTypeName {
162 match self {
163 ExaDataType::Null => Self::NULL.into(),
164 ExaDataType::Boolean => Self::BOOLEAN.into(),
165 ExaDataType::Date => Self::DATE.into(),
166 ExaDataType::Double => Self::DOUBLE.into(),
167 ExaDataType::Timestamp => Self::TIMESTAMP.into(),
168 ExaDataType::TimestampWithLocalTimeZone => Self::TIMESTAMP_WITH_LOCAL_TIME_ZONE.into(),
169 ExaDataType::Char(c) | ExaDataType::Varchar(c) => {
170 format_args!("{}({}) {}", self.as_ref(), c.size, c.character_set).into()
171 }
172 ExaDataType::Decimal(d) => {
173 format_args!("{}({}, {})", self.as_ref(), d.precision, d.scale).into()
174 }
175 ExaDataType::Geometry(g) => format_args!("{}({})", self.as_ref(), g.srid).into(),
176 ExaDataType::IntervalDayToSecond(ids) => format_args!(
177 "INTERVAL DAY({}) TO SECOND({})",
178 ids.precision, ids.fraction
179 )
180 .into(),
181 ExaDataType::IntervalYearToMonth(iym) => {
182 format_args!("INTERVAL YEAR({}) TO MONTH", iym.precision).into()
183 }
184 ExaDataType::HashType(_) => format_args!("{}", self.as_ref()).into(),
185 }
186 }
187}
188
189impl AsRef<str> for ExaDataType {
190 fn as_ref(&self) -> &str {
191 match self {
192 ExaDataType::Null => Self::NULL,
193 ExaDataType::Boolean => Self::BOOLEAN,
194 ExaDataType::Char(_) => Self::CHAR,
195 ExaDataType::Date => Self::DATE,
196 ExaDataType::Decimal(_) => Self::DECIMAL,
197 ExaDataType::Double => Self::DOUBLE,
198 ExaDataType::Geometry(_) => Self::GEOMETRY,
199 ExaDataType::IntervalDayToSecond(_) => Self::INTERVAL_DAY_TO_SECOND,
200 ExaDataType::IntervalYearToMonth(_) => Self::INTERVAL_YEAR_TO_MONTH,
201 ExaDataType::Timestamp => Self::TIMESTAMP,
202 ExaDataType::TimestampWithLocalTimeZone => Self::TIMESTAMP_WITH_LOCAL_TIME_ZONE,
203 ExaDataType::Varchar(_) => Self::VARCHAR,
204 ExaDataType::HashType(_) => Self::HASHTYPE,
205 }
206 }
207}
208
209#[derive(Debug, Clone, Copy)]
215pub enum DataTypeName {
216 Static(&'static str),
217 Inline(ArrayString<30>),
218}
219
220impl AsRef<str> for DataTypeName {
221 fn as_ref(&self) -> &str {
222 match self {
223 DataTypeName::Static(s) => s,
224 DataTypeName::Inline(s) => s.as_str(),
225 }
226 }
227}
228
229impl Display for DataTypeName {
230 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
231 write!(f, "{}", self.as_ref())
232 }
233}
234
235impl From<&'static str> for DataTypeName {
236 fn from(value: &'static str) -> Self {
237 Self::Static(value)
238 }
239}
240
241impl From<Arguments<'_>> for DataTypeName {
242 fn from(value: Arguments<'_>) -> Self {
243 Self::Inline(ArrayString::try_from(value).expect("inline data type name too large"))
244 }
245}
246
247#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq)]
249#[serde(rename_all = "camelCase")]
250pub struct StringLike {
251 size: usize,
252 character_set: Charset,
253}
254
255impl StringLike {
256 pub const MAX_VARCHAR_LEN: usize = 2_000_000;
257 pub const MAX_CHAR_LEN: usize = 2000;
258
259 pub fn new(size: usize, character_set: Charset) -> Self {
260 Self {
261 size,
262 character_set,
263 }
264 }
265
266 pub fn size(&self) -> usize {
267 self.size
268 }
269
270 pub fn character_set(&self) -> Charset {
271 self.character_set
272 }
273
274 #[allow(clippy::unused_self)]
279 pub fn compatible(&self, ty: &ExaDataType) -> bool {
280 matches!(
281 ty,
282 ExaDataType::Char(_)
283 | ExaDataType::Varchar(_)
284 | ExaDataType::Null
285 | ExaDataType::Date
286 | ExaDataType::Geometry(_)
287 | ExaDataType::HashType(_)
288 | ExaDataType::IntervalDayToSecond(_)
289 | ExaDataType::IntervalYearToMonth(_)
290 | ExaDataType::Timestamp
291 | ExaDataType::TimestampWithLocalTimeZone
292 )
293 }
294}
295
296#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq)]
298#[serde(rename_all = "UPPERCASE")]
299pub enum Charset {
300 Utf8,
301 Ascii,
302}
303
304impl AsRef<str> for Charset {
305 fn as_ref(&self) -> &str {
306 match self {
307 Charset::Utf8 => "UTF8",
308 Charset::Ascii => "ASCII",
309 }
310 }
311}
312
313impl Display for Charset {
314 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
315 write!(f, "{}", self.as_ref())
316 }
317}
318
319#[derive(Debug, Copy, Clone, Deserialize, Serialize, PartialEq)]
321#[serde(rename_all = "camelCase")]
322pub struct Decimal {
323 precision: u32,
324 scale: u32,
325}
326
327impl Decimal {
328 pub const MAX_8BIT_PRECISION: u32 = 3;
329 pub const MAX_16BIT_PRECISION: u32 = 5;
330 pub const MAX_32BIT_PRECISION: u32 = 10;
331 pub const MAX_64BIT_PRECISION: u32 = 20;
332 pub const MAX_128BIT_PRECISION: u32 = 39;
335 pub const MAX_PRECISION: u32 = 36;
336 pub const MAX_SCALE: u32 = 35;
337
338 pub fn new(precision: u32, scale: u32) -> Self {
339 Self { precision, scale }
340 }
341
342 pub fn precision(self) -> u32 {
343 self.precision
344 }
345
346 pub fn scale(self) -> u32 {
347 self.scale
348 }
349
350 pub fn compatible(self, ty: &ExaDataType) -> bool {
351 match ty {
352 ExaDataType::Decimal(d) => self >= *d,
353 ExaDataType::Double => self.scale > 0,
354 ExaDataType::Null => true,
355 _ => false,
356 }
357 }
358}
359
360#[rustfmt::skip] impl PartialOrd for Decimal {
378 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
379 let self_diff = self.precision - self.scale;
380 let other_diff = other.precision - other.scale;
381
382 let scale_cmp = self.scale.partial_cmp(&other.scale);
383 let diff_cmp = self_diff.partial_cmp(&other_diff);
384
385 #[allow(clippy::match_same_arms)] match (scale_cmp, diff_cmp) {
387 (Some(Ordering::Greater), Some(Ordering::Greater)) => Some(Ordering::Greater),
388 (Some(Ordering::Greater), Some(Ordering::Equal)) => Some(Ordering::Greater),
389 (Some(Ordering::Equal), ord) => ord,
390 (Some(Ordering::Less), Some(Ordering::Less)) => Some(Ordering::Less),
391 (Some(Ordering::Less), Some(Ordering::Equal)) => Some(Ordering::Less),
392 _ => None,
393 }
394 }
395}
396
397#[derive(Debug, Copy, Clone, Deserialize, Serialize, PartialEq)]
399#[serde(rename_all = "camelCase")]
400pub struct Geometry {
401 srid: u16,
402}
403
404impl Geometry {
405 pub fn new(srid: u16) -> Self {
406 Self { srid }
407 }
408
409 pub fn srid(self) -> u16 {
410 self.srid
411 }
412
413 pub fn compatible(self, ty: &ExaDataType) -> bool {
414 match ty {
415 ExaDataType::Geometry(g) => self.srid == g.srid,
416 ExaDataType::Varchar(_) | ExaDataType::Char(_) | ExaDataType::Null => true,
417 _ => false,
418 }
419 }
420}
421
422#[derive(Debug, Copy, Clone, Deserialize, Serialize, PartialEq)]
424#[serde(rename_all = "camelCase")]
425pub struct IntervalDayToSecond {
426 precision: u32,
427 fraction: u32,
428}
429
430impl PartialOrd for IntervalDayToSecond {
431 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
432 let precision_cmp = self.precision.partial_cmp(&other.precision);
433 let fraction_cmp = self.fraction.partial_cmp(&other.fraction);
434
435 match (precision_cmp, fraction_cmp) {
436 (Some(Ordering::Equal), Some(Ordering::Equal)) => Some(Ordering::Equal),
437 (Some(Ordering::Equal | Ordering::Less), Some(Ordering::Less))
438 | (Some(Ordering::Less), Some(Ordering::Equal)) => Some(Ordering::Less),
439 (Some(Ordering::Equal | Ordering::Greater), Some(Ordering::Greater))
440 | (Some(Ordering::Greater), Some(Ordering::Equal)) => Some(Ordering::Greater),
441 _ => None,
442 }
443 }
444}
445
446impl IntervalDayToSecond {
447 pub const MAX_SUPPORTED_FRACTION: u32 = 3;
456 pub const MAX_PRECISION: u32 = 9;
457
458 pub fn new(precision: u32, fraction: u32) -> Self {
459 Self {
460 precision,
461 fraction,
462 }
463 }
464
465 pub fn precision(self) -> u32 {
466 self.precision
467 }
468
469 pub fn fraction(self) -> u32 {
470 self.fraction
471 }
472
473 pub fn compatible(self, ty: &ExaDataType) -> bool {
474 match ty {
475 ExaDataType::IntervalDayToSecond(i) => self >= *i,
476 ExaDataType::Varchar(_) | ExaDataType::Char(_) | ExaDataType::Null => true,
477 _ => false,
478 }
479 }
480}
481
482#[derive(Debug, Copy, Clone, Deserialize, Serialize, PartialEq, PartialOrd)]
484#[serde(rename_all = "camelCase")]
485pub struct IntervalYearToMonth {
486 precision: u32,
487}
488
489impl IntervalYearToMonth {
490 pub const MAX_PRECISION: u32 = 9;
491
492 pub fn new(precision: u32) -> Self {
493 Self { precision }
494 }
495
496 pub fn precision(self) -> u32 {
497 self.precision
498 }
499
500 pub fn compatible(self, ty: &ExaDataType) -> bool {
501 match ty {
502 ExaDataType::IntervalYearToMonth(i) => self >= *i,
503 ExaDataType::Varchar(_) | ExaDataType::Char(_) | ExaDataType::Null => true,
504 _ => false,
505 }
506 }
507}
508
509#[derive(Debug, Copy, Clone, Deserialize, Serialize, PartialEq, PartialOrd)]
518#[serde(rename_all = "camelCase")]
519pub struct HashType {}
520
521#[cfg(test)]
526mod tests {
527 use super::*;
528
529 #[test]
530 fn test_null_name() {
531 let data_type = ExaDataType::Null;
532 assert_eq!(data_type.full_name().as_ref(), "NULL");
533 }
534
535 #[test]
536 fn test_boolean_name() {
537 let data_type = ExaDataType::Boolean;
538 assert_eq!(data_type.full_name().as_ref(), "BOOLEAN");
539 }
540
541 #[test]
542 fn test_max_char_name() {
543 let string_like = StringLike::new(StringLike::MAX_CHAR_LEN, Charset::Ascii);
544 let data_type = ExaDataType::Char(string_like);
545 assert_eq!(
546 data_type.full_name().as_ref(),
547 format!("CHAR({}) ASCII", StringLike::MAX_CHAR_LEN)
548 );
549 }
550
551 #[test]
552 fn test_date_name() {
553 let data_type = ExaDataType::Date;
554 assert_eq!(data_type.full_name().as_ref(), "DATE");
555 }
556
557 #[test]
558 fn test_max_decimal_name() {
559 let decimal = Decimal::new(Decimal::MAX_PRECISION, Decimal::MAX_SCALE);
560 let data_type = ExaDataType::Decimal(decimal);
561 assert_eq!(
562 data_type.full_name().as_ref(),
563 format!(
564 "DECIMAL({}, {})",
565 Decimal::MAX_PRECISION,
566 Decimal::MAX_SCALE
567 )
568 );
569 }
570
571 #[test]
572 fn test_double_name() {
573 let data_type = ExaDataType::Double;
574 assert_eq!(data_type.full_name().as_ref(), "DOUBLE PRECISION");
575 }
576
577 #[test]
578 fn test_max_geometry_name() {
579 let geometry = Geometry::new(u16::MAX);
580 let data_type = ExaDataType::Geometry(geometry);
581 assert_eq!(
582 data_type.full_name().as_ref(),
583 format!("GEOMETRY({})", u16::MAX)
584 );
585 }
586
587 #[test]
588 fn test_max_interval_day_name() {
589 let ids = IntervalDayToSecond::new(
590 IntervalDayToSecond::MAX_PRECISION,
591 IntervalDayToSecond::MAX_SUPPORTED_FRACTION,
592 );
593 let data_type = ExaDataType::IntervalDayToSecond(ids);
594 assert_eq!(
595 data_type.full_name().as_ref(),
596 format!(
597 "INTERVAL DAY({}) TO SECOND({})",
598 IntervalDayToSecond::MAX_PRECISION,
599 IntervalDayToSecond::MAX_SUPPORTED_FRACTION
600 )
601 );
602 }
603
604 #[test]
605 fn test_max_interval_year_name() {
606 let iym = IntervalYearToMonth::new(IntervalYearToMonth::MAX_PRECISION);
607 let data_type = ExaDataType::IntervalYearToMonth(iym);
608 assert_eq!(
609 data_type.full_name().as_ref(),
610 format!(
611 "INTERVAL YEAR({}) TO MONTH",
612 IntervalYearToMonth::MAX_PRECISION,
613 )
614 );
615 }
616
617 #[test]
618 fn test_timestamp_name() {
619 let data_type = ExaDataType::Timestamp;
620 assert_eq!(data_type.full_name().as_ref(), "TIMESTAMP");
621 }
622
623 #[test]
624 fn test_timestamp_with_tz_name() {
625 let data_type = ExaDataType::TimestampWithLocalTimeZone;
626 assert_eq!(
627 data_type.full_name().as_ref(),
628 "TIMESTAMP WITH LOCAL TIME ZONE"
629 );
630 }
631
632 #[test]
633 fn test_max_varchar_name() {
634 let string_like = StringLike::new(StringLike::MAX_VARCHAR_LEN, Charset::Ascii);
635 let data_type = ExaDataType::Varchar(string_like);
636 assert_eq!(
637 data_type.full_name().as_ref(),
638 format!("VARCHAR({}) ASCII", StringLike::MAX_VARCHAR_LEN)
639 );
640 }
641}