Skip to main content

mz_repr/
strconv.rs

1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Use of this software is governed by the Business Source License
4// included in the LICENSE file.
5//
6// As of the Change Date specified in that file, in accordance with
7// the Business Source License, use of this software will be governed
8// by the Apache License, Version 2.0.
9
10//! Routines for converting datum values to and from their string
11//! representation.
12//!
13//! The functions in this module are tightly related to the variants of
14//! [`SqlScalarType`](crate::SqlScalarType). Each variant has a pair of functions in
15//! this module named `parse_VARIANT` and `format_VARIANT`. The type returned
16//! by `parse` functions, and the type accepted by `format` functions, will
17//! be a type that is easily converted into the [`Datum`](crate::Datum) variant
18//! for that type. The functions do not directly convert from `Datum`s to
19//! `String`s so that the logic can be reused when `Datum`s are not available or
20//! desired, as in the pgrepr crate.
21//!
22//! The string representations used are exactly the same as the PostgreSQL
23//! string representations for the corresponding PostgreSQL type. Deviations
24//! should be considered a bug.
25
26use 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/// Yes should be provided for types that will *never* return true for [`ElementEscaper::needs_escaping`]
70#[derive(Debug)]
71pub enum Nestable {
72    Yes,
73    MayNeedEscaping,
74}
75
76/// Parses a [`bool`] from `s`.
77///
78/// The accepted values are "true", "false", "yes", "no", "on", "off", "1", and
79/// "0", or any unambiguous prefix of one of those values. Leading or trailing
80/// whitespace is permissible.
81pub 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
89/// Like `format_bool`, but returns a string with a static lifetime.
90///
91/// This function should be preferred to `format_bool` when applicable, as it
92/// avoids an allocation.
93pub fn format_bool_static(b: bool) -> &'static str {
94    match b {
95        true => "t",
96        false => "f",
97    }
98}
99
100/// Writes a boolean value into `buf`.
101///
102/// `true` is encoded as the char `'t'` and `false` is encoded as the char
103/// `'f'`.
104pub 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
112/// Parses an [`i16`] from `s`.
113///
114/// Valid values are whatever the [`std::str::FromStr`] implementation on `i16` accepts,
115/// plus leading and trailing whitespace.
116pub 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
122/// Writes an [`i16`] to `buf`.
123pub fn format_int16<F>(buf: &mut F, i: i16) -> Nestable
124where
125    F: FormatBuffer,
126{
127    write!(buf, "{}", i);
128    Nestable::Yes
129}
130
131/// Parses an [`i32`] from `s`.
132///
133/// Valid values are whatever the [`std::str::FromStr`] implementation on `i32` accepts,
134/// plus leading and trailing whitespace.
135pub 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
141/// Writes an [`i32`] to `buf`.
142pub fn format_int32<F>(buf: &mut F, i: i32) -> Nestable
143where
144    F: FormatBuffer,
145{
146    write!(buf, "{}", i);
147    Nestable::Yes
148}
149
150/// Parses an `i64` from `s`.
151pub 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
157/// Writes an `i64` to `buf`.
158pub fn format_int64<F>(buf: &mut F, i: i64) -> Nestable
159where
160    F: FormatBuffer,
161{
162    write!(buf, "{}", i);
163    Nestable::Yes
164}
165
166/// Parses an [`u16`] from `s`.
167///
168/// Valid values are whatever the [`std::str::FromStr`] implementation on `u16` accepts,
169/// plus leading and trailing whitespace.
170pub 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
176/// Writes an `u16` to `buf`.
177pub fn format_uint16<F>(buf: &mut F, u: u16) -> Nestable
178where
179    F: FormatBuffer,
180{
181    write!(buf, "{}", u);
182    Nestable::Yes
183}
184
185/// Parses an [`u32`] from `s`.
186///
187/// Valid values are whatever the [`std::str::FromStr`] implementation on `u32` accepts,
188/// plus leading and trailing whitespace.
189pub 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
195/// Writes an `u32` to `buf`.
196pub fn format_uint32<F>(buf: &mut F, u: u32) -> Nestable
197where
198    F: FormatBuffer,
199{
200    write!(buf, "{}", u);
201    Nestable::Yes
202}
203
204/// Parses an `u64` from `s`.
205pub 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
211/// Writes an `u64` to `buf`.
212pub fn format_uint64<F>(buf: &mut F, u: u64) -> Nestable
213where
214    F: FormatBuffer,
215{
216    write!(buf, "{}", u);
217    Nestable::Yes
218}
219
220/// Parses an `mz_timestamp` from `s`.
221pub 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
227/// Parses an `mz_timestamp` from `s` with the frozen legacy date parsing in
228/// its timestamp fallback.
229///
230/// NOTE: This exists solely to keep the storage source cast
231/// `CastStringToMzTimestamp` evaluation-stable across releases (see the
232/// stability contract in `mz_storage_types::sources::casts`). Use
233/// [`parse_mz_timestamp`] everywhere else.
234pub 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
239/// Writes an `mz_timestamp` to `buf`.
240pub 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
248/// Parses an OID from `s`.
249pub fn parse_oid(s: &str) -> Result<u32, ParseError> {
250    // For historical reasons, PostgreSQL accepts OID inputs whose value fits in
251    // the range of either `u32` or `i32`. The full `u32` range is accepted
252    // directly, while values given with a minus sign are parsed as `i32` and
253    // reinterpreted as `u32` (e.g. `-1` becomes `4294967295`). Anything outside
254    // both ranges is rejected.
255    //
256    // Do not use this as a model for behavior in other contexts. OIDs should
257    // not in general be thought of as freely convertible from `i32`s.
258    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
268/// Parses an OID from `s`, accepting only the `i32` range.
269///
270/// This is the historical [`parse_oid`] behavior: values are parsed as `i32`
271/// and reinterpreted as `u32`, so text in `2147483648..=4294967295` is
272/// rejected even though it denotes a valid OID.
273///
274/// NOTE: This exists solely to keep the persisted PostgreSQL source cast
275/// `CastStringToOid` evaluation-stable across releases (see the stability
276/// contract in `mz_storage_types::sources::casts`). PostgreSQL replication
277/// re-casts the old tuple on delete, so widening this cast would let a value
278/// ingested pre-upgrade as an error be retracted post-upgrade as a value,
279/// leaving the error stuck. Use [`parse_oid`] everywhere else.
280pub 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    // Matching PostgreSQL's float parsing behavior is tricky. PostgreSQL's
293    // implementation delegates almost entirely to strtof(3)/strtod(3), which
294    // will report an out-of-range error if a number was rounded to zero or
295    // infinity. For example, parsing "1e70" as a 32-bit float will yield an
296    // out-of-range error because it is rounded to infinity, but parsing an
297    // explicitly-specified "inf" will yield infinity without an error.
298    //
299    // To @benesch's knowledge, there is no Rust implementation of float parsing
300    // that reports whether underflow or overflow occurred. So we figure it out
301    // ourselves after the fact. If parsing the float returns infinity and the input
302    // was not an explicitly-specified infinity, then we know overflow occurred.
303    // If parsing the float returns zero and the input was not an explicitly-specified
304    // zero, then we know underflow occurred.
305
306    // Matches `0`, `-0`, `+0`, `000000.00000`, `0.0e10`, 0., .0, et al.
307    static ZERO_RE: LazyLock<Regex> =
308        LazyLock::new(|| Regex::new(r#"(?i-u)^[-+]?(0+(\.0*)?|\.0+)(e|$)"#).unwrap());
309    // Matches `inf`, `-inf`, `+inf`, `infinity`, et al.
310    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    // Use ryu rather than the standard library. ryu uses scientific notation
333    // when possible, which better matches PostgreSQL. The standard library's
334    // `ToString` implementations print all available digits, which is rather
335    // verbose.
336    //
337    // Note that we have to fix up ryu's formatting in a few cases to match
338    // PostgreSQL. PostgreSQL spells out "Infinity" in full, never emits a
339    // trailing ".0", formats positive exponents as e.g. "1e+10" rather than
340    // "1e10", and emits a negative sign for negative zero. If we need to speed
341    // up float formatting, we can look into forking ryu and making these edits
342    // directly, but for now it doesn't seem worth it.
343
344    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
369/// Parses an `f32` from `s`.
370pub fn parse_float32(s: &str) -> Result<f32, ParseError> {
371    parse_float("real", s)
372}
373
374/// Writes an `f32` to `buf`.
375pub fn format_float32<F>(buf: &mut F, f: f32) -> Nestable
376where
377    F: FormatBuffer,
378{
379    format_float(buf, f)
380}
381
382/// Parses an `f64` from `s`.
383pub fn parse_float64(s: &str) -> Result<f64, ParseError> {
384    parse_float("double precision", s)
385}
386
387/// Writes an `f64` to `buf`.
388pub fn format_float64<F>(buf: &mut F, f: f64) -> Nestable
389where
390    F: FormatBuffer,
391{
392    format_float(buf, f)
393}
394
395/// Use the following grammar to parse `s` into:
396///
397/// - `NaiveDate`
398/// - `NaiveTime`
399/// - Timezone string
400///
401/// `NaiveDate` and `NaiveTime` are appropriate to compute a `NaiveDateTime`,
402/// which can be used in conjunction with a timezone string to generate a
403/// `DateTime<Utc>`.
404///
405/// ```text
406/// <unquoted timestamp string> ::=
407///     <date value> <space> <time value> [ <time zone interval> ]
408/// <date value> ::=
409///     <years value> <minus sign> <months value> <minus sign> <days value>
410/// <time zone interval> ::=
411///     <sign> <hours value> <colon> <minutes value>
412/// ```
413fn 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    // PostgreSQL special date-time inputs
422    // https://www.postgresql.org/docs/12/datatype-datetime.html#id-1.5.7.13.18.8
423    // We should add support for other values here, e.g. infinity
424    // which @quodlibetor is willing to add to the chrono package.
425    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
448/// Parses a [`Date`] from `s`.
449pub fn parse_date(s: &str) -> Result<Date, ParseError> {
450    parse_date_inner(s, DateOrder::Mdy)
451}
452
453/// Parses a [`Date`] from `s` with the frozen legacy year-month-day
454/// interpretation of ambiguous dates.
455///
456/// NOTE: This exists solely to keep the storage source cast `CastStringToDate`
457/// evaluation-stable across releases (see the stability contract in
458/// `mz_storage_types::sources::casts`). Use [`parse_date`] everywhere else.
459pub 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
470/// Writes a [`Date`] to `buf`.
471pub 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        // The ` BC` suffix carries whitespace, which every element escaper
481        // treats as needing quotes. `Nestable::Yes` is only for renderings that
482        // can *never* need escaping, so it would be a lie here. A Common Era
483        // date is plain `YYYY-MM-DD` and keeps the cheaper answer.
484        return Nestable::MayNeedEscaping;
485    }
486    Nestable::Yes
487}
488
489/// Parses a `NaiveTime` from `s`, using the following grammar.
490///
491/// ```text
492/// <time value> ::=
493///     <hours value> <colon> <minutes value> <colon> <seconds integer value>
494///     [ <period> [ <seconds fraction> ] ]
495/// ```
496pub fn parse_time(s: &str) -> Result<NaiveTime, ParseError> {
497    parse_time_inner(s, TimeFields::Required)
498}
499
500/// Parses a `NaiveTime` from `s`, resolving a string that names no time field at
501/// all to midnight.
502///
503/// NOTE: This exists solely to keep the storage source cast `CastStringToTime`
504/// evaluation-stable across releases (see the stability contract in
505/// `mz_storage_types::sources::casts`). Use [`parse_time`] everywhere else.
506pub fn parse_time_legacy(s: &str) -> Result<NaiveTime, ParseError> {
507    parse_time_inner(s, TimeFields::Optional)
508}
509
510/// Whether a TIME string has to name at least one time field.
511enum TimeFields {
512    /// Reject a string that names none, as PostgreSQL does.
513    Required,
514    /// Read a string that names none as midnight.
515    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            // A string carrying no time field at all, `""` or `":"`, tokenizes
522            // to nothing the time grammar has to consume, so it parses without
523            // filling a single field and every field then defaults to zero.
524            // PostgreSQL rejects such a string rather than reading it as
525            // midnight. Hour, minute and second are the only fields the time
526            // grammar fills, and the only ones `compute_time` reads.
527            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
539/// Writes a [`NaiveTime`] to `buf`.
540pub 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    // A carry out of the last second of the day has nowhere to go: a
546    // `NaiveTime` wraps to midnight rather than reaching PostgreSQL's
547    // `24:00:00`, and rendering a time a whole day early is a worse lie than
548    // dropping the carry. Saturate at the largest renderable fraction instead.
549    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
565/// Parses a `NaiveDateTime` from `s`.
566pub fn parse_timestamp(s: &str) -> Result<CheckedTimestamp<NaiveDateTime>, ParseError> {
567    parse_timestamp_inner(s, DateOrder::Mdy, LeapSecond::RollOver)
568}
569
570/// Parses a `NaiveDateTime` from `s` with the frozen legacy year-month-day
571/// interpretation of ambiguous dates.
572///
573/// NOTE: This exists solely to keep the storage source cast
574/// `CastStringToTimestamp` evaluation-stable across releases (see the
575/// stability contract in `mz_storage_types::sources::casts`). Use
576/// [`parse_timestamp`] everywhere else.
577pub 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/// Whether parsing rolls a `:60` second into the next minute.
601///
602/// The SQL entry points roll over, matching Postgres. The `_legacy` entry
603/// points backing the frozen storage source casts keep chrono's leap-second
604/// representation, because rolling it over would change the datum (or error)
605/// an existing source produces for the same input string, breaking the
606/// stability contract in `mz_storage_types::sources::casts`.
607#[derive(Clone, Copy)]
608enum LeapSecond {
609    RollOver,
610    Keep,
611}
612
613/// Postgres normalizes a parsed `:60` second by rolling it into the next
614/// minute. chrono instead keeps a leap-second representation (nanos >= 1e9)
615/// that sorts before the following second while epoch-style conversions
616/// count it at or past that second, which breaks the monotonicity contracts
617/// persist filter pushdown derives ranges with. Roll it over at the parse
618/// boundary so the leap representation never enters a parsed timestamp.
619/// `TIME` keeps the leap representation: rolling it over would wrap to
620/// 00:00:00 and reverse its ordering, and the whole-second leap time is
621/// harmless.
622///
623/// Returns `None` when the rollover overflows chrono's range (a leap second
624/// on the maximum date), which callers report as out of range.
625fn 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
636/// Writes a [`NaiveDateTime`] timestamp to `buf`.
637pub 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    // This always needs escaping because of the whitespace
649    Nestable::MayNeedEscaping
650}
651
652/// Parses a `DateTime<Utc>` from `s`. See `mz_expr::scalar::func::timezone_timestamp` for timezone anomaly considerations.
653pub fn parse_timestamptz(s: &str) -> Result<CheckedTimestamp<DateTime<Utc>>, ParseError> {
654    parse_timestamptz_inner(s, DateOrder::Mdy, LeapSecond::RollOver)
655}
656
657/// Parses a `DateTime<Utc>` from `s` with the frozen legacy year-month-day
658/// interpretation of ambiguous dates.
659///
660/// NOTE: This exists solely to keep the storage source cast
661/// `CastStringToTimestampTz` evaluation-stable across releases (see the
662/// stability contract in `mz_storage_types::sources::casts`). Use
663/// [`parse_timestamptz`] everywhere else.
664pub 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    // The rollover applies to the local wall clock, before the offset shifts it
680    // to UTC. A local time that rolls out of chrono's range is rejected even
681    // when the UTC instant it denotes would be representable.
682    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    // `HIGH_DATE` is exactly `NaiveDate::MAX`, so applying a westward offset to
704    // a time late on that day leaves chrono's range, and chrono's own
705    // `NaiveDateTime - FixedOffset` panics there. This runs before the
706    // `CheckedTimestamp` bound check below, so that check cannot save it.
707    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
712/// Writes a [`DateTime<Utc>`] timestamp to `buf`.
713pub 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    // This always needs escaping because of the whitespace
726    Nestable::MayNeedEscaping
727}
728
729/// parse
730///
731/// ```text
732/// <unquoted interval string> ::=
733///   [ <sign> ] { <year-month literal> | <day-time literal> }
734/// <year-month literal> ::=
735///     <years value> [ <minus sign> <months value> ]
736///   | <months value>
737/// <day-time literal> ::=
738///     <day-time interval>
739///   | <time interval>
740/// <day-time interval> ::=
741///   <days value> [ <space> <hours value> [ <colon> <minutes value>
742///       [ <colon> <seconds value> ] ] ]
743/// <time interval> ::=
744///     <hours value> [ <colon> <minutes value> [ <colon> <seconds value> ] ]
745///   | <minutes value> [ <colon> <seconds value> ]
746///   | <seconds value>
747/// ```
748pub fn parse_interval(s: &str) -> Result<Interval, ParseError> {
749    parse_interval_w_disambiguator(s, None, DateTimeField::Second)
750}
751
752/// Parse an interval string, using an optional leading precision for time (H:M:S)
753/// and a specific mz_sql_parser::ast::DateTimeField to identify ambiguous elements.
754/// For more information about this operation, see the documentation on
755/// ParsedDateTime::build_parsed_datetime_interval.
756pub 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    // Check for values that can only be generated by invalid syntax.
786    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    // Process value; only errors if value is out of range of numeric's max precision.
794    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    // To match PostgreSQL, we truncate the string to 64 bytes, while being
824    // careful not to truncate in the middle of any multibyte characters.
825    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 the input starts with "\x", then the remaining bytes are hex encoded
839    // [0]. Otherwise the bytes use the traditional "escape" format. [1]
840    //
841    // [0]: https://www.postgresql.org/docs/current/datatype-binary.html#id-1.5.7.12.9
842    // [1]: https://www.postgresql.org/docs/current/datatype-binary.html#id-1.5.7.12.10
843    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    // Can't use `hex::decode` here, as it doesn't tolerate whitespace
854    // between encoded bytes.
855
856    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    // Bytes are interpreted literally, save for the special escape sequences
881    // "\\", which represents a single backslash, and "\NNN", where each N
882    // is an octal digit, which represents the byte whose octal value is NNN.
883    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
956/// Splits a sub-second nanosecond count into a whole-second carry and the
957/// microsecond fraction to render, rounding half away from zero.
958///
959/// The returned fraction is always below one second, so it can never be written
960/// as a fractional field of more than six digits. Only a nanosecond count of
961/// `.9999995` or more produces a carry.
962///
963/// chrono spells a leap second as a nanosecond count of one second or more, and
964/// its `%S` already accounts for that, rendering `60` on a second-of-minute of
965/// 59 and folding into the next second elsewhere. The leap second is therefore
966/// already in the seconds field and only the part below one second is ours to
967/// write.
968fn 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
977/// Rounds `ts` to microseconds, returning the value whose date and seconds are
978/// to be rendered plus the fraction to append to it.
979///
980/// A fraction that rounds up to a full second is carried into the seconds field
981/// rather than written as a fractional `1_000_000` microseconds, which the
982/// trailing-zero stripper would turn into a nonsense `.1`, roughly one second
983/// early. At the very end of chrono's range the carry has nowhere to go, so the
984/// fraction saturates at `.999999` rather than the value rolling over to an
985/// unrepresentable date.
986fn 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    // Dropping the fraction before adding the second is what makes this correct
992    // for a leap second too: `23:59:59` plus chrono's leap nanos is `23:59:60`,
993    // and the second after it is `00:00:00` of the next minute, which is exactly
994    // where clearing the nanos and adding a second lands.
995    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
1005/// Writes a microsecond fraction to `buf` with trailing zeros stripped, or
1006/// nothing at all when it is zero.
1007///
1008/// `micros` must be below one second, which is what [`split_nanos_to_micros`]
1009/// guarantees. A larger value would be written as a fraction it does not fit
1010/// in, silently shifting the rendered time by about a second.
1011fn 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        // If None, still discovering this dimension's permitted width;
1079        // otherwise only permits `length` elements per dimension.
1080        length: Option<usize>,
1081        // Whether this dimension has a staged element that can be committed.
1082        // This prevents us from accepting "empty" elements, e.g. `{1,}` or
1083        // `{1,,2}`.
1084        staged_element: bool,
1085        // The total number of elements committed in this dimension since it was
1086        // last entered. Zeroed out when exited.
1087        committed_element_count: usize,
1088    }
1089
1090    #[derive(Clone, Debug, Default)]
1091    struct ArrayBuilder<'a> {
1092        // The current character we're operating from.
1093        current_command_char: char,
1094        // The dimension information, which will get turned into
1095        // `ArrayDimensions`.
1096        dimensions: Vec<Dimension>,
1097        // THe current dimension we're operating on.
1098        current_dim: usize,
1099        // Whether or not this array may be modified any further.
1100        sealed: bool,
1101        // The elements extracted from the input str. This is on the array
1102        // builder to necessitate using `insert_element` so we understand when
1103        // elements are staged.
1104        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            // TODO: support parsing array dimensions
1114            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                // We enter the builder at the element-bearing dimension, which is the last
1139                // dimension.
1140                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                // Filter command state from terminal states.
1152                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                // Run command char
1162                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                // Empty arrays are represented by empty dimensions
1179                // rather than one dimension with 0 length.
1180                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        /// Descend into another dimension of the array.
1198        fn enter_dim(&mut self) -> Result<(), ArrayParsingError> {
1199            let d = &mut self.dimensions[self.current_dim];
1200            // Cannot enter a new dimension with an uncommitted element.
1201            if d.staged_element {
1202                return Err(UnexpectedChar(self.current_command_char));
1203            }
1204
1205            self.current_dim += 1;
1206
1207            // You have exceeded the maximum dimensions.
1208            if self.current_dim >= self.dimensions.len() {
1209                return Err(NonRectilinearDims);
1210            }
1211
1212            Ok(())
1213        }
1214
1215        /// Insert a new element into the array, ensuring it is in the proper dimension.
1216        fn insert_element(&mut self, s: Option<Cow<'a, str>>) -> Result<(), ArrayParsingError> {
1217            // Can only insert elements into data-bearing dimension, which is
1218            // the last one.
1219            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        /// Stage an element to be committed. Only one element can be staged at
1231        /// a time and staged elements must be committed before moving onto the
1232        /// next element or leaving the dimension.
1233        fn stage_element(&mut self) -> Result<(), ArrayParsingError> {
1234            let d = &mut self.dimensions[self.current_dim];
1235            // Cannot stage two elements at once, i.e. previous element wasn't
1236            // followed by committing token (`,` or `}`).
1237            if d.staged_element {
1238                return Err(UnexpectedElement);
1239            }
1240            d.staged_element = true;
1241            Ok(())
1242        }
1243
1244        /// Commit the currently staged element, which can be made optional.
1245        /// This ensures that each element has an appropriate terminal character
1246        /// after it.
1247        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                // - , requires a preceding staged element
1251                // - } does not require a preceding staged element only when
1252                //   it's the close of an empty dimension.
1253                return if require_staged || d.committed_element_count > 0 {
1254                    Err(UnexpectedChar(self.current_command_char))
1255                } else {
1256                    // This indicates that we have an empty value in this
1257                    // dimension and want to exit before incrementing the
1258                    // committed element count.
1259                    Ok(())
1260                };
1261            }
1262            d.staged_element = false;
1263            d.committed_element_count += 1;
1264
1265            Ok(())
1266        }
1267
1268        /// Exit the current dimension, committing any currently staged element
1269        /// in this dimension, and marking the interior array that this is part
1270        /// of as staged itself. If this is the 0th dimension, i.e. the closed
1271        /// brace matching the first open brace, seal the builder from further
1272        /// modification.
1273        fn exit_dim(&mut self) -> Result<(), ArrayParsingError> {
1274            // Commit an element of this dimension
1275            self.commit_element(false)?;
1276
1277            let ndims = self.dimensions.len();
1278            let d = &mut self.dimensions[self.current_dim];
1279
1280            // Empty dimensions are only permitted in one-dimensional
1281            // arrays, i.e. the only valid empty array literal is `{}`.
1282            if d.committed_element_count == 0 && ndims > 1 {
1283                return Err(UnexpectedChar(self.current_command_char));
1284            }
1285
1286            // Ensure that the elements in this dimension conform to the expected shape.
1287            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            // Reset this dimension's counter in case it's re-entered.
1297            d.committed_element_count = 0;
1298
1299            // If we closed the last dimension, this array may not be modified
1300            // any longer.
1301            if self.current_dim == 0 {
1302                self.sealed = true;
1303            } else {
1304                self.current_dim -= 1;
1305                // This object is an element of a higher dimension.
1306                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
1342// `parse_list_inner`'s separation from `parse_list` simplifies error handling
1343// by allowing subprocedures to return `String` errors.
1344fn 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    // Consume opening paren.
1357    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    // Simplifies calls to `gen_elem` by handling errors
1368    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    // Consume elements.
1373    loop {
1374        buf.take_while(|ch| ch.is_ascii_whitespace());
1375        // Check for terminals.
1376        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        // Get elements.
1390        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
1432/// Parses PostgreSQL's legacy whitespace-separated vector syntax (used in
1433/// Materialize for `int2vector`). Unlike [`parse_array`], this grammar has
1434/// no token for `NULL`, which is why `int2vector` cannot represent `NULL`
1435/// elements. See [`crate::scalar::Int2Vector`].
1436pub 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    // `Cow::Borrowed` optimization for quoted strings without escapes
1467    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(); // Next character is escaped, so ignore it
1496            }
1497            Some('"') => in_escape = !in_escape, // Begin or end escape
1498            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
1508// Result of `None` indicates element is NULL.
1509fn 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    // first char is guaranteed to be non-whitespace
1515    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    // `Cow::Borrowed` optimization for elements without special characters.
1520    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    // Track whether there are any escaped characters to determine if the string
1532    // "NULL" should be treated as a NULL, or if it had any escaped characters
1533    // and should be treated as the string "NULL".
1534    let mut escaped_char = false;
1535
1536    let mut s = s.to_string();
1537    // As we go, we keep track of where to truncate to in order to remove any
1538    // trailing whitespace.
1539    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                // End of literal characters as the first character indicates
1552                // a missing element definition.
1553                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    // Consume opening paren.
1603    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    // Simplifies calls to generators by handling errors
1614    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        // Check for terminals.
1627        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        // Get key.
1639        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        // Assert mapping arrow (=>) is present.
1648        buf.take_while(|ch| ch.is_ascii_whitespace());
1649        if !buf.consume('=') || !buf.consume('>') {
1650            bail!("expected =>")
1651        }
1652
1653        // Get value.
1654        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        // Insert elements.
1672        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        // Map key values are always Strings, which always evaluate to
1689        // Nestable::MayNeedEscaping.
1690        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        // A bound whose rendering needs escaping is emitted quoted by
1752        // `format_range`, so the quotes have to come back off here. Without this
1753        // the raw substring reaches the element parser, which mostly tolerates a
1754        // stray `"` and then misreads it the moment it abuts a meaningful token,
1755        // for example the `BC"` of a pre-Common-Era date or timestamp.
1756        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        // See the lower bound above.
1778        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
1817/// Writes a [`Range`] to `buf`.
1818pub 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/// A helper for `format_range` that formats a single record element.
1866#[derive(Debug)]
1867pub struct RangeElementWriter<'a, F>(&'a mut F);
1868
1869impl<'a, F> RangeElementWriter<'a, F>
1870where
1871    F: FormatBuffer,
1872{
1873    /// Marks this record element as null.
1874    pub fn write_null(self) -> Nestable {
1875        // In ranges these "null" values represent infinite bounds, which are
1876        // not represented as values, but rather the absence of a value.
1877        Nestable::Yes
1878    }
1879
1880    /// Returns a [`FormatBuffer`] into which a non-null element can be
1881    /// written.
1882    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
1968/// Writes each `elem` into `buf`, separating the elems with `sep`.
1969pub 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
1991/// Writes an `mz_acl_item` to `buf`.
1992pub 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
2000/// Parses an MzAclItem from `s`.
2001pub 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
2007/// Writes an `acl_item` to `buf`.
2008pub 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
2016/// Parses an AclItem from `s`.
2017pub 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        // The parser treats any case variant of unquoted "NULL" as null, so
2033        // any such element must be quoted to round-trip.
2034        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
2078/// Reports whether `elem` would be quoted by the list, map or record element
2079/// escaper.
2080///
2081/// This is the union of the three, because one rendering can be nested in any of
2082/// them. Returning [`Nestable::Yes`] is a promise that this is false, so a
2083/// formatter's oracle can check the promise rather than restate the rules.
2084pub fn element_needs_escaping(elem: &[u8]) -> bool {
2085    ListElementEscaper::needs_escaping(elem)
2086        || MapElementEscaper::needs_escaping(elem)
2087        || RecordElementEscaper::needs_escaping(elem)
2088}
2089
2090/// Escapes a list, record, or map element in place.
2091///
2092/// The element must start at `start` and extend to the end of the buffer. The
2093/// buffer will be resized if escaping is necessary to account for the
2094/// additional escape characters.
2095///
2096/// The `needs_escaping` function is used to determine whether an element needs
2097/// to be escaped. It is provided with the bytes of each element and should
2098/// return whether the element needs to be escaped.
2099fn 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    // We'll need two extra bytes for the quotes at the start and end of the
2110    // element, plus an extra byte for each quote and backslash.
2111    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    // Pad the buffer to the new length. These characters will all be
2116    // overwritten.
2117    //
2118    // NOTE(benesch): we never read these characters, so we could instead use
2119    // uninitialized memory, but that's a level of unsafety I'm currently
2120    // uncomfortable with. The performance gain is negligible anyway.
2121    for _ in 0..extras {
2122        buf.write_char('\0');
2123    }
2124
2125    // SAFETY: inserting ASCII characters before other ASCII characters
2126    // preserves UTF-8 encoding.
2127    let elem = unsafe { buf.as_bytes_mut() };
2128
2129    // Walk the string backwards, writing characters at the new end index while
2130    // reading from the old end index, adding quotes at the beginning and end,
2131    // and adding a backslash before every backslash or quote.
2132    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/// A helper for `format_list` that formats a single list element.
2149#[derive(Debug)]
2150pub struct ListElementWriter<'a, F>(&'a mut F);
2151
2152impl<'a, F> ListElementWriter<'a, F>
2153where
2154    F: FormatBuffer,
2155{
2156    /// Marks this list element as null.
2157    pub fn write_null(self) -> Nestable {
2158        self.0.write_str("NULL");
2159        Nestable::Yes
2160    }
2161
2162    /// Returns a [`FormatBuffer`] into which a non-null element can be
2163    /// written.
2164    pub fn nonnull_buffer(self) -> &'a mut F {
2165        self.0
2166    }
2167}
2168
2169/// A helper for `format_map` that formats a single map value.
2170#[derive(Debug)]
2171pub struct MapValueWriter<'a, F>(&'a mut F);
2172
2173impl<'a, F> MapValueWriter<'a, F>
2174where
2175    F: FormatBuffer,
2176{
2177    /// Marks this value element as null.
2178    pub fn write_null(self) -> Nestable {
2179        self.0.write_str("NULL");
2180        Nestable::Yes
2181    }
2182
2183    /// Returns a [`FormatBuffer`] into which a non-null element can be
2184    /// written.
2185    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/// A helper for `format_record` that formats a single record element.
2214#[derive(Debug)]
2215pub struct RecordElementWriter<'a, F>(&'a mut F);
2216
2217impl<'a, F> RecordElementWriter<'a, F>
2218where
2219    F: FormatBuffer,
2220{
2221    /// Marks this record element as null.
2222    pub fn write_null(self) -> Nestable {
2223        Nestable::Yes
2224    }
2225
2226    /// Returns a [`FormatBuffer`] into which a non-null element can be
2227    /// written.
2228    pub fn nonnull_buffer(self) -> &'a mut F {
2229        self.0
2230    }
2231}
2232
2233/// An error while parsing an input as a type.
2234#[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    // To ensure that reversing the parameters causes a compile-time error, we
2273    // require that `type_name` be a string literal, even though `ParseError`
2274    // itself stores the type name as a `String`.
2275    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    /// Rolling a leap second over must not panic at the timestamp maximum:
2439    /// chrono's max date parses, and adding the rollover second overflows.
2440    /// The leap value on the max date errors as out of range instead.
2441    #[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        // One second below the maximum still rolls over successfully.
2446        assert_ok!(parse_timestamp("262142-12-31 23:59:59"));
2447        // The frozen legacy parse keeps the leap representation instead of
2448        // rolling it over, so the same input stays in range there.
2449        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    /// The `timestamptz` rollover applies to the local wall clock, so an
2454    /// offset can move the overflow to either side of it. Both sides must
2455    /// report out of range rather than panic, and this pins which inputs the
2456    /// wall-clock-first order rejects.
2457    #[mz_ore::test]
2458    fn leap_second_rollover_at_max_date_with_offset_errors() {
2459        // The rollover itself stays in range, then the westward offset carries
2460        // the UTC instant past the maximum date.
2461        assert!(parse_timestamptz("262142-12-31 08:59:60-15").is_err());
2462        // The rollover leaves chrono's range before the offset is applied,
2463        // even though the UTC instant it denotes, 262142-12-31 23:00:00, is
2464        // representable.
2465        assert!(parse_timestamptz("262142-12-31 23:59:60+01").is_err());
2466        // An eastward offset on the maximum date leaves room for the rollover.
2467        assert_ok!(parse_timestamptz("262142-12-31 08:59:60+15"));
2468    }
2469
2470    proptest! {
2471        #[mz_ore::test]
2472        #[cfg_attr(miri, ignore)] // too slow
2473        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)] // too slow
2483        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            // Rounds up to a full second, which belongs in the seconds field.
2501            // Written as a fraction it would render as `.1`.
2502            (999_999_500, true, ""),
2503            (999_999_900, true, ""),
2504            // chrono's leap-second representation. The leap is already in the
2505            // seconds field, so only the part below one second is rendered.
2506            (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        // The Hebrew character Aleph (א) has a length of 2 bytes.
2531        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        // The full u32 range is accepted, matching PostgreSQL.
2541        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        // Negative values in the i32 range are reinterpreted as u32.
2547        assert_eq!(parse_oid("-1").unwrap(), 4294967295);
2548        assert_eq!(parse_oid("-2147483648").unwrap(), 2147483648);
2549
2550        // Surrounding whitespace is ignored.
2551        assert_eq!(parse_oid("  42 ").unwrap(), 42);
2552
2553        // Values outside both the u32 and i32 ranges are rejected.
2554        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        // Only the i32 range is accepted, reinterpreting negatives as u32.
2562        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        // The frozen behavior rejects the u32-only range that `parse_oid`
2568        // accepts. This divergence must not change (storage stability).
2569        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        // Pinned `DateStyle = ISO, MDY`: a one- or two-digit leading field
2581        // starts a month-day-year date, and a two-digit year is windowed
2582        // into 1970..=2069.
2583        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        // A leading field of three or more digits is a year, with no
2589        // windowing.
2590        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        // BC years are never windowed. Year 3 BC is year -2 in chrono.
2593        assert_eq!(parse_date("01/02/03 BC").unwrap(), date(-2, 1, 2));
2594        // A two-digit leading field is a month, so these are out of range.
2595        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        // The frozen behavior keeps year-month-day for ambiguous dates, with
2611        // no windowing. This divergence from `parse_date` must not change
2612        // (storage stability).
2613        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        // The mz_timestamp fallback keeps the frozen interpretation, under
2623        // which 0001-02-03 is out of range.
2624        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}