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_pgrepr_consts::oid::TYPE_INT2_OID;
20use mz_pgwire_common::Format;
21use mz_repr::adt::array::ArrayDimension;
22use mz_repr::adt::char;
23use mz_repr::adt::date::Date;
24use mz_repr::adt::jsonb::JsonbRef;
25use mz_repr::adt::mz_acl_item::{AclItem, MzAclItem};
26use mz_repr::adt::numeric::{self as mz_repr_numeric, NumericMaxScale, rescale};
27use mz_repr::adt::pg_legacy_name::NAME_MAX_BYTES;
28use mz_repr::adt::range::{Range, RangeInner};
29use mz_repr::adt::timestamp::CheckedTimestamp;
30use mz_repr::strconv::{self, Nestable};
31use mz_repr::{Datum, RowArena, RowPacker, RowRef, SqlRelationType, SqlScalarType};
32use postgres_types::{FromSql, IsNull, ToSql, Type as PgType};
33use uuid::Uuid;
34
35use crate::types::{NumericConstraints, UINT2, UINT4, UINT8};
36use crate::value::error::{IntoDatumError, NulCharacterError};
37use crate::{Interval, Jsonb, Numeric, Type, UInt2, UInt4, UInt8};
38
39pub mod error;
40pub mod interval;
41pub mod jsonb;
42pub mod numeric;
43pub mod record;
44pub mod unsigned;
45
46/// A PostgreSQL datum.
47#[derive(Debug)]
48pub enum Value {
49    /// A variable-length, multi-dimensional array of values.
50    Array {
51        /// The dimensions of the array.
52        dims: Vec<ArrayDimension>,
53        /// The elements of the array.
54        elements: Vec<Option<Value>>,
55    },
56    /// A boolean value.
57    Bool(bool),
58    /// A byte array, i.e., a variable-length binary string.
59    Bytea(Vec<u8>),
60    /// A single-byte character.
61    Char(u8),
62    /// A date.
63    Date(Date),
64    /// A 4-byte floating point number.
65    Float4(f32),
66    /// An 8-byte floating point number.
67    Float8(f64),
68    /// A 2-byte signed integer.
69    Int2(i16),
70    /// A 4-byte signed integer.
71    Int4(i32),
72    /// An 8-byte signed integer.
73    Int8(i64),
74    /// A 2-byte unsigned integer.
75    UInt2(UInt2),
76    /// A 4-byte unsigned integer.
77    UInt4(UInt4),
78    /// An 8-byte unsigned integer.
79    UInt8(UInt8),
80    /// A time interval.
81    Interval(Interval),
82    /// A binary JSON blob.
83    Jsonb(Jsonb),
84    /// A sequence of homogeneous values.
85    List(Vec<Option<Value>>),
86    /// A map of string keys and homogeneous values.
87    Map(BTreeMap<String, Option<Value>>),
88    /// An identifier string of no more than 64 characters in length.
89    Name(String),
90    /// An arbitrary precision number.
91    Numeric(Numeric),
92    /// An object identifier.
93    Oid(u32),
94    /// A sequence of heterogeneous values.
95    Record(Vec<Option<Value>>),
96    /// A time.
97    Time(NaiveTime),
98    /// A date and time, without a timezone.
99    Timestamp(CheckedTimestamp<NaiveDateTime>),
100    /// A date and time, with a timezone.
101    TimestampTz(CheckedTimestamp<DateTime<Utc>>),
102    /// A variable-length string.
103    Text(String),
104    /// A fixed-length string.
105    BpChar(String),
106    /// A variable-length string with an optional limit.
107    VarChar(String),
108    /// A universally unique identifier.
109    Uuid(Uuid),
110    /// A small int vector.
111    Int2Vector {
112        /// The elements of the vector.
113        elements: Vec<Option<Value>>,
114    },
115    /// A Materialize timestamp.
116    MzTimestamp(mz_repr::Timestamp),
117    /// A contiguous range of values along a domain.
118    Range(Range<Box<Value>>),
119    /// A list of privileges granted to a role, that uses [`mz_repr::role_id::RoleId`]s for role
120    /// references.
121    MzAclItem(MzAclItem),
122    /// A list of privileges granted to a user that uses [`mz_repr::adt::system::Oid`]s for role
123    /// references. This type is used primarily for compatibility with PostgreSQL.
124    AclItem(AclItem),
125}
126
127impl Value {
128    /// Constructs a new `Value` from a Materialize datum.
129    ///
130    /// The conversion happens in the obvious manner, except that `Datum::Null`
131    /// is converted to `None` to align with how PostgreSQL handles NULL.
132    pub fn from_datum(datum: Datum, typ: &SqlScalarType) -> Option<Value> {
133        match (datum, typ) {
134            (Datum::Null, _) => None,
135            (Datum::True, SqlScalarType::Bool) => Some(Value::Bool(true)),
136            (Datum::False, SqlScalarType::Bool) => Some(Value::Bool(false)),
137            (Datum::Int16(i), SqlScalarType::Int16) => Some(Value::Int2(i)),
138            (Datum::Int32(i), SqlScalarType::Int32) => Some(Value::Int4(i)),
139            (Datum::Int64(i), SqlScalarType::Int64) => Some(Value::Int8(i)),
140            (Datum::UInt8(c), SqlScalarType::PgLegacyChar) => Some(Value::Char(c)),
141            (Datum::UInt16(u), SqlScalarType::UInt16) => Some(Value::UInt2(UInt2(u))),
142            (Datum::UInt32(oid), SqlScalarType::Oid) => Some(Value::Oid(oid)),
143            (Datum::UInt32(oid), SqlScalarType::RegClass) => Some(Value::Oid(oid)),
144            (Datum::UInt32(oid), SqlScalarType::RegProc) => Some(Value::Oid(oid)),
145            (Datum::UInt32(oid), SqlScalarType::RegType) => Some(Value::Oid(oid)),
146            (Datum::UInt32(u), SqlScalarType::UInt32) => Some(Value::UInt4(UInt4(u))),
147            (Datum::UInt64(u), SqlScalarType::UInt64) => Some(Value::UInt8(UInt8(u))),
148            (Datum::Float32(f), SqlScalarType::Float32) => Some(Value::Float4(*f)),
149            (Datum::Float64(f), SqlScalarType::Float64) => Some(Value::Float8(*f)),
150            (Datum::Numeric(d), SqlScalarType::Numeric { .. }) => Some(Value::Numeric(Numeric(d))),
151            (Datum::MzTimestamp(t), SqlScalarType::MzTimestamp) => Some(Value::MzTimestamp(t)),
152            (Datum::MzAclItem(mai), SqlScalarType::MzAclItem) => Some(Value::MzAclItem(mai)),
153            (Datum::AclItem(ai), SqlScalarType::AclItem) => Some(Value::AclItem(ai)),
154            (Datum::Date(d), SqlScalarType::Date) => Some(Value::Date(d)),
155            (Datum::Time(t), SqlScalarType::Time) => Some(Value::Time(t)),
156            (Datum::Timestamp(ts), SqlScalarType::Timestamp { .. }) => Some(Value::Timestamp(ts)),
157            (Datum::TimestampTz(ts), SqlScalarType::TimestampTz { .. }) => {
158                Some(Value::TimestampTz(ts))
159            }
160            (Datum::Interval(iv), SqlScalarType::Interval) => Some(Value::Interval(Interval(iv))),
161            (Datum::Bytes(b), SqlScalarType::Bytes) => Some(Value::Bytea(b.to_vec())),
162            (Datum::String(s), SqlScalarType::String) => Some(Value::Text(s.to_owned())),
163            (Datum::String(s), SqlScalarType::VarChar { .. }) => Some(Value::VarChar(s.to_owned())),
164            (Datum::String(s), SqlScalarType::Char { length }) => {
165                Some(Value::BpChar(char::format_str_pad(s, *length)))
166            }
167            (Datum::String(s), SqlScalarType::PgLegacyName) => Some(Value::Name(s.into())),
168            (_, SqlScalarType::Jsonb) => {
169                Some(Value::Jsonb(Jsonb(JsonbRef::from_datum(datum).to_owned())))
170            }
171            (Datum::Uuid(u), SqlScalarType::Uuid) => Some(Value::Uuid(u)),
172            (Datum::Array(array), SqlScalarType::Array(elem_type)) => {
173                let dims = array.dims().into_iter().collect();
174                let elements = array
175                    .elements()
176                    .iter()
177                    .map(|elem| Value::from_datum(elem, elem_type))
178                    .collect();
179                Some(Value::Array { dims, elements })
180            }
181            (Datum::Array(array), SqlScalarType::Int2Vector) => {
182                assert!(
183                    array.has_int2vector_dims(),
184                    "int2vector must be 1 dimensional, or empty"
185                );
186                let elements = array
187                    .elements()
188                    .iter()
189                    .map(|elem| Value::from_datum(elem, &SqlScalarType::Int16))
190                    .collect();
191                Some(Value::Int2Vector { elements })
192            }
193            (Datum::List(list), SqlScalarType::List { element_type, .. }) => {
194                let elements = list
195                    .iter()
196                    .map(|elem| Value::from_datum(elem, element_type))
197                    .collect();
198                Some(Value::List(elements))
199            }
200            (Datum::List(record), SqlScalarType::Record { fields, .. }) => {
201                let fields = record
202                    .iter()
203                    .zip_eq(fields)
204                    .map(|(e, (_name, ty))| Value::from_datum(e, &ty.scalar_type))
205                    .collect();
206                Some(Value::Record(fields))
207            }
208            (Datum::Map(dict), SqlScalarType::Map { value_type, .. }) => {
209                let entries = dict
210                    .iter()
211                    .map(|(k, v)| (k.to_owned(), Value::from_datum(v, value_type)))
212                    .collect();
213                Some(Value::Map(entries))
214            }
215            (Datum::Range(range), SqlScalarType::Range { element_type }) => {
216                let value_range = range.into_bounds(|b| {
217                    Box::new(
218                        Value::from_datum(b.datum(), element_type)
219                            .expect("RangeBounds never contain Datum::Null"),
220                    )
221                });
222                Some(Value::Range(value_range))
223            }
224            _ => panic!("can't serialize {}::{:?}", datum, typ),
225        }
226    }
227
228    /// Converts a Materialize datum from this value.
229    pub fn into_datum<'a>(
230        self,
231        buf: &'a RowArena,
232        typ: &Type,
233    ) -> Result<Datum<'a>, IntoDatumError> {
234        Ok(match self {
235            Value::Array { dims, elements } => {
236                let element_pg_type = match typ {
237                    Type::Array(t) => &*t,
238                    _ => panic!("Value::Array should have type Type::Array. Found {:?}", typ),
239                };
240                let elements: Result<Vec<_>, _> = elements
241                    .into_iter()
242                    .map(|element| match element {
243                        Some(element) => element.into_datum(buf, element_pg_type),
244                        None => Ok(Datum::Null),
245                    })
246                    .collect();
247                let elements = elements?;
248                buf.try_make_datum(|packer| {
249                    packer
250                        .try_push_array(&dims, elements)
251                        .map_err(IntoDatumError::from)
252                })?
253            }
254            Value::Int2Vector { .. } => {
255                // This situation is handled gracefully by Value::decode; if we
256                // wind up here it's a programming error.
257                unreachable!("into_datum cannot be called on Value::Int2Vector");
258            }
259            Value::Bool(true) => Datum::True,
260            Value::Bool(false) => Datum::False,
261            Value::Bytea(b) => Datum::Bytes(buf.push_bytes(b)),
262            Value::Char(c) => Datum::UInt8(c),
263            Value::Date(d) => Datum::Date(d),
264            Value::Float4(f) => Datum::Float32(f.into()),
265            Value::Float8(f) => Datum::Float64(f.into()),
266            Value::Int2(i) => Datum::Int16(i),
267            Value::Int4(i) => Datum::Int32(i),
268            Value::Int8(i) => Datum::Int64(i),
269            Value::UInt2(u) => Datum::UInt16(u.0),
270            Value::UInt4(u) => Datum::UInt32(u.0),
271            Value::UInt8(u) => Datum::UInt64(u.0),
272            Value::Jsonb(js) => buf.push_unary_row(js.0.into_row()),
273            Value::List(elems) => {
274                let elem_pg_type = match typ {
275                    Type::List(t) => &*t,
276                    _ => panic!("Value::List should have type Type::List. Found {:?}", typ),
277                };
278                let elems: Result<Vec<_>, _> = elems
279                    .into_iter()
280                    .map(|elem| match elem {
281                        Some(elem) => elem.into_datum(buf, elem_pg_type),
282                        None => Ok(Datum::Null),
283                    })
284                    .collect();
285                let elems = elems?;
286                buf.make_datum(|packer| packer.push_list(elems))
287            }
288            Value::Map(map) => {
289                let elem_pg_type = match typ {
290                    Type::Map { value_type } => &*value_type,
291                    _ => panic!("Value::Map should have type Type::Map. Found {:?}", typ),
292                };
293                buf.try_make_datum(|packer| {
294                    packer.try_push_dict_with(|row| {
295                        for (k, v) in map {
296                            row.push(Datum::String(buf.push_string(k)));
297                            let datum = match v {
298                                Some(elem) => elem.into_datum(buf, elem_pg_type)?,
299                                None => Datum::Null,
300                            };
301                            row.push(datum);
302                        }
303                        Ok::<_, IntoDatumError>(())
304                    })
305                })?
306            }
307            Value::Oid(oid) => Datum::UInt32(oid),
308            Value::Record(_) => {
309                // This situation is handled gracefully by Value::decode; if we
310                // wind up here it's a programming error.
311                unreachable!("into_datum cannot be called on Value::Record");
312            }
313            Value::Time(t) => Datum::Time(t),
314            Value::Timestamp(ts) => Datum::Timestamp(ts),
315            Value::TimestampTz(ts) => Datum::TimestampTz(ts),
316            Value::Interval(iv) => Datum::Interval(iv.0),
317            Value::Text(s) | Value::VarChar(s) | Value::Name(s) => {
318                Datum::String(buf.push_string(s))
319            }
320            Value::BpChar(s) => Datum::String(buf.push_string(s.trim_end().into())),
321            Value::Uuid(u) => Datum::Uuid(u),
322            Value::Numeric(n) => Datum::Numeric(n.0),
323            Value::MzTimestamp(t) => Datum::MzTimestamp(t),
324            Value::Range(range) => {
325                let elem_pg_type = match typ {
326                    Type::Range { element_type } => &*element_type,
327                    _ => panic!("Value::Range should have type Type::Range. Found {:?}", typ),
328                };
329                let range = range.try_into_bounds(|elem| elem.into_datum(buf, elem_pg_type))?;
330                buf.try_make_datum(|packer| packer.push_range(range).map_err(IntoDatumError::from))?
331            }
332            Value::MzAclItem(mz_acl_item) => Datum::MzAclItem(mz_acl_item),
333            Value::AclItem(acl_item) => Datum::AclItem(acl_item),
334        })
335    }
336
337    /// Like [`Self::into_datum`] but maps the error to a formatted string for decode/parameter contexts.
338    ///
339    /// Callers can then convert the `String` to their preferred error type (e.g. `io::Error`,
340    /// protocol error). The message is `"unable to decode {context}: {error}"`.
341    pub fn into_datum_decode_error<'a>(
342        self,
343        buf: &'a RowArena,
344        typ: &Type,
345        context: &str,
346    ) -> Result<Datum<'a>, String> {
347        self.into_datum(buf, typ)
348            .map_err(|e| format!("unable to decode {}: {}", context, e))
349    }
350
351    /// Serializes this value to `buf` in the specified `format`.
352    pub fn encode(&self, ty: &Type, format: Format, buf: &mut BytesMut) -> Result<(), io::Error> {
353        match format {
354            Format::Text => {
355                self.encode_text(buf);
356                Ok(())
357            }
358            Format::Binary => self.encode_binary(ty, buf),
359        }
360    }
361
362    /// Serializes this value to `buf` using the [text encoding
363    /// format](Format::Text).
364    pub fn encode_text(&self, buf: &mut BytesMut) -> Nestable {
365        match self {
366            Value::Array { dims, elements } => {
367                strconv::format_array(buf, dims, elements, |buf, elem| match elem {
368                    None => Ok::<_, ()>(buf.write_null()),
369                    Some(elem) => Ok(elem.encode_text(buf.nonnull_buffer())),
370                })
371                .expect("provided closure never fails")
372            }
373            Value::Int2Vector { elements } => {
374                strconv::format_legacy_vector(buf, elements, |buf, elem| {
375                    Ok::<_, ()>(
376                        elem.as_ref()
377                            .expect("Int2Vector does not support NULL values")
378                            .encode_text(buf.nonnull_buffer()),
379                    )
380                })
381                .expect("provided closure never fails")
382            }
383            Value::Bool(b) => strconv::format_bool(buf, *b),
384            Value::Bytea(b) => strconv::format_bytes(buf, b),
385            Value::Char(c) => {
386                buf.put_u8(*c);
387                Nestable::MayNeedEscaping
388            }
389            Value::Date(d) => strconv::format_date(buf, *d),
390            Value::Int2(i) => strconv::format_int16(buf, *i),
391            Value::Int4(i) => strconv::format_int32(buf, *i),
392            Value::Int8(i) => strconv::format_int64(buf, *i),
393            Value::UInt2(u) => strconv::format_uint16(buf, u.0),
394            Value::UInt4(u) => strconv::format_uint32(buf, u.0),
395            Value::UInt8(u) => strconv::format_uint64(buf, u.0),
396            Value::Interval(iv) => strconv::format_interval(buf, iv.0),
397            Value::Float4(f) => strconv::format_float32(buf, *f),
398            Value::Float8(f) => strconv::format_float64(buf, *f),
399            Value::Jsonb(js) => strconv::format_jsonb(buf, js.0.as_ref()),
400            Value::List(elems) => strconv::format_list(buf, elems, |buf, elem| match elem {
401                None => Ok::<_, ()>(buf.write_null()),
402                Some(elem) => Ok(elem.encode_text(buf.nonnull_buffer())),
403            })
404            .expect("provided closure never fails"),
405            Value::Map(elems) => strconv::format_map(buf, elems, |buf, value| match value {
406                None => Ok::<_, ()>(buf.write_null()),
407                Some(elem) => Ok(elem.encode_text(buf.nonnull_buffer())),
408            })
409            .expect("provided closure never fails"),
410            Value::Oid(oid) => strconv::format_uint32(buf, *oid),
411            Value::Record(elems) => strconv::format_record(buf, elems, |buf, elem| match elem {
412                None => Ok::<_, ()>(buf.write_null()),
413                Some(elem) => Ok(elem.encode_text(buf.nonnull_buffer())),
414            })
415            .expect("provided closure never fails"),
416            Value::Text(s) | Value::VarChar(s) | Value::BpChar(s) | Value::Name(s) => {
417                strconv::format_string(buf, s)
418            }
419            Value::Time(t) => strconv::format_time(buf, *t),
420            Value::Timestamp(ts) => strconv::format_timestamp(buf, ts),
421            Value::TimestampTz(ts) => strconv::format_timestamptz(buf, ts),
422            Value::Uuid(u) => strconv::format_uuid(buf, *u),
423            Value::Numeric(d) => strconv::format_numeric(buf, &d.0),
424            Value::MzTimestamp(t) => strconv::format_mz_timestamp(buf, *t),
425            Value::Range(range) => strconv::format_range(buf, range, |buf, elem| match elem {
426                Some(elem) => Ok(elem.encode_text(buf.nonnull_buffer())),
427                None => Ok::<_, ()>(buf.write_null()),
428            })
429            .expect("provided closure never fails"),
430            Value::MzAclItem(mz_acl_item) => strconv::format_mz_acl_item(buf, *mz_acl_item),
431            Value::AclItem(acl_item) => strconv::format_acl_item(buf, *acl_item),
432        }
433    }
434
435    /// Serializes this value to `buf` using the [binary encoding
436    /// format](Format::Binary).
437    pub fn encode_binary(&self, ty: &Type, buf: &mut BytesMut) -> Result<(), io::Error> {
438        // NOTE: If implementing binary encoding for a previously unsupported `Value` type,
439        // please update the `binary_encoding_error` method below.
440        let is_null = match self {
441            Value::Array { dims, elements } => {
442                let ndims = pg_len("number of array dimensions", dims.len())?;
443                let has_null = elements.iter().any(|e| e.is_none());
444                let elem_type = match ty {
445                    Type::Array(elem_type) => elem_type,
446                    _ => unreachable!(),
447                };
448                buf.put_i32(ndims);
449                buf.put_i32(has_null.into());
450                buf.put_u32(elem_type.oid());
451                for dim in dims {
452                    buf.put_i32(pg_len("array dimension length", dim.length)?);
453                    buf.put_i32(dim.lower_bound.try_into().map_err(|_| {
454                        io::Error::new(
455                            io::ErrorKind::InvalidData,
456                            "array dimension lower bound does not fit into an i32",
457                        )
458                    })?);
459                }
460                for elem in elements {
461                    encode_element(buf, elem.as_ref(), elem_type)?;
462                }
463                Ok(postgres_types::IsNull::No)
464            }
465            Value::Int2Vector { elements } => {
466                // this should always be `false`, but there are exceptions in postgres
467                // feels better to compute this than to assert otherwise
468                let has_null = elements.iter().any(|e| e.is_none());
469                buf.put_i32(1);
470                buf.put_i32(has_null.into());
471                buf.put_u32(TYPE_INT2_OID);
472                buf.put_i32(pg_len("int2vector dimension length", elements.len())?);
473                buf.put_i32(0);
474                for elem in elements {
475                    encode_element(buf, elem.as_ref(), &Type::Int2)?;
476                }
477                Ok(postgres_types::IsNull::No)
478            }
479            Value::Bool(b) => b.to_sql(&PgType::BOOL, buf),
480            Value::Bytea(b) => b.to_sql(&PgType::BYTEA, buf),
481            Value::Char(c) => i8::reinterpret_cast(*c).to_sql(&PgType::CHAR, buf),
482            Value::Date(d) => d.pg_epoch_days().to_sql(&PgType::DATE, buf),
483            Value::Float4(f) => f.to_sql(&PgType::FLOAT4, buf),
484            Value::Float8(f) => f.to_sql(&PgType::FLOAT8, buf),
485            Value::Int2(i) => i.to_sql(&PgType::INT2, buf),
486            Value::Int4(i) => i.to_sql(&PgType::INT4, buf),
487            Value::Int8(i) => i.to_sql(&PgType::INT8, buf),
488            Value::UInt2(u) => u.to_sql(&*UINT2, buf),
489            Value::UInt4(u) => u.to_sql(&*UINT4, buf),
490            Value::UInt8(u) => u.to_sql(&*UINT8, buf),
491            Value::Interval(iv) => iv.to_sql(&PgType::INTERVAL, buf),
492            Value::Jsonb(js) => js.to_sql(&PgType::JSONB, buf),
493            Value::List(_) => {
494                // A binary encoding for list is tricky. We only get one OID to
495                // describe the type of this list to the client. And we can't
496                // just up front allocate an OID for every possible list type,
497                // like PostgreSQL does for arrays, because, unlike arrays,
498                // lists can be arbitrarily nested.
499                //
500                // So, we'd need to synthesize a type with a stable OID whenever
501                // a new anonymous list type is *observed* in Materialize. Or we
502                // could mandate that only named list types can be sent over
503                // pgwire, and not anonymous list types, since named list types
504                // get a stable OID when they're created. Then we'd need to
505                // expose a table with the list OID -> element OID mapping for
506                // clients to query. And THEN we'd need to teach every client we
507                // care about how to query this table.
508                //
509                // This isn't intractible. It's how PostgreSQL's range type
510                // works, which is supported by many drivers. But our job is
511                // harder because most PostgreSQL drivers don't want to carry
512                // around code for Materialize-specific types. So we'd have to
513                // add type plugin infrastructure for those drivers, then
514                // distribute the list/map support as a plugin.
515                //
516                // Serializing the actual list would be simple, though: just a
517                // 32-bit integer describing the list length, followed by the
518                // encoding of each element in order.
519                //
520                // tl;dr it's a lot of work. For now, the recommended workaround
521                // is to either use the text encoding or convert the list to a
522                // different type (JSON, an array, unnest into rows) that does
523                // have a binary encoding.
524                Err("binary encoding of list types is not implemented".into())
525            }
526            Value::Map(_) => {
527                // Map binary encodings are hard for the same reason as list
528                // binary encodings (described above). You just have key and
529                // value OIDs to deal with rather than an element OID.
530                Err("binary encoding of map types is not implemented".into())
531            }
532            Value::Name(s) => s.to_sql(&PgType::NAME, buf),
533            Value::Oid(i) => i.to_sql(&PgType::OID, buf),
534            Value::Record(fields) => {
535                let nfields = pg_len("record field length", fields.len())?;
536                buf.put_i32(nfields);
537                let field_types = match ty {
538                    Type::Record(fields) => fields,
539                    _ => unreachable!(),
540                };
541                for (f, ty) in fields.iter().zip_eq(field_types) {
542                    buf.put_u32(ty.oid());
543                    encode_element(buf, f.as_ref(), ty)?;
544                }
545                Ok(postgres_types::IsNull::No)
546            }
547            Value::Text(s) => s.to_sql(&PgType::TEXT, buf),
548            Value::BpChar(s) => s.to_sql(&PgType::BPCHAR, buf),
549            Value::VarChar(s) => s.to_sql(&PgType::VARCHAR, buf),
550            Value::Time(t) => t.to_sql(&PgType::TIME, buf),
551            Value::Timestamp(ts) => ts.to_sql(&PgType::TIMESTAMP, buf),
552            Value::TimestampTz(ts) => ts.to_sql(&PgType::TIMESTAMPTZ, buf),
553            Value::Uuid(u) => u.to_sql(&PgType::UUID, buf),
554            Value::Numeric(a) => a.to_sql(&PgType::NUMERIC, buf),
555            Value::MzTimestamp(t) => t.to_string().to_sql(&PgType::TEXT, buf),
556            Value::Range(range) => {
557                buf.put_u8(range.pg_flag_bits());
558
559                let elem_type = match ty {
560                    Type::Range { element_type } => element_type,
561                    _ => unreachable!(),
562                };
563
564                if let Some(RangeInner { lower, upper }) = &range.inner {
565                    for bound in [&lower.bound, &upper.bound] {
566                        if let Some(bound) = bound {
567                            let base = buf.len();
568                            buf.put_i32(0);
569                            bound.encode_binary(elem_type, buf)?;
570                            let len = pg_len("encoded range bound", buf.len() - base - 4)?;
571                            buf[base..base + 4].copy_from_slice(&len.to_be_bytes());
572                        }
573                    }
574                }
575                Ok(postgres_types::IsNull::No)
576            }
577            Value::MzAclItem(mz_acl_item) => {
578                buf.extend_from_slice(&mz_acl_item.encode_binary());
579                Ok(postgres_types::IsNull::No)
580            }
581            Value::AclItem(_) => Err("aclitem has no binary encoding".into()),
582        }
583        .expect("encode_binary should never trigger a to_sql failure");
584        if let IsNull::Yes = is_null {
585            panic!("encode_binary impossibly called on a null value")
586        }
587        Ok(())
588    }
589
590    /// Static helper method to pre-validate that a Datum corresponding to
591    /// the provided `SqlScalarType` can be converted into a `Value` and then
592    /// encoded as binary using `encode_binary` without an error.
593    ///
594    /// Returns `Ok(())` if the type (including all of its nested element/field
595    /// types) supports binary encoding, or `Err(reason)` describing the first
596    /// unsupported type encountered. Container types are checked recursively, so
597    /// e.g. a record or array that contains a `list` is rejected.
598    ///
599    /// The error messages mirror PostgreSQL's `no binary output function
600    /// available for type <t>` so that drivers and users see a familiar
601    /// diagnostic. Callers should report these errors with the SQLSTATE
602    /// PostgreSQL uses for the same condition, `42883` (undefined_function).
603    pub fn binary_encoding_error(typ: &SqlScalarType) -> Result<(), &'static str> {
604        match typ {
605            SqlScalarType::Bool => Ok(()),
606            SqlScalarType::Int16 => Ok(()),
607            SqlScalarType::Int32 => Ok(()),
608            SqlScalarType::Int64 => Ok(()),
609            SqlScalarType::PgLegacyChar => Ok(()),
610            SqlScalarType::UInt16 => Ok(()),
611            SqlScalarType::Oid => Ok(()),
612            SqlScalarType::RegClass => Ok(()),
613            SqlScalarType::RegProc => Ok(()),
614            SqlScalarType::RegType => Ok(()),
615            SqlScalarType::UInt32 => Ok(()),
616            SqlScalarType::UInt64 => Ok(()),
617            SqlScalarType::Float32 => Ok(()),
618            SqlScalarType::Float64 => Ok(()),
619            SqlScalarType::Numeric { .. } => Ok(()),
620            SqlScalarType::MzTimestamp => Ok(()),
621            SqlScalarType::MzAclItem => Ok(()),
622            SqlScalarType::AclItem => Err("no binary output function available for type aclitem"),
623            SqlScalarType::Date => Ok(()),
624            SqlScalarType::Time => Ok(()),
625            SqlScalarType::Timestamp { .. } => Ok(()),
626            SqlScalarType::TimestampTz { .. } => Ok(()),
627            SqlScalarType::Interval => Ok(()),
628            SqlScalarType::Bytes => Ok(()),
629            SqlScalarType::String => Ok(()),
630            SqlScalarType::VarChar { .. } => Ok(()),
631            SqlScalarType::Char { .. } => Ok(()),
632            SqlScalarType::PgLegacyName => Ok(()),
633            SqlScalarType::Jsonb => Ok(()),
634            SqlScalarType::Uuid => Ok(()),
635            SqlScalarType::Array(elem_type) => Self::binary_encoding_error(elem_type),
636            SqlScalarType::Int2Vector => Ok(()),
637            SqlScalarType::List { .. } => Err("no binary output function available for type list"),
638            SqlScalarType::Map { .. } => Err("no binary output function available for type map"),
639            SqlScalarType::Record { fields, .. } => fields
640                .iter()
641                .try_for_each(|(_, ty)| Self::binary_encoding_error(&ty.scalar_type)),
642            SqlScalarType::Range { element_type } => Self::binary_encoding_error(element_type),
643        }
644    }
645
646    /// Returns whether a value of the given `SqlScalarType` can be encoded using
647    /// the binary format. See [`Value::binary_encoding_error`] for details,
648    /// including the (recursive) handling of container types.
649    pub fn can_encode_binary(typ: &SqlScalarType) -> bool {
650        Self::binary_encoding_error(typ).is_ok()
651    }
652
653    /// Deserializes a value of type `ty` from `raw` using the specified
654    /// `format`.
655    pub fn decode(
656        format: Format,
657        ty: &Type,
658        raw: &[u8],
659    ) -> Result<Value, Box<dyn Error + Sync + Send>> {
660        match format {
661            Format::Text => Value::decode_text(ty, raw),
662            Format::Binary => Value::decode_binary(ty, raw),
663        }
664    }
665
666    /// Deserializes a value of type `ty` from `raw` using the [text encoding
667    /// format](Format::Text).
668    pub fn decode_text<'a>(
669        ty: &'a Type,
670        raw: &'a [u8],
671    ) -> Result<Value, Box<dyn Error + Sync + Send>> {
672        let s = str::from_utf8(raw)?;
673        // Match PostgreSQL, which rejects NUL bytes in text-format values of
674        // any type as part of client encoding verification.
675        reject_nul(s)?;
676        Ok(match ty {
677            Type::Array(elem_type) => {
678                let (elements, dims) = strconv::parse_array(
679                    s,
680                    || None,
681                    |elem_text| Value::decode_text(elem_type, elem_text.as_bytes()).map(Some),
682                )?;
683                Value::Array { dims, elements }
684            }
685            Type::Int2Vector { .. } => {
686                return Err("input of Int2Vector types is not implemented".into());
687            }
688            Type::Bool => Value::Bool(strconv::parse_bool(s)?),
689            Type::Bytea => Value::Bytea(strconv::parse_bytes(s)?),
690            Type::Char => Value::Char(raw.get(0).copied().unwrap_or(0)),
691            Type::Date => Value::Date(strconv::parse_date(s)?),
692            Type::Float4 => Value::Float4(strconv::parse_float32(s)?),
693            Type::Float8 => Value::Float8(strconv::parse_float64(s)?),
694            Type::Int2 => Value::Int2(strconv::parse_int16(s)?),
695            Type::Int4 => Value::Int4(strconv::parse_int32(s)?),
696            Type::Int8 => Value::Int8(strconv::parse_int64(s)?),
697            Type::UInt2 => Value::UInt2(UInt2(strconv::parse_uint16(s)?)),
698            Type::UInt4 => Value::UInt4(UInt4(strconv::parse_uint32(s)?)),
699            Type::UInt8 => Value::UInt8(UInt8(strconv::parse_uint64(s)?)),
700            Type::Interval { .. } => Value::Interval(Interval(strconv::parse_interval(s)?)),
701            Type::Json => return Err("input of json types is not implemented".into()),
702            Type::Jsonb => Value::Jsonb(Jsonb(strconv::parse_jsonb(s)?)),
703            Type::List(elem_type) => Value::List(strconv::parse_list(
704                s,
705                matches!(**elem_type, Type::List(..)),
706                || None,
707                |elem_text| Value::decode_text(elem_type, elem_text.as_bytes()).map(Some),
708            )?),
709            Type::Map { value_type } => Value::Map(strconv::parse_map(
710                s,
711                matches!(**value_type, Type::Map { .. }),
712                |elem_text| {
713                    elem_text
714                        .map(|t| Value::decode_text(value_type, t.as_bytes()))
715                        .transpose()
716                },
717            )?),
718            Type::Name => Value::Name(strconv::parse_pg_legacy_name(s)),
719            Type::Numeric { constraints } => Value::Numeric(Numeric(rescale_numeric(
720                strconv::parse_numeric(s)?,
721                constraints.as_ref(),
722            )?)),
723            Type::Oid | Type::RegClass | Type::RegProc | Type::RegType => {
724                Value::Oid(strconv::parse_oid(s)?)
725            }
726            Type::Record(_) => {
727                return Err("input of anonymous composite types is not implemented".into());
728            }
729            Type::Text => Value::Text(s.to_owned()),
730            Type::BpChar { .. } => Value::BpChar(s.to_owned()),
731            Type::VarChar { .. } => Value::VarChar(s.to_owned()),
732            Type::Time { .. } => Value::Time(strconv::parse_time(s)?),
733            Type::TimeTz { .. } => return Err("input of timetz types is not implemented".into()),
734            Type::Timestamp { .. } => Value::Timestamp(strconv::parse_timestamp(s)?),
735            Type::TimestampTz { .. } => Value::TimestampTz(strconv::parse_timestamptz(s)?),
736            Type::Uuid => Value::Uuid(strconv::parse_uuid(s)?),
737            Type::MzTimestamp => Value::MzTimestamp(strconv::parse_mz_timestamp(s)?),
738            Type::Range { element_type } => Value::Range(strconv::parse_range(s, |elem_text| {
739                Value::decode_text(element_type, elem_text.as_bytes()).map(Box::new)
740            })?),
741            Type::MzAclItem => Value::MzAclItem(strconv::parse_mz_acl_item(s)?),
742            Type::AclItem => Value::AclItem(strconv::parse_acl_item(s)?),
743        })
744    }
745
746    /// Deserializes a value of type `ty` from `s` using the [text encoding format](Format::Text).
747    pub fn decode_text_into_row<'a>(
748        ty: &'a Type,
749        s: &'a str,
750        packer: &mut RowPacker,
751    ) -> Result<(), Box<dyn Error + Sync + Send>> {
752        // Match PostgreSQL, which rejects NUL bytes in text-format values of
753        // any type as part of client encoding verification.
754        reject_nul(s)?;
755        Ok(match ty {
756            Type::Array(elem_type) => {
757                let (elements, dims) =
758                    strconv::parse_array(s, || None, |elem_text| Ok::<_, String>(Some(elem_text)))?;
759                // SAFETY: The function returns the number of times it called `push` on the packer.
760                unsafe {
761                    packer.push_array_with_unchecked(&dims, |packer| {
762                        let mut nelements = 0;
763                        for element in elements {
764                            match element {
765                                Some(elem_text) => {
766                                    Value::decode_text_into_row(elem_type, &elem_text, packer)?
767                                }
768
769                                None => packer.push(Datum::Null),
770                            }
771                            nelements += 1;
772                        }
773                        Ok::<_, Box<dyn Error + Sync + Send>>(nelements)
774                    })?
775                }
776            }
777            Type::Int2Vector { .. } => {
778                return Err("input of Int2Vector types is not implemented".into());
779            }
780            Type::Bool => packer.push(Datum::from(strconv::parse_bool(s)?)),
781            Type::Bytea => packer.push(Datum::Bytes(&strconv::parse_bytes(s)?)),
782            Type::Char => packer.push(Datum::UInt8(s.as_bytes().get(0).copied().unwrap_or(0))),
783            Type::Date => packer.push(Datum::Date(strconv::parse_date(s)?)),
784            Type::Float4 => packer.push(Datum::Float32(strconv::parse_float32(s)?.into())),
785            Type::Float8 => packer.push(Datum::Float64(strconv::parse_float64(s)?.into())),
786            Type::Int2 => packer.push(Datum::Int16(strconv::parse_int16(s)?)),
787            Type::Int4 => packer.push(Datum::Int32(strconv::parse_int32(s)?)),
788            Type::Int8 => packer.push(Datum::Int64(strconv::parse_int64(s)?)),
789            Type::UInt2 => packer.push(Datum::UInt16(strconv::parse_uint16(s)?)),
790            Type::UInt4 => packer.push(Datum::UInt32(strconv::parse_uint32(s)?)),
791            Type::UInt8 => packer.push(Datum::UInt64(strconv::parse_uint64(s)?)),
792            Type::Interval { .. } => packer.push(Datum::Interval(strconv::parse_interval(s)?)),
793            Type::Json => return Err("input of json types is not implemented".into()),
794            Type::Jsonb => packer.push(strconv::parse_jsonb(s)?.into_row().unpack_first()),
795            Type::List(elem_type) => {
796                let elems = strconv::parse_list(
797                    s,
798                    matches!(**elem_type, Type::List(..)),
799                    || None,
800                    |elem_text| Ok::<_, String>(Some(elem_text)),
801                )?;
802                packer.push_list_with(|packer| {
803                    for elem in elems {
804                        match elem {
805                            Some(elem) => Value::decode_text_into_row(elem_type, &elem, packer)?,
806                            None => packer.push(Datum::Null),
807                        }
808                    }
809                    Ok::<_, Box<dyn Error + Sync + Send>>(())
810                })?;
811            }
812            Type::Map { value_type } => {
813                let map =
814                    strconv::parse_map(s, matches!(**value_type, Type::Map { .. }), |elem_text| {
815                        elem_text.map(Ok::<_, String>).transpose()
816                    })?;
817                packer.push_dict_with(|row| {
818                    for (k, v) in map {
819                        row.push(Datum::String(&k));
820                        match v {
821                            Some(elem) => Value::decode_text_into_row(value_type, &elem, row)?,
822                            None => row.push(Datum::Null),
823                        }
824                    }
825                    Ok::<_, Box<dyn Error + Sync + Send>>(())
826                })?;
827            }
828            Type::Name => packer.push(Datum::String(&strconv::parse_pg_legacy_name(s))),
829            Type::Numeric { constraints } => packer.push(Datum::Numeric(rescale_numeric(
830                strconv::parse_numeric(s)?,
831                constraints.as_ref(),
832            )?)),
833            Type::Oid | Type::RegClass | Type::RegProc | Type::RegType => {
834                packer.push(Datum::UInt32(strconv::parse_oid(s)?))
835            }
836            Type::Record(_) => {
837                return Err("input of anonymous composite types is not implemented".into());
838            }
839            Type::Text => packer.push(Datum::String(s)),
840            Type::BpChar { .. } => packer.push(Datum::String(s.trim_end())),
841            Type::VarChar { .. } => packer.push(Datum::String(s)),
842            Type::Time { .. } => packer.push(Datum::Time(strconv::parse_time(s)?)),
843            Type::TimeTz { .. } => return Err("input of timetz types is not implemented".into()),
844            Type::Timestamp { .. } => packer.push(Datum::Timestamp(strconv::parse_timestamp(s)?)),
845            Type::TimestampTz { .. } => {
846                packer.push(Datum::TimestampTz(strconv::parse_timestamptz(s)?))
847            }
848            Type::Uuid => packer.push(Datum::Uuid(strconv::parse_uuid(s)?)),
849            Type::MzTimestamp => packer.push(Datum::MzTimestamp(strconv::parse_mz_timestamp(s)?)),
850            Type::Range { element_type } => {
851                let range = strconv::parse_range(s, |elem_text| {
852                    Value::decode_text(element_type, elem_text.as_bytes()).map(Box::new)
853                })?;
854                // TODO: We should be able to push ranges without scratch space, but that requires
855                // a different `push_range` API.
856                let buf = RowArena::new();
857                let range = range
858                    .try_into_bounds(|elem| elem.into_datum(&buf, element_type))
859                    .map_err(Box::<dyn Error + Sync + Send>::from)?;
860                packer
861                    .push_range(range)
862                    .map_err(Box::<dyn Error + Sync + Send>::from)?;
863            }
864            Type::MzAclItem => packer.push(Datum::MzAclItem(strconv::parse_mz_acl_item(s)?)),
865            Type::AclItem => packer.push(Datum::AclItem(strconv::parse_acl_item(s)?)),
866        })
867    }
868
869    /// Deserializes a value of type `ty` from `raw` using the [binary encoding
870    /// format](Format::Binary).
871    pub fn decode_binary(ty: &Type, raw: &[u8]) -> Result<Value, Box<dyn Error + Sync + Send>> {
872        match ty {
873            Type::Array(_) => Err("input of array types is not implemented".into()),
874            Type::Int2Vector => Err("input of int2vector types is not implemented".into()),
875            Type::Bool => bool::from_sql(ty.inner(), raw).map(Value::Bool),
876            Type::Bytea => Vec::<u8>::from_sql(ty.inner(), raw).map(Value::Bytea),
877            Type::Char => {
878                i8::from_sql(ty.inner(), raw).map(|c| Value::Char(u8::reinterpret_cast(c)))
879            }
880            Type::Date => {
881                let days = i32::from_sql(ty.inner(), raw)?;
882                Ok(Value::Date(Date::from_pg_epoch(days)?))
883            }
884            Type::Float4 => f32::from_sql(ty.inner(), raw).map(Value::Float4),
885            Type::Float8 => f64::from_sql(ty.inner(), raw).map(Value::Float8),
886            Type::Int2 => i16::from_sql(ty.inner(), raw).map(Value::Int2),
887            Type::Int4 => i32::from_sql(ty.inner(), raw).map(Value::Int4),
888            Type::Int8 => i64::from_sql(ty.inner(), raw).map(Value::Int8),
889            Type::UInt2 => UInt2::from_sql(ty.inner(), raw).map(Value::UInt2),
890            Type::UInt4 => UInt4::from_sql(ty.inner(), raw).map(Value::UInt4),
891            Type::UInt8 => UInt8::from_sql(ty.inner(), raw).map(Value::UInt8),
892            Type::Interval { .. } => Interval::from_sql(ty.inner(), raw).map(Value::Interval),
893            Type::Json => Err("input of json types is not implemented".into()),
894            Type::Jsonb => Jsonb::from_sql(ty.inner(), raw).map(Value::Jsonb),
895            Type::List(_) => Err("binary decoding of list types is not implemented".into()),
896            Type::Map { .. } => Err("binary decoding of map types is not implemented".into()),
897            Type::Name => {
898                let s = String::from_sql(ty.inner(), raw)?;
899                reject_nul(&s)?;
900                if s.len() > NAME_MAX_BYTES {
901                    return Err("identifier too long".into());
902                }
903                Ok(Value::Name(s))
904            }
905            Type::Numeric { constraints } => {
906                let n = Numeric::from_sql(ty.inner(), raw)?;
907                Ok(Value::Numeric(Numeric(rescale_numeric(
908                    n.0,
909                    constraints.as_ref(),
910                )?)))
911            }
912            Type::Oid | Type::RegClass | Type::RegProc | Type::RegType => {
913                u32::from_sql(ty.inner(), raw).map(Value::Oid)
914            }
915            Type::Record(_) => Err("input of anonymous composite types is not implemented".into()),
916            Type::Text => decode_binary_string(ty, raw).map(Value::Text),
917            Type::BpChar { .. } => decode_binary_string(ty, raw).map(Value::BpChar),
918            Type::VarChar { .. } => decode_binary_string(ty, raw).map(Value::VarChar),
919            Type::Time { .. } => NaiveTime::from_sql(ty.inner(), raw).map(Value::Time),
920            Type::TimeTz { .. } => Err("input of timetz types is not implemented".into()),
921            Type::Timestamp { .. } => {
922                let ts = NaiveDateTime::from_sql(ty.inner(), raw)?;
923                Ok(Value::Timestamp(CheckedTimestamp::from_timestamplike(ts)?))
924            }
925            Type::TimestampTz { .. } => {
926                let ts = DateTime::<Utc>::from_sql(ty.inner(), raw)?;
927                Ok(Value::TimestampTz(CheckedTimestamp::from_timestamplike(
928                    ts,
929                )?))
930            }
931            Type::Uuid => Uuid::from_sql(ty.inner(), raw).map(Value::Uuid),
932            Type::MzTimestamp => {
933                let s = String::from_sql(ty.inner(), raw)?;
934                let t: mz_repr::Timestamp = s.parse()?;
935                Ok(Value::MzTimestamp(t))
936            }
937            Type::Range { .. } => Err("binary decoding of range types is not implemented".into()),
938            Type::MzAclItem => {
939                let mz_acl_item = MzAclItem::decode_binary(raw)?;
940                Ok(Value::MzAclItem(mz_acl_item))
941            }
942            Type::AclItem => Err("aclitem has no binary encoding".into()),
943        }
944    }
945}
946
947/// Returns an error if `s` contains a NUL character, which PostgreSQL rejects
948/// in text values.
949fn reject_nul(s: &str) -> Result<(), Box<dyn Error + Sync + Send>> {
950    if s.contains('\0') {
951        Err(Box::new(NulCharacterError))
952    } else {
953        Ok(())
954    }
955}
956
957/// Decodes a binary-format string value, rejecting embedded NUL characters.
958fn decode_binary_string(ty: &Type, raw: &[u8]) -> Result<String, Box<dyn Error + Sync + Send>> {
959    let s = String::from_sql(ty.inner(), raw)?;
960    reject_nul(&s)?;
961    Ok(s)
962}
963
964/// Rescales `n` to the scale required by `constraints`, if any.
965fn rescale_numeric(
966    mut n: OrderedDecimal<mz_repr_numeric::Numeric>,
967    constraints: Option<&NumericConstraints>,
968) -> Result<OrderedDecimal<mz_repr_numeric::Numeric>, Box<dyn Error + Sync + Send>> {
969    if let Some(constraints) = constraints {
970        rescale(
971            &mut n.0,
972            NumericMaxScale::try_from(i64::from(constraints.max_scale()))?.into_u8(),
973        )?;
974    }
975    Ok(n)
976}
977
978fn encode_element(buf: &mut BytesMut, elem: Option<&Value>, ty: &Type) -> Result<(), io::Error> {
979    match elem {
980        None => buf.put_i32(-1),
981        Some(elem) => {
982            let base = buf.len();
983            buf.put_i32(0);
984            elem.encode_binary(ty, buf)?;
985            let len = pg_len("encoded element", buf.len() - base - 4)?;
986            buf[base..base + 4].copy_from_slice(&len.to_be_bytes());
987        }
988    }
989    Ok(())
990}
991
992fn pg_len(what: &str, len: usize) -> Result<i32, io::Error> {
993    len.try_into().map_err(|_| {
994        io::Error::new(
995            io::ErrorKind::InvalidData,
996            format!("{} does not fit into an i32", what),
997        )
998    })
999}
1000
1001/// Converts a Materialize row into a vector of PostgreSQL values.
1002///
1003/// Calling this function is equivalent to mapping [`Value::from_datum`] over
1004/// every datum in `row`.
1005pub fn values_from_row(row: &RowRef, typ: &SqlRelationType) -> Vec<Option<Value>> {
1006    row.iter()
1007        .zip_eq(typ.column_types.iter())
1008        .map(|(col, typ)| Value::from_datum(col, &typ.scalar_type))
1009        .collect()
1010}
1011
1012#[cfg(test)]
1013mod tests {
1014    use mz_repr::arb_datum_for_scalar;
1015    use proptest::prelude::*;
1016
1017    use super::*;
1018
1019    /// Property test: [`Value::binary_encoding_error`] agrees with the actual
1020    /// behavior of [`Value::encode_binary`] for every `(SqlScalarType, Datum)`
1021    /// pair the proptest infrastructure can generate.
1022    ///
1023    /// This guards against future drift between the static predicate and the
1024    /// encoder: any new `SqlScalarType` variant whose classification disagrees
1025    /// with what `encode_binary` actually does will surface here.
1026    #[mz_ore::test]
1027    #[cfg_attr(miri, ignore)] // numeric/decimal contexts unsupported under miri
1028    fn proptest_binary_encoding_error_matches_encode_binary() {
1029        let strat =
1030            any::<SqlScalarType>().prop_flat_map(|ty| (Just(ty.clone()), arb_datum_for_scalar(ty)));
1031        proptest!(ProptestConfig::with_cases(256), |((ty, prop_datum) in strat)| {
1032            // `binary_encoding_error` is a precondition for callers of
1033            // `encode_binary`: if it returns `Ok`, then encoding must succeed
1034            // (and not panic via the internal `.expect`).
1035            if Value::binary_encoding_error(&ty).is_err() {
1036                return Ok(());
1037            }
1038            let datum = Datum::from(&prop_datum);
1039            let value = match Value::from_datum(datum, &ty) {
1040                Some(v) => v,
1041                // `Datum::Null` produces `None`; nothing to encode.
1042                None => return Ok(()),
1043            };
1044            let pg_ty = Type::from(&ty);
1045            let mut buf = BytesMut::new();
1046            value
1047                .encode_binary(&pg_ty, &mut buf)
1048                .expect("encode_binary must succeed when binary_encoding_error returns Ok");
1049        });
1050    }
1051
1052    /// Verifies that we correctly print the chain of parsing errors, all the way through the stack.
1053    #[mz_ore::test]
1054    fn decode_text_error_smoke_test() {
1055        let bool_array = Value::Array {
1056            dims: vec![ArrayDimension {
1057                lower_bound: 0,
1058                length: 1,
1059            }],
1060            elements: vec![Some(Value::Bool(true))],
1061        };
1062
1063        let mut buf = BytesMut::new();
1064        bool_array.encode_text(&mut buf);
1065        let buf = buf.to_vec();
1066
1067        let int_array_tpe = Type::Array(Box::new(Type::Int4));
1068        let decoded_int_array = Value::decode_text(&int_array_tpe, &buf);
1069
1070        assert_eq!(
1071            decoded_int_array.map_err(|e| e.to_string()).unwrap_err(),
1072            "invalid input syntax for type array: Specifying array lower bounds is not supported: \"[0:0]={t}\"".to_string()
1073        );
1074    }
1075
1076    /// Decoding a numeric must round it to the destination's declared scale,
1077    /// and the text and binary paths must agree. `COPY ... FROM` relies on the
1078    /// text/CSV side of this (SS-193); binary parameters rely on the binary
1079    /// side. `COPY ... FORMAT BINARY` is unsupported, so the binary path is
1080    /// exercised here rather than via mzcompose.
1081    #[mz_ore::test]
1082    #[cfg_attr(miri, ignore)] // numeric/decimal contexts unsupported under miri
1083    fn decode_numeric_applies_destination_scale() {
1084        // A `numeric(10, 2)` destination: scale 2.
1085        let ty = Type::from(&SqlScalarType::Numeric {
1086            max_scale: Some(NumericMaxScale::try_from(2_i64).unwrap()),
1087        });
1088        let expected = strconv::parse_numeric("10.45").unwrap();
1089
1090        // Encode an over-scale value (10.447, scale 3) to binary, then decode
1091        // it back through the scale-2 type.
1092        let input = Value::Numeric(Numeric(strconv::parse_numeric("10.447").unwrap()));
1093        let mut buf = BytesMut::new();
1094        input
1095            .encode_binary(&ty, &mut buf)
1096            .expect("encoding 10.447 as numeric must succeed");
1097        let Value::Numeric(Numeric(binary)) = Value::decode_binary(&ty, &buf).unwrap() else {
1098            panic!("decode_binary of a numeric must yield Value::Numeric");
1099        };
1100        assert_eq!(binary, expected, "binary decode did not rescale to scale 2");
1101
1102        // The text path must agree with the binary path.
1103        let Value::Numeric(Numeric(text)) = Value::decode_text(&ty, b"10.447").unwrap() else {
1104            panic!("decode_text of a numeric must yield Value::Numeric");
1105        };
1106        assert_eq!(text, expected, "text decode did not rescale to scale 2");
1107    }
1108
1109    /// Text values must never contain NUL characters, in either wire format.
1110    #[mz_ore::test]
1111    fn decode_rejects_nul_in_strings() {
1112        const NUL_ERR: &str = "invalid byte sequence for encoding \"UTF8\": 0x00";
1113        let raw = b"foo\x00bar";
1114
1115        for ty in [
1116            Type::Text,
1117            Type::BpChar { length: None },
1118            Type::VarChar { max_length: None },
1119            Type::Name,
1120        ] {
1121            for format in [Format::Text, Format::Binary] {
1122                let res = Value::decode(format, &ty, raw);
1123                assert_eq!(
1124                    res.map(|_| ()).map_err(|e| e.to_string()).unwrap_err(),
1125                    NUL_ERR,
1126                    "{ty:?} in {format:?} format must reject NUL",
1127                );
1128            }
1129        }
1130
1131        // The text format rejects NUL bytes regardless of the target type.
1132        let res = Value::decode_text(&Type::Bytea, b"\\x00\x00");
1133        assert_eq!(
1134            res.map(|_| ()).map_err(|e| e.to_string()).unwrap_err(),
1135            NUL_ERR,
1136        );
1137
1138        // NUL-free values still decode.
1139        let Value::Text(s) = Value::decode(Format::Binary, &Type::Text, b"foobar").unwrap() else {
1140            panic!("decoding a text value must yield Value::Text");
1141        };
1142        assert_eq!(s, "foobar");
1143    }
1144}