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