Skip to main content

mz_pgrepr/
value.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
10use std::collections::BTreeMap;
11use std::error::Error;
12use std::{io, str};
13
14use bytes::{BufMut, BytesMut};
15use chrono::{DateTime, NaiveDateTime, NaiveTime, Utc};
16use dec::OrderedDecimal;
17use itertools::Itertools;
18use mz_ore::cast::ReinterpretCast;
19use mz_ore::fmt::FormatBuffer;
20use mz_pgrepr_consts::oid::TYPE_INT2_OID;
21use mz_pgwire_common::Format;
22use mz_repr::adt::array::ArrayDimension;
23use mz_repr::adt::char;
24use mz_repr::adt::date::Date;
25use mz_repr::adt::jsonb::JsonbRef;
26use mz_repr::adt::mz_acl_item::{AclItem, MzAclItem};
27use mz_repr::adt::numeric::{self as mz_repr_numeric, NumericMaxScale, rescale};
28use mz_repr::adt::pg_legacy_name::NAME_MAX_BYTES;
29use mz_repr::adt::range::{Range, RangeInner};
30use mz_repr::adt::timestamp::CheckedTimestamp;
31use mz_repr::strconv::{self, Nestable};
32use mz_repr::{Datum, RowArena, RowPacker, RowRef, SqlRelationType, SqlScalarType};
33use postgres_types::{FromSql, IsNull, ToSql, Type as PgType};
34use uuid::Uuid;
35
36use crate::types::{NumericConstraints, UINT2, UINT4, UINT8};
37use crate::value::error::{IntoDatumError, NulCharacterError};
38use crate::{Interval, Jsonb, Numeric, Type, UInt2, UInt4, UInt8, regproc};
39
40pub mod error;
41pub mod interval;
42pub mod jsonb;
43pub mod numeric;
44pub mod record;
45pub mod unsigned;
46
47/// A PostgreSQL datum.
48#[derive(Debug)]
49pub enum Value {
50    /// A variable-length, multi-dimensional array of values.
51    Array {
52        /// The dimensions of the array.
53        dims: Vec<ArrayDimension>,
54        /// The elements of the array.
55        elements: Vec<Option<Value>>,
56    },
57    /// A boolean value.
58    Bool(bool),
59    /// A byte array, i.e., a variable-length binary string.
60    Bytea(Vec<u8>),
61    /// A single-byte character.
62    Char(u8),
63    /// A date.
64    Date(Date),
65    /// A 4-byte floating point number.
66    Float4(f32),
67    /// An 8-byte floating point number.
68    Float8(f64),
69    /// A 2-byte signed integer.
70    Int2(i16),
71    /// A 4-byte signed integer.
72    Int4(i32),
73    /// An 8-byte signed integer.
74    Int8(i64),
75    /// A 2-byte unsigned integer.
76    UInt2(UInt2),
77    /// A 4-byte unsigned integer.
78    UInt4(UInt4),
79    /// An 8-byte unsigned integer.
80    UInt8(UInt8),
81    /// A time interval.
82    Interval(Interval),
83    /// A binary JSON blob.
84    Jsonb(Jsonb),
85    /// A sequence of homogeneous values.
86    List(Vec<Option<Value>>),
87    /// A map of string keys and homogeneous values.
88    Map(BTreeMap<String, Option<Value>>),
89    /// An identifier string of no more than 64 characters in length.
90    Name(String),
91    /// An arbitrary precision number.
92    Numeric(Numeric),
93    /// An object identifier.
94    Oid(u32),
95    /// An object identifier naming a function.
96    ///
97    /// Kept distinct from [`Value::Oid`] because the text encoding resolves the
98    /// OID to the function's name, matching PostgreSQL's `regprocout`. The
99    /// binary encoding is identical to an `oid`, as it is in PostgreSQL.
100    RegProc(u32),
101    /// A sequence of heterogeneous values.
102    Record(Vec<Option<Value>>),
103    /// A time.
104    Time(NaiveTime),
105    /// A date and time, without a timezone.
106    Timestamp(CheckedTimestamp<NaiveDateTime>),
107    /// A date and time, with a timezone.
108    TimestampTz(CheckedTimestamp<DateTime<Utc>>),
109    /// A variable-length string.
110    Text(String),
111    /// A fixed-length string.
112    BpChar(String),
113    /// A variable-length string with an optional limit.
114    VarChar(String),
115    /// A universally unique identifier.
116    Uuid(Uuid),
117    /// A small int vector.
118    Int2Vector {
119        /// The elements of the vector.
120        elements: Vec<Option<Value>>,
121    },
122    /// A Materialize timestamp.
123    MzTimestamp(mz_repr::Timestamp),
124    /// A contiguous range of values along a domain.
125    Range(Range<Box<Value>>),
126    /// A list of privileges granted to a role, that uses [`mz_repr::role_id::RoleId`]s for role
127    /// references.
128    MzAclItem(MzAclItem),
129    /// A list of privileges granted to a user that uses [`mz_repr::adt::system::Oid`]s for role
130    /// references. This type is used primarily for compatibility with PostgreSQL.
131    AclItem(AclItem),
132}
133
134impl Value {
135    /// Constructs a new `Value` from a Materialize datum.
136    ///
137    /// The conversion happens in the obvious manner, except that `Datum::Null`
138    /// is converted to `None` to align with how PostgreSQL handles NULL.
139    pub fn from_datum(datum: Datum, typ: &SqlScalarType) -> Option<Value> {
140        match (datum, typ) {
141            (Datum::Null, _) => None,
142            (Datum::True, SqlScalarType::Bool) => Some(Value::Bool(true)),
143            (Datum::False, SqlScalarType::Bool) => Some(Value::Bool(false)),
144            (Datum::Int16(i), SqlScalarType::Int16) => Some(Value::Int2(i)),
145            (Datum::Int32(i), SqlScalarType::Int32) => Some(Value::Int4(i)),
146            (Datum::Int64(i), SqlScalarType::Int64) => Some(Value::Int8(i)),
147            (Datum::UInt8(c), SqlScalarType::PgLegacyChar) => Some(Value::Char(c)),
148            (Datum::UInt16(u), SqlScalarType::UInt16) => Some(Value::UInt2(UInt2(u))),
149            (Datum::UInt32(oid), SqlScalarType::Oid) => Some(Value::Oid(oid)),
150            (Datum::UInt32(oid), SqlScalarType::RegProc) => Some(Value::RegProc(oid)),
151            // NOTE: `regclass` and `regtype` collapse into `Value::Oid`, so they
152            // render as digits where PostgreSQL renders a name. Unlike `regproc`
153            // they can name objects a user created, which no static table can
154            // know. Resolution for those two lives in the SQL cast to `text`.
155            (Datum::UInt32(oid), SqlScalarType::RegClass) => Some(Value::Oid(oid)),
156            (Datum::UInt32(oid), SqlScalarType::RegType) => Some(Value::Oid(oid)),
157            (Datum::UInt32(u), SqlScalarType::UInt32) => Some(Value::UInt4(UInt4(u))),
158            (Datum::UInt64(u), SqlScalarType::UInt64) => Some(Value::UInt8(UInt8(u))),
159            (Datum::Float32(f), SqlScalarType::Float32) => Some(Value::Float4(*f)),
160            (Datum::Float64(f), SqlScalarType::Float64) => Some(Value::Float8(*f)),
161            (Datum::Numeric(d), SqlScalarType::Numeric { .. }) => Some(Value::Numeric(Numeric(d))),
162            (Datum::MzTimestamp(t), SqlScalarType::MzTimestamp) => Some(Value::MzTimestamp(t)),
163            (Datum::MzAclItem(mai), SqlScalarType::MzAclItem) => Some(Value::MzAclItem(mai)),
164            (Datum::AclItem(ai), SqlScalarType::AclItem) => Some(Value::AclItem(ai)),
165            (Datum::Date(d), SqlScalarType::Date) => Some(Value::Date(d)),
166            (Datum::Time(t), SqlScalarType::Time) => Some(Value::Time(t)),
167            (Datum::Timestamp(ts), SqlScalarType::Timestamp { .. }) => Some(Value::Timestamp(ts)),
168            (Datum::TimestampTz(ts), SqlScalarType::TimestampTz { .. }) => {
169                Some(Value::TimestampTz(ts))
170            }
171            (Datum::Interval(iv), SqlScalarType::Interval) => Some(Value::Interval(Interval(iv))),
172            (Datum::Bytes(b), SqlScalarType::Bytes) => Some(Value::Bytea(b.to_vec())),
173            (Datum::String(s), SqlScalarType::String) => Some(Value::Text(s.to_owned())),
174            (Datum::String(s), SqlScalarType::VarChar { .. }) => Some(Value::VarChar(s.to_owned())),
175            (Datum::String(s), SqlScalarType::Char { length }) => {
176                Some(Value::BpChar(char::format_str_pad(s, *length)))
177            }
178            (Datum::String(s), SqlScalarType::PgLegacyName) => Some(Value::Name(s.into())),
179            (_, SqlScalarType::Jsonb) => {
180                Some(Value::Jsonb(Jsonb(JsonbRef::from_datum(datum).to_owned())))
181            }
182            (Datum::Uuid(u), SqlScalarType::Uuid) => Some(Value::Uuid(u)),
183            (Datum::Array(array), SqlScalarType::Array(elem_type)) => {
184                let dims = array.dims().into_iter().collect();
185                let elements = array
186                    .elements()
187                    .iter()
188                    .map(|elem| Value::from_datum(elem, elem_type))
189                    .collect();
190                Some(Value::Array { dims, elements })
191            }
192            (Datum::Array(array), SqlScalarType::Int2Vector) => {
193                assert!(
194                    array.has_int2vector_dims(),
195                    "int2vector must be 1 dimensional, or empty"
196                );
197                let elements = array
198                    .elements()
199                    .iter()
200                    .map(|elem| Value::from_datum(elem, &SqlScalarType::Int16))
201                    .collect();
202                Some(Value::Int2Vector { elements })
203            }
204            (Datum::List(list), SqlScalarType::List { element_type, .. }) => {
205                let elements = list
206                    .iter()
207                    .map(|elem| Value::from_datum(elem, element_type))
208                    .collect();
209                Some(Value::List(elements))
210            }
211            (Datum::List(record), SqlScalarType::Record { fields, .. }) => {
212                let fields = record
213                    .iter()
214                    .zip_eq(fields)
215                    .map(|(e, (_name, ty))| Value::from_datum(e, &ty.scalar_type))
216                    .collect();
217                Some(Value::Record(fields))
218            }
219            (Datum::Map(dict), SqlScalarType::Map { value_type, .. }) => {
220                let entries = dict
221                    .iter()
222                    .map(|(k, v)| (k.to_owned(), Value::from_datum(v, value_type)))
223                    .collect();
224                Some(Value::Map(entries))
225            }
226            (Datum::Range(range), SqlScalarType::Range { element_type }) => {
227                let value_range = range.into_bounds(|b| {
228                    Box::new(
229                        Value::from_datum(b.datum(), element_type)
230                            .expect("RangeBounds never contain Datum::Null"),
231                    )
232                });
233                Some(Value::Range(value_range))
234            }
235            _ => panic!("can't serialize {}::{:?}", datum, typ),
236        }
237    }
238
239    /// Converts a Materialize datum from this value.
240    pub fn into_datum<'a>(
241        self,
242        buf: &'a RowArena,
243        typ: &Type,
244    ) -> Result<Datum<'a>, IntoDatumError> {
245        Ok(match self {
246            Value::Array { dims, elements } => {
247                let element_pg_type = match typ {
248                    Type::Array(t) => &*t,
249                    _ => panic!("Value::Array should have type Type::Array. Found {:?}", typ),
250                };
251                let elements: Result<Vec<_>, _> = elements
252                    .into_iter()
253                    .map(|element| match element {
254                        Some(element) => element.into_datum(buf, element_pg_type),
255                        None => Ok(Datum::Null),
256                    })
257                    .collect();
258                let elements = elements?;
259                buf.try_make_datum(|packer| {
260                    packer
261                        .try_push_array(&dims, elements)
262                        .map_err(IntoDatumError::from)
263                })?
264            }
265            Value::Int2Vector { .. } => {
266                // This situation is handled gracefully by Value::decode; if we
267                // wind up here it's a programming error.
268                unreachable!("into_datum cannot be called on Value::Int2Vector");
269            }
270            Value::Bool(true) => Datum::True,
271            Value::Bool(false) => Datum::False,
272            Value::Bytea(b) => Datum::Bytes(buf.push_bytes(b)),
273            Value::Char(c) => Datum::UInt8(c),
274            Value::Date(d) => Datum::Date(d),
275            Value::Float4(f) => Datum::Float32(f.into()),
276            Value::Float8(f) => Datum::Float64(f.into()),
277            Value::Int2(i) => Datum::Int16(i),
278            Value::Int4(i) => Datum::Int32(i),
279            Value::Int8(i) => Datum::Int64(i),
280            Value::UInt2(u) => Datum::UInt16(u.0),
281            Value::UInt4(u) => Datum::UInt32(u.0),
282            Value::UInt8(u) => Datum::UInt64(u.0),
283            Value::Jsonb(js) => buf.push_unary_row(js.0.into_row()),
284            Value::List(elems) => {
285                let elem_pg_type = match typ {
286                    Type::List(t) => &*t,
287                    _ => panic!("Value::List should have type Type::List. Found {:?}", typ),
288                };
289                let elems: Result<Vec<_>, _> = elems
290                    .into_iter()
291                    .map(|elem| match elem {
292                        Some(elem) => elem.into_datum(buf, elem_pg_type),
293                        None => Ok(Datum::Null),
294                    })
295                    .collect();
296                let elems = elems?;
297                buf.make_datum(|packer| packer.push_list(elems))
298            }
299            Value::Map(map) => {
300                let elem_pg_type = match typ {
301                    Type::Map { value_type } => &*value_type,
302                    _ => panic!("Value::Map should have type Type::Map. Found {:?}", typ),
303                };
304                buf.try_make_datum(|packer| {
305                    packer.try_push_dict_with(|row| {
306                        for (k, v) in map {
307                            row.push(Datum::String(buf.push_string(k)));
308                            let datum = match v {
309                                Some(elem) => elem.into_datum(buf, elem_pg_type)?,
310                                None => Datum::Null,
311                            };
312                            row.push(datum);
313                        }
314                        Ok::<_, IntoDatumError>(())
315                    })
316                })?
317            }
318            Value::Oid(oid) | Value::RegProc(oid) => Datum::UInt32(oid),
319            Value::Record(_) => {
320                // This situation is handled gracefully by Value::decode; if we
321                // wind up here it's a programming error.
322                unreachable!("into_datum cannot be called on Value::Record");
323            }
324            Value::Time(t) => Datum::Time(t),
325            Value::Timestamp(ts) => Datum::Timestamp(ts),
326            Value::TimestampTz(ts) => Datum::TimestampTz(ts),
327            Value::Interval(iv) => Datum::Interval(iv.0),
328            Value::Text(s) | Value::VarChar(s) | Value::Name(s) => {
329                Datum::String(buf.push_string(s))
330            }
331            Value::BpChar(s) => Datum::String(buf.push_string(s.trim_end().into())),
332            Value::Uuid(u) => Datum::Uuid(u),
333            Value::Numeric(n) => Datum::Numeric(n.0),
334            Value::MzTimestamp(t) => Datum::MzTimestamp(t),
335            Value::Range(range) => {
336                let elem_pg_type = match typ {
337                    Type::Range { element_type } => &*element_type,
338                    _ => panic!("Value::Range should have type Type::Range. Found {:?}", typ),
339                };
340                let range = range.try_into_bounds(|elem| elem.into_datum(buf, elem_pg_type))?;
341                buf.try_make_datum(|packer| packer.push_range(range).map_err(IntoDatumError::from))?
342            }
343            Value::MzAclItem(mz_acl_item) => Datum::MzAclItem(mz_acl_item),
344            Value::AclItem(acl_item) => Datum::AclItem(acl_item),
345        })
346    }
347
348    /// Like [`Self::into_datum`] but maps the error to a formatted string for decode/parameter contexts.
349    ///
350    /// Callers can then convert the `String` to their preferred error type (e.g. `io::Error`,
351    /// protocol error). The message is `"unable to decode {context}: {error}"`.
352    pub fn into_datum_decode_error<'a>(
353        self,
354        buf: &'a RowArena,
355        typ: &Type,
356        context: &str,
357    ) -> Result<Datum<'a>, String> {
358        self.into_datum(buf, typ)
359            .map_err(|e| format!("unable to decode {}: {}", context, e))
360    }
361
362    /// Serializes this value to `buf` in the specified `format`.
363    ///
364    /// `settings` affects only the text encoding.
365    pub fn encode(
366        &self,
367        ty: &Type,
368        format: Format,
369        buf: &mut BytesMut,
370        settings: TextEncodeSettings,
371    ) -> Result<(), io::Error> {
372        match format {
373            Format::Text => {
374                self.encode_text(buf, settings);
375                Ok(())
376            }
377            Format::Binary => self.encode_binary(ty, buf),
378        }
379    }
380
381    /// Serializes this value to `buf` using the [text encoding
382    /// format](Format::Text).
383    pub fn encode_text(&self, buf: &mut BytesMut, settings: TextEncodeSettings) -> Nestable {
384        let extra_float_digits = settings.extra_float_digits;
385        match self {
386            Value::Array { dims, elements } => {
387                strconv::format_array(buf, dims, elements, |buf, elem| match elem {
388                    None => Ok::<_, ()>(buf.write_null()),
389                    Some(elem) => Ok(elem.encode_text(buf.nonnull_buffer(), settings)),
390                })
391                .expect("provided closure never fails")
392            }
393            Value::Int2Vector { elements } => {
394                strconv::format_legacy_vector(buf, elements, |buf, elem| {
395                    Ok::<_, ()>(
396                        elem.as_ref()
397                            .expect("Int2Vector does not support NULL values")
398                            .encode_text(buf.nonnull_buffer(), settings),
399                    )
400                })
401                .expect("provided closure never fails")
402            }
403            Value::Bool(b) => strconv::format_bool(buf, *b),
404            Value::Bytea(b) => strconv::format_bytes(buf, b),
405            Value::Char(c) => {
406                buf.put_u8(*c);
407                Nestable::MayNeedEscaping
408            }
409            Value::Date(d) => strconv::format_date(buf, *d),
410            Value::Int2(i) => strconv::format_int16(buf, *i),
411            Value::Int4(i) => strconv::format_int32(buf, *i),
412            Value::Int8(i) => strconv::format_int64(buf, *i),
413            Value::UInt2(u) => strconv::format_uint16(buf, u.0),
414            Value::UInt4(u) => strconv::format_uint32(buf, u.0),
415            Value::UInt8(u) => strconv::format_uint64(buf, u.0),
416            Value::Interval(iv) => strconv::format_interval(buf, iv.0),
417            Value::Float4(f) if extra_float_digits > 0 => strconv::format_float32(buf, *f),
418            Value::Float4(f) => {
419                format_float_limited(buf, f64::from(*f), FLOAT4_DIGITS + extra_float_digits)
420            }
421            Value::Float8(f) if extra_float_digits > 0 => strconv::format_float64(buf, *f),
422            Value::Float8(f) => format_float_limited(buf, *f, FLOAT8_DIGITS + extra_float_digits),
423            Value::Jsonb(js) => strconv::format_jsonb(buf, js.0.as_ref()),
424            Value::List(elems) => strconv::format_list(buf, elems, |buf, elem| match elem {
425                None => Ok::<_, ()>(buf.write_null()),
426                Some(elem) => Ok(elem.encode_text(buf.nonnull_buffer(), settings)),
427            })
428            .expect("provided closure never fails"),
429            Value::Map(elems) => strconv::format_map(buf, elems, |buf, value| match value {
430                None => Ok::<_, ()>(buf.write_null()),
431                Some(elem) => Ok(elem.encode_text(buf.nonnull_buffer(), settings)),
432            })
433            .expect("provided closure never fails"),
434            Value::Oid(oid) => strconv::format_uint32(buf, *oid),
435            // Mirrors PostgreSQL's `regprocout`.
436            Value::RegProc(oid) => match *oid {
437                0 => strconv::format_string(buf, REGPROC_NULL),
438                oid => match regproc::name(oid) {
439                    Some(name) => strconv::format_string(buf, name),
440                    None => strconv::format_uint32(buf, oid),
441                },
442            },
443            Value::Record(elems) => strconv::format_record(buf, elems, |buf, elem| match elem {
444                None => Ok::<_, ()>(buf.write_null()),
445                Some(elem) => Ok(elem.encode_text(buf.nonnull_buffer(), settings)),
446            })
447            .expect("provided closure never fails"),
448            Value::Text(s) | Value::VarChar(s) | Value::BpChar(s) | Value::Name(s) => {
449                strconv::format_string(buf, s)
450            }
451            Value::Time(t) => strconv::format_time(buf, *t),
452            Value::Timestamp(ts) => strconv::format_timestamp(buf, ts),
453            Value::TimestampTz(ts) => strconv::format_timestamptz(buf, ts),
454            Value::Uuid(u) => strconv::format_uuid(buf, *u),
455            Value::Numeric(d) => strconv::format_numeric(buf, &d.0),
456            Value::MzTimestamp(t) => strconv::format_mz_timestamp(buf, *t),
457            Value::Range(range) => strconv::format_range(buf, range, |buf, elem| match elem {
458                Some(elem) => Ok(elem.encode_text(buf.nonnull_buffer(), settings)),
459                None => Ok::<_, ()>(buf.write_null()),
460            })
461            .expect("provided closure never fails"),
462            Value::MzAclItem(mz_acl_item) => strconv::format_mz_acl_item(buf, *mz_acl_item),
463            Value::AclItem(acl_item) => strconv::format_acl_item(buf, *acl_item),
464        }
465    }
466
467    /// Serializes this value to `buf` using the [binary encoding
468    /// format](Format::Binary).
469    pub fn encode_binary(&self, ty: &Type, buf: &mut BytesMut) -> Result<(), io::Error> {
470        // NOTE: If implementing binary encoding for a previously unsupported `Value` type,
471        // please update the `binary_encoding_error` method below.
472        let is_null = match self {
473            Value::Array { dims, elements } => {
474                let ndims = pg_len("number of array dimensions", dims.len())?;
475                let has_null = elements.iter().any(|e| e.is_none());
476                let elem_type = match ty {
477                    Type::Array(elem_type) => elem_type,
478                    _ => unreachable!(),
479                };
480                buf.put_i32(ndims);
481                buf.put_i32(has_null.into());
482                buf.put_u32(elem_type.oid());
483                for dim in dims {
484                    buf.put_i32(pg_len("array dimension length", dim.length)?);
485                    buf.put_i32(dim.lower_bound.try_into().map_err(|_| {
486                        io::Error::new(
487                            io::ErrorKind::InvalidData,
488                            "array dimension lower bound does not fit into an i32",
489                        )
490                    })?);
491                }
492                for elem in elements {
493                    encode_element(buf, elem.as_ref(), elem_type)?;
494                }
495                Ok(postgres_types::IsNull::No)
496            }
497            Value::Int2Vector { elements } => {
498                // this should always be `false`, but there are exceptions in postgres
499                // feels better to compute this than to assert otherwise
500                let has_null = elements.iter().any(|e| e.is_none());
501                buf.put_i32(1);
502                buf.put_i32(has_null.into());
503                buf.put_u32(TYPE_INT2_OID);
504                buf.put_i32(pg_len("int2vector dimension length", elements.len())?);
505                buf.put_i32(0);
506                for elem in elements {
507                    encode_element(buf, elem.as_ref(), &Type::Int2)?;
508                }
509                Ok(postgres_types::IsNull::No)
510            }
511            Value::Bool(b) => b.to_sql(&PgType::BOOL, buf),
512            Value::Bytea(b) => b.to_sql(&PgType::BYTEA, buf),
513            Value::Char(c) => i8::reinterpret_cast(*c).to_sql(&PgType::CHAR, buf),
514            Value::Date(d) => d.pg_epoch_days().to_sql(&PgType::DATE, buf),
515            Value::Float4(f) => f.to_sql(&PgType::FLOAT4, buf),
516            Value::Float8(f) => f.to_sql(&PgType::FLOAT8, buf),
517            Value::Int2(i) => i.to_sql(&PgType::INT2, buf),
518            Value::Int4(i) => i.to_sql(&PgType::INT4, buf),
519            Value::Int8(i) => i.to_sql(&PgType::INT8, buf),
520            Value::UInt2(u) => u.to_sql(&*UINT2, buf),
521            Value::UInt4(u) => u.to_sql(&*UINT4, buf),
522            Value::UInt8(u) => u.to_sql(&*UINT8, buf),
523            Value::Interval(iv) => iv.to_sql(&PgType::INTERVAL, buf),
524            Value::Jsonb(js) => js.to_sql(&PgType::JSONB, buf),
525            Value::List(_) => {
526                // A binary encoding for list is tricky. We only get one OID to
527                // describe the type of this list to the client. And we can't
528                // just up front allocate an OID for every possible list type,
529                // like PostgreSQL does for arrays, because, unlike arrays,
530                // lists can be arbitrarily nested.
531                //
532                // So, we'd need to synthesize a type with a stable OID whenever
533                // a new anonymous list type is *observed* in Materialize. Or we
534                // could mandate that only named list types can be sent over
535                // pgwire, and not anonymous list types, since named list types
536                // get a stable OID when they're created. Then we'd need to
537                // expose a table with the list OID -> element OID mapping for
538                // clients to query. And THEN we'd need to teach every client we
539                // care about how to query this table.
540                //
541                // This isn't intractible. It's how PostgreSQL's range type
542                // works, which is supported by many drivers. But our job is
543                // harder because most PostgreSQL drivers don't want to carry
544                // around code for Materialize-specific types. So we'd have to
545                // add type plugin infrastructure for those drivers, then
546                // distribute the list/map support as a plugin.
547                //
548                // Serializing the actual list would be simple, though: just a
549                // 32-bit integer describing the list length, followed by the
550                // encoding of each element in order.
551                //
552                // tl;dr it's a lot of work. For now, the recommended workaround
553                // is to either use the text encoding or convert the list to a
554                // different type (JSON, an array, unnest into rows) that does
555                // have a binary encoding.
556                Err("binary encoding of list types is not implemented".into())
557            }
558            Value::Map(_) => {
559                // Map binary encodings are hard for the same reason as list
560                // binary encodings (described above). You just have key and
561                // value OIDs to deal with rather than an element OID.
562                Err("binary encoding of map types is not implemented".into())
563            }
564            Value::Name(s) => s.to_sql(&PgType::NAME, buf),
565            // PostgreSQL's `regprocsend` is `int4send`, so binary is identical to
566            // an `oid`.
567            Value::Oid(i) | Value::RegProc(i) => i.to_sql(&PgType::OID, buf),
568            Value::Record(fields) => {
569                let nfields = pg_len("record field length", fields.len())?;
570                buf.put_i32(nfields);
571                let field_types = match ty {
572                    Type::Record(fields) => fields,
573                    _ => unreachable!(),
574                };
575                for (f, ty) in fields.iter().zip_eq(field_types) {
576                    buf.put_u32(ty.oid());
577                    encode_element(buf, f.as_ref(), ty)?;
578                }
579                Ok(postgres_types::IsNull::No)
580            }
581            Value::Text(s) => s.to_sql(&PgType::TEXT, buf),
582            Value::BpChar(s) => s.to_sql(&PgType::BPCHAR, buf),
583            Value::VarChar(s) => s.to_sql(&PgType::VARCHAR, buf),
584            Value::Time(t) => t.to_sql(&PgType::TIME, buf),
585            Value::Timestamp(ts) => ts.to_sql(&PgType::TIMESTAMP, buf),
586            Value::TimestampTz(ts) => ts.to_sql(&PgType::TIMESTAMPTZ, buf),
587            Value::Uuid(u) => u.to_sql(&PgType::UUID, buf),
588            Value::Numeric(a) => a.to_sql(&PgType::NUMERIC, buf),
589            Value::MzTimestamp(t) => t.to_string().to_sql(&PgType::TEXT, buf),
590            Value::Range(range) => {
591                buf.put_u8(range.pg_flag_bits());
592
593                let elem_type = match ty {
594                    Type::Range { element_type } => element_type,
595                    _ => unreachable!(),
596                };
597
598                if let Some(RangeInner { lower, upper }) = &range.inner {
599                    for bound in [&lower.bound, &upper.bound] {
600                        if let Some(bound) = bound {
601                            let base = buf.len();
602                            buf.put_i32(0);
603                            bound.encode_binary(elem_type, buf)?;
604                            let len = pg_len("encoded range bound", buf.len() - base - 4)?;
605                            buf[base..base + 4].copy_from_slice(&len.to_be_bytes());
606                        }
607                    }
608                }
609                Ok(postgres_types::IsNull::No)
610            }
611            Value::MzAclItem(mz_acl_item) => {
612                buf.extend_from_slice(&mz_acl_item.encode_binary());
613                Ok(postgres_types::IsNull::No)
614            }
615            Value::AclItem(_) => Err("aclitem has no binary encoding".into()),
616        }
617        .expect("encode_binary should never trigger a to_sql failure");
618        if let IsNull::Yes = is_null {
619            panic!("encode_binary impossibly called on a null value")
620        }
621        Ok(())
622    }
623
624    /// Static helper method to pre-validate that a Datum corresponding to
625    /// the provided `SqlScalarType` can be converted into a `Value` and then
626    /// encoded as binary using `encode_binary` without an error.
627    ///
628    /// Returns `Ok(())` if the type (including all of its nested element/field
629    /// types) supports binary encoding, or `Err(reason)` describing the first
630    /// unsupported type encountered. Container types are checked recursively, so
631    /// e.g. a record or array that contains a `list` is rejected.
632    ///
633    /// The error messages mirror PostgreSQL's `no binary output function
634    /// available for type <t>` so that drivers and users see a familiar
635    /// diagnostic. Callers should report these errors with the SQLSTATE
636    /// PostgreSQL uses for the same condition, `42883` (undefined_function).
637    pub fn binary_encoding_error(typ: &SqlScalarType) -> Result<(), &'static str> {
638        match typ {
639            SqlScalarType::Bool => Ok(()),
640            SqlScalarType::Int16 => Ok(()),
641            SqlScalarType::Int32 => Ok(()),
642            SqlScalarType::Int64 => Ok(()),
643            SqlScalarType::PgLegacyChar => Ok(()),
644            SqlScalarType::UInt16 => Ok(()),
645            SqlScalarType::Oid => Ok(()),
646            SqlScalarType::RegClass => Ok(()),
647            SqlScalarType::RegProc => Ok(()),
648            SqlScalarType::RegType => Ok(()),
649            SqlScalarType::UInt32 => Ok(()),
650            SqlScalarType::UInt64 => Ok(()),
651            SqlScalarType::Float32 => Ok(()),
652            SqlScalarType::Float64 => Ok(()),
653            SqlScalarType::Numeric { .. } => Ok(()),
654            SqlScalarType::MzTimestamp => Ok(()),
655            SqlScalarType::MzAclItem => Ok(()),
656            SqlScalarType::AclItem => Err("no binary output function available for type aclitem"),
657            SqlScalarType::Date => Ok(()),
658            SqlScalarType::Time => Ok(()),
659            SqlScalarType::Timestamp { .. } => Ok(()),
660            SqlScalarType::TimestampTz { .. } => Ok(()),
661            SqlScalarType::Interval => Ok(()),
662            SqlScalarType::Bytes => Ok(()),
663            SqlScalarType::String => Ok(()),
664            SqlScalarType::VarChar { .. } => Ok(()),
665            SqlScalarType::Char { .. } => Ok(()),
666            SqlScalarType::PgLegacyName => Ok(()),
667            SqlScalarType::Jsonb => Ok(()),
668            SqlScalarType::Uuid => Ok(()),
669            SqlScalarType::Array(elem_type) => Self::binary_encoding_error(elem_type),
670            SqlScalarType::Int2Vector => Ok(()),
671            SqlScalarType::List { .. } => Err("no binary output function available for type list"),
672            SqlScalarType::Map { .. } => Err("no binary output function available for type map"),
673            SqlScalarType::Record { fields, .. } => fields
674                .iter()
675                .try_for_each(|(_, ty)| Self::binary_encoding_error(&ty.scalar_type)),
676            SqlScalarType::Range { element_type } => Self::binary_encoding_error(element_type),
677        }
678    }
679
680    /// Returns whether a value of the given `SqlScalarType` can be encoded using
681    /// the binary format. See [`Value::binary_encoding_error`] for details,
682    /// including the (recursive) handling of container types.
683    pub fn can_encode_binary(typ: &SqlScalarType) -> bool {
684        Self::binary_encoding_error(typ).is_ok()
685    }
686
687    /// Deserializes a value of type `ty` from `raw` using the specified
688    /// `format`.
689    pub fn decode(
690        format: Format,
691        ty: &Type,
692        raw: &[u8],
693    ) -> Result<Value, Box<dyn Error + Sync + Send>> {
694        match format {
695            Format::Text => Value::decode_text(ty, raw),
696            Format::Binary => Value::decode_binary(ty, raw),
697        }
698    }
699
700    /// Deserializes a value of type `ty` from `raw` using the [text encoding
701    /// format](Format::Text).
702    pub fn decode_text<'a>(
703        ty: &'a Type,
704        raw: &'a [u8],
705    ) -> Result<Value, Box<dyn Error + Sync + Send>> {
706        let s = str::from_utf8(raw)?;
707        // Match PostgreSQL, which rejects NUL bytes in text-format values of
708        // any type as part of client encoding verification.
709        reject_nul(s)?;
710        Ok(match ty {
711            Type::Array(elem_type) => {
712                let (elements, dims) = strconv::parse_array(
713                    s,
714                    || None,
715                    |elem_text| Value::decode_text(elem_type, elem_text.as_bytes()).map(Some),
716                )?;
717                Value::Array { dims, elements }
718            }
719            Type::Int2Vector { .. } => {
720                return Err("input of Int2Vector types is not implemented".into());
721            }
722            Type::Bool => Value::Bool(strconv::parse_bool(s)?),
723            Type::Bytea => Value::Bytea(strconv::parse_bytes(s)?),
724            Type::Char => Value::Char(raw.get(0).copied().unwrap_or(0)),
725            Type::Date => Value::Date(strconv::parse_date(s)?),
726            Type::Float4 => Value::Float4(strconv::parse_float32(s)?),
727            Type::Float8 => Value::Float8(strconv::parse_float64(s)?),
728            Type::Int2 => Value::Int2(strconv::parse_int16(s)?),
729            Type::Int4 => Value::Int4(strconv::parse_int32(s)?),
730            Type::Int8 => Value::Int8(strconv::parse_int64(s)?),
731            Type::UInt2 => Value::UInt2(UInt2(strconv::parse_uint16(s)?)),
732            Type::UInt4 => Value::UInt4(UInt4(strconv::parse_uint32(s)?)),
733            Type::UInt8 => Value::UInt8(UInt8(strconv::parse_uint64(s)?)),
734            Type::Interval { .. } => Value::Interval(Interval(strconv::parse_interval(s)?)),
735            Type::Json => return Err("input of json types is not implemented".into()),
736            Type::Jsonb => Value::Jsonb(Jsonb(strconv::parse_jsonb(s)?)),
737            Type::List(elem_type) => Value::List(strconv::parse_list(
738                s,
739                matches!(**elem_type, Type::List(..)),
740                || None,
741                |elem_text| Value::decode_text(elem_type, elem_text.as_bytes()).map(Some),
742            )?),
743            Type::Map { value_type } => Value::Map(strconv::parse_map(
744                s,
745                matches!(**value_type, Type::Map { .. }),
746                |elem_text| {
747                    elem_text
748                        .map(|t| Value::decode_text(value_type, t.as_bytes()))
749                        .transpose()
750                },
751            )?),
752            Type::Name => Value::Name(strconv::parse_pg_legacy_name(s)),
753            Type::Numeric { constraints } => Value::Numeric(Numeric(rescale_numeric(
754                strconv::parse_numeric(s)?,
755                constraints.as_ref(),
756            )?)),
757            Type::Oid | Type::RegClass | Type::RegType => Value::Oid(strconv::parse_oid(s)?),
758            Type::RegProc => Value::RegProc(parse_regproc(s)?),
759            Type::Record(_) => {
760                return Err("input of anonymous composite types is not implemented".into());
761            }
762            Type::Text => Value::Text(s.to_owned()),
763            Type::BpChar { .. } => Value::BpChar(s.to_owned()),
764            Type::VarChar { .. } => Value::VarChar(s.to_owned()),
765            Type::Time { .. } => Value::Time(strconv::parse_time(s)?),
766            Type::TimeTz { .. } => return Err("input of timetz types is not implemented".into()),
767            Type::Timestamp { .. } => Value::Timestamp(strconv::parse_timestamp(s)?),
768            Type::TimestampTz { .. } => Value::TimestampTz(strconv::parse_timestamptz(s)?),
769            Type::Uuid => Value::Uuid(strconv::parse_uuid(s)?),
770            Type::MzTimestamp => Value::MzTimestamp(strconv::parse_mz_timestamp(s)?),
771            Type::Range { element_type } => Value::Range(strconv::parse_range(s, |elem_text| {
772                Value::decode_text(element_type, elem_text.as_bytes()).map(Box::new)
773            })?),
774            Type::MzAclItem => Value::MzAclItem(strconv::parse_mz_acl_item(s)?),
775            Type::AclItem => Value::AclItem(strconv::parse_acl_item(s)?),
776        })
777    }
778
779    /// Deserializes a value of type `ty` from `s` using the [text encoding format](Format::Text).
780    pub fn decode_text_into_row<'a>(
781        ty: &'a Type,
782        s: &'a str,
783        packer: &mut RowPacker,
784    ) -> Result<(), Box<dyn Error + Sync + Send>> {
785        // Match PostgreSQL, which rejects NUL bytes in text-format values of
786        // any type as part of client encoding verification.
787        reject_nul(s)?;
788        Ok(match ty {
789            Type::Array(elem_type) => {
790                let (elements, dims) =
791                    strconv::parse_array(s, || None, |elem_text| Ok::<_, String>(Some(elem_text)))?;
792                // SAFETY: The function returns the number of times it called `push` on the packer.
793                unsafe {
794                    packer.push_array_with_unchecked(&dims, |packer| {
795                        let mut nelements = 0;
796                        for element in elements {
797                            match element {
798                                Some(elem_text) => {
799                                    Value::decode_text_into_row(elem_type, &elem_text, packer)?
800                                }
801
802                                None => packer.push(Datum::Null),
803                            }
804                            nelements += 1;
805                        }
806                        Ok::<_, Box<dyn Error + Sync + Send>>(nelements)
807                    })?
808                }
809            }
810            Type::Int2Vector { .. } => {
811                return Err("input of Int2Vector types is not implemented".into());
812            }
813            Type::Bool => packer.push(Datum::from(strconv::parse_bool(s)?)),
814            Type::Bytea => packer.push(Datum::Bytes(&strconv::parse_bytes(s)?)),
815            Type::Char => packer.push(Datum::UInt8(s.as_bytes().get(0).copied().unwrap_or(0))),
816            Type::Date => packer.push(Datum::Date(strconv::parse_date(s)?)),
817            Type::Float4 => packer.push(Datum::Float32(strconv::parse_float32(s)?.into())),
818            Type::Float8 => packer.push(Datum::Float64(strconv::parse_float64(s)?.into())),
819            Type::Int2 => packer.push(Datum::Int16(strconv::parse_int16(s)?)),
820            Type::Int4 => packer.push(Datum::Int32(strconv::parse_int32(s)?)),
821            Type::Int8 => packer.push(Datum::Int64(strconv::parse_int64(s)?)),
822            Type::UInt2 => packer.push(Datum::UInt16(strconv::parse_uint16(s)?)),
823            Type::UInt4 => packer.push(Datum::UInt32(strconv::parse_uint32(s)?)),
824            Type::UInt8 => packer.push(Datum::UInt64(strconv::parse_uint64(s)?)),
825            Type::Interval { .. } => packer.push(Datum::Interval(strconv::parse_interval(s)?)),
826            Type::Json => return Err("input of json types is not implemented".into()),
827            Type::Jsonb => packer.push(strconv::parse_jsonb(s)?.into_row().unpack_first()),
828            Type::List(elem_type) => {
829                let elems = strconv::parse_list(
830                    s,
831                    matches!(**elem_type, Type::List(..)),
832                    || None,
833                    |elem_text| Ok::<_, String>(Some(elem_text)),
834                )?;
835                packer.push_list_with(|packer| {
836                    for elem in elems {
837                        match elem {
838                            Some(elem) => Value::decode_text_into_row(elem_type, &elem, packer)?,
839                            None => packer.push(Datum::Null),
840                        }
841                    }
842                    Ok::<_, Box<dyn Error + Sync + Send>>(())
843                })?;
844            }
845            Type::Map { value_type } => {
846                let map =
847                    strconv::parse_map(s, matches!(**value_type, Type::Map { .. }), |elem_text| {
848                        elem_text.map(Ok::<_, String>).transpose()
849                    })?;
850                packer.push_dict_with(|row| {
851                    for (k, v) in map {
852                        row.push(Datum::String(&k));
853                        match v {
854                            Some(elem) => Value::decode_text_into_row(value_type, &elem, row)?,
855                            None => row.push(Datum::Null),
856                        }
857                    }
858                    Ok::<_, Box<dyn Error + Sync + Send>>(())
859                })?;
860            }
861            Type::Name => packer.push(Datum::String(&strconv::parse_pg_legacy_name(s))),
862            Type::Numeric { constraints } => packer.push(Datum::Numeric(rescale_numeric(
863                strconv::parse_numeric(s)?,
864                constraints.as_ref(),
865            )?)),
866            Type::Oid | Type::RegClass | Type::RegType => {
867                packer.push(Datum::UInt32(strconv::parse_oid(s)?))
868            }
869            Type::RegProc => packer.push(Datum::UInt32(parse_regproc(s)?)),
870            Type::Record(_) => {
871                return Err("input of anonymous composite types is not implemented".into());
872            }
873            Type::Text => packer.push(Datum::String(s)),
874            Type::BpChar { .. } => packer.push(Datum::String(s.trim_end())),
875            Type::VarChar { .. } => packer.push(Datum::String(s)),
876            Type::Time { .. } => packer.push(Datum::Time(strconv::parse_time(s)?)),
877            Type::TimeTz { .. } => return Err("input of timetz types is not implemented".into()),
878            Type::Timestamp { .. } => packer.push(Datum::Timestamp(strconv::parse_timestamp(s)?)),
879            Type::TimestampTz { .. } => {
880                packer.push(Datum::TimestampTz(strconv::parse_timestamptz(s)?))
881            }
882            Type::Uuid => packer.push(Datum::Uuid(strconv::parse_uuid(s)?)),
883            Type::MzTimestamp => packer.push(Datum::MzTimestamp(strconv::parse_mz_timestamp(s)?)),
884            Type::Range { element_type } => {
885                let range = strconv::parse_range(s, |elem_text| {
886                    Value::decode_text(element_type, elem_text.as_bytes()).map(Box::new)
887                })?;
888                // TODO: We should be able to push ranges without scratch space, but that requires
889                // a different `push_range` API.
890                let buf = RowArena::new();
891                let range = range
892                    .try_into_bounds(|elem| elem.into_datum(&buf, element_type))
893                    .map_err(Box::<dyn Error + Sync + Send>::from)?;
894                packer
895                    .push_range(range)
896                    .map_err(Box::<dyn Error + Sync + Send>::from)?;
897            }
898            Type::MzAclItem => packer.push(Datum::MzAclItem(strconv::parse_mz_acl_item(s)?)),
899            Type::AclItem => packer.push(Datum::AclItem(strconv::parse_acl_item(s)?)),
900        })
901    }
902
903    /// Deserializes a value of type `ty` from `raw` using the [binary encoding
904    /// format](Format::Binary).
905    pub fn decode_binary(ty: &Type, raw: &[u8]) -> Result<Value, Box<dyn Error + Sync + Send>> {
906        match ty {
907            Type::Array(_) => Err("input of array types is not implemented".into()),
908            Type::Int2Vector => Err("input of int2vector types is not implemented".into()),
909            Type::Bool => bool::from_sql(ty.inner(), raw).map(Value::Bool),
910            Type::Bytea => Vec::<u8>::from_sql(ty.inner(), raw).map(Value::Bytea),
911            Type::Char => {
912                i8::from_sql(ty.inner(), raw).map(|c| Value::Char(u8::reinterpret_cast(c)))
913            }
914            Type::Date => {
915                let days = i32::from_sql(ty.inner(), raw)?;
916                Ok(Value::Date(Date::from_pg_epoch(days)?))
917            }
918            Type::Float4 => f32::from_sql(ty.inner(), raw).map(Value::Float4),
919            Type::Float8 => f64::from_sql(ty.inner(), raw).map(Value::Float8),
920            Type::Int2 => i16::from_sql(ty.inner(), raw).map(Value::Int2),
921            Type::Int4 => i32::from_sql(ty.inner(), raw).map(Value::Int4),
922            Type::Int8 => i64::from_sql(ty.inner(), raw).map(Value::Int8),
923            Type::UInt2 => UInt2::from_sql(ty.inner(), raw).map(Value::UInt2),
924            Type::UInt4 => UInt4::from_sql(ty.inner(), raw).map(Value::UInt4),
925            Type::UInt8 => UInt8::from_sql(ty.inner(), raw).map(Value::UInt8),
926            Type::Interval { .. } => Interval::from_sql(ty.inner(), raw).map(Value::Interval),
927            Type::Json => Err("input of json types is not implemented".into()),
928            Type::Jsonb => Jsonb::from_sql(ty.inner(), raw).map(Value::Jsonb),
929            Type::List(_) => Err("binary decoding of list types is not implemented".into()),
930            Type::Map { .. } => Err("binary decoding of map types is not implemented".into()),
931            Type::Name => {
932                let s = String::from_sql(ty.inner(), raw)?;
933                reject_nul(&s)?;
934                if s.len() > NAME_MAX_BYTES {
935                    return Err("identifier too long".into());
936                }
937                Ok(Value::Name(s))
938            }
939            Type::Numeric { constraints } => {
940                let n = Numeric::from_sql(ty.inner(), raw)?;
941                // The wire format's `0xD000`/`0xF000` sign words spell
942                // `±Infinity`, which `Numeric::from_sql` decodes because it also
943                // decodes query *results*, where an infinite numeric is
944                // legitimate (aggregation overflow produces one). As a parameter
945                // it must be rejected: the text path rejects `'Infinity'::numeric`
946                // (`strconv::parse_numeric`), so accepting the binary spelling
947                // would let a client smuggle in a value no SQL literal can name.
948                if n.0.0.is_infinite() {
949                    return Err("numeric infinity is not supported".into());
950                }
951                Ok(Value::Numeric(Numeric(rescale_numeric(
952                    n.0,
953                    constraints.as_ref(),
954                )?)))
955            }
956            Type::Oid | Type::RegClass | Type::RegType => {
957                u32::from_sql(ty.inner(), raw).map(Value::Oid)
958            }
959            Type::RegProc => u32::from_sql(ty.inner(), raw).map(Value::RegProc),
960            Type::Record(_) => Err("input of anonymous composite types is not implemented".into()),
961            Type::Text => decode_binary_string(ty, raw).map(Value::Text),
962            Type::BpChar { .. } => decode_binary_string(ty, raw).map(Value::BpChar),
963            Type::VarChar { .. } => decode_binary_string(ty, raw).map(Value::VarChar),
964            Type::Time { .. } => {
965                // The wire value is microseconds since midnight. Do not use
966                // `NaiveTime::from_sql`. Its duration arithmetic silently
967                // wraps around midnight, turning out-of-range values like
968                // 24:00:00 into 00:00:00. Reject them instead, including
969                // 24:00:00 itself, which `NaiveTime` cannot represent and
970                // the text path rejects too.
971                const USECS_PER_DAY: i64 = 24 * 60 * 60 * 1_000_000;
972                let usecs = i64::from_sql(ty.inner(), raw)?;
973                if !(0..USECS_PER_DAY).contains(&usecs) {
974                    return Err("time out of range".into());
975                }
976                let secs = u32::try_from(usecs / 1_000_000).expect("less than 86,400");
977                let nanos = u32::try_from(usecs % 1_000_000).expect("less than 1,000,000") * 1_000;
978                Ok(Value::Time(
979                    NaiveTime::from_num_seconds_from_midnight_opt(secs, nanos)
980                        .expect("validated against USECS_PER_DAY"),
981                ))
982            }
983            Type::TimeTz { .. } => Err("input of timetz types is not implemented".into()),
984            Type::Timestamp { .. } => {
985                let ts = NaiveDateTime::from_sql(ty.inner(), raw)?;
986                Ok(Value::Timestamp(CheckedTimestamp::from_timestamplike(ts)?))
987            }
988            Type::TimestampTz { .. } => {
989                let ts = DateTime::<Utc>::from_sql(ty.inner(), raw)?;
990                Ok(Value::TimestampTz(CheckedTimestamp::from_timestamplike(
991                    ts,
992                )?))
993            }
994            Type::Uuid => Uuid::from_sql(ty.inner(), raw).map(Value::Uuid),
995            Type::MzTimestamp => {
996                let s = String::from_sql(ty.inner(), raw)?;
997                let t: mz_repr::Timestamp = s.parse()?;
998                Ok(Value::MzTimestamp(t))
999            }
1000            Type::Range { .. } => Err("binary decoding of range types is not implemented".into()),
1001            Type::MzAclItem => {
1002                let mz_acl_item = MzAclItem::decode_binary(raw)?;
1003                Ok(Value::MzAclItem(mz_acl_item))
1004            }
1005            Type::AclItem => Err("aclitem has no binary encoding".into()),
1006        }
1007    }
1008}
1009
1010/// The text PostgreSQL's `regprocout` emits for OID 0, and that `regprocin`
1011/// reads back as OID 0.
1012const REGPROC_NULL: &str = "-";
1013
1014/// Parses the text format of a `regproc`, matching PostgreSQL's `regprocin`.
1015///
1016/// Accepting names is what keeps a `COPY ... TO` rendering loadable by
1017/// `COPY ... FROM`, since the text encoding emits names. An overloaded name
1018/// renders schema-qualified and names every one of its impls, so it does not
1019/// round trip, which is true of PostgreSQL as well.
1020fn parse_regproc(s: &str) -> Result<u32, Box<dyn Error + Sync + Send>> {
1021    if s == REGPROC_NULL {
1022        return Ok(0);
1023    }
1024    match regproc::oid(s) {
1025        Ok(oid) => Ok(oid),
1026        // PostgreSQL refuses the same input rather than picking an overload.
1027        Err(regproc::NameLookupError::Ambiguous) => {
1028            Err(format!("more than one function named \"{}\"", s).into())
1029        }
1030        Err(regproc::NameLookupError::NotFound) => match strconv::parse_oid(s) {
1031            Ok(oid) => Ok(oid),
1032            // Number-shaped input keeps its numeric diagnosis, which is what
1033            // reports an out of range OID. Anything else was meant as a name, so
1034            // report a missing function, matching PostgreSQL.
1035            Err(err) if is_number_shaped(s) => Err(err.into()),
1036            Err(_) => Err(format!("function \"{}\" does not exist", s).into()),
1037        },
1038    }
1039}
1040
1041/// Whether `s` is shaped like a decimal number, ignoring whether it is in range.
1042fn is_number_shaped(s: &str) -> bool {
1043    let digits = s.trim();
1044    let digits = digits.strip_prefix(['+', '-']).unwrap_or(digits);
1045    !digits.is_empty() && digits.bytes().all(|b| b.is_ascii_digit())
1046}
1047
1048/// Session settings that affect how values are encoded as text.
1049///
1050/// PostgreSQL's text output for some types depends on session state. Encoders
1051/// whose output must not depend on the session, such as everything evaluated
1052/// in the dataflow layer, use [`TextEncodeSettings::STABLE`].
1053#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1054pub struct TextEncodeSettings {
1055    /// PostgreSQL's `extra_float_digits`: positive values select the shortest
1056    /// round-trippable encoding for `float4` and `float8`, while zero and
1057    /// negative values limit output to `FLOAT4_DIGITS` or `FLOAT8_DIGITS` plus
1058    /// this value significant digits.
1059    pub extra_float_digits: i32,
1060}
1061
1062impl TextEncodeSettings {
1063    /// Settings that do not depend on session state.
1064    pub const STABLE: TextEncodeSettings = TextEncodeSettings {
1065        extra_float_digits: 1,
1066    };
1067}
1068
1069/// The number of significant decimal digits that survive a round trip through
1070/// `f32` and `f64` respectively.
1071const FLOAT4_DIGITS: i32 = 6;
1072const FLOAT8_DIGITS: i32 = 15;
1073
1074/// Formats `f` with `ndig` significant digits like C's `%.*g`, mirroring
1075/// PostgreSQL's `float4out`/`float8out` when `extra_float_digits` is zero or
1076/// negative. Like PostgreSQL, `ndig` values below 1 are clamped to 1, and
1077/// non-finite values are spelled `NaN`, `Infinity`, and `-Infinity`.
1078fn format_float_limited(buf: &mut BytesMut, f: f64, ndig: i32) -> Nestable {
1079    if f.is_nan() {
1080        buf.write_str("NaN");
1081        return Nestable::Yes;
1082    }
1083    if f.is_infinite() {
1084        buf.write_str(if f < 0.0 { "-Infinity" } else { "Infinity" });
1085        return Nestable::Yes;
1086    }
1087    let ndig = ndig.max(1);
1088    // The exponent decides between the two notations, and it must be taken
1089    // after rounding to `ndig` digits, as rounding can carry into the next
1090    // exponent. `sci` has the shape `d[.ddd]e<exp>`.
1091    let prec = usize::try_from(ndig - 1).expect("ndig is at least 1");
1092    let sci = format!("{:.prec$e}", f);
1093    let (mantissa, exp) = sci.split_once('e').expect("e format has an exponent");
1094    let exp: i32 = exp.parse().expect("valid exponent");
1095    if exp < -4 || exp >= ndig {
1096        // Scientific notation. `%g` strips the fraction's trailing zeros and
1097        // pads the exponent to at least two digits.
1098        let mantissa = mantissa.trim_end_matches('0').trim_end_matches('.');
1099        write!(buf, "{mantissa}e{exp:+03}");
1100    } else {
1101        // Fixed-point notation. Rounding to `ndig - 1 - exp` decimal places
1102        // leaves exactly `ndig` significant digits. Trailing zeros are
1103        // stripped.
1104        let decimals = usize::try_from(ndig - 1 - exp).expect("exp is less than ndig");
1105        let fixed = format!("{f:.decimals$}");
1106        let fixed = match fixed.contains('.') {
1107            true => fixed.trim_end_matches('0').trim_end_matches('.'),
1108            false => &fixed,
1109        };
1110        buf.write_str(fixed);
1111    }
1112    Nestable::Yes
1113}
1114
1115/// Returns an error if `s` contains a NUL character, which PostgreSQL rejects
1116/// in text values.
1117fn reject_nul(s: &str) -> Result<(), Box<dyn Error + Sync + Send>> {
1118    if s.contains('\0') {
1119        Err(Box::new(NulCharacterError))
1120    } else {
1121        Ok(())
1122    }
1123}
1124
1125/// Decodes a binary-format string value, rejecting embedded NUL characters.
1126fn decode_binary_string(ty: &Type, raw: &[u8]) -> Result<String, Box<dyn Error + Sync + Send>> {
1127    let s = String::from_sql(ty.inner(), raw)?;
1128    reject_nul(&s)?;
1129    Ok(s)
1130}
1131
1132/// Rescales `n` to the scale required by `constraints`, if any.
1133fn rescale_numeric(
1134    mut n: OrderedDecimal<mz_repr_numeric::Numeric>,
1135    constraints: Option<&NumericConstraints>,
1136) -> Result<OrderedDecimal<mz_repr_numeric::Numeric>, Box<dyn Error + Sync + Send>> {
1137    if let Some(constraints) = constraints {
1138        rescale(
1139            &mut n.0,
1140            NumericMaxScale::try_from(i64::from(constraints.max_scale()))?.into_u8(),
1141        )?;
1142    }
1143    Ok(n)
1144}
1145
1146fn encode_element(buf: &mut BytesMut, elem: Option<&Value>, ty: &Type) -> Result<(), io::Error> {
1147    match elem {
1148        None => buf.put_i32(-1),
1149        Some(elem) => {
1150            let base = buf.len();
1151            buf.put_i32(0);
1152            elem.encode_binary(ty, buf)?;
1153            let len = pg_len("encoded element", buf.len() - base - 4)?;
1154            buf[base..base + 4].copy_from_slice(&len.to_be_bytes());
1155        }
1156    }
1157    Ok(())
1158}
1159
1160fn pg_len(what: &str, len: usize) -> Result<i32, io::Error> {
1161    len.try_into().map_err(|_| {
1162        io::Error::new(
1163            io::ErrorKind::InvalidData,
1164            format!("{} does not fit into an i32", what),
1165        )
1166    })
1167}
1168
1169/// Converts a Materialize row into a vector of PostgreSQL values.
1170///
1171/// Calling this function is equivalent to mapping [`Value::from_datum`] over
1172/// every datum in `row`.
1173pub fn values_from_row(row: &RowRef, typ: &SqlRelationType) -> Vec<Option<Value>> {
1174    row.iter()
1175        .zip_eq(typ.column_types.iter())
1176        .map(|(col, typ)| Value::from_datum(col, &typ.scalar_type))
1177        .collect()
1178}
1179
1180#[cfg(test)]
1181mod tests {
1182    use mz_repr::arb_datum_for_scalar;
1183    use proptest::prelude::*;
1184
1185    use super::*;
1186
1187    /// Property test: [`Value::binary_encoding_error`] agrees with the actual
1188    /// behavior of [`Value::encode_binary`] for every `(SqlScalarType, Datum)`
1189    /// pair the proptest infrastructure can generate.
1190    ///
1191    /// This guards against future drift between the static predicate and the
1192    /// encoder: any new `SqlScalarType` variant whose classification disagrees
1193    /// with what `encode_binary` actually does will surface here.
1194    #[mz_ore::test]
1195    #[cfg_attr(miri, ignore)] // numeric/decimal contexts unsupported under miri
1196    fn proptest_binary_encoding_error_matches_encode_binary() {
1197        let strat =
1198            any::<SqlScalarType>().prop_flat_map(|ty| (Just(ty.clone()), arb_datum_for_scalar(ty)));
1199        proptest!(ProptestConfig::with_cases(256), |((ty, prop_datum) in strat)| {
1200            // `binary_encoding_error` is a precondition for callers of
1201            // `encode_binary`: if it returns `Ok`, then encoding must succeed
1202            // (and not panic via the internal `.expect`).
1203            if Value::binary_encoding_error(&ty).is_err() {
1204                return Ok(());
1205            }
1206            let datum = Datum::from(&prop_datum);
1207            let value = match Value::from_datum(datum, &ty) {
1208                Some(v) => v,
1209                // `Datum::Null` produces `None`; nothing to encode.
1210                None => return Ok(()),
1211            };
1212            let pg_ty = Type::from(&ty);
1213            let mut buf = BytesMut::new();
1214            value
1215                .encode_binary(&pg_ty, &mut buf)
1216                .expect("encode_binary must succeed when binary_encoding_error returns Ok");
1217        });
1218    }
1219
1220    /// A `regproc` renders as the function's name in text format and as a bare
1221    /// OID in binary, which is what PostgreSQL's `regprocout` and
1222    /// `regprocsend` do.
1223    #[mz_ore::test]
1224    fn regproc_text_encoding_resolves_names() {
1225        // 1242 is `boolin`, uniquely named. 1398 is one of six `abs` overloads,
1226        // so PostgreSQL qualifies it. 99999 names no function. The qualified
1227        // overload does not round trip, because its rendering names all six.
1228        for (oid, expected, round_trips) in [
1229            (0, "-", true),
1230            (1242, "boolin", true),
1231            (1398, "pg_catalog.abs", false),
1232            (99999, "99999", true),
1233            (u32::MAX, "4294967295", true),
1234        ] {
1235            let mut buf = BytesMut::new();
1236            Value::from_datum(Datum::UInt32(oid), &SqlScalarType::RegProc)
1237                .expect("a non-null datum encodes")
1238                .encode_text(&mut buf, TextEncodeSettings::STABLE);
1239            assert_eq!(str::from_utf8(&buf).unwrap(), expected, "regproc {oid}");
1240
1241            if round_trips {
1242                let Value::RegProc(decoded) = Value::decode_text(&Type::RegProc, &buf).unwrap()
1243                else {
1244                    panic!("decoding a regproc must yield Value::RegProc");
1245                };
1246                assert_eq!(decoded, oid, "text rendering {expected} did not round trip");
1247            }
1248
1249            let mut binary = BytesMut::new();
1250            Value::RegProc(oid)
1251                .encode_binary(&Type::RegProc, &mut binary)
1252                .expect("regproc has a binary encoding");
1253            assert_eq!(&binary[..], &oid.to_be_bytes()[..], "regproc {oid} binary");
1254        }
1255    }
1256
1257    /// An overloaded name is an error rather than an arbitrary choice, which is
1258    /// what PostgreSQL does too.
1259    #[mz_ore::test]
1260    fn regproc_text_decoding_rejects_ambiguous_names() {
1261        assert_eq!(
1262            Value::decode_text(&Type::RegProc, b"pg_catalog.abs")
1263                .map(|_| ())
1264                .map_err(|e| e.to_string())
1265                .unwrap_err(),
1266            "more than one function named \"pg_catalog.abs\"",
1267        );
1268        assert_eq!(
1269            Value::decode_text(&Type::RegProc, b"no_such_function")
1270                .map(|_| ())
1271                .map_err(|e| e.to_string())
1272                .unwrap_err(),
1273            "function \"no_such_function\" does not exist",
1274        );
1275        let out_of_range = Value::decode_text(&Type::RegProc, b"99999999999999")
1276            .map(|_| ())
1277            .map_err(|e| e.to_string())
1278            .unwrap_err();
1279        assert!(
1280            out_of_range.starts_with("invalid input syntax for type oid"),
1281            "unexpected error for an out of range OID: {out_of_range}",
1282        );
1283    }
1284
1285    /// [`format_float_limited`] must match C's `%.*g`, which PostgreSQL's
1286    /// `float4out`/`float8out` use when `extra_float_digits` is zero or
1287    /// negative.
1288    #[mz_ore::test]
1289    fn format_float_limited_matches_printf_g() {
1290        for (f, ndig, expected) in [
1291            (0.1_f64 + 0.2_f64, 15, "0.3"),
1292            (0.1_f64 + 0.2_f64, 1, "0.3"),
1293            (f64::from(123.45679_f32), 6, "123.457"),
1294            (f64::from(123.45679_f32), 3, "123"),
1295            (1e15, 15, "1e+15"),
1296            (-123456.0, 3, "-1.23e+05"),
1297            (999.999, 3, "1e+03"),
1298            (0.0001, 15, "0.0001"),
1299            (-0.00001, 15, "-1e-05"),
1300            (0.0, 15, "0"),
1301            (-0.0, 15, "-0"),
1302            (100.0, 15, "100"),
1303            (1.23456789012345, 12, "1.23456789012"),
1304            (f64::NAN, 15, "NaN"),
1305            (f64::INFINITY, 15, "Infinity"),
1306            (f64::NEG_INFINITY, -100, "-Infinity"),
1307            // `ndig` values below 1 clamp to 1.
1308            (0.1_f64 + 0.2_f64, -5, "0.3"),
1309        ] {
1310            let mut buf = BytesMut::new();
1311            format_float_limited(&mut buf, f, ndig);
1312            assert_eq!(
1313                str::from_utf8(&buf).unwrap(),
1314                expected,
1315                "{f} with {ndig} digits"
1316            );
1317        }
1318    }
1319
1320    /// Verifies that we correctly print the chain of parsing errors, all the way through the stack.
1321    #[mz_ore::test]
1322    fn decode_text_error_smoke_test() {
1323        let bool_array = Value::Array {
1324            dims: vec![ArrayDimension {
1325                lower_bound: 0,
1326                length: 1,
1327            }],
1328            elements: vec![Some(Value::Bool(true))],
1329        };
1330
1331        let mut buf = BytesMut::new();
1332        bool_array.encode_text(&mut buf, TextEncodeSettings::STABLE);
1333        let buf = buf.to_vec();
1334
1335        let int_array_tpe = Type::Array(Box::new(Type::Int4));
1336        let decoded_int_array = Value::decode_text(&int_array_tpe, &buf);
1337
1338        assert_eq!(
1339            decoded_int_array.map_err(|e| e.to_string()).unwrap_err(),
1340            "invalid input syntax for type array: Specifying array lower bounds is not supported: \"[0:0]={t}\"".to_string()
1341        );
1342    }
1343
1344    /// Decoding a numeric must round it to the destination's declared scale,
1345    /// and the text and binary paths must agree. `COPY ... FROM` relies on the
1346    /// text/CSV side of this (SS-193); binary parameters rely on the binary
1347    /// side. `COPY ... FORMAT BINARY` is unsupported, so the binary path is
1348    /// exercised here rather than via mzcompose.
1349    #[mz_ore::test]
1350    #[cfg_attr(miri, ignore)] // numeric/decimal contexts unsupported under miri
1351    fn decode_numeric_applies_destination_scale() {
1352        // A `numeric(10, 2)` destination: scale 2.
1353        let ty = Type::from(&SqlScalarType::Numeric {
1354            max_scale: Some(NumericMaxScale::try_from(2_i64).unwrap()),
1355        });
1356        let expected = strconv::parse_numeric("10.45").unwrap();
1357
1358        // Encode an over-scale value (10.447, scale 3) to binary, then decode
1359        // it back through the scale-2 type.
1360        let input = Value::Numeric(Numeric(strconv::parse_numeric("10.447").unwrap()));
1361        let mut buf = BytesMut::new();
1362        input
1363            .encode_binary(&ty, &mut buf)
1364            .expect("encoding 10.447 as numeric must succeed");
1365        let Value::Numeric(Numeric(binary)) = Value::decode_binary(&ty, &buf).unwrap() else {
1366            panic!("decode_binary of a numeric must yield Value::Numeric");
1367        };
1368        assert_eq!(binary, expected, "binary decode did not rescale to scale 2");
1369
1370        // The text path must agree with the binary path.
1371        let Value::Numeric(Numeric(text)) = Value::decode_text(&ty, b"10.447").unwrap() else {
1372            panic!("decode_text of a numeric must yield Value::Numeric");
1373        };
1374        assert_eq!(text, expected, "text decode did not rescale to scale 2");
1375    }
1376
1377    /// The numeric wire format's `±Infinity` sign words must be rejected as a
1378    /// parameter, matching the text path, which rejects `'Infinity'::numeric`.
1379    #[mz_ore::test]
1380    #[cfg_attr(miri, ignore)] // numeric/decimal contexts unsupported under miri
1381    fn decode_binary_numeric_rejects_infinity() {
1382        let ty = Type::Numeric { constraints: None };
1383        // units = 0, weight = 0, sign, dscale = 0.
1384        let header = |sign: u16| {
1385            let mut b = Vec::new();
1386            b.extend_from_slice(&0i16.to_be_bytes());
1387            b.extend_from_slice(&0i16.to_be_bytes());
1388            b.extend_from_slice(&sign.to_be_bytes());
1389            b.extend_from_slice(&0i16.to_be_bytes());
1390            b
1391        };
1392        // +Infinity and -Infinity.
1393        for sign in [0xD000, 0xF000] {
1394            let res = Value::decode_binary(&ty, &header(sign));
1395            assert_eq!(
1396                res.map(|_| ()).map_err(|e| e.to_string()).unwrap_err(),
1397                "numeric infinity is not supported",
1398                "sign {sign:#x} must be rejected",
1399            );
1400        }
1401        // NaN, which the text path does accept, still decodes.
1402        let Value::Numeric(Numeric(nan)) = Value::decode_binary(&ty, &header(0xC000)).unwrap()
1403        else {
1404            panic!("decode_binary of a numeric must yield Value::Numeric");
1405        };
1406        assert_eq!(nan, strconv::parse_numeric("NaN").unwrap());
1407    }
1408
1409    /// Binary time values are microseconds since midnight. Out-of-range
1410    /// values, including 24:00:00, must error rather than silently wrap
1411    /// around midnight (SQL-473).
1412    #[mz_ore::test]
1413    fn decode_binary_time_rejects_out_of_range() {
1414        const USECS_PER_DAY: i64 = 24 * 60 * 60 * 1_000_000;
1415        let ty = Type::Time { precision: None };
1416
1417        for usecs in [USECS_PER_DAY, USECS_PER_DAY + 1, -1, i64::MIN, i64::MAX] {
1418            let res = Value::decode_binary(&ty, &usecs.to_be_bytes());
1419            assert_eq!(
1420                res.map(|_| ()).map_err(|e| e.to_string()).unwrap_err(),
1421                "time out of range",
1422                "{usecs} microseconds must be rejected",
1423            );
1424        }
1425
1426        let Value::Time(t) = Value::decode_binary(&ty, &(USECS_PER_DAY - 1).to_be_bytes()).unwrap()
1427        else {
1428            panic!("decoding a time value must yield Value::Time");
1429        };
1430        assert_eq!(
1431            t,
1432            NaiveTime::from_hms_micro_opt(23, 59, 59, 999_999).unwrap()
1433        );
1434    }
1435
1436    /// Text values must never contain NUL characters, in either wire format.
1437    #[mz_ore::test]
1438    fn decode_rejects_nul_in_strings() {
1439        const NUL_ERR: &str = "invalid byte sequence for encoding \"UTF8\": 0x00";
1440        let raw = b"foo\x00bar";
1441
1442        for ty in [
1443            Type::Text,
1444            Type::BpChar { length: None },
1445            Type::VarChar { max_length: None },
1446            Type::Name,
1447        ] {
1448            for format in [Format::Text, Format::Binary] {
1449                let res = Value::decode(format, &ty, raw);
1450                assert_eq!(
1451                    res.map(|_| ()).map_err(|e| e.to_string()).unwrap_err(),
1452                    NUL_ERR,
1453                    "{ty:?} in {format:?} format must reject NUL",
1454                );
1455            }
1456        }
1457
1458        // The text format rejects NUL bytes regardless of the target type.
1459        let res = Value::decode_text(&Type::Bytea, b"\\x00\x00");
1460        assert_eq!(
1461            res.map(|_| ()).map_err(|e| e.to_string()).unwrap_err(),
1462            NUL_ERR,
1463        );
1464
1465        // NUL-free values still decode.
1466        let Value::Text(s) = Value::decode(Format::Binary, &Type::Text, b"foobar").unwrap() else {
1467            panic!("decoding a text value must yield Value::Text");
1468        };
1469        assert_eq!(s, "foobar");
1470    }
1471}