1use std::error::Error;
11use std::fmt;
12use std::mem::size_of;
13use std::sync::LazyLock;
14
15use mz_repr::SqlScalarType;
16use mz_repr::adt::char::{CharLength as AdtCharLength, InvalidCharLengthError};
17use mz_repr::adt::mz_acl_item::{AclItem, MzAclItem};
18use mz_repr::adt::numeric::{
19 InvalidNumericMaxScaleError, NUMERIC_DATUM_MAX_PRECISION, NumericMaxScale,
20};
21use mz_repr::adt::timestamp::{
22 InvalidTimestampPrecisionError, TimestampPrecision as AdtTimestampPrecision,
23};
24use mz_repr::adt::varchar::{InvalidVarCharMaxLengthError, VarCharMaxLength};
25use mz_repr::namespaces::MZ_CATALOG_SCHEMA;
26
27use crate::oid;
28
29const VARHDRSZ: i32 = 4;
33
34const MAX_INTERVAL_PRECISION: i32 = 6;
38
39const MAX_TIMESTAMP_PRECISION: i32 = 6;
43
44const MAX_TIME_PRECISION: i32 = 6;
48
49#[derive(Debug, Clone, Eq, PartialEq)]
54pub enum Type {
55 Array(Box<Type>),
57 Bool,
59 Bytea,
61 Char,
63 Date,
65 Float4,
67 Float8,
69 Int2,
71 Int4,
73 Int8,
75 UInt2,
77 UInt4,
79 UInt8,
81 Interval {
83 constraints: Option<IntervalConstraints>,
85 },
86 Json,
88 Jsonb,
90 List(Box<Type>),
92 Map {
94 value_type: Box<Type>,
96 },
97 Name,
100 Numeric {
102 constraints: Option<NumericConstraints>,
104 },
105 Oid,
107 Record(Vec<Type>),
109 Text,
111 BpChar {
113 length: Option<CharLength>,
117 },
118 VarChar {
120 max_length: Option<CharLength>,
122 },
123 Time {
125 precision: Option<TimePrecision>,
127 },
128 TimeTz {
130 precision: Option<TimePrecision>,
132 },
133 Timestamp {
135 precision: Option<TimestampPrecision>,
137 },
138 TimestampTz {
140 precision: Option<TimestampPrecision>,
142 },
143 Uuid,
145 RegProc,
147 RegType,
149 RegClass,
151 Int2Vector,
153 MzTimestamp,
155 Range {
157 element_type: Box<Type>,
159 },
160 MzAclItem,
163 AclItem,
166}
167
168pub trait TypeConstraint: fmt::Display {
170 fn from_typmod(typmod: i32) -> Result<Option<Self>, String>
172 where
173 Self: Sized;
174
175 fn into_typmod(&self) -> i32;
177}
178
179#[derive(Debug, Clone, Copy, Eq, PartialEq)]
181pub struct CharLength(i32);
182
183impl TypeConstraint for CharLength {
184 fn from_typmod(typmod: i32) -> Result<Option<CharLength>, String> {
185 if typmod >= VARHDRSZ {
187 Ok(Some(CharLength(typmod - VARHDRSZ)))
188 } else {
189 Ok(None)
190 }
191 }
192
193 fn into_typmod(&self) -> i32 {
194 self.0 + VARHDRSZ
196 }
197}
198
199impl CharLength {
200 pub fn into_i32(self) -> i32 {
202 self.0
203 }
204}
205
206impl From<AdtCharLength> for CharLength {
207 fn from(length: AdtCharLength) -> CharLength {
208 CharLength(i32::try_from(length.into_u32()).unwrap())
211 }
212}
213
214impl From<VarCharMaxLength> for CharLength {
215 fn from(length: VarCharMaxLength) -> CharLength {
216 CharLength(i32::try_from(length.into_u32()).unwrap())
219 }
220}
221
222impl fmt::Display for CharLength {
223 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
224 write!(f, "({})", self.0)
226 }
227}
228
229#[derive(Debug, Clone, Copy, Eq, PartialEq)]
231pub struct IntervalConstraints {
232 range: i32,
234 precision: i32,
236}
237
238impl TypeConstraint for IntervalConstraints {
239 fn from_typmod(typmod: i32) -> Result<Option<IntervalConstraints>, String> {
240 if typmod < 0 {
241 Ok(None)
242 } else {
243 let range = (typmod >> 16) & 0x7fff;
245 let precision = typmod & 0xffff;
246 if precision > MAX_INTERVAL_PRECISION {
247 return Err(format!(
248 "exceeds maximum interval precision {MAX_INTERVAL_PRECISION}"
249 ));
250 }
251 Ok(Some(IntervalConstraints { range, precision }))
252 }
253 }
254
255 fn into_typmod(&self) -> i32 {
256 (self.range << 16) | self.precision
257 }
258}
259
260impl fmt::Display for IntervalConstraints {
261 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
262 write!(f, "({})", self.precision)
265 }
266}
267
268#[derive(Debug, Clone, Copy, Eq, PartialEq)]
270pub struct TimePrecision(i32);
271
272impl TypeConstraint for TimePrecision {
273 fn from_typmod(typmod: i32) -> Result<Option<TimePrecision>, String> {
274 if typmod > MAX_TIME_PRECISION {
275 Err(format!(
276 "exceeds maximum time precision {MAX_TIME_PRECISION}"
277 ))
278 } else if typmod >= 0 {
279 Ok(Some(TimePrecision(typmod)))
280 } else {
281 Ok(None)
282 }
283 }
284
285 fn into_typmod(&self) -> i32 {
286 self.0
287 }
288}
289
290impl fmt::Display for TimePrecision {
291 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
292 write!(f, "({})", self.0)
294 }
295}
296
297#[derive(Debug, Clone, Copy, Eq, PartialEq)]
299pub struct TimestampPrecision(i32);
300
301impl TimestampPrecision {
302 pub fn into_i32(self) -> i32 {
304 self.0
305 }
306}
307
308impl TypeConstraint for TimestampPrecision {
309 fn from_typmod(typmod: i32) -> Result<Option<TimestampPrecision>, String> {
310 if typmod > MAX_TIMESTAMP_PRECISION {
311 Err(format!(
312 "exceeds maximum timestamp precision {MAX_TIMESTAMP_PRECISION}"
313 ))
314 } else if typmod >= 0 {
315 Ok(Some(TimestampPrecision(typmod)))
316 } else {
317 Ok(None)
318 }
319 }
320
321 fn into_typmod(&self) -> i32 {
322 self.0
323 }
324}
325
326impl From<AdtTimestampPrecision> for TimestampPrecision {
327 fn from(precision: AdtTimestampPrecision) -> TimestampPrecision {
328 TimestampPrecision(i32::from(precision.into_u8()))
329 }
330}
331
332impl fmt::Display for TimestampPrecision {
333 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
334 write!(f, "({})", self.0)
336 }
337}
338
339#[derive(Debug, Clone, Copy, Eq, PartialEq)]
341pub struct NumericConstraints {
342 max_precision: i32,
344 max_scale: i32,
346}
347
348impl NumericConstraints {
349 pub fn max_precision(&self) -> i32 {
351 self.max_precision
352 }
353
354 pub fn max_scale(&self) -> i32 {
356 self.max_scale
357 }
358}
359
360impl TypeConstraint for NumericConstraints {
361 fn from_typmod(typmod: i32) -> Result<Option<NumericConstraints>, String> {
362 if typmod >= VARHDRSZ {
364 Ok(Some(NumericConstraints {
365 max_precision: ((typmod - VARHDRSZ) >> 16) & 0xffff,
366 max_scale: (((typmod - VARHDRSZ) & 0x7ff) ^ 1024) - 1024,
367 }))
368 } else {
369 Ok(None)
370 }
371 }
372
373 fn into_typmod(&self) -> i32 {
374 ((self.max_precision << 16) | (self.max_scale & 0x7ff)) + VARHDRSZ
376 }
377}
378
379impl fmt::Display for NumericConstraints {
380 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
381 write!(f, "({},{})", self.max_precision, self.max_scale)
383 }
384}
385
386pub static LIST: LazyLock<postgres_types::Type> = LazyLock::new(|| {
388 postgres_types::Type::new(
389 "list".to_owned(),
390 oid::TYPE_LIST_OID,
395 postgres_types::Kind::Pseudo,
396 MZ_CATALOG_SCHEMA.to_owned(),
397 )
398});
399
400pub static MAP: LazyLock<postgres_types::Type> = LazyLock::new(|| {
402 postgres_types::Type::new(
403 "map".to_owned(),
404 oid::TYPE_MAP_OID,
406 postgres_types::Kind::Pseudo,
407 MZ_CATALOG_SCHEMA.to_owned(),
408 )
409});
410
411pub static ANYCOMPATIBLELIST: LazyLock<postgres_types::Type> = LazyLock::new(|| {
413 postgres_types::Type::new(
414 "anycompatiblelist".to_owned(),
415 oid::TYPE_ANYCOMPATIBLELIST_OID,
416 postgres_types::Kind::Pseudo,
417 MZ_CATALOG_SCHEMA.to_owned(),
418 )
419});
420
421pub static ANYCOMPATIBLEMAP: LazyLock<postgres_types::Type> = LazyLock::new(|| {
423 postgres_types::Type::new(
424 "anycompatiblemap".to_owned(),
425 oid::TYPE_ANYCOMPATIBLEMAP_OID,
426 postgres_types::Kind::Pseudo,
427 MZ_CATALOG_SCHEMA.to_owned(),
428 )
429});
430
431pub static UINT2: LazyLock<postgres_types::Type> = LazyLock::new(|| {
433 postgres_types::Type::new(
434 "uint2".to_owned(),
435 oid::TYPE_UINT2_OID,
436 postgres_types::Kind::Pseudo,
437 MZ_CATALOG_SCHEMA.to_owned(),
438 )
439});
440
441pub static UINT4: LazyLock<postgres_types::Type> = LazyLock::new(|| {
443 postgres_types::Type::new(
444 "uint4".to_owned(),
445 oid::TYPE_UINT4_OID,
446 postgres_types::Kind::Pseudo,
447 MZ_CATALOG_SCHEMA.to_owned(),
448 )
449});
450
451pub static UINT8: LazyLock<postgres_types::Type> = LazyLock::new(|| {
453 postgres_types::Type::new(
454 "uint8".to_owned(),
455 oid::TYPE_UINT8_OID,
456 postgres_types::Kind::Pseudo,
457 MZ_CATALOG_SCHEMA.to_owned(),
458 )
459});
460
461pub static UINT2_ARRAY: LazyLock<postgres_types::Type> = LazyLock::new(|| {
463 postgres_types::Type::new(
464 "_uint2".to_owned(),
465 oid::TYPE_UINT2_ARRAY_OID,
466 postgres_types::Kind::Pseudo,
467 MZ_CATALOG_SCHEMA.to_owned(),
468 )
469});
470
471pub static UINT4_ARRAY: LazyLock<postgres_types::Type> = LazyLock::new(|| {
473 postgres_types::Type::new(
474 "_uint4".to_owned(),
475 oid::TYPE_UINT4_ARRAY_OID,
476 postgres_types::Kind::Pseudo,
477 MZ_CATALOG_SCHEMA.to_owned(),
478 )
479});
480
481pub static UINT8_ARRAY: LazyLock<postgres_types::Type> = LazyLock::new(|| {
483 postgres_types::Type::new(
484 "_uint8".to_owned(),
485 oid::TYPE_UINT8_ARRAY_OID,
486 postgres_types::Kind::Pseudo,
487 MZ_CATALOG_SCHEMA.to_owned(),
488 )
489});
490
491pub static MZ_TIMESTAMP: LazyLock<postgres_types::Type> = LazyLock::new(|| {
493 postgres_types::Type::new(
494 "mz_timestamp".to_owned(),
495 oid::TYPE_MZ_TIMESTAMP_OID,
496 postgres_types::Kind::Pseudo,
497 MZ_CATALOG_SCHEMA.to_owned(),
498 )
499});
500
501pub static MZ_TIMESTAMP_ARRAY: LazyLock<postgres_types::Type> = LazyLock::new(|| {
503 postgres_types::Type::new(
504 "_mz_timestamp".to_owned(),
505 oid::TYPE_MZ_TIMESTAMP_ARRAY_OID,
506 postgres_types::Kind::Pseudo,
507 MZ_CATALOG_SCHEMA.to_owned(),
508 )
509});
510
511pub static MZ_ACL_ITEM: LazyLock<postgres_types::Type> = LazyLock::new(|| {
513 postgres_types::Type::new(
514 "mz_aclitem".to_owned(),
515 oid::TYPE_MZ_ACL_ITEM_OID,
516 postgres_types::Kind::Pseudo,
517 MZ_CATALOG_SCHEMA.to_owned(),
518 )
519});
520
521pub static MZ_ACL_ITEM_ARRAY: LazyLock<postgres_types::Type> = LazyLock::new(|| {
523 postgres_types::Type::new(
524 "_mz_aclitem".to_owned(),
525 oid::TYPE_MZ_ACL_ITEM_ARRAY_OID,
526 postgres_types::Kind::Pseudo,
527 MZ_CATALOG_SCHEMA.to_owned(),
528 )
529});
530
531impl Type {
532 pub fn from_oid(oid: u32) -> Result<Type, TypeFromOidError> {
534 Type::from_oid_and_typmod(oid, -1)
535 }
536
537 pub fn name_from_oid(oid: u32) -> Option<&'static str> {
549 let typ = match oid {
550 oid::TYPE_NAME_OID => Type::Name,
551 oid::TYPE_NAME_ARRAY_OID => Type::Array(Box::new(Type::Name)),
552 o if o == postgres_types::Type::ACLITEM.oid() => Type::AclItem,
553 o if o == postgres_types::Type::ACLITEM_ARRAY.oid() => {
554 Type::Array(Box::new(Type::AclItem))
555 }
556 oid::TYPE_UINT2_OID => Type::UInt2,
557 oid::TYPE_UINT2_ARRAY_OID => Type::Array(Box::new(Type::UInt2)),
558 oid::TYPE_UINT4_OID => Type::UInt4,
559 oid::TYPE_UINT4_ARRAY_OID => Type::Array(Box::new(Type::UInt4)),
560 oid::TYPE_UINT8_OID => Type::UInt8,
561 oid::TYPE_UINT8_ARRAY_OID => Type::Array(Box::new(Type::UInt8)),
562 oid::TYPE_MZ_TIMESTAMP_OID => Type::MzTimestamp,
563 oid::TYPE_MZ_TIMESTAMP_ARRAY_OID => Type::Array(Box::new(Type::MzTimestamp)),
564 oid::TYPE_MZ_ACL_ITEM_OID => Type::MzAclItem,
565 oid::TYPE_MZ_ACL_ITEM_ARRAY_OID => Type::Array(Box::new(Type::MzAclItem)),
566 oid::TYPE_RECORD_OID => Type::Record(vec![]),
567 oid::TYPE_RECORD_ARRAY_OID => Type::Array(Box::new(Type::Record(vec![]))),
568 oid::TYPE_LIST_OID => return Some(LIST.name()),
569 oid::TYPE_MAP_OID => return Some(MAP.name()),
570 _ => Type::from_oid(oid).ok()?,
571 };
572 Some(typ.name())
573 }
574
575 pub fn from_oid_and_typmod(oid: u32, typmod: i32) -> Result<Type, TypeFromOidError> {
585 let typ = postgres_types::Type::from_oid(oid).ok_or(TypeFromOidError::UnknownOid(oid))?;
586 let mut typ = match typ {
587 postgres_types::Type::BOOL => Type::Bool,
588 postgres_types::Type::BYTEA => Type::Bytea,
589 postgres_types::Type::DATE => Type::Date,
590 postgres_types::Type::FLOAT4 => Type::Float4,
591 postgres_types::Type::FLOAT8 => Type::Float8,
592 postgres_types::Type::INT2 => Type::Int2,
593 postgres_types::Type::INT4 => Type::Int4,
594 postgres_types::Type::INT8 => Type::Int8,
595 postgres_types::Type::INTERVAL => Type::Interval { constraints: None },
596 postgres_types::Type::JSON => Type::Json,
597 postgres_types::Type::JSONB => Type::Jsonb,
598 postgres_types::Type::NUMERIC => Type::Numeric { constraints: None },
599 postgres_types::Type::OID => Type::Oid,
600 postgres_types::Type::TEXT => Type::Text,
601 postgres_types::Type::BPCHAR => Type::BpChar { length: None },
602 postgres_types::Type::CHAR => Type::Char,
603 postgres_types::Type::VARCHAR => Type::VarChar { max_length: None },
604 postgres_types::Type::TIME => Type::Time { precision: None },
605 postgres_types::Type::TIMETZ => Type::TimeTz { precision: None },
606 postgres_types::Type::TIMESTAMP => Type::Timestamp { precision: None },
607 postgres_types::Type::TIMESTAMPTZ => Type::TimestampTz { precision: None },
608 postgres_types::Type::UUID => Type::Uuid,
609 postgres_types::Type::REGCLASS => Type::RegClass,
610 postgres_types::Type::REGPROC => Type::RegProc,
611 postgres_types::Type::REGTYPE => Type::RegType,
612 postgres_types::Type::BOOL_ARRAY => Type::Array(Box::new(Type::Bool)),
613 postgres_types::Type::BYTEA_ARRAY => Type::Array(Box::new(Type::Bytea)),
614 postgres_types::Type::BPCHAR_ARRAY => {
615 Type::Array(Box::new(Type::BpChar { length: None }))
616 }
617 postgres_types::Type::CHAR_ARRAY => Type::Array(Box::new(Type::Char)),
618 postgres_types::Type::DATE_ARRAY => Type::Array(Box::new(Type::Date)),
619 postgres_types::Type::FLOAT4_ARRAY => Type::Array(Box::new(Type::Float4)),
620 postgres_types::Type::FLOAT8_ARRAY => Type::Array(Box::new(Type::Float8)),
621 postgres_types::Type::INT2_ARRAY => Type::Array(Box::new(Type::Int2)),
622 postgres_types::Type::INT4_ARRAY => Type::Array(Box::new(Type::Int4)),
623 postgres_types::Type::INT8_ARRAY => Type::Array(Box::new(Type::Int8)),
624 postgres_types::Type::INTERVAL_ARRAY => {
625 Type::Array(Box::new(Type::Interval { constraints: None }))
626 }
627 postgres_types::Type::JSON_ARRAY => Type::Array(Box::new(Type::Json)),
628 postgres_types::Type::JSONB_ARRAY => Type::Array(Box::new(Type::Jsonb)),
629 postgres_types::Type::NUMERIC_ARRAY => {
630 Type::Array(Box::new(Type::Numeric { constraints: None }))
631 }
632 postgres_types::Type::OID_ARRAY => Type::Array(Box::new(Type::Oid)),
633 postgres_types::Type::TEXT_ARRAY => Type::Array(Box::new(Type::Text)),
634 postgres_types::Type::TIME_ARRAY => {
635 Type::Array(Box::new(Type::Time { precision: None }))
636 }
637 postgres_types::Type::TIMETZ_ARRAY => {
638 Type::Array(Box::new(Type::TimeTz { precision: None }))
639 }
640 postgres_types::Type::TIMESTAMP_ARRAY => {
641 Type::Array(Box::new(Type::Timestamp { precision: None }))
642 }
643 postgres_types::Type::TIMESTAMPTZ_ARRAY => {
644 Type::Array(Box::new(Type::TimestampTz { precision: None }))
645 }
646 postgres_types::Type::UUID_ARRAY => Type::Array(Box::new(Type::Uuid)),
647 postgres_types::Type::VARCHAR_ARRAY => {
648 Type::Array(Box::new(Type::VarChar { max_length: None }))
649 }
650 postgres_types::Type::REGCLASS_ARRAY => Type::Array(Box::new(Type::RegClass)),
651 postgres_types::Type::REGPROC_ARRAY => Type::Array(Box::new(Type::RegProc)),
652 postgres_types::Type::REGTYPE_ARRAY => Type::Array(Box::new(Type::RegType)),
653 postgres_types::Type::INT2_VECTOR => Type::Int2Vector,
654 postgres_types::Type::INT2_VECTOR_ARRAY => Type::Array(Box::new(Type::Int2Vector)),
655 postgres_types::Type::INT4_RANGE => Type::Range {
656 element_type: Box::new(Type::Int4),
657 },
658 postgres_types::Type::INT4_RANGE_ARRAY => Type::Array(Box::new(Type::Range {
659 element_type: Box::new(Type::Int4),
660 })),
661 postgres_types::Type::INT8_RANGE => Type::Range {
662 element_type: Box::new(Type::Int8),
663 },
664 postgres_types::Type::INT8_RANGE_ARRAY => Type::Array(Box::new(Type::Range {
665 element_type: Box::new(Type::Int8),
666 })),
667 postgres_types::Type::NUM_RANGE => Type::Range {
668 element_type: Box::new(Type::Numeric { constraints: None }),
669 },
670 postgres_types::Type::NUM_RANGE_ARRAY => Type::Array(Box::new(Type::Range {
671 element_type: Box::new(Type::Numeric { constraints: None }),
672 })),
673 postgres_types::Type::TS_RANGE => Type::Range {
674 element_type: Box::new(Type::Timestamp { precision: None }),
675 },
676 postgres_types::Type::TS_RANGE_ARRAY => Type::Array(Box::new(Type::Range {
677 element_type: Box::new(Type::Timestamp { precision: None }),
678 })),
679 postgres_types::Type::TSTZ_RANGE => Type::Range {
680 element_type: Box::new(Type::TimestampTz { precision: None }),
681 },
682 postgres_types::Type::TSTZ_RANGE_ARRAY => Type::Array(Box::new(Type::Range {
683 element_type: Box::new(Type::TimestampTz { precision: None }),
684 })),
685 postgres_types::Type::DATE_RANGE => Type::Range {
686 element_type: Box::new(Type::Date),
687 },
688 postgres_types::Type::DATE_RANGE_ARRAY => Type::Array(Box::new(Type::Range {
689 element_type: Box::new(Type::Date),
690 })),
691 _ => return Err(TypeFromOidError::UnknownOid(oid)),
692 };
693
694 let res = ({
698 let typ = &mut typ;
699 || {
700 let elem_typ = match typ {
701 Type::Array(typ) => &mut **typ,
702 typ => typ,
703 };
704 match elem_typ {
705 Type::BpChar { length } => *length = CharLength::from_typmod(typmod)?,
706 Type::Numeric { constraints } => {
707 *constraints = NumericConstraints::from_typmod(typmod)?
708 }
709 Type::Interval { constraints } => {
710 *constraints = IntervalConstraints::from_typmod(typmod)?
711 }
712 Type::Time { precision } => *precision = TimePrecision::from_typmod(typmod)?,
713 Type::TimeTz { precision } => *precision = TimePrecision::from_typmod(typmod)?,
714 Type::Timestamp { precision } => {
715 *precision = TimestampPrecision::from_typmod(typmod)?
716 }
717 Type::TimestampTz { precision } => {
718 *precision = TimestampPrecision::from_typmod(typmod)?
719 }
720 Type::VarChar { max_length } => *max_length = CharLength::from_typmod(typmod)?,
721 _ if typmod != -1 => return Err("type does not support type modifiers".into()),
722 _ => (),
723 }
724 Ok(())
725 }
726 })();
727 match res {
728 Ok(()) => Ok(typ),
729 Err(detail) => Err(TypeFromOidError::InvalidTypmod {
730 typ,
731 typmod,
732 detail,
733 }),
734 }
735 }
736
737 pub(crate) fn inner(&self) -> &'static postgres_types::Type {
738 match self {
739 Type::AclItem => &postgres_types::Type::ACLITEM,
740 Type::Array(t) => match &**t {
741 Type::AclItem => &postgres_types::Type::ACLITEM_ARRAY,
742 Type::Array(_) => unreachable!(),
743 Type::Bool => &postgres_types::Type::BOOL_ARRAY,
744 Type::Bytea => &postgres_types::Type::BYTEA_ARRAY,
745 Type::Char => &postgres_types::Type::CHAR_ARRAY,
746 Type::Date => &postgres_types::Type::DATE_ARRAY,
747 Type::Float4 => &postgres_types::Type::FLOAT4_ARRAY,
748 Type::Float8 => &postgres_types::Type::FLOAT8_ARRAY,
749 Type::Int2 => &postgres_types::Type::INT2_ARRAY,
750 Type::Int4 => &postgres_types::Type::INT4_ARRAY,
751 Type::Int8 => &postgres_types::Type::INT8_ARRAY,
752 Type::UInt2 => &UINT2_ARRAY,
753 Type::UInt4 => &UINT4_ARRAY,
754 Type::UInt8 => &UINT8_ARRAY,
755 Type::Interval { .. } => &postgres_types::Type::INTERVAL_ARRAY,
756 Type::Json => &postgres_types::Type::JSON_ARRAY,
757 Type::Jsonb => &postgres_types::Type::JSONB_ARRAY,
758 Type::List(_) => unreachable!(),
759 Type::Map { .. } => unreachable!(),
760 Type::Name { .. } => &postgres_types::Type::NAME_ARRAY,
761 Type::Numeric { .. } => &postgres_types::Type::NUMERIC_ARRAY,
762 Type::Oid => &postgres_types::Type::OID_ARRAY,
763 Type::Record(_) => &postgres_types::Type::RECORD_ARRAY,
764 Type::Text => &postgres_types::Type::TEXT_ARRAY,
765 Type::BpChar { .. } => &postgres_types::Type::BPCHAR_ARRAY,
766 Type::VarChar { .. } => &postgres_types::Type::VARCHAR_ARRAY,
767 Type::Time { .. } => &postgres_types::Type::TIME_ARRAY,
768 Type::TimeTz { .. } => &postgres_types::Type::TIMETZ_ARRAY,
769 Type::Timestamp { .. } => &postgres_types::Type::TIMESTAMP_ARRAY,
770 Type::TimestampTz { .. } => &postgres_types::Type::TIMESTAMPTZ_ARRAY,
771 Type::Uuid => &postgres_types::Type::UUID_ARRAY,
772 Type::RegClass => &postgres_types::Type::REGCLASS_ARRAY,
773 Type::RegProc => &postgres_types::Type::REGPROC_ARRAY,
774 Type::RegType => &postgres_types::Type::REGTYPE_ARRAY,
775 Type::Int2Vector => &postgres_types::Type::INT2_VECTOR_ARRAY,
776 Type::MzTimestamp => &MZ_TIMESTAMP_ARRAY,
777 Type::Range { element_type } => match **element_type {
778 Type::Int4 => &postgres_types::Type::INT4_RANGE_ARRAY,
779 Type::Int8 => &postgres_types::Type::INT8_RANGE_ARRAY,
780 Type::Numeric { .. } => &postgres_types::Type::NUM_RANGE_ARRAY,
781 Type::Timestamp { .. } => &postgres_types::Type::TS_RANGE_ARRAY,
782 Type::TimestampTz { .. } => &postgres_types::Type::TSTZ_RANGE_ARRAY,
783 Type::Date => &postgres_types::Type::DATE_RANGE_ARRAY,
784 _ => unreachable!(),
785 },
786 Type::MzAclItem => &MZ_ACL_ITEM_ARRAY,
787 },
788 Type::Bool => &postgres_types::Type::BOOL,
789 Type::Bytea => &postgres_types::Type::BYTEA,
790 Type::Char => &postgres_types::Type::CHAR,
791 Type::Date => &postgres_types::Type::DATE,
792 Type::Float4 => &postgres_types::Type::FLOAT4,
793 Type::Float8 => &postgres_types::Type::FLOAT8,
794 Type::Int2 => &postgres_types::Type::INT2,
795 Type::Int4 => &postgres_types::Type::INT4,
796 Type::Int8 => &postgres_types::Type::INT8,
797 Type::UInt2 => &UINT2,
798 Type::UInt4 => &UINT4,
799 Type::UInt8 => &UINT8,
800 Type::Interval { .. } => &postgres_types::Type::INTERVAL,
801 Type::Json => &postgres_types::Type::JSON,
802 Type::Jsonb => &postgres_types::Type::JSONB,
803 Type::List(_) => &LIST,
804 Type::Map { .. } => &MAP,
805 Type::Name => &postgres_types::Type::NAME,
806 Type::Numeric { .. } => &postgres_types::Type::NUMERIC,
807 Type::Oid => &postgres_types::Type::OID,
808 Type::Record(_) => &postgres_types::Type::RECORD,
809 Type::Text => &postgres_types::Type::TEXT,
810 Type::BpChar { .. } => &postgres_types::Type::BPCHAR,
811 Type::VarChar { .. } => &postgres_types::Type::VARCHAR,
812 Type::Time { .. } => &postgres_types::Type::TIME,
813 Type::TimeTz { .. } => &postgres_types::Type::TIMETZ,
814 Type::Timestamp { .. } => &postgres_types::Type::TIMESTAMP,
815 Type::TimestampTz { .. } => &postgres_types::Type::TIMESTAMPTZ,
816 Type::Uuid => &postgres_types::Type::UUID,
817 Type::RegClass => &postgres_types::Type::REGCLASS,
818 Type::RegProc => &postgres_types::Type::REGPROC,
819 Type::RegType => &postgres_types::Type::REGTYPE,
820 Type::Int2Vector => &postgres_types::Type::INT2_VECTOR,
821 Type::MzTimestamp => &MZ_TIMESTAMP,
822 Type::Range { element_type } => match &**element_type {
823 Type::Int4 => &postgres_types::Type::INT4_RANGE,
824 Type::Int8 => &postgres_types::Type::INT8_RANGE,
825 Type::Numeric { .. } => &postgres_types::Type::NUM_RANGE,
826 Type::Timestamp { .. } => &postgres_types::Type::TS_RANGE,
827 Type::TimestampTz { .. } => &postgres_types::Type::TSTZ_RANGE,
828 Type::Date => &postgres_types::Type::DATE_RANGE,
829 t => unreachable!("{t:?} is not a range element type"),
830 },
831 Type::MzAclItem => &MZ_ACL_ITEM,
832 }
833 }
834
835 pub fn catalog_name(&self) -> &'static str {
838 self.inner().name()
839 }
840
841 pub fn name(&self) -> &'static str {
843 match self.inner() {
846 &postgres_types::Type::ACLITEM_ARRAY => "aclitem[]",
847 &postgres_types::Type::BOOL_ARRAY => "boolean[]",
848 &postgres_types::Type::BYTEA_ARRAY => "bytea[]",
849 &postgres_types::Type::BPCHAR_ARRAY => "character[]",
850 &postgres_types::Type::DATE_ARRAY => "date[]",
851 &postgres_types::Type::FLOAT4_ARRAY => "real[]",
852 &postgres_types::Type::FLOAT8_ARRAY => "double precision[]",
853 &postgres_types::Type::INT2_ARRAY => "smallint[]",
854 &postgres_types::Type::INT4_ARRAY => "integer[]",
855 &postgres_types::Type::INT8_ARRAY => "bigint[]",
856 &postgres_types::Type::INTERVAL_ARRAY => "interval[]",
857 &postgres_types::Type::JSONB_ARRAY => "jsonb[]",
858 &postgres_types::Type::NUMERIC_ARRAY => "numeric[]",
859 &postgres_types::Type::OID_ARRAY => "oid[]",
860 &postgres_types::Type::RECORD_ARRAY => "record[]",
861 &postgres_types::Type::TEXT_ARRAY => "text[]",
862 &postgres_types::Type::TIME_ARRAY => "time[]",
863 &postgres_types::Type::TIMESTAMP_ARRAY => "timestamp without time zone[]",
864 &postgres_types::Type::TIMESTAMPTZ_ARRAY => "timestamp with time zone[]",
865 &postgres_types::Type::UUID_ARRAY => "uuid[]",
866 &postgres_types::Type::VARCHAR_ARRAY => "character varying[]",
867 &postgres_types::Type::BOOL => "boolean",
868 &postgres_types::Type::BPCHAR => "character",
869 &postgres_types::Type::CHAR => "\"char\"",
870 &postgres_types::Type::CHAR_ARRAY => "\"char\"[]",
871 &postgres_types::Type::FLOAT4 => "real",
872 &postgres_types::Type::FLOAT8 => "double precision",
873 &postgres_types::Type::INT2 => "smallint",
874 &postgres_types::Type::INT4 => "integer",
875 &postgres_types::Type::INT8 => "bigint",
876 &postgres_types::Type::TIMESTAMP => "timestamp without time zone",
877 &postgres_types::Type::TIMESTAMPTZ => "timestamp with time zone",
878 &postgres_types::Type::VARCHAR => "character varying",
879 &postgres_types::Type::REGCLASS_ARRAY => "regclass[]",
880 &postgres_types::Type::REGPROC_ARRAY => "regproc[]",
881 &postgres_types::Type::REGTYPE_ARRAY => "regtype[]",
882 &postgres_types::Type::INT2_VECTOR => "int2vector",
883 other => match other.oid() {
884 oid::TYPE_UINT2_ARRAY_OID => "uint2[]",
885 oid::TYPE_UINT4_ARRAY_OID => "uint4[]",
886 oid::TYPE_UINT8_ARRAY_OID => "uint8[]",
887 oid::TYPE_MZ_TIMESTAMP_ARRAY_OID => "mz_timestamp[]",
888 oid::TYPE_MZ_ACL_ITEM_ARRAY_OID => "mz_aclitem[]",
889 _ => other.name(),
890 },
891 }
892 }
893
894 pub fn oid(&self) -> u32 {
898 self.inner().oid()
899 }
900
901 pub fn constraint(&self) -> Option<&dyn TypeConstraint> {
903 match self {
904 Type::BpChar {
905 length: Some(length),
906 } => Some(length),
907 Type::VarChar {
908 max_length: Some(max_length),
909 } => Some(max_length),
910 Type::Numeric {
911 constraints: Some(constraints),
912 } => Some(constraints),
913 Type::Interval {
914 constraints: Some(constraints),
915 } => Some(constraints),
916 Type::Time {
917 precision: Some(precision),
918 } => Some(precision),
919 Type::TimeTz {
920 precision: Some(precision),
921 } => Some(precision),
922 Type::Timestamp {
923 precision: Some(precision),
924 } => Some(precision),
925 Type::TimestampTz {
926 precision: Some(precision),
927 } => Some(precision),
928 Type::AclItem
929 | Type::Array(_)
930 | Type::Bool
931 | Type::Bytea
932 | Type::BpChar { length: None }
933 | Type::Char
934 | Type::Date
935 | Type::Float4
936 | Type::Float8
937 | Type::Int2
938 | Type::Int4
939 | Type::Int8
940 | Type::UInt2
941 | Type::UInt4
942 | Type::UInt8
943 | Type::Interval { constraints: None }
944 | Type::Json
945 | Type::Jsonb
946 | Type::List(_)
947 | Type::Map { .. }
948 | Type::Name
949 | Type::Numeric { constraints: None }
950 | Type::Int2Vector
951 | Type::Oid
952 | Type::Record(_)
953 | Type::RegClass
954 | Type::RegProc
955 | Type::RegType
956 | Type::Text
957 | Type::Time { precision: None }
958 | Type::TimeTz { precision: None }
959 | Type::Timestamp { precision: None }
960 | Type::TimestampTz { precision: None }
961 | Type::Uuid
962 | Type::MzTimestamp
963 | Type::VarChar { max_length: None }
964 | Type::Range { .. }
965 | Type::MzAclItem => None,
966 }
967 }
968
969 pub fn typlen(&self) -> i16 {
972 match self {
973 Type::Array(_) => -1,
974 Type::Bool => 1,
975 Type::Bytea => -1,
976 Type::Char => 1,
977 Type::Date => 4,
978 Type::Float4 => 4,
979 Type::Float8 => 8,
980 Type::Int2 => 2,
981 Type::Int4 => 4,
982 Type::Int8 => 8,
983 Type::UInt2 => 2,
984 Type::UInt4 => 4,
985 Type::UInt8 => 8,
986 Type::Interval { .. } => 16,
987 Type::Json => -1,
988 Type::Jsonb => -1,
989 Type::List(_) => -1,
990 Type::Map { .. } => -1,
991 Type::Name { .. } => 64,
992 Type::Numeric { .. } => -1,
993 Type::Oid => 4,
994 Type::Record(_) => -1,
995 Type::Text => -1,
996 Type::BpChar { .. } => -1,
997 Type::VarChar { .. } => -1,
998 Type::Time { .. } => 8,
999 Type::TimeTz { .. } => 12,
1000 Type::Timestamp { .. } => 8,
1001 Type::TimestampTz { .. } => 8,
1002 Type::Uuid => 16,
1003 Type::RegClass => 4,
1004 Type::RegProc => 4,
1005 Type::RegType => 4,
1006 Type::Int2Vector => -1,
1007 Type::MzTimestamp => size_of::<mz_repr::Timestamp>()
1010 .try_into()
1011 .expect("must fit"),
1012 Type::Range { .. } => -1,
1013 Type::MzAclItem => MzAclItem::binary_size().try_into().expect("must fit"),
1014 Type::AclItem => AclItem::binary_size().try_into().expect("must fit"),
1015 }
1016 }
1017
1018 pub fn typmod(&self) -> i32 {
1027 match self.constraint() {
1028 Some(constraint) => constraint.into_typmod(),
1029 None => -1,
1030 }
1031 }
1032}
1033
1034impl fmt::Display for Type {
1035 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1036 f.write_str(self.inner().name())?;
1037 if let Some(constraint) = self.constraint() {
1038 constraint.fmt(f)?;
1039 }
1040 Ok(())
1041 }
1042}
1043
1044impl TryFrom<&Type> for SqlScalarType {
1045 type Error = TypeConversionError;
1046
1047 fn try_from(typ: &Type) -> Result<SqlScalarType, TypeConversionError> {
1048 match typ {
1049 Type::AclItem => Ok(SqlScalarType::AclItem),
1050 Type::Array(t) => Ok(SqlScalarType::Array(Box::new(TryFrom::try_from(&**t)?))),
1051 Type::Bool => Ok(SqlScalarType::Bool),
1052 Type::Bytea => Ok(SqlScalarType::Bytes),
1053 Type::Char => Ok(SqlScalarType::PgLegacyChar),
1054 Type::Date => Ok(SqlScalarType::Date),
1055 Type::Float4 => Ok(SqlScalarType::Float32),
1056 Type::Float8 => Ok(SqlScalarType::Float64),
1057 Type::Int2 => Ok(SqlScalarType::Int16),
1058 Type::Int4 => Ok(SqlScalarType::Int32),
1059 Type::Int8 => Ok(SqlScalarType::Int64),
1060 Type::UInt2 => Ok(SqlScalarType::UInt16),
1061 Type::UInt4 => Ok(SqlScalarType::UInt32),
1062 Type::UInt8 => Ok(SqlScalarType::UInt64),
1063 Type::Interval { .. } => Ok(SqlScalarType::Interval),
1064 Type::Json => Err(TypeConversionError::UnsupportedType(Type::Json)),
1065 Type::Jsonb => Ok(SqlScalarType::Jsonb),
1066 Type::List(t) => Ok(SqlScalarType::List {
1067 element_type: Box::new(TryFrom::try_from(&**t)?),
1068 custom_id: None,
1069 }),
1070 Type::Map { value_type } => Ok(SqlScalarType::Map {
1071 value_type: Box::new(TryFrom::try_from(&**value_type)?),
1072 custom_id: None,
1073 }),
1074 Type::Name => Ok(SqlScalarType::PgLegacyName),
1075 Type::Numeric { constraints } => {
1076 let max_scale = match constraints {
1077 Some(constraints) => {
1078 if constraints.max_precision > i32::from(NUMERIC_DATUM_MAX_PRECISION) {
1079 return Err(TypeConversionError::InvalidNumericConstraint(format!(
1080 "precision for type numeric must be between 1 and {}",
1081 NUMERIC_DATUM_MAX_PRECISION,
1082 )));
1083 }
1084 if constraints.max_scale > constraints.max_precision {
1085 return Err(TypeConversionError::InvalidNumericConstraint(format!(
1086 "scale for type numeric must be between 0 and precision {}",
1087 constraints.max_precision,
1088 )));
1089 }
1090 Some(NumericMaxScale::try_from(i64::from(constraints.max_scale))?)
1091 }
1092 None => None,
1093 };
1094 Ok(SqlScalarType::Numeric { max_scale })
1095 }
1096 Type::Oid => Ok(SqlScalarType::Oid),
1097 Type::Record(_) => Ok(SqlScalarType::Record {
1098 fields: [].into(),
1099 custom_id: None,
1100 }),
1101 Type::Text => Ok(SqlScalarType::String),
1102 Type::Time { precision: None } => Ok(SqlScalarType::Time),
1103 Type::Time { precision: Some(_) } => {
1104 Err(TypeConversionError::UnsupportedType(typ.clone()))
1105 }
1106 Type::TimeTz { .. } => Err(TypeConversionError::UnsupportedType(typ.clone())),
1107 Type::BpChar { length } => Ok(SqlScalarType::Char {
1108 length: match length {
1109 Some(length) => Some(AdtCharLength::try_from(i64::from(length.into_i32()))?),
1110 None => None,
1111 },
1112 }),
1113 Type::VarChar { max_length } => Ok(SqlScalarType::VarChar {
1114 max_length: match max_length {
1115 Some(max_length) => Some(VarCharMaxLength::try_from(i64::from(
1116 max_length.into_i32(),
1117 ))?),
1118 None => None,
1119 },
1120 }),
1121 Type::Timestamp { precision } => Ok(SqlScalarType::Timestamp {
1122 precision: match precision {
1123 Some(precision) => Some(AdtTimestampPrecision::try_from(i64::from(
1124 precision.into_i32(),
1125 ))?),
1126 None => None,
1127 },
1128 }),
1129 Type::TimestampTz { precision } => Ok(SqlScalarType::TimestampTz {
1130 precision: match precision {
1131 Some(precision) => Some(AdtTimestampPrecision::try_from(i64::from(
1132 precision.into_i32(),
1133 ))?),
1134 None => None,
1135 },
1136 }),
1137 Type::Uuid => Ok(SqlScalarType::Uuid),
1138 Type::RegClass => Ok(SqlScalarType::RegClass),
1139 Type::RegProc => Ok(SqlScalarType::RegProc),
1140 Type::RegType => Ok(SqlScalarType::RegType),
1141 Type::Int2Vector => Ok(SqlScalarType::Int2Vector),
1142 Type::MzTimestamp => Ok(SqlScalarType::MzTimestamp),
1143 Type::Range { element_type } => Ok(SqlScalarType::Range {
1144 element_type: Box::new(TryFrom::try_from(&**element_type)?),
1145 }),
1146 Type::MzAclItem => Ok(SqlScalarType::MzAclItem),
1147 }
1148 }
1149}
1150
1151#[derive(Debug, Clone)]
1153pub enum TypeFromOidError {
1154 UnknownOid(u32),
1156 InvalidTypmod {
1158 typ: Type,
1160 typmod: i32,
1162 detail: String,
1164 },
1165}
1166
1167impl fmt::Display for TypeFromOidError {
1168 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1169 match self {
1170 TypeFromOidError::UnknownOid(oid) => write!(f, "type with OID {oid} is unknown"),
1171 TypeFromOidError::InvalidTypmod {
1172 typ,
1173 typmod,
1174 detail,
1175 } => {
1176 write!(
1177 f,
1178 "typmod {typmod} is invalid for type {}: {detail}",
1179 typ.name()
1180 )
1181 }
1182 }
1183 }
1184}
1185
1186impl Error for TypeFromOidError {}
1187
1188#[derive(Debug, Clone)]
1190pub enum TypeConversionError {
1191 UnsupportedType(Type),
1193 InvalidNumericMaxScale(InvalidNumericMaxScaleError),
1196 InvalidNumericConstraint(String),
1199 InvalidCharLength(InvalidCharLengthError),
1202 InvalidVarCharMaxLength(InvalidVarCharMaxLengthError),
1205 InvalidTimestampPrecision(InvalidTimestampPrecisionError),
1208}
1209
1210impl fmt::Display for TypeConversionError {
1211 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1212 match self {
1213 TypeConversionError::UnsupportedType(ty) => write!(f, "type {ty} not supported"),
1214 TypeConversionError::InvalidNumericMaxScale(e) => e.fmt(f),
1215 TypeConversionError::InvalidNumericConstraint(msg) => f.write_str(msg),
1216 TypeConversionError::InvalidCharLength(e) => e.fmt(f),
1217 TypeConversionError::InvalidVarCharMaxLength(e) => e.fmt(f),
1218 TypeConversionError::InvalidTimestampPrecision(e) => e.fmt(f),
1219 }
1220 }
1221}
1222
1223impl Error for TypeConversionError {}
1224
1225impl From<InvalidNumericMaxScaleError> for TypeConversionError {
1226 fn from(e: InvalidNumericMaxScaleError) -> TypeConversionError {
1227 TypeConversionError::InvalidNumericMaxScale(e)
1228 }
1229}
1230
1231impl From<InvalidCharLengthError> for TypeConversionError {
1232 fn from(e: InvalidCharLengthError) -> TypeConversionError {
1233 TypeConversionError::InvalidCharLength(e)
1234 }
1235}
1236
1237impl From<InvalidVarCharMaxLengthError> for TypeConversionError {
1238 fn from(e: InvalidVarCharMaxLengthError) -> TypeConversionError {
1239 TypeConversionError::InvalidVarCharMaxLength(e)
1240 }
1241}
1242
1243impl From<InvalidTimestampPrecisionError> for TypeConversionError {
1244 fn from(e: InvalidTimestampPrecisionError) -> TypeConversionError {
1245 TypeConversionError::InvalidTimestampPrecision(e)
1246 }
1247}
1248
1249impl From<&SqlScalarType> for Type {
1250 fn from(typ: &SqlScalarType) -> Type {
1251 match typ {
1252 SqlScalarType::AclItem => Type::AclItem,
1253 SqlScalarType::Array(t) => Type::Array(Box::new(From::from(&**t))),
1254 SqlScalarType::Bool => Type::Bool,
1255 SqlScalarType::Bytes => Type::Bytea,
1256 SqlScalarType::PgLegacyChar => Type::Char,
1257 SqlScalarType::Date => Type::Date,
1258 SqlScalarType::Float64 => Type::Float8,
1259 SqlScalarType::Float32 => Type::Float4,
1260 SqlScalarType::Int16 => Type::Int2,
1261 SqlScalarType::Int32 => Type::Int4,
1262 SqlScalarType::Int64 => Type::Int8,
1263 SqlScalarType::UInt16 => Type::UInt2,
1264 SqlScalarType::UInt32 => Type::UInt4,
1265 SqlScalarType::UInt64 => Type::UInt8,
1266 SqlScalarType::Interval => Type::Interval { constraints: None },
1267 SqlScalarType::Jsonb => Type::Jsonb,
1268 SqlScalarType::List { element_type, .. } => {
1269 Type::List(Box::new(From::from(&**element_type)))
1270 }
1271 SqlScalarType::Map { value_type, .. } => Type::Map {
1272 value_type: Box::new(From::from(&**value_type)),
1273 },
1274 SqlScalarType::PgLegacyName => Type::Name,
1275 SqlScalarType::Oid => Type::Oid,
1276 SqlScalarType::Record { fields, .. } => Type::Record(
1277 fields
1278 .iter()
1279 .map(|(_name, ty)| Type::from(&ty.scalar_type))
1280 .collect(),
1281 ),
1282 SqlScalarType::String => Type::Text,
1283 SqlScalarType::Char { length } => Type::BpChar {
1284 length: (*length).map(CharLength::from),
1285 },
1286 SqlScalarType::VarChar { max_length } => Type::VarChar {
1287 max_length: (*max_length).map(CharLength::from),
1288 },
1289 SqlScalarType::Time => Type::Time { precision: None },
1290 SqlScalarType::Timestamp { precision } => Type::Timestamp {
1291 precision: (*precision).map(TimestampPrecision::from),
1292 },
1293 SqlScalarType::TimestampTz { precision } => Type::TimestampTz {
1294 precision: (*precision).map(TimestampPrecision::from),
1295 },
1296 SqlScalarType::Uuid => Type::Uuid,
1297 SqlScalarType::Numeric { max_scale } => Type::Numeric {
1298 constraints: max_scale.map(|max_scale| NumericConstraints {
1299 max_precision: i32::from(NUMERIC_DATUM_MAX_PRECISION),
1300 max_scale: i32::from(max_scale.into_u8()),
1301 }),
1302 },
1303 SqlScalarType::RegClass => Type::RegClass,
1304 SqlScalarType::RegProc => Type::RegProc,
1305 SqlScalarType::RegType => Type::RegType,
1306 SqlScalarType::Int2Vector => Type::Int2Vector,
1307 SqlScalarType::MzTimestamp => Type::MzTimestamp,
1308 SqlScalarType::Range { element_type } => Type::Range {
1309 element_type: Box::new(From::from(&**element_type)),
1310 },
1311 SqlScalarType::MzAclItem => Type::MzAclItem,
1312 }
1313 }
1314}