1use std::borrow::Cow;
27use std::collections::BTreeMap;
28use std::error::Error;
29use std::fmt;
30use std::num::FpCategory;
31use std::str::FromStr;
32use std::sync::LazyLock;
33
34use chrono::offset::{Offset, TimeZone};
35use chrono::{DateTime, Datelike, Duration, NaiveDate, NaiveDateTime, NaiveTime, Timelike, Utc};
36use dec::OrderedDecimal;
37use mz_ore::cast::ReinterpretCast;
38use mz_ore::error::ErrorExt;
39use mz_ore::fmt::FormatBuffer;
40use mz_ore::lex::LexBuf;
41use mz_ore::str::StrExt;
42use mz_pgtz::timezone::{Timezone, TimezoneSpec};
43use mz_proto::{ProtoType, RustType, TryFromProtoError};
44use num_traits::Float as NumFloat;
45#[cfg(any(test, feature = "proptest"))]
46use proptest_derive::Arbitrary;
47use regex::bytes::Regex;
48use ryu::Float as RyuFloat;
49use serde::{Deserialize, Serialize};
50use uuid::Uuid;
51
52use crate::adt::array::ArrayDimension;
53use crate::adt::date::Date;
54use crate::adt::datetime::{self, DateOrder, DateTimeField, ParsedDateTime};
55use crate::adt::interval::Interval;
56use crate::adt::jsonb::{Jsonb, JsonbRef};
57use crate::adt::mz_acl_item::{AclItem, MzAclItem};
58use crate::adt::numeric::{self, NUMERIC_DATUM_MAX_PRECISION, Numeric};
59use crate::adt::pg_legacy_name::NAME_MAX_BYTES;
60use crate::adt::range::{Range, RangeBound, RangeInner};
61use crate::adt::timestamp::{CheckedTimestamp, checked_sub_with_leapsecond};
62
63include!(concat!(env!("OUT_DIR"), "/mz_repr.strconv.rs"));
64
65macro_rules! bail {
66 ($($arg:tt)*) => { return Err(format!($($arg)*)) };
67}
68
69#[derive(Debug)]
71pub enum Nestable {
72 Yes,
73 MayNeedEscaping,
74}
75
76pub fn parse_bool(s: &str) -> Result<bool, ParseError> {
82 match s.trim().to_lowercase().as_str() {
83 "t" | "tr" | "tru" | "true" | "y" | "ye" | "yes" | "on" | "1" => Ok(true),
84 "f" | "fa" | "fal" | "fals" | "false" | "n" | "no" | "of" | "off" | "0" => Ok(false),
85 _ => Err(ParseError::invalid_input_syntax("boolean", s)),
86 }
87}
88
89pub fn format_bool_static(b: bool) -> &'static str {
94 match b {
95 true => "t",
96 false => "f",
97 }
98}
99
100pub fn format_bool<F>(buf: &mut F, b: bool) -> Nestable
105where
106 F: FormatBuffer,
107{
108 buf.write_str(format_bool_static(b));
109 Nestable::Yes
110}
111
112pub fn parse_int16(s: &str) -> Result<i16, ParseError> {
117 s.trim()
118 .parse()
119 .map_err(|e| ParseError::invalid_input_syntax("smallint", s).with_details(e))
120}
121
122pub fn format_int16<F>(buf: &mut F, i: i16) -> Nestable
124where
125 F: FormatBuffer,
126{
127 write!(buf, "{}", i);
128 Nestable::Yes
129}
130
131pub fn parse_int32(s: &str) -> Result<i32, ParseError> {
136 s.trim()
137 .parse()
138 .map_err(|e| ParseError::invalid_input_syntax("integer", s).with_details(e))
139}
140
141pub fn format_int32<F>(buf: &mut F, i: i32) -> Nestable
143where
144 F: FormatBuffer,
145{
146 write!(buf, "{}", i);
147 Nestable::Yes
148}
149
150pub fn parse_int64(s: &str) -> Result<i64, ParseError> {
152 s.trim()
153 .parse()
154 .map_err(|e| ParseError::invalid_input_syntax("bigint", s).with_details(e))
155}
156
157pub fn format_int64<F>(buf: &mut F, i: i64) -> Nestable
159where
160 F: FormatBuffer,
161{
162 write!(buf, "{}", i);
163 Nestable::Yes
164}
165
166pub fn parse_uint16(s: &str) -> Result<u16, ParseError> {
171 s.trim()
172 .parse()
173 .map_err(|e| ParseError::invalid_input_syntax("uint2", s).with_details(e))
174}
175
176pub fn format_uint16<F>(buf: &mut F, u: u16) -> Nestable
178where
179 F: FormatBuffer,
180{
181 write!(buf, "{}", u);
182 Nestable::Yes
183}
184
185pub fn parse_uint32(s: &str) -> Result<u32, ParseError> {
190 s.trim()
191 .parse()
192 .map_err(|e| ParseError::invalid_input_syntax("uint4", s).with_details(e))
193}
194
195pub fn format_uint32<F>(buf: &mut F, u: u32) -> Nestable
197where
198 F: FormatBuffer,
199{
200 write!(buf, "{}", u);
201 Nestable::Yes
202}
203
204pub fn parse_uint64(s: &str) -> Result<u64, ParseError> {
206 s.trim()
207 .parse()
208 .map_err(|e| ParseError::invalid_input_syntax("uint8", s).with_details(e))
209}
210
211pub fn format_uint64<F>(buf: &mut F, u: u64) -> Nestable
213where
214 F: FormatBuffer,
215{
216 write!(buf, "{}", u);
217 Nestable::Yes
218}
219
220pub fn parse_mz_timestamp(s: &str) -> Result<crate::Timestamp, ParseError> {
222 s.trim()
223 .parse()
224 .map_err(|e| ParseError::invalid_input_syntax("mz_timestamp", s).with_details(e))
225}
226
227pub fn parse_mz_timestamp_legacy(s: &str) -> Result<crate::Timestamp, ParseError> {
235 crate::Timestamp::from_str_legacy(s.trim())
236 .map_err(|e| ParseError::invalid_input_syntax("mz_timestamp", s).with_details(e))
237}
238
239pub fn format_mz_timestamp<F>(buf: &mut F, u: crate::Timestamp) -> Nestable
241where
242 F: FormatBuffer,
243{
244 write!(buf, "{}", u);
245 Nestable::Yes
246}
247
248pub fn parse_oid(s: &str) -> Result<u32, ParseError> {
250 let trimmed = s.trim();
259 if let Ok(oid) = trimmed.parse::<u32>() {
260 return Ok(oid);
261 }
262 let oid: i32 = trimmed
263 .parse()
264 .map_err(|e| ParseError::invalid_input_syntax("oid", s).with_details(e))?;
265 Ok(u32::reinterpret_cast(oid))
266}
267
268pub fn parse_oid_legacy(s: &str) -> Result<u32, ParseError> {
281 let oid: i32 = s
282 .trim()
283 .parse()
284 .map_err(|e| ParseError::invalid_input_syntax("oid", s).with_details(e))?;
285 Ok(u32::reinterpret_cast(oid))
286}
287
288fn parse_float<Fl>(type_name: &'static str, s: &str) -> Result<Fl, ParseError>
289where
290 Fl: NumFloat + FromStr,
291{
292 static ZERO_RE: LazyLock<Regex> =
308 LazyLock::new(|| Regex::new(r#"(?i-u)^[-+]?(0+(\.0*)?|\.0+)(e|$)"#).unwrap());
309 static INF_RE: LazyLock<Regex> = LazyLock::new(|| Regex::new("(?i-u)^[-+]?inf").unwrap());
311
312 let buf = s.trim();
313 let f: Fl = buf
314 .parse()
315 .map_err(|_| ParseError::invalid_input_syntax(type_name, s))?;
316 match f.classify() {
317 FpCategory::Infinite if !INF_RE.is_match(buf.as_bytes()) => {
318 Err(ParseError::out_of_range(type_name, s))
319 }
320 FpCategory::Zero if !ZERO_RE.is_match(buf.as_bytes()) => {
321 Err(ParseError::out_of_range(type_name, s))
322 }
323 _ => Ok(f),
324 }
325}
326
327fn format_float<F, Fl>(buf: &mut F, f: Fl) -> Nestable
328where
329 F: FormatBuffer,
330 Fl: NumFloat + RyuFloat,
331{
332 match f.classify() {
345 FpCategory::Infinite if f.is_sign_negative() => buf.write_str("-Infinity"),
346 FpCategory::Infinite => buf.write_str("Infinity"),
347 FpCategory::Nan => buf.write_str("NaN"),
348 FpCategory::Zero if f.is_sign_negative() => buf.write_str("-0"),
349 _ => {
350 mz_ore::soft_assert_no_log!(f.is_finite());
351 let mut ryu_buf = ryu::Buffer::new();
352 let mut s = ryu_buf.format_finite(f);
353 if let Some(trimmed) = s.strip_suffix(".0") {
354 s = trimmed;
355 }
356 let mut chars = s.chars().peekable();
357 while let Some(ch) = chars.next() {
358 buf.write_char(ch);
359 if ch == 'e' && chars.peek() != Some(&'-') {
360 buf.write_char('+');
361 }
362 }
363 }
364 }
365
366 Nestable::Yes
367}
368
369pub fn parse_float32(s: &str) -> Result<f32, ParseError> {
371 parse_float("real", s)
372}
373
374pub fn format_float32<F>(buf: &mut F, f: f32) -> Nestable
376where
377 F: FormatBuffer,
378{
379 format_float(buf, f)
380}
381
382pub fn parse_float64(s: &str) -> Result<f64, ParseError> {
384 parse_float("double precision", s)
385}
386
387pub fn format_float64<F>(buf: &mut F, f: f64) -> Nestable
389where
390 F: FormatBuffer,
391{
392 format_float(buf, f)
393}
394
395fn parse_timestamp_string(
414 s: &str,
415 order: DateOrder,
416) -> Result<(NaiveDate, NaiveTime, Timezone), String> {
417 if s.is_empty() {
418 return Err("timestamp string is empty".into());
419 }
420
421 if s == "epoch" {
426 return Ok((
427 NaiveDate::from_ymd_opt(1970, 1, 1).unwrap(),
428 NaiveTime::from_hms_opt(0, 0, 0).unwrap(),
429 Default::default(),
430 ));
431 }
432
433 let (ts_string, tz_string, era) = datetime::split_timestamp_string(s);
434
435 let pdt = ParsedDateTime::build_parsed_datetime_timestamp(ts_string, era, order)?;
436 let d: NaiveDate = pdt.compute_date()?;
437 let t: NaiveTime = pdt.compute_time()?;
438
439 let offset = if tz_string.is_empty() {
440 Default::default()
441 } else {
442 Timezone::parse(tz_string, TimezoneSpec::Iso)?
443 };
444
445 Ok((d, t, offset))
446}
447
448pub fn parse_date(s: &str) -> Result<Date, ParseError> {
450 parse_date_inner(s, DateOrder::Mdy)
451}
452
453pub fn parse_date_legacy(s: &str) -> Result<Date, ParseError> {
460 parse_date_inner(s, DateOrder::LegacyYmd)
461}
462
463fn parse_date_inner(s: &str, order: DateOrder) -> Result<Date, ParseError> {
464 match parse_timestamp_string(s, order) {
465 Ok((date, _, _)) => Date::try_from(date).map_err(|_| ParseError::out_of_range("date", s)),
466 Err(e) => Err(ParseError::invalid_input_syntax("date", s).with_details(e)),
467 }
468}
469
470pub fn format_date<F>(buf: &mut F, d: Date) -> Nestable
472where
473 F: FormatBuffer,
474{
475 let d: NaiveDate = d.into();
476 let (year_ad, year) = d.year_ce();
477 write!(buf, "{:04}-{}", year, d.format("%m-%d"));
478 if !year_ad {
479 write!(buf, " BC");
480 return Nestable::MayNeedEscaping;
485 }
486 Nestable::Yes
487}
488
489pub fn parse_time(s: &str) -> Result<NaiveTime, ParseError> {
497 parse_time_inner(s, TimeFields::Required)
498}
499
500pub fn parse_time_legacy(s: &str) -> Result<NaiveTime, ParseError> {
507 parse_time_inner(s, TimeFields::Optional)
508}
509
510enum TimeFields {
512 Required,
514 Optional,
516}
517
518fn parse_time_inner(s: &str, fields: TimeFields) -> Result<NaiveTime, ParseError> {
519 ParsedDateTime::build_parsed_datetime_time(s)
520 .and_then(|pdt| {
521 if matches!(fields, TimeFields::Required)
528 && pdt.hour.is_none()
529 && pdt.minute.is_none()
530 && pdt.second.is_none()
531 {
532 return Err("no time fields found".into());
533 }
534 pdt.compute_time()
535 })
536 .map_err(|e| ParseError::invalid_input_syntax("time", s).with_details(e))
537}
538
539pub fn format_time<F>(buf: &mut F, t: NaiveTime) -> Nestable
541where
542 F: FormatBuffer,
543{
544 let (carry, micros) = split_nanos_to_micros(t.nanosecond());
545 let (t, micros) = match carry {
550 false => (t, micros),
551 true => match t
552 .with_nanosecond(0)
553 .expect("0 is a valid nanosecond")
554 .overflowing_add_signed(Duration::seconds(1))
555 {
556 (carried, 0) => (carried, 0),
557 (_, _wrapped) => (t, MICROS_PER_SECOND - 1),
558 },
559 };
560 write!(buf, "{}", t.format("%H:%M:%S"));
561 format_micros(buf, micros);
562 Nestable::Yes
563}
564
565pub fn parse_timestamp(s: &str) -> Result<CheckedTimestamp<NaiveDateTime>, ParseError> {
567 parse_timestamp_inner(s, DateOrder::Mdy, LeapSecond::RollOver)
568}
569
570pub fn parse_timestamp_legacy(s: &str) -> Result<CheckedTimestamp<NaiveDateTime>, ParseError> {
578 parse_timestamp_inner(s, DateOrder::LegacyYmd, LeapSecond::Keep)
579}
580
581fn parse_timestamp_inner(
582 s: &str,
583 order: DateOrder,
584 leap: LeapSecond,
585) -> Result<CheckedTimestamp<NaiveDateTime>, ParseError> {
586 match parse_timestamp_string(s, order) {
587 Ok((date, time, _)) => {
588 let dt = match leap {
589 LeapSecond::RollOver => roll_over_leap_second(date.and_time(time))
590 .ok_or_else(|| ParseError::out_of_range("timestamp", s))?,
591 LeapSecond::Keep => date.and_time(time),
592 };
593 CheckedTimestamp::from_timestamplike(dt)
594 .map_err(|_| ParseError::out_of_range("timestamp", s))
595 }
596 Err(e) => Err(ParseError::invalid_input_syntax("timestamp", s).with_details(e)),
597 }
598}
599
600#[derive(Clone, Copy)]
608enum LeapSecond {
609 RollOver,
610 Keep,
611}
612
613fn roll_over_leap_second(dt: NaiveDateTime) -> Option<NaiveDateTime> {
626 use chrono::Timelike;
627 match dt.nanosecond().checked_sub(1_000_000_000) {
628 Some(nanos) => dt
629 .with_nanosecond(nanos)
630 .expect("in range")
631 .checked_add_signed(Duration::try_seconds(1).unwrap()),
632 None => Some(dt),
633 }
634}
635
636pub fn format_timestamp<F>(buf: &mut F, ts: &NaiveDateTime) -> Nestable
638where
639 F: FormatBuffer,
640{
641 let (ts, micros) = round_to_micros(*ts);
642 let (year_ad, year) = ts.year_ce();
643 write!(buf, "{:04}-{}", year, ts.format("%m-%d %H:%M:%S"));
644 format_micros(buf, micros);
645 if !year_ad {
646 write!(buf, " BC");
647 }
648 Nestable::MayNeedEscaping
650}
651
652pub fn parse_timestamptz(s: &str) -> Result<CheckedTimestamp<DateTime<Utc>>, ParseError> {
654 parse_timestamptz_inner(s, DateOrder::Mdy, LeapSecond::RollOver)
655}
656
657pub fn parse_timestamptz_legacy(s: &str) -> Result<CheckedTimestamp<DateTime<Utc>>, ParseError> {
665 parse_timestamptz_inner(s, DateOrder::LegacyYmd, LeapSecond::Keep)
666}
667
668fn parse_timestamptz_inner(
669 s: &str,
670 order: DateOrder,
671 leap: LeapSecond,
672) -> Result<CheckedTimestamp<DateTime<Utc>>, ParseError> {
673 let invalid_syntax = |details: String| {
674 ParseError::invalid_input_syntax("timestamp with time zone", s).with_details(details)
675 };
676 let out_of_range = || ParseError::out_of_range("timestamp with time zone", s);
677
678 let (date, time, timezone) = parse_timestamp_string(s, order).map_err(&invalid_syntax)?;
679 let mut dt = match leap {
683 LeapSecond::RollOver => {
684 roll_over_leap_second(date.and_time(time)).ok_or_else(out_of_range)?
685 }
686 LeapSecond::Keep => date.and_time(time),
687 };
688 let offset = match timezone {
689 Timezone::FixedOffset(offset) => offset,
690 Timezone::Tz(tz) => match tz.offset_from_local_datetime(&dt).latest() {
691 Some(offset) => offset.fix(),
692 None => {
693 dt = dt
694 .checked_add_signed(Duration::try_hours(1).unwrap())
695 .ok_or_else(out_of_range)?;
696 tz.offset_from_local_datetime(&dt)
697 .latest()
698 .ok_or_else(|| invalid_syntax("invalid timezone conversion".to_owned()))?
699 .fix()
700 }
701 },
702 };
703 let dt = checked_sub_with_leapsecond(&dt, &offset).ok_or_else(out_of_range)?;
708 CheckedTimestamp::from_timestamplike(DateTime::from_naive_utc_and_offset(dt, Utc))
709 .map_err(|_| out_of_range())
710}
711
712pub fn format_timestamptz<F>(buf: &mut F, ts: &DateTime<Utc>) -> Nestable
714where
715 F: FormatBuffer,
716{
717 let (ts, micros) = round_to_micros(ts.naive_utc());
718 let (year_ad, year) = ts.year_ce();
719 write!(buf, "{:04}-{}", year, ts.format("%m-%d %H:%M:%S"));
720 format_micros(buf, micros);
721 write!(buf, "+00");
722 if !year_ad {
723 write!(buf, " BC");
724 }
725 Nestable::MayNeedEscaping
727}
728
729pub fn parse_interval(s: &str) -> Result<Interval, ParseError> {
749 parse_interval_w_disambiguator(s, None, DateTimeField::Second)
750}
751
752pub fn parse_interval_w_disambiguator(
757 s: &str,
758 leading_time_precision: Option<DateTimeField>,
759 d: DateTimeField,
760) -> Result<Interval, ParseError> {
761 ParsedDateTime::build_parsed_datetime_interval(s, leading_time_precision, d)
762 .and_then(|pdt| pdt.compute_interval())
763 .map_err(|e| ParseError::invalid_input_syntax("interval", s).with_details(e))
764}
765
766pub fn format_interval<F>(buf: &mut F, iv: Interval) -> Nestable
767where
768 F: FormatBuffer,
769{
770 write!(buf, "{}", iv);
771 Nestable::MayNeedEscaping
772}
773
774pub fn parse_numeric(s: &str) -> Result<OrderedDecimal<Numeric>, ParseError> {
775 let mut cx = numeric::cx_datum();
776 let mut n = match cx.parse(s.trim()) {
777 Ok(n) => n,
778 Err(..) => {
779 return Err(ParseError::invalid_input_syntax("numeric", s));
780 }
781 };
782
783 let cx_status = cx.status();
784
785 if (n.is_infinite() && !cx_status.overflow())
787 || (n.is_nan() && n.is_negative())
788 || n.is_signaling_nan()
789 {
790 return Err(ParseError::invalid_input_syntax("numeric", s));
791 }
792
793 let out_of_range = numeric::munge_numeric(&mut n).is_err();
795
796 if cx_status.overflow() || cx_status.subnormal() || out_of_range {
797 Err(ParseError::out_of_range("numeric", s).with_details(format!(
798 "exceeds maximum precision {}",
799 NUMERIC_DATUM_MAX_PRECISION
800 )))
801 } else {
802 Ok(OrderedDecimal(n))
803 }
804}
805
806pub fn format_numeric<F>(buf: &mut F, n: &OrderedDecimal<Numeric>) -> Nestable
807where
808 F: FormatBuffer,
809{
810 write!(buf, "{}", n.0.to_standard_notation_string());
811 Nestable::Yes
812}
813
814pub fn format_string<F>(buf: &mut F, s: &str) -> Nestable
815where
816 F: FormatBuffer,
817{
818 buf.write_str(s);
819 Nestable::MayNeedEscaping
820}
821
822pub fn parse_pg_legacy_name(s: &str) -> String {
823 let mut out = String::new();
826 let mut len = 0;
827 for c in s.chars() {
828 len += c.len_utf8();
829 if len > NAME_MAX_BYTES {
830 break;
831 }
832 out.push(c);
833 }
834 out
835}
836
837pub fn parse_bytes(s: &str) -> Result<Vec<u8>, ParseError> {
838 if let Some(remainder) = s.strip_prefix(r"\x") {
844 parse_bytes_hex(remainder).map_err(|e| {
845 ParseError::invalid_input_syntax("bytea", s).with_details(e.to_string_with_causes())
846 })
847 } else {
848 parse_bytes_traditional(s)
849 }
850}
851
852pub fn parse_bytes_hex(s: &str) -> Result<Vec<u8>, ParseHexError> {
853 let decode_nibble = |b| match b {
857 b'a'..=b'f' => Ok(b - b'a' + 10),
858 b'A'..=b'F' => Ok(b - b'A' + 10),
859 b'0'..=b'9' => Ok(b - b'0'),
860 _ => Err(ParseHexError::InvalidHexDigit(char::from(b))),
861 };
862
863 let mut buf = vec![];
864 let mut nibbles = s.as_bytes().iter().copied();
865 while let Some(n) = nibbles.next() {
866 if let b' ' | b'\n' | b'\t' | b'\r' = n {
867 continue;
868 }
869 let n = decode_nibble(n)?;
870 let n2 = match nibbles.next() {
871 None => return Err(ParseHexError::OddLength),
872 Some(n2) => decode_nibble(n2)?,
873 };
874 buf.push((n << 4) | n2);
875 }
876 Ok(buf)
877}
878
879pub fn parse_bytes_traditional(s: &str) -> Result<Vec<u8>, ParseError> {
880 let mut out = Vec::with_capacity(s.len());
884 let mut bytes = s.as_bytes().iter().fuse();
885 while let Some(&b) = bytes.next() {
886 if b != b'\\' {
887 out.push(b);
888 continue;
889 }
890 match bytes.next() {
891 None => {
892 return Err(ParseError::invalid_input_syntax("bytea", s)
893 .with_details("ends with escape character"));
894 }
895 Some(b'\\') => out.push(b'\\'),
896 b => match (b, bytes.next(), bytes.next()) {
897 (Some(d2 @ b'0'..=b'3'), Some(d1 @ b'0'..=b'7'), Some(d0 @ b'0'..=b'7')) => {
898 out.push(((d2 - b'0') << 6) + ((d1 - b'0') << 3) + (d0 - b'0'));
899 }
900 _ => {
901 return Err(ParseError::invalid_input_syntax("bytea", s)
902 .with_details("invalid escape sequence"));
903 }
904 },
905 }
906 }
907 Ok(out)
908}
909
910pub fn format_bytes<F>(buf: &mut F, bytes: &[u8]) -> Nestable
911where
912 F: FormatBuffer,
913{
914 write!(buf, "\\x{}", hex::encode(bytes));
915 Nestable::MayNeedEscaping
916}
917
918pub fn parse_jsonb(s: &str) -> Result<Jsonb, ParseError> {
919 s.trim()
920 .parse()
921 .map_err(|e| ParseError::invalid_input_syntax("jsonb", s).with_details(e))
922}
923
924pub fn format_jsonb<F>(buf: &mut F, jsonb: JsonbRef) -> Nestable
925where
926 F: FormatBuffer,
927{
928 write!(buf, "{}", jsonb);
929 Nestable::MayNeedEscaping
930}
931
932pub fn format_jsonb_pretty<F>(buf: &mut F, jsonb: JsonbRef)
933where
934 F: FormatBuffer,
935{
936 write!(buf, "{:#}", jsonb)
937}
938
939pub fn parse_uuid(s: &str) -> Result<Uuid, ParseError> {
940 s.trim()
941 .parse()
942 .map_err(|e| ParseError::invalid_input_syntax("uuid", s).with_details(e))
943}
944
945pub fn format_uuid<F>(buf: &mut F, uuid: Uuid) -> Nestable
946where
947 F: FormatBuffer,
948{
949 write!(buf, "{}", uuid);
950 Nestable::Yes
951}
952
953const NANOS_PER_SECOND: u32 = 1_000_000_000;
954const MICROS_PER_SECOND: u32 = 1_000_000;
955
956fn split_nanos_to_micros(nanos: u32) -> (bool, u32) {
969 let micros = (nanos % NANOS_PER_SECOND + 500) / 1_000;
970 if micros >= MICROS_PER_SECOND {
971 (true, 0)
972 } else {
973 (false, micros)
974 }
975}
976
977fn round_to_micros(ts: NaiveDateTime) -> (NaiveDateTime, u32) {
987 let (carry, micros) = split_nanos_to_micros(ts.and_utc().timestamp_subsec_nanos());
988 if !carry {
989 return (ts, micros);
990 }
991 match ts
996 .with_nanosecond(0)
997 .expect("0 is a valid nanosecond")
998 .checked_add_signed(Duration::seconds(1))
999 {
1000 Some(carried) => (carried, 0),
1001 None => (ts, MICROS_PER_SECOND - 1),
1002 }
1003}
1004
1005fn format_micros<F>(buf: &mut F, micros: u32)
1012where
1013 F: FormatBuffer,
1014{
1015 assert!(micros < MICROS_PER_SECOND);
1016 if micros == 0 {
1017 return;
1018 }
1019 let mut micros = micros;
1020 let mut width = 6;
1021 while micros % 10 == 0 {
1022 width -= 1;
1023 micros /= 10;
1024 }
1025 write!(buf, ".{:0width$}", micros, width = width);
1026}
1027
1028#[derive(Debug, thiserror::Error)]
1029enum ArrayParsingError {
1030 #[error("Array value must start with \"{{\"")]
1031 OpeningBraceMissing,
1032 #[error("Specifying array lower bounds is not supported")]
1033 DimsUnsupported,
1034 #[error("{0}")]
1035 Generic(String),
1036 #[error("Unexpected \"{0}\" character.")]
1037 UnexpectedChar(char),
1038 #[error("Multidimensional arrays must have sub-arrays with matching dimensions.")]
1039 NonRectilinearDims,
1040 #[error("Unexpected array element.")]
1041 UnexpectedElement,
1042 #[error("Junk after closing right brace.")]
1043 Junk,
1044 #[error("Unexpected end of input.")]
1045 EarlyTerm,
1046}
1047
1048impl From<String> for ArrayParsingError {
1049 fn from(value: String) -> Self {
1050 ArrayParsingError::Generic(value)
1051 }
1052}
1053
1054pub fn parse_array<'a, T, E>(
1055 s: &'a str,
1056 make_null: impl FnMut() -> T,
1057 gen_elem: impl FnMut(Cow<'a, str>) -> Result<T, E>,
1058) -> Result<(Vec<T>, Vec<ArrayDimension>), ParseError>
1059where
1060 E: ToString,
1061{
1062 parse_array_inner(s, make_null, gen_elem)
1063 .map_err(|details| ParseError::invalid_input_syntax("array", s).with_details(details))
1064}
1065
1066fn parse_array_inner<'a, T, E>(
1067 s: &'a str,
1068 mut make_null: impl FnMut() -> T,
1069 mut gen_elem: impl FnMut(Cow<'a, str>) -> Result<T, E>,
1070) -> Result<(Vec<T>, Vec<ArrayDimension>), ArrayParsingError>
1071where
1072 E: ToString,
1073{
1074 use ArrayParsingError::*;
1075
1076 #[derive(Clone, Debug, Default)]
1077 struct Dimension {
1078 length: Option<usize>,
1081 staged_element: bool,
1085 committed_element_count: usize,
1088 }
1089
1090 #[derive(Clone, Debug, Default)]
1091 struct ArrayBuilder<'a> {
1092 current_command_char: char,
1094 dimensions: Vec<Dimension>,
1097 current_dim: usize,
1099 sealed: bool,
1101 elements: Vec<Option<Cow<'a, str>>>,
1105 }
1106
1107 impl<'a> ArrayBuilder<'a> {
1108 fn build(
1109 s: &'a str,
1110 ) -> Result<(Vec<Option<Cow<'a, str>>>, Vec<ArrayDimension>), ArrayParsingError> {
1111 let buf = &mut LexBuf::new(s);
1112
1113 if buf.consume('[') {
1115 Err(DimsUnsupported)?;
1116 }
1117
1118 buf.take_while(|ch| ch.is_ascii_whitespace());
1119
1120 if !buf.consume('{') {
1121 Err(OpeningBraceMissing)?;
1122 }
1123
1124 let mut dimensions = 1;
1125
1126 loop {
1127 buf.take_while(|ch| ch.is_ascii_whitespace());
1128 if buf.consume('{') {
1129 dimensions += 1;
1130 } else {
1131 break;
1132 }
1133 }
1134
1135 let mut builder = ArrayBuilder {
1136 current_command_char: '{',
1137 dimensions: vec![Dimension::default(); dimensions],
1138 current_dim: dimensions - 1,
1141 sealed: false,
1142 elements: vec![],
1143 };
1144
1145 let is_special_char = |c| matches!(c, '{' | '}' | ',' | '\\' | '"');
1146 let is_end_of_literal = |c| matches!(c, ',' | '}');
1147
1148 loop {
1149 buf.take_while(|ch| ch.is_ascii_whitespace());
1150
1151 match buf.next() {
1153 None if builder.sealed => {
1154 break;
1155 }
1156 None => Err(EarlyTerm)?,
1157 Some(_) if builder.sealed => Err(Junk)?,
1158 Some(c) => builder.current_command_char = c,
1159 }
1160
1161 match builder.current_command_char {
1163 '{' => builder.enter_dim()?,
1164 '}' => builder.exit_dim()?,
1165 ',' => builder.commit_element(true)?,
1166 c => {
1167 buf.prev();
1168 let s = match c {
1169 '"' => Some(lex_quoted_element(buf)?),
1170 _ => lex_unquoted_element(buf, is_special_char, is_end_of_literal)?,
1171 };
1172 builder.insert_element(s)?;
1173 }
1174 }
1175 }
1176
1177 if builder.elements.is_empty() {
1178 return Ok((vec![], vec![]));
1181 }
1182
1183 let dims = builder
1184 .dimensions
1185 .into_iter()
1186 .map(|dim| ArrayDimension {
1187 length: dim
1188 .length
1189 .expect("every dimension must have its length discovered"),
1190 lower_bound: 1,
1191 })
1192 .collect();
1193
1194 Ok((builder.elements, dims))
1195 }
1196
1197 fn enter_dim(&mut self) -> Result<(), ArrayParsingError> {
1199 let d = &mut self.dimensions[self.current_dim];
1200 if d.staged_element {
1202 return Err(UnexpectedChar(self.current_command_char));
1203 }
1204
1205 self.current_dim += 1;
1206
1207 if self.current_dim >= self.dimensions.len() {
1209 return Err(NonRectilinearDims);
1210 }
1211
1212 Ok(())
1213 }
1214
1215 fn insert_element(&mut self, s: Option<Cow<'a, str>>) -> Result<(), ArrayParsingError> {
1217 if self.current_dim != self.dimensions.len() - 1 {
1220 return Err(UnexpectedElement);
1221 }
1222
1223 self.stage_element()?;
1224
1225 self.elements.push(s);
1226
1227 Ok(())
1228 }
1229
1230 fn stage_element(&mut self) -> Result<(), ArrayParsingError> {
1234 let d = &mut self.dimensions[self.current_dim];
1235 if d.staged_element {
1238 return Err(UnexpectedElement);
1239 }
1240 d.staged_element = true;
1241 Ok(())
1242 }
1243
1244 fn commit_element(&mut self, require_staged: bool) -> Result<(), ArrayParsingError> {
1248 let d = &mut self.dimensions[self.current_dim];
1249 if !d.staged_element {
1250 return if require_staged || d.committed_element_count > 0 {
1254 Err(UnexpectedChar(self.current_command_char))
1255 } else {
1256 Ok(())
1260 };
1261 }
1262 d.staged_element = false;
1263 d.committed_element_count += 1;
1264
1265 Ok(())
1266 }
1267
1268 fn exit_dim(&mut self) -> Result<(), ArrayParsingError> {
1274 self.commit_element(false)?;
1276
1277 let ndims = self.dimensions.len();
1278 let d = &mut self.dimensions[self.current_dim];
1279
1280 if d.committed_element_count == 0 && ndims > 1 {
1283 return Err(UnexpectedChar(self.current_command_char));
1284 }
1285
1286 match d.length {
1288 None => d.length = Some(d.committed_element_count),
1289 Some(l) => {
1290 if l != d.committed_element_count {
1291 return Err(NonRectilinearDims);
1292 }
1293 }
1294 }
1295
1296 d.committed_element_count = 0;
1298
1299 if self.current_dim == 0 {
1302 self.sealed = true;
1303 } else {
1304 self.current_dim -= 1;
1305 self.stage_element()?;
1307 }
1308
1309 Ok(())
1310 }
1311 }
1312
1313 let (raw_elems, dims) = ArrayBuilder::build(s)?;
1314
1315 let mut elems = Vec::with_capacity(raw_elems.len());
1316
1317 let mut generated = |elem| gen_elem(elem).map_err(|e| e.to_string());
1318
1319 for elem in raw_elems.into_iter() {
1320 elems.push(match elem {
1321 Some(elem) => generated(elem)?,
1322 None => make_null(),
1323 });
1324 }
1325
1326 Ok((elems, dims))
1327}
1328
1329pub fn parse_list<'a, T, E>(
1330 s: &'a str,
1331 is_element_type_list: bool,
1332 make_null: impl FnMut() -> T,
1333 gen_elem: impl FnMut(Cow<'a, str>) -> Result<T, E>,
1334) -> Result<Vec<T>, ParseError>
1335where
1336 E: ToString,
1337{
1338 parse_list_inner(s, is_element_type_list, make_null, gen_elem)
1339 .map_err(|details| ParseError::invalid_input_syntax("list", s).with_details(details))
1340}
1341
1342fn parse_list_inner<'a, T, E>(
1345 s: &'a str,
1346 is_element_type_list: bool,
1347 mut make_null: impl FnMut() -> T,
1348 mut gen_elem: impl FnMut(Cow<'a, str>) -> Result<T, E>,
1349) -> Result<Vec<T>, String>
1350where
1351 E: ToString,
1352{
1353 let mut elems = vec![];
1354 let buf = &mut LexBuf::new(s);
1355
1356 if !buf.consume('{') {
1358 bail!(
1359 "expected '{{', found {}",
1360 match buf.next() {
1361 Some(c) => format!("{}", c),
1362 None => "empty string".to_string(),
1363 }
1364 )
1365 }
1366
1367 let mut generated = |elem| gen_elem(elem).map_err(|e| e.to_string());
1369 let is_special_char = |c| matches!(c, '{' | '}' | ',' | '\\' | '"');
1370 let is_end_of_literal = |c| matches!(c, ',' | '}');
1371
1372 loop {
1374 buf.take_while(|ch| ch.is_ascii_whitespace());
1375 match buf.next() {
1377 Some('}') => {
1378 break;
1379 }
1380 _ if elems.len() == 0 => {
1381 buf.prev();
1382 }
1383 Some(',') => {}
1384 Some(c) => bail!("expected ',' or '}}', got '{}'", c),
1385 None => bail!("unexpected end of input"),
1386 }
1387
1388 buf.take_while(|ch| ch.is_ascii_whitespace());
1389 let elem = match buf.peek() {
1391 Some('"') => generated(lex_quoted_element(buf)?)?,
1392 Some('{') => {
1393 if !is_element_type_list {
1394 bail!(
1395 "unescaped '{{' at beginning of element; perhaps you \
1396 want a nested list, e.g. '{{a}}'::text list list"
1397 )
1398 }
1399 generated(lex_embedded_element(buf)?)?
1400 }
1401 Some(_) => match lex_unquoted_element(buf, is_special_char, is_end_of_literal)? {
1402 Some(elem) => generated(elem)?,
1403 None => make_null(),
1404 },
1405 None => bail!("unexpected end of input"),
1406 };
1407 elems.push(elem);
1408 }
1409
1410 buf.take_while(|ch| ch.is_ascii_whitespace());
1411 if let Some(c) = buf.next() {
1412 bail!(
1413 "malformed array literal; contains '{}' after terminal '}}'",
1414 c
1415 )
1416 }
1417
1418 Ok(elems)
1419}
1420
1421pub fn parse_legacy_vector<'a, T, E>(
1422 s: &'a str,
1423 gen_elem: impl FnMut(Cow<'a, str>) -> Result<T, E>,
1424) -> Result<Vec<T>, ParseError>
1425where
1426 E: ToString,
1427{
1428 parse_legacy_vector_inner(s, gen_elem)
1429 .map_err(|details| ParseError::invalid_input_syntax("int2vector", s).with_details(details))
1430}
1431
1432pub fn parse_legacy_vector_inner<'a, T, E>(
1437 s: &'a str,
1438 mut gen_elem: impl FnMut(Cow<'a, str>) -> Result<T, E>,
1439) -> Result<Vec<T>, String>
1440where
1441 E: ToString,
1442{
1443 let mut elems = vec![];
1444 let buf = &mut LexBuf::new(s);
1445
1446 let mut generated = |elem| gen_elem(elem).map_err(|e| e.to_string());
1447
1448 loop {
1449 buf.take_while(|ch| ch.is_ascii_whitespace());
1450 match buf.peek() {
1451 Some(_) => {
1452 let elem = buf.take_while(|ch| !ch.is_ascii_whitespace());
1453 elems.push(generated(elem.into())?);
1454 }
1455 None => break,
1456 }
1457 }
1458
1459 Ok(elems)
1460}
1461
1462fn lex_quoted_element<'a>(buf: &mut LexBuf<'a>) -> Result<Cow<'a, str>, String> {
1463 assert!(buf.consume('"'));
1464 let s = buf.take_while(|ch| !matches!(ch, '"' | '\\'));
1465
1466 if let Some('"') = buf.peek() {
1468 buf.next();
1469 return Ok(s.into());
1470 }
1471
1472 let mut s = s.to_string();
1473 loop {
1474 match buf.next() {
1475 Some('\\') => match buf.next() {
1476 Some(c) => s.push(c),
1477 None => bail!("unterminated quoted string"),
1478 },
1479 Some('"') => break,
1480 Some(c) => s.push(c),
1481 None => bail!("unterminated quoted string"),
1482 }
1483 }
1484 Ok(s.into())
1485}
1486
1487fn lex_embedded_element<'a>(buf: &mut LexBuf<'a>) -> Result<Cow<'a, str>, String> {
1488 let pos = buf.pos();
1489 assert!(matches!(buf.next(), Some('{')));
1490 let mut depth = 1;
1491 let mut in_escape = false;
1492 while depth > 0 {
1493 match buf.next() {
1494 Some('\\') => {
1495 buf.next(); }
1497 Some('"') => in_escape = !in_escape, Some('{') if !in_escape => depth += 1,
1499 Some('}') if !in_escape => depth -= 1,
1500 Some(_) => (),
1501 None => bail!("unterminated embedded element"),
1502 }
1503 }
1504 let s = &buf.inner()[pos..buf.pos()];
1505 Ok(Cow::Borrowed(s))
1506}
1507
1508fn lex_unquoted_element<'a>(
1510 buf: &mut LexBuf<'a>,
1511 is_special_char: impl Fn(char) -> bool,
1512 is_end_of_literal: impl Fn(char) -> bool,
1513) -> Result<Option<Cow<'a, str>>, String> {
1514 assert!(!buf.peek().unwrap().is_ascii_whitespace());
1516
1517 let s = buf.take_while(|ch| !is_special_char(ch) && !ch.is_ascii_whitespace());
1518
1519 match buf.peek() {
1521 Some(',') | Some('}') if !s.is_empty() => {
1522 return Ok(if s.to_uppercase() == "NULL" {
1523 None
1524 } else {
1525 Some(s.into())
1526 });
1527 }
1528 _ => {}
1529 }
1530
1531 let mut escaped_char = false;
1535
1536 let mut s = s.to_string();
1537 let mut trimmed_len = s.len();
1540 loop {
1541 match buf.next() {
1542 Some('\\') => match buf.next() {
1543 Some(c) => {
1544 escaped_char = true;
1545 s.push(c);
1546 trimmed_len = s.len();
1547 }
1548 None => return Err("unterminated element".into()),
1549 },
1550 Some(c) if is_end_of_literal(c) => {
1551 if s.is_empty() {
1554 bail!("malformed literal; missing element")
1555 }
1556 buf.prev();
1557 break;
1558 }
1559 Some(c) if is_special_char(c) => {
1560 bail!("malformed literal; must escape special character '{}'", c)
1561 }
1562 Some(c) => {
1563 s.push(c);
1564 if !c.is_ascii_whitespace() {
1565 trimmed_len = s.len();
1566 }
1567 }
1568 None => bail!("unterminated element"),
1569 }
1570 }
1571 s.truncate(trimmed_len);
1572 Ok(if s.to_uppercase() == "NULL" && !escaped_char {
1573 None
1574 } else {
1575 Some(Cow::Owned(s))
1576 })
1577}
1578
1579pub fn parse_map<'a, V, E>(
1580 s: &'a str,
1581 is_value_type_map: bool,
1582 gen_elem: impl FnMut(Option<Cow<'a, str>>) -> Result<V, E>,
1583) -> Result<BTreeMap<String, V>, ParseError>
1584where
1585 E: ToString,
1586{
1587 parse_map_inner(s, is_value_type_map, gen_elem)
1588 .map_err(|details| ParseError::invalid_input_syntax("map", s).with_details(details))
1589}
1590
1591fn parse_map_inner<'a, V, E>(
1592 s: &'a str,
1593 is_value_type_map: bool,
1594 mut gen_elem: impl FnMut(Option<Cow<'a, str>>) -> Result<V, E>,
1595) -> Result<BTreeMap<String, V>, String>
1596where
1597 E: ToString,
1598{
1599 let mut map = BTreeMap::new();
1600 let buf = &mut LexBuf::new(s);
1601
1602 if !buf.consume('{') {
1604 bail!(
1605 "expected '{{', found {}",
1606 match buf.next() {
1607 Some(c) => format!("{}", c),
1608 None => "empty string".to_string(),
1609 }
1610 )
1611 }
1612
1613 let gen_key = |key: Option<Cow<'a, str>>| -> Result<String, String> {
1615 match key {
1616 Some(Cow::Owned(s)) => Ok(s),
1617 Some(Cow::Borrowed(s)) => Ok(s.to_owned()),
1618 None => Err("expected key".to_owned()),
1619 }
1620 };
1621 let mut gen_value = |elem| gen_elem(elem).map_err(|e| e.to_string());
1622 let is_special_char = |c| matches!(c, '{' | '}' | ',' | '"' | '=' | '>' | '\\');
1623 let is_end_of_literal = |c| matches!(c, ',' | '}' | '=');
1624
1625 loop {
1626 buf.take_while(|ch| ch.is_ascii_whitespace());
1628 match buf.next() {
1629 Some('}') => break,
1630 _ if map.len() == 0 => {
1631 buf.prev();
1632 }
1633 Some(',') => {}
1634 Some(c) => bail!("expected ',' or end of input, got '{}'", c),
1635 None => bail!("unexpected end of input"),
1636 }
1637
1638 buf.take_while(|ch| ch.is_ascii_whitespace());
1640 let key = match buf.peek() {
1641 Some('"') => Some(lex_quoted_element(buf)?),
1642 Some(_) => lex_unquoted_element(buf, is_special_char, is_end_of_literal)?,
1643 None => bail!("unexpected end of input"),
1644 };
1645 let key = gen_key(key)?;
1646
1647 buf.take_while(|ch| ch.is_ascii_whitespace());
1649 if !buf.consume('=') || !buf.consume('>') {
1650 bail!("expected =>")
1651 }
1652
1653 buf.take_while(|ch| ch.is_ascii_whitespace());
1655 let value = match buf.peek() {
1656 Some('"') => Some(lex_quoted_element(buf)?),
1657 Some('{') => {
1658 if !is_value_type_map {
1659 bail!(
1660 "unescaped '{{' at beginning of value; perhaps you \
1661 want a nested map, e.g. '{{a=>{{a=>1}}}}'::map[text=>map[text=>int]]"
1662 )
1663 }
1664 Some(lex_embedded_element(buf)?)
1665 }
1666 Some(_) => lex_unquoted_element(buf, is_special_char, is_end_of_literal)?,
1667 None => bail!("unexpected end of input"),
1668 };
1669 let value = gen_value(value)?;
1670
1671 map.insert(key, value);
1673 }
1674 Ok(map)
1675}
1676
1677pub fn format_map<F, T, E>(
1678 buf: &mut F,
1679 elems: impl IntoIterator<Item = (impl AsRef<str>, T)>,
1680 mut format_elem: impl FnMut(MapValueWriter<F>, T) -> Result<Nestable, E>,
1681) -> Result<Nestable, E>
1682where
1683 F: FormatBuffer,
1684{
1685 buf.write_char('{');
1686 let mut elems = elems.into_iter().peekable();
1687 while let Some((key, value)) = elems.next() {
1688 let key_start = buf.len();
1691 buf.write_str(key.as_ref());
1692 escape_elem::<_, MapElementEscaper>(buf, key_start);
1693
1694 buf.write_str("=>");
1695
1696 let value_start = buf.len();
1697 if let Nestable::MayNeedEscaping = format_elem(MapValueWriter(buf), value)? {
1698 escape_elem::<_, MapElementEscaper>(buf, value_start);
1699 }
1700
1701 if elems.peek().is_some() {
1702 buf.write_char(',');
1703 }
1704 }
1705 buf.write_char('}');
1706 Ok(Nestable::Yes)
1707}
1708
1709pub fn parse_range<'a, V, E>(
1710 s: &'a str,
1711 gen_elem: impl FnMut(Cow<'a, str>) -> Result<V, E>,
1712) -> Result<Range<V>, ParseError>
1713where
1714 E: ToString,
1715{
1716 Ok(Range {
1717 inner: parse_range_inner(s, gen_elem).map_err(|details| {
1718 ParseError::invalid_input_syntax("range", s).with_details(details)
1719 })?,
1720 })
1721}
1722
1723fn parse_range_inner<'a, V, E>(
1724 s: &'a str,
1725 mut gen_elem: impl FnMut(Cow<'a, str>) -> Result<V, E>,
1726) -> Result<Option<RangeInner<V>>, String>
1727where
1728 E: ToString,
1729{
1730 let buf = &mut LexBuf::new(s);
1731
1732 buf.take_while(|ch| ch.is_ascii_whitespace());
1733
1734 if buf.consume_str("empty") {
1735 buf.take_while(|ch| ch.is_ascii_whitespace());
1736 if buf.next().is_none() {
1737 return Ok(None);
1738 } else {
1739 bail!("Junk after \"empty\" key word.")
1740 }
1741 }
1742
1743 let lower_inclusive = match buf.next() {
1744 Some('[') => true,
1745 Some('(') => false,
1746 _ => bail!("Missing left parenthesis or bracket."),
1747 };
1748
1749 let lower_bound = match buf.peek() {
1750 Some(',') => None,
1751 Some('"') => {
1757 let v = lex_quoted_element(buf)?;
1758 let v = gen_elem(v).map_err(|e| e.to_string())?;
1759 Some(v)
1760 }
1761 Some(_) => {
1762 let v = buf.take_while(|c| !matches!(c, ','));
1763 let v = gen_elem(Cow::from(v)).map_err(|e| e.to_string())?;
1764 Some(v)
1765 }
1766 None => bail!("Unexpected end of input."),
1767 };
1768
1769 buf.take_while(|ch| ch.is_ascii_whitespace());
1770
1771 if buf.next() != Some(',') {
1772 bail!("Missing comma after lower bound.")
1773 }
1774
1775 let upper_bound = match buf.peek() {
1776 Some(']' | ')') => None,
1777 Some('"') => {
1779 let v = lex_quoted_element(buf)?;
1780 let v = gen_elem(v).map_err(|e| e.to_string())?;
1781 Some(v)
1782 }
1783 Some(_) => {
1784 let v = buf.take_while(|c| !matches!(c, ')' | ']'));
1785 let v = gen_elem(Cow::from(v)).map_err(|e| e.to_string())?;
1786 Some(v)
1787 }
1788 None => bail!("Unexpected end of input."),
1789 };
1790
1791 let upper_inclusive = match buf.next() {
1792 Some(']') => true,
1793 Some(')') => false,
1794 _ => bail!("Missing left parenthesis or bracket."),
1795 };
1796
1797 buf.take_while(|ch| ch.is_ascii_whitespace());
1798
1799 if buf.next().is_some() {
1800 bail!("Junk after right parenthesis or bracket.")
1801 }
1802
1803 let range = Some(RangeInner {
1804 lower: RangeBound {
1805 inclusive: lower_inclusive,
1806 bound: lower_bound,
1807 },
1808 upper: RangeBound {
1809 inclusive: upper_inclusive,
1810 bound: upper_bound,
1811 },
1812 });
1813
1814 Ok(range)
1815}
1816
1817pub fn format_range<F, V, E>(
1819 buf: &mut F,
1820 r: &Range<V>,
1821 mut format_elem: impl FnMut(RangeElementWriter<F>, Option<&V>) -> Result<Nestable, E>,
1822) -> Result<Nestable, E>
1823where
1824 F: FormatBuffer,
1825{
1826 let range = match &r.inner {
1827 None => {
1828 buf.write_str("empty");
1829 return Ok(Nestable::MayNeedEscaping);
1830 }
1831 Some(i) => i,
1832 };
1833
1834 if range.lower.inclusive {
1835 buf.write_char('[');
1836 } else {
1837 buf.write_char('(');
1838 }
1839
1840 let start = buf.len();
1841 if let Nestable::MayNeedEscaping =
1842 format_elem(RangeElementWriter(buf), range.lower.bound.as_ref())?
1843 {
1844 escape_elem::<_, ListElementEscaper>(buf, start);
1845 }
1846
1847 buf.write_char(',');
1848
1849 let start = buf.len();
1850 if let Nestable::MayNeedEscaping =
1851 format_elem(RangeElementWriter(buf), range.upper.bound.as_ref())?
1852 {
1853 escape_elem::<_, ListElementEscaper>(buf, start);
1854 }
1855
1856 if range.upper.inclusive {
1857 buf.write_char(']');
1858 } else {
1859 buf.write_char(')');
1860 }
1861
1862 Ok(Nestable::MayNeedEscaping)
1863}
1864
1865#[derive(Debug)]
1867pub struct RangeElementWriter<'a, F>(&'a mut F);
1868
1869impl<'a, F> RangeElementWriter<'a, F>
1870where
1871 F: FormatBuffer,
1872{
1873 pub fn write_null(self) -> Nestable {
1875 Nestable::Yes
1878 }
1879
1880 pub fn nonnull_buffer(self) -> &'a mut F {
1883 self.0
1884 }
1885}
1886
1887pub fn format_array<F, T, E>(
1888 buf: &mut F,
1889 dims: &[ArrayDimension],
1890 elems: impl IntoIterator<Item = T>,
1891 mut format_elem: impl FnMut(ListElementWriter<F>, T) -> Result<Nestable, E>,
1892) -> Result<Nestable, E>
1893where
1894 F: FormatBuffer,
1895{
1896 if dims.iter().any(|dim| dim.lower_bound != 1) {
1897 for d in dims.iter() {
1898 let (lower, upper) = d.dimension_bounds();
1899 write!(buf, "[{}:{}]", lower, upper);
1900 }
1901 buf.write_char('=');
1902 }
1903
1904 format_array_inner(buf, dims, &mut elems.into_iter(), &mut format_elem)?;
1905 Ok(Nestable::Yes)
1906}
1907
1908pub fn format_array_inner<F, T, E>(
1909 buf: &mut F,
1910 dims: &[ArrayDimension],
1911 elems: &mut impl Iterator<Item = T>,
1912 format_elem: &mut impl FnMut(ListElementWriter<F>, T) -> Result<Nestable, E>,
1913) -> Result<(), E>
1914where
1915 F: FormatBuffer,
1916{
1917 if dims.is_empty() {
1918 buf.write_str("{}");
1919 return Ok(());
1920 }
1921
1922 buf.write_char('{');
1923 for j in 0..dims[0].length {
1924 if j > 0 {
1925 buf.write_char(',');
1926 }
1927 if dims.len() == 1 {
1928 let start = buf.len();
1929 let elem = elems.next().unwrap();
1930 if let Nestable::MayNeedEscaping = format_elem(ListElementWriter(buf), elem)? {
1931 escape_elem::<_, ListElementEscaper>(buf, start);
1932 }
1933 } else {
1934 format_array_inner(buf, &dims[1..], elems, format_elem)?;
1935 }
1936 }
1937 buf.write_char('}');
1938
1939 Ok(())
1940}
1941
1942pub fn format_legacy_vector<F, T, E>(
1943 buf: &mut F,
1944 elems: impl IntoIterator<Item = T>,
1945 format_elem: impl FnMut(ListElementWriter<F>, T) -> Result<Nestable, E>,
1946) -> Result<Nestable, E>
1947where
1948 F: FormatBuffer,
1949{
1950 format_elems(buf, elems, format_elem, ' ')?;
1951 Ok(Nestable::MayNeedEscaping)
1952}
1953
1954pub fn format_list<F, T, E>(
1955 buf: &mut F,
1956 elems: impl IntoIterator<Item = T>,
1957 format_elem: impl FnMut(ListElementWriter<F>, T) -> Result<Nestable, E>,
1958) -> Result<Nestable, E>
1959where
1960 F: FormatBuffer,
1961{
1962 buf.write_char('{');
1963 format_elems(buf, elems, format_elem, ',')?;
1964 buf.write_char('}');
1965 Ok(Nestable::Yes)
1966}
1967
1968pub fn format_elems<F, T, E>(
1970 buf: &mut F,
1971 elems: impl IntoIterator<Item = T>,
1972 mut format_elem: impl FnMut(ListElementWriter<F>, T) -> Result<Nestable, E>,
1973 sep: char,
1974) -> Result<(), E>
1975where
1976 F: FormatBuffer,
1977{
1978 let mut elems = elems.into_iter().peekable();
1979 while let Some(elem) = elems.next() {
1980 let start = buf.len();
1981 if let Nestable::MayNeedEscaping = format_elem(ListElementWriter(buf), elem)? {
1982 escape_elem::<_, ListElementEscaper>(buf, start);
1983 }
1984 if elems.peek().is_some() {
1985 buf.write_char(sep)
1986 }
1987 }
1988 Ok(())
1989}
1990
1991pub fn format_mz_acl_item<F>(buf: &mut F, mz_acl_item: MzAclItem) -> Nestable
1993where
1994 F: FormatBuffer,
1995{
1996 write!(buf, "{mz_acl_item}");
1997 Nestable::Yes
1998}
1999
2000pub fn parse_mz_acl_item(s: &str) -> Result<MzAclItem, ParseError> {
2002 s.trim()
2003 .parse()
2004 .map_err(|e| ParseError::invalid_input_syntax("mz_aclitem", s).with_details(e))
2005}
2006
2007pub fn format_acl_item<F>(buf: &mut F, acl_item: AclItem) -> Nestable
2009where
2010 F: FormatBuffer,
2011{
2012 write!(buf, "{acl_item}");
2013 Nestable::Yes
2014}
2015
2016pub fn parse_acl_item(s: &str) -> Result<AclItem, ParseError> {
2018 s.trim()
2019 .parse()
2020 .map_err(|e| ParseError::invalid_input_syntax("aclitem", s).with_details(e))
2021}
2022
2023pub trait ElementEscaper {
2024 fn needs_escaping(elem: &[u8]) -> bool;
2025 fn escape_char(c: u8) -> u8;
2026}
2027
2028struct ListElementEscaper;
2029
2030impl ElementEscaper for ListElementEscaper {
2031 fn needs_escaping(elem: &[u8]) -> bool {
2032 elem.is_empty()
2035 || elem.eq_ignore_ascii_case(b"NULL")
2036 || elem
2037 .iter()
2038 .any(|c| matches!(c, b'{' | b'}' | b',' | b'"' | b'\\') || c.is_ascii_whitespace())
2039 }
2040
2041 fn escape_char(_: u8) -> u8 {
2042 b'\\'
2043 }
2044}
2045
2046struct MapElementEscaper;
2047
2048impl ElementEscaper for MapElementEscaper {
2049 fn needs_escaping(elem: &[u8]) -> bool {
2050 elem.is_empty()
2051 || elem.eq_ignore_ascii_case(b"NULL")
2052 || elem.iter().any(|c| {
2053 matches!(c, b'{' | b'}' | b',' | b'"' | b'=' | b'>' | b'\\')
2054 || c.is_ascii_whitespace()
2055 })
2056 }
2057
2058 fn escape_char(_: u8) -> u8 {
2059 b'\\'
2060 }
2061}
2062
2063struct RecordElementEscaper;
2064
2065impl ElementEscaper for RecordElementEscaper {
2066 fn needs_escaping(elem: &[u8]) -> bool {
2067 elem.is_empty()
2068 || elem
2069 .iter()
2070 .any(|c| matches!(c, b'(' | b')' | b',' | b'"' | b'\\') || c.is_ascii_whitespace())
2071 }
2072
2073 fn escape_char(c: u8) -> u8 {
2074 if c == b'"' { b'"' } else { b'\\' }
2075 }
2076}
2077
2078pub fn element_needs_escaping(elem: &[u8]) -> bool {
2085 ListElementEscaper::needs_escaping(elem)
2086 || MapElementEscaper::needs_escaping(elem)
2087 || RecordElementEscaper::needs_escaping(elem)
2088}
2089
2090fn escape_elem<F, E>(buf: &mut F, start: usize)
2100where
2101 F: FormatBuffer,
2102 E: ElementEscaper,
2103{
2104 let elem = &buf.as_ref()[start..];
2105 if !E::needs_escaping(elem) {
2106 return;
2107 }
2108
2109 let extras = 2 + elem.iter().filter(|b| matches!(b, b'"' | b'\\')).count();
2112 let orig_end = buf.len();
2113 let new_end = buf.len() + extras;
2114
2115 for _ in 0..extras {
2122 buf.write_char('\0');
2123 }
2124
2125 let elem = unsafe { buf.as_bytes_mut() };
2128
2129 let mut wi = new_end - 1;
2133 elem[wi] = b'"';
2134 wi -= 1;
2135 for ri in (start..orig_end).rev() {
2136 elem[wi] = elem[ri];
2137 wi -= 1;
2138 if let b'\\' | b'"' = elem[ri] {
2139 elem[wi] = E::escape_char(elem[ri]);
2140 wi -= 1;
2141 }
2142 }
2143 elem[wi] = b'"';
2144
2145 assert!(wi == start);
2146}
2147
2148#[derive(Debug)]
2150pub struct ListElementWriter<'a, F>(&'a mut F);
2151
2152impl<'a, F> ListElementWriter<'a, F>
2153where
2154 F: FormatBuffer,
2155{
2156 pub fn write_null(self) -> Nestable {
2158 self.0.write_str("NULL");
2159 Nestable::Yes
2160 }
2161
2162 pub fn nonnull_buffer(self) -> &'a mut F {
2165 self.0
2166 }
2167}
2168
2169#[derive(Debug)]
2171pub struct MapValueWriter<'a, F>(&'a mut F);
2172
2173impl<'a, F> MapValueWriter<'a, F>
2174where
2175 F: FormatBuffer,
2176{
2177 pub fn write_null(self) -> Nestable {
2179 self.0.write_str("NULL");
2180 Nestable::Yes
2181 }
2182
2183 pub fn nonnull_buffer(self) -> &'a mut F {
2186 self.0
2187 }
2188}
2189
2190pub fn format_record<F, T, E>(
2191 buf: &mut F,
2192 elems: impl IntoIterator<Item = T>,
2193 mut format_elem: impl FnMut(RecordElementWriter<F>, T) -> Result<Nestable, E>,
2194) -> Result<Nestable, E>
2195where
2196 F: FormatBuffer,
2197{
2198 buf.write_char('(');
2199 let mut elems = elems.into_iter().peekable();
2200 while let Some(elem) = elems.next() {
2201 let start = buf.len();
2202 if let Nestable::MayNeedEscaping = format_elem(RecordElementWriter(buf), elem)? {
2203 escape_elem::<_, RecordElementEscaper>(buf, start);
2204 }
2205 if elems.peek().is_some() {
2206 buf.write_char(',')
2207 }
2208 }
2209 buf.write_char(')');
2210 Ok(Nestable::MayNeedEscaping)
2211}
2212
2213#[derive(Debug)]
2215pub struct RecordElementWriter<'a, F>(&'a mut F);
2216
2217impl<'a, F> RecordElementWriter<'a, F>
2218where
2219 F: FormatBuffer,
2220{
2221 pub fn write_null(self) -> Nestable {
2223 Nestable::Yes
2224 }
2225
2226 pub fn nonnull_buffer(self) -> &'a mut F {
2229 self.0
2230 }
2231}
2232
2233#[derive(
2235 Ord,
2236 PartialOrd,
2237 Clone,
2238 Debug,
2239 Eq,
2240 PartialEq,
2241 Serialize,
2242 Deserialize,
2243 Hash
2244)]
2245#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))]
2246pub struct ParseError {
2247 pub kind: ParseErrorKind,
2248 pub type_name: Box<str>,
2249 pub input: Box<str>,
2250 pub details: Option<Box<str>>,
2251}
2252
2253#[derive(
2254 Ord,
2255 PartialOrd,
2256 Clone,
2257 Copy,
2258 Debug,
2259 Eq,
2260 PartialEq,
2261 Serialize,
2262 Deserialize,
2263 Hash
2264)]
2265#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))]
2266pub enum ParseErrorKind {
2267 OutOfRange,
2268 InvalidInputSyntax,
2269}
2270
2271impl ParseError {
2272 fn new<S>(kind: ParseErrorKind, type_name: &'static str, input: S) -> ParseError
2276 where
2277 S: Into<Box<str>>,
2278 {
2279 ParseError {
2280 kind,
2281 type_name: type_name.into(),
2282 input: input.into(),
2283 details: None,
2284 }
2285 }
2286
2287 fn out_of_range<S>(type_name: &'static str, input: S) -> ParseError
2288 where
2289 S: Into<Box<str>>,
2290 {
2291 ParseError::new(ParseErrorKind::OutOfRange, type_name, input)
2292 }
2293
2294 fn invalid_input_syntax<S>(type_name: &'static str, input: S) -> ParseError
2295 where
2296 S: Into<Box<str>>,
2297 {
2298 ParseError::new(ParseErrorKind::InvalidInputSyntax, type_name, input)
2299 }
2300
2301 fn with_details<D>(mut self, details: D) -> ParseError
2302 where
2303 D: fmt::Display,
2304 {
2305 self.details = Some(details.to_string().into());
2306 self
2307 }
2308}
2309
2310impl fmt::Display for ParseError {
2311 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2312 match self.kind {
2313 ParseErrorKind::OutOfRange => {
2314 write!(
2315 f,
2316 "{} is out of range for type {}",
2317 self.input.quoted(),
2318 self.type_name
2319 )?;
2320 if let Some(details) = &self.details {
2321 write!(f, ": {}", details)?;
2322 }
2323 Ok(())
2324 }
2325 ParseErrorKind::InvalidInputSyntax => {
2326 write!(f, "invalid input syntax for type {}: ", self.type_name)?;
2327 if let Some(details) = &self.details {
2328 write!(f, "{}: ", details)?;
2329 }
2330 write!(f, "{}", self.input.quoted())
2331 }
2332 }
2333 }
2334}
2335
2336impl Error for ParseError {}
2337
2338impl RustType<ProtoParseError> for ParseError {
2339 fn into_proto(&self) -> ProtoParseError {
2340 use Kind::*;
2341 use proto_parse_error::*;
2342 let kind = match self.kind {
2343 ParseErrorKind::OutOfRange => OutOfRange(()),
2344 ParseErrorKind::InvalidInputSyntax => InvalidInputSyntax(()),
2345 };
2346 ProtoParseError {
2347 kind: Some(kind),
2348 type_name: self.type_name.into_proto(),
2349 input: self.input.into_proto(),
2350 details: self.details.into_proto(),
2351 }
2352 }
2353
2354 fn from_proto(proto: ProtoParseError) -> Result<Self, TryFromProtoError> {
2355 use proto_parse_error::Kind::*;
2356
2357 if let Some(kind) = proto.kind {
2358 Ok(ParseError {
2359 kind: match kind {
2360 OutOfRange(()) => ParseErrorKind::OutOfRange,
2361 InvalidInputSyntax(()) => ParseErrorKind::InvalidInputSyntax,
2362 },
2363 type_name: proto.type_name.into(),
2364 input: proto.input.into(),
2365 details: proto.details.into_rust()?,
2366 })
2367 } else {
2368 Err(TryFromProtoError::missing_field("ProtoParseError::kind"))
2369 }
2370 }
2371}
2372
2373#[derive(
2374 Ord,
2375 PartialOrd,
2376 Copy,
2377 Clone,
2378 Debug,
2379 Eq,
2380 PartialEq,
2381 Serialize,
2382 Deserialize,
2383 Hash
2384)]
2385#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))]
2386pub enum ParseHexError {
2387 InvalidHexDigit(char),
2388 OddLength,
2389}
2390impl Error for ParseHexError {}
2391
2392impl fmt::Display for ParseHexError {
2393 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2394 match self {
2395 ParseHexError::InvalidHexDigit(c) => {
2396 write!(f, "invalid hexadecimal digit: \"{}\"", c.escape_default())
2397 }
2398 ParseHexError::OddLength => {
2399 f.write_str("invalid hexadecimal data: odd number of digits")
2400 }
2401 }
2402 }
2403}
2404
2405impl RustType<ProtoParseHexError> for ParseHexError {
2406 fn into_proto(&self) -> ProtoParseHexError {
2407 use Kind::*;
2408 use proto_parse_hex_error::*;
2409 let kind = match self {
2410 ParseHexError::InvalidHexDigit(v) => InvalidHexDigit(v.into_proto()),
2411 ParseHexError::OddLength => OddLength(()),
2412 };
2413 ProtoParseHexError { kind: Some(kind) }
2414 }
2415
2416 fn from_proto(error: ProtoParseHexError) -> Result<Self, TryFromProtoError> {
2417 use proto_parse_hex_error::Kind::*;
2418 match error.kind {
2419 Some(kind) => match kind {
2420 InvalidHexDigit(v) => Ok(ParseHexError::InvalidHexDigit(char::from_proto(v)?)),
2421 OddLength(()) => Ok(ParseHexError::OddLength),
2422 },
2423 None => Err(TryFromProtoError::missing_field(
2424 "`ProtoParseHexError::kind`",
2425 )),
2426 }
2427 }
2428}
2429
2430#[cfg(test)]
2431mod tests {
2432 use mz_ore::assert_ok;
2433 use mz_proto::protobuf_roundtrip;
2434 use proptest::prelude::*;
2435
2436 use super::*;
2437
2438 #[mz_ore::test]
2442 fn leap_second_rollover_at_max_date_errors() {
2443 assert!(parse_timestamp("262142-12-31 23:59:60").is_err());
2444 assert!(parse_timestamptz("262142-12-31 23:59:60+00").is_err());
2445 assert_ok!(parse_timestamp("262142-12-31 23:59:59"));
2447 assert_ok!(parse_timestamp_legacy("262142-12-31 23:59:60"));
2450 assert_ok!(parse_timestamptz_legacy("262142-12-31 23:59:60+00"));
2451 }
2452
2453 #[mz_ore::test]
2458 fn leap_second_rollover_at_max_date_with_offset_errors() {
2459 assert!(parse_timestamptz("262142-12-31 08:59:60-15").is_err());
2462 assert!(parse_timestamptz("262142-12-31 23:59:60+01").is_err());
2466 assert_ok!(parse_timestamptz("262142-12-31 08:59:60+15"));
2468 }
2469
2470 proptest! {
2471 #[mz_ore::test]
2472 #[cfg_attr(miri, ignore)] fn parse_error_protobuf_roundtrip(expect in any::<ParseError>()) {
2474 let actual = protobuf_roundtrip::<_, ProtoParseError>(&expect);
2475 assert_ok!(actual);
2476 assert_eq!(actual.unwrap(), expect);
2477 }
2478 }
2479
2480 proptest! {
2481 #[mz_ore::test]
2482 #[cfg_attr(miri, ignore)] fn parse_hex_error_protobuf_roundtrip(expect in any::<ParseHexError>()) {
2484 let actual = protobuf_roundtrip::<_, ProtoParseHexError>(&expect);
2485 assert_ok!(actual);
2486 assert_eq!(actual.unwrap(), expect);
2487 }
2488 }
2489
2490 #[mz_ore::test]
2491 fn test_split_nanos_to_micros() {
2492 let cases: Vec<(u32, bool, &str)> = vec![
2493 (0, false, ""),
2494 (1, false, ""),
2495 (499, false, ""),
2496 (500, false, ".000001"),
2497 (500_000, false, ".0005"),
2498 (5_000_000, false, ".005"),
2499 (999_999_499, false, ".999999"),
2500 (999_999_500, true, ""),
2503 (999_999_900, true, ""),
2504 (1_000_000_000, false, ""),
2507 (1_500_000_000, false, ".5"),
2508 (1_999_999_999, true, ""),
2509 ];
2510 for (nanos, expect_carry, expect) in cases {
2511 let (carry, micros) = split_nanos_to_micros(nanos);
2512 assert_eq!(carry, expect_carry, "carry for {nanos}ns");
2513 let mut buf = String::new();
2514 format_micros(&mut buf, micros);
2515 assert_eq!(&buf, expect, "fraction for {nanos}ns");
2516 }
2517 }
2518
2519 #[mz_ore::test]
2520 fn test_parse_pg_legacy_name() {
2521 let s = "hello world";
2522 assert_eq!(s, parse_pg_legacy_name(s));
2523
2524 let s = "x".repeat(63);
2525 assert_eq!(s, parse_pg_legacy_name(&s));
2526
2527 let s = "x".repeat(64);
2528 assert_eq!("x".repeat(63), parse_pg_legacy_name(&s));
2529
2530 let s = format!("{}{}", "x".repeat(61), "א");
2532 assert_eq!(s, parse_pg_legacy_name(&s));
2533
2534 let s = format!("{}{}", "x".repeat(62), "א");
2535 assert_eq!("x".repeat(62), parse_pg_legacy_name(&s));
2536 }
2537
2538 #[mz_ore::test]
2539 fn test_parse_oid() {
2540 assert_eq!(parse_oid("0").unwrap(), 0);
2542 assert_eq!(parse_oid("2147483647").unwrap(), 2147483647);
2543 assert_eq!(parse_oid("2147483648").unwrap(), 2147483648);
2544 assert_eq!(parse_oid("4294967295").unwrap(), 4294967295);
2545
2546 assert_eq!(parse_oid("-1").unwrap(), 4294967295);
2548 assert_eq!(parse_oid("-2147483648").unwrap(), 2147483648);
2549
2550 assert_eq!(parse_oid(" 42 ").unwrap(), 42);
2552
2553 assert!(parse_oid("4294967296").is_err());
2555 assert!(parse_oid("-2147483649").is_err());
2556 assert!(parse_oid("nope").is_err());
2557 }
2558
2559 #[mz_ore::test]
2560 fn test_parse_oid_legacy() {
2561 assert_eq!(parse_oid_legacy("0").unwrap(), 0);
2563 assert_eq!(parse_oid_legacy("2147483647").unwrap(), 2147483647);
2564 assert_eq!(parse_oid_legacy("-1").unwrap(), 4294967295);
2565 assert_eq!(parse_oid_legacy("-2147483648").unwrap(), 2147483648);
2566
2567 assert!(parse_oid_legacy("2147483648").is_err());
2570 assert!(parse_oid_legacy("4294967295").is_err());
2571 assert!(parse_oid_legacy("nope").is_err());
2572 }
2573
2574 fn date(y: i32, m: u32, d: u32) -> Date {
2575 Date::try_from(NaiveDate::from_ymd_opt(y, m, d).unwrap()).unwrap()
2576 }
2577
2578 #[mz_ore::test]
2579 fn test_parse_date_mdy() {
2580 assert_eq!(parse_date("01/02/03").unwrap(), date(2003, 1, 2));
2584 assert_eq!(parse_date("1-2-3").unwrap(), date(2003, 1, 2));
2585 assert_eq!(parse_date("01/02/69").unwrap(), date(2069, 1, 2));
2586 assert_eq!(parse_date("01/02/70").unwrap(), date(1970, 1, 2));
2587 assert_eq!(parse_date("01/02/1999").unwrap(), date(1999, 1, 2));
2588 assert_eq!(parse_date("0099-01-08").unwrap(), date(99, 1, 8));
2591 assert_eq!(parse_date("2003-01-02").unwrap(), date(2003, 1, 2));
2592 assert_eq!(parse_date("01/02/03 BC").unwrap(), date(-2, 1, 2));
2594 assert!(parse_date("99-01-08").is_err());
2596 assert!(parse_date("13/01/08").is_err());
2597
2598 assert_eq!(
2599 parse_timestamp("01/02/03 04:05:06").unwrap().to_string(),
2600 "2003-01-02 04:05:06"
2601 );
2602 assert_eq!(
2603 parse_mz_timestamp("01/02/03").unwrap(),
2604 parse_mz_timestamp("2003-01-02").unwrap()
2605 );
2606 }
2607
2608 #[mz_ore::test]
2609 fn test_parse_date_legacy() {
2610 assert_eq!(parse_date_legacy("01/02/03").unwrap(), date(1, 2, 3));
2614 assert_eq!(parse_date_legacy("99-01-08").unwrap(), date(99, 1, 8));
2615 assert!(parse_date_legacy("01/02/1999").is_err());
2616 assert_eq!(
2617 parse_timestamp_legacy("01/02/03 04:05:06")
2618 .unwrap()
2619 .to_string(),
2620 "0001-02-03 04:05:06"
2621 );
2622 assert!(parse_mz_timestamp_legacy("01/02/03").is_err());
2625 assert_eq!(
2626 parse_mz_timestamp_legacy("2003-01-02").unwrap(),
2627 parse_mz_timestamp("2003-01-02").unwrap()
2628 );
2629 }
2630}