Skip to main content

mz_expr/scalar/
func.rs

1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Use of this software is governed by the Business Source License
4// included in the LICENSE file.
5//
6// As of the Change Date specified in that file, in accordance with
7// the Business Source License, use of this software will be governed
8// by the Apache License, Version 2.0.
9//
10// Portions of this file are derived from the PostgreSQL project. The original
11// source code is subject to the terms of the PostgreSQL license, a copy of
12// which can be found in the LICENSE file at the root of this repository.
13
14use std::borrow::Cow;
15use std::cmp::Ordering;
16use std::convert::{TryFrom, TryInto};
17use std::str::FromStr;
18use std::{iter, str};
19
20use ::encoding::DecoderTrap;
21use ::encoding::label::encoding_from_whatwg_label;
22use aws_lc_rs::constant_time::verify_slices_are_equal;
23use aws_lc_rs::digest;
24use chrono::{DateTime, Duration, NaiveDate, NaiveDateTime, TimeZone, Timelike, Utc};
25use chrono_tz::{OffsetComponents, OffsetName, Tz};
26use dec::OrderedDecimal;
27use itertools::Itertools;
28use md5::{Digest, Md5};
29use mz_expr_derive::sqlfunc;
30use mz_ore::cast::{self, CastFrom};
31use mz_ore::fmt::FormatBuffer;
32use mz_ore::lex::LexBuf;
33use mz_ore::option::OptionExt;
34use mz_pgrepr::Type;
35use mz_pgtz::timezone::{Timezone, TimezoneSpec};
36use mz_repr::adt::array::{Array, ArrayDimension};
37use mz_repr::adt::date::Date;
38use mz_repr::adt::interval::{Interval, RoundBehavior};
39use mz_repr::adt::jsonb::JsonbRef;
40use mz_repr::adt::mz_acl_item::{AclMode, MzAclItem};
41use mz_repr::adt::numeric::{self, Numeric};
42use mz_repr::adt::range::Range;
43use mz_repr::adt::regex::Regex;
44use mz_repr::adt::timestamp::{CheckedTimestamp, TimestampLike};
45use mz_repr::{
46    ArrayRustType, Datum, DatumList, DatumMap, ExcludeNull, FromDatum, InputDatumType, Row,
47    RowArena, SqlScalarType, strconv,
48};
49use mz_sql_parser::ast::display::{AstDisplay, FormatMode};
50use mz_sql_pretty::{PrettyConfig, pretty_str};
51use num::traits::CheckedNeg;
52
53use crate::scalar::func::format::DateTimeFormat;
54use crate::{EvalError, like_pattern};
55
56#[macro_use]
57mod macros;
58mod binary;
59mod encoding;
60pub(crate) mod format;
61pub(crate) mod impls;
62mod jq;
63#[cfg(feature = "func-registry")]
64pub mod registry;
65mod unary;
66mod unmaterializable;
67pub mod variadic;
68
69pub use binary::BinaryFunc;
70pub use impls::*;
71pub use jq::{Jq, JqProgram};
72pub use unary::{EagerUnaryFunc, LazyUnaryFunc, UnaryFunc};
73pub use unmaterializable::UnmaterializableFunc;
74pub use variadic::VariadicFunc;
75
76/// The canonical name of a scalar function, and its source when it is
77/// generated by `#[sqlfunc]`.
78///
79/// For functions generated by `#[sqlfunc]` the name is that of the underlying
80/// Rust function. Hand-written functions declare it explicitly. Test tooling
81/// refers to exact function variants by these names, via
82/// [`UnaryFunc::from_variant_name`] and its binary and variadic counterparts.
83pub trait FuncName {
84    const NAME: &'static str;
85
86    /// The `#[sqlfunc]` declaration this function was generated from, or
87    /// `None` for hand-written functions. Consumed by [`registry`].
88    #[cfg(feature = "func-registry")]
89    const SQLFUNC: Option<SqlFuncSource> = None;
90
91    /// The column types a `#[sqlfunc]` function naturally consumes, one per
92    /// argument, when every parameter type maps to one. `None` for
93    /// hand-written functions and for parameters typed as a bare `Datum`.
94    /// Consumed by [`registry`] to probe the output type.
95    #[cfg(feature = "func-registry")]
96    fn sqlfunc_input_types() -> Option<Vec<mz_repr::SqlColumnType>> {
97        None
98    }
99}
100
101/// The source of a `#[sqlfunc]`-generated function, as captured by the macro.
102///
103/// Comments and formatting are invisible to both fields, because they are
104/// derived from token streams. Helpers the body calls are not covered, so an
105/// unchanged fingerprint does not prove unchanged behavior.
106#[cfg(feature = "func-registry")]
107#[derive(Clone, Copy, Debug, PartialEq, Eq)]
108pub struct SqlFuncSource {
109    /// The attribute arguments and the function signature, rendered as
110    /// compact source text: `#[sqlfunc(<args>)] fn <name>(<params>) -> <ret>`.
111    pub decl: &'static str,
112    /// The parameter and return types alone, as written: `fn(<types>) -> <ret>`.
113    pub signature: &'static str,
114    /// FNV-1a fingerprint of the function body.
115    pub body_fingerprint: u64,
116}
117
118/// Declares the canonical [`FuncName`] of hand-written function structs.
119/// Functions generated by `#[sqlfunc]` get theirs from that macro instead.
120/// Structs generic over an expression type declare a leading `<E>`.
121macro_rules! func_name {
122    () => {};
123    (<$param:ident> $ty:ty => $name:literal, $($rest:tt)*) => {
124        impl<$param> FuncName for $ty {
125            const NAME: &'static str = $name;
126        }
127        func_name!($($rest)*);
128    };
129    ($ty:ty => $name:literal, $($rest:tt)*) => {
130        impl FuncName for $ty {
131            const NAME: &'static str = $name;
132        }
133        func_name!($($rest)*);
134    };
135}
136
137func_name! {
138    CaseLiteral => "case_literal",
139    <E> CastArrayToArray<E> => "cast_array_to_array",
140    <E> CastArrayToJsonb<E> => "cast_array_to_jsonb",
141    <E> CastList1ToList2<E> => "cast_list1_to_list2",
142    <E> CastListToJsonb<E> => "cast_list_to_jsonb",
143    <E> CastRecord1ToRecord2<E> => "cast_record1_to_record2",
144    <E> CastStringToArray<E> => "cast_string_to_array",
145    <E> CastStringToList<E> => "cast_string_to_list",
146    <E> CastStringToMap<E> => "cast_string_to_map",
147    <E> CastStringToRange<E> => "cast_string_to_range",
148    variadic::ErrorIfNull => "error_if_null",
149    <E> TryCast<E> => "try_cast",
150    variadic::And => "and",
151    variadic::Coalesce => "coalesce",
152    variadic::Greatest => "greatest",
153    variadic::Least => "least",
154    variadic::Or => "or",
155}
156
157/// The maximum size of the result strings of certain string functions, such as `repeat` and `lpad`.
158/// Chosen to be the smallest number to keep our tests passing without changing. 100MiB is probably
159/// higher than what we want, but it's better than no limit.
160///
161/// Note: This number appears in our user-facing documentation in the function reference for every
162/// function where it applies.
163pub const MAX_STRING_FUNC_RESULT_BYTES: usize = 1024 * 1024 * 100;
164
165/// The largest result a string function may build into `temp_storage`.
166///
167/// [`MAX_STRING_FUNC_RESULT_BYTES`] unless the arena carries a tighter budget, which is how an
168/// expression evaluated in `environmentd` on behalf of a request (a webhook `CHECK`) is held to a
169/// size proportionate to that request rather than to the constant, which is sized for a cluster.
170///
171/// A function that can predict its result size must consult this *before* building the result: the
172/// arena's own budget is only observable after the bytes exist, which for an amplifying function is
173/// exactly too late.
174pub fn max_string_func_result_bytes(temp_storage: &RowArena) -> usize {
175    std::cmp::min(
176        MAX_STRING_FUNC_RESULT_BYTES,
177        temp_storage.budget_remaining(),
178    )
179}
180
181/// Refuses a collection `temp_storage`'s budget cannot afford, before it is packed.
182///
183/// The collection builders (`ARRAY[..]`, `LIST[..]`, `ROW(..)`, `MAP[..]`, `jsonb_build_*`) pack the
184/// datums they are handed straight into `temp_storage`, so the result is as large as those datums
185/// times however many times the expression names each one. Unlike the string functions bounded by
186/// [`max_string_func_result_bytes`], `ARRAY[body, body, ..]` has no ceiling of its own.
187///
188/// Like that ceiling, this must be consulted *before* the result is built, since the arena's budget
189/// is only observable once the bytes exist. [`mz_repr::datum_size`] is the size a datum occupies
190/// once packed, so summing it bounds the result without allocating anything. Without a budget
191/// `budget_remaining` is `usize::MAX` and nothing is refused.
192pub fn check_datums_fit_budget<'a>(
193    datums: impl IntoIterator<Item = Datum<'a>>,
194    temp_storage: &RowArena,
195) -> Result<(), EvalError> {
196    let need: usize = datums
197        .into_iter()
198        .map(|d| mz_repr::datum_size(&d))
199        .fold(0, usize::saturating_add);
200    if need > temp_storage.budget_remaining() {
201        return Err(EvalError::TempStorageBudgetExceeded);
202    }
203    Ok(())
204}
205
206/// Refuses an input-scaled transient the arena's budget cannot afford, before it is built.
207///
208/// A function that gathers `n_elems` of `elem_size` bytes into its own `Vec` before packing them
209/// holds that allocation on the stack, where the arena never sees it. The evaluator's post-call
210/// check counts arena bytes only, so a transient wider than the result it becomes can slip past a
211/// budget the packed result fits under. A `Vec<&str>` of split chunks is one: a 16-byte fat pointer
212/// per chunk against the ~2 bytes an empty chunk packs to. Pair this with
213/// [`check_datums_fit_budget`], which bounds the packed result.
214///
215/// `n_elems` is a closure so an unbudgeted arena, which is every arena in a dataflow, never pays for
216/// a count that can cost a pass over the input.
217///
218/// NOTE: the reformatters (`jsonb_pretty`, `pretty_sql`, `redact_sql`) are the known exception.
219/// Sizing their output needs the same walk that produces it, so only the post-call check bounds
220/// them.
221pub fn check_build_fits_budget(
222    n_elems: impl FnOnce() -> usize,
223    elem_size: usize,
224    temp_storage: &RowArena,
225) -> Result<(), EvalError> {
226    let budget_remaining = temp_storage.budget_remaining();
227    // `usize::MAX` is the unbudgeted sentinel. Return before the count so an unbudgeted arena never
228    // pays for it.
229    if budget_remaining == usize::MAX {
230        return Ok(());
231    }
232    if n_elems().saturating_mul(elem_size) > budget_remaining {
233        return Err(EvalError::TempStorageBudgetExceeded);
234    }
235    Ok(())
236}
237
238pub fn jsonb_stringify<'a>(a: Datum<'a>, temp_storage: &'a RowArena) -> Option<&'a str> {
239    match a {
240        Datum::JsonNull => None,
241        Datum::String(s) => Some(s),
242        _ => {
243            let s = cast_jsonb_to_string(JsonbRef::from_datum(a));
244            Some(temp_storage.push_string(s))
245        }
246    }
247}
248
249#[sqlfunc(
250    is_monotone = "(true, true)",
251    is_infix_op = true,
252    sqlname = "+",
253    propagates_nulls = true
254)]
255fn add_int16(a: i16, b: i16) -> Result<i16, EvalError> {
256    a.checked_add(b).ok_or(EvalError::NumericFieldOverflow)
257}
258
259#[sqlfunc(
260    is_monotone = "(true, true)",
261    is_infix_op = true,
262    sqlname = "+",
263    propagates_nulls = true
264)]
265fn add_int32(a: i32, b: i32) -> Result<i32, EvalError> {
266    a.checked_add(b).ok_or(EvalError::NumericFieldOverflow)
267}
268
269#[sqlfunc(
270    is_monotone = "(true, true)",
271    is_infix_op = true,
272    sqlname = "+",
273    propagates_nulls = true
274)]
275fn add_int64(a: i64, b: i64) -> Result<i64, EvalError> {
276    a.checked_add(b).ok_or(EvalError::NumericFieldOverflow)
277}
278
279#[sqlfunc(
280    is_monotone = "(true, true)",
281    is_infix_op = true,
282    sqlname = "+",
283    propagates_nulls = true
284)]
285fn add_uint16(a: u16, b: u16) -> Result<u16, EvalError> {
286    a.checked_add(b)
287        .ok_or_else(|| EvalError::UInt16OutOfRange(format!("{a} + {b}").into()))
288}
289
290#[sqlfunc(
291    is_monotone = "(true, true)",
292    is_infix_op = true,
293    sqlname = "+",
294    propagates_nulls = true
295)]
296fn add_uint32(a: u32, b: u32) -> Result<u32, EvalError> {
297    a.checked_add(b)
298        .ok_or_else(|| EvalError::UInt32OutOfRange(format!("{a} + {b}").into()))
299}
300
301#[sqlfunc(
302    is_monotone = "(true, true)",
303    is_infix_op = true,
304    sqlname = "+",
305    propagates_nulls = true
306)]
307fn add_uint64(a: u64, b: u64) -> Result<u64, EvalError> {
308    a.checked_add(b)
309        .ok_or_else(|| EvalError::UInt64OutOfRange(format!("{a} + {b}").into()))
310}
311
312#[sqlfunc(
313    is_monotone = "(true, true)",
314    is_infix_op = true,
315    sqlname = "+",
316    propagates_nulls = true
317)]
318fn add_float32(a: f32, b: f32) -> Result<f32, EvalError> {
319    let sum = a + b;
320    if sum.is_infinite() && !a.is_infinite() && !b.is_infinite() {
321        Err(EvalError::FloatOverflow)
322    } else {
323        Ok(sum)
324    }
325}
326
327#[sqlfunc(
328    is_monotone = "(true, true)",
329    is_infix_op = true,
330    sqlname = "+",
331    propagates_nulls = true
332)]
333fn add_float64(a: f64, b: f64) -> Result<f64, EvalError> {
334    let sum = a + b;
335    if sum.is_infinite() && !a.is_infinite() && !b.is_infinite() {
336        Err(EvalError::FloatOverflow)
337    } else {
338        Ok(sum)
339    }
340}
341
342// `Interval` is lex-ordered (months, days, micros), but adding an interval to a
343// timestamp adds *calendar* months (with day-clamping) which does not respect
344// that ordering: e.g. `i1 = {0 months, 31 days}` is lex-less than
345// `i2 = {1 month, 0 days}`, but `2024-01-31 + i1 = 2024-03-02` is greater than
346// `2024-01-31 + i2 = 2024-02-29`. Day-clamping plus preserved sub-day time also
347// breaks monotonicity in the first argument near month boundaries.
348#[sqlfunc(is_monotone = "(false, false)", is_infix_op = true, sqlname = "+")]
349fn add_timestamp_interval(
350    a: CheckedTimestamp<NaiveDateTime>,
351    b: Interval,
352) -> Result<CheckedTimestamp<NaiveDateTime>, EvalError> {
353    add_timestamplike_interval(a, b)
354}
355
356#[sqlfunc(is_monotone = "(false, false)", is_infix_op = true, sqlname = "+")]
357fn add_timestamp_tz_interval(
358    a: CheckedTimestamp<DateTime<Utc>>,
359    b: Interval,
360) -> Result<CheckedTimestamp<DateTime<Utc>>, EvalError> {
361    add_timestamplike_interval(a, b)
362}
363
364fn add_timestamplike_interval<T>(
365    a: CheckedTimestamp<T>,
366    b: Interval,
367) -> Result<CheckedTimestamp<T>, EvalError>
368where
369    T: TimestampLike,
370{
371    let dt = a.date_time();
372    let dt = add_timestamp_months(&dt, b.months)?;
373    let dt = dt
374        .checked_add_signed(b.duration_as_chrono())
375        .ok_or(EvalError::TimestampOutOfRange)?;
376    Ok(CheckedTimestamp::from_timestamplike(T::from_date_time(dt))?)
377}
378
379// See `add_timestamp_interval` for why this is not monotone.
380#[sqlfunc(is_monotone = "(false, false)", is_infix_op = true, sqlname = "-")]
381fn sub_timestamp_interval(
382    a: CheckedTimestamp<NaiveDateTime>,
383    b: Interval,
384) -> Result<CheckedTimestamp<NaiveDateTime>, EvalError> {
385    sub_timestamplike_interval(a, b)
386}
387
388#[sqlfunc(is_monotone = "(false, false)", is_infix_op = true, sqlname = "-")]
389fn sub_timestamp_tz_interval(
390    a: CheckedTimestamp<DateTime<Utc>>,
391    b: Interval,
392) -> Result<CheckedTimestamp<DateTime<Utc>>, EvalError> {
393    sub_timestamplike_interval(a, b)
394}
395
396fn sub_timestamplike_interval<T>(
397    a: CheckedTimestamp<T>,
398    b: Interval,
399) -> Result<CheckedTimestamp<T>, EvalError>
400where
401    T: TimestampLike,
402{
403    neg_interval_inner(b).and_then(|i| add_timestamplike_interval(a, i))
404}
405
406#[sqlfunc(is_monotone = "(true, true)", is_infix_op = true, sqlname = "+")]
407fn add_date_time(
408    date: Date,
409    time: chrono::NaiveTime,
410) -> Result<CheckedTimestamp<NaiveDateTime>, EvalError> {
411    // A leap-second TIME (nanos >= 1e9) rolls over into the next minute,
412    // matching what parsing the equivalent timestamp literal produces. The
413    // leap representation must not enter a timestamp: it sorts before the
414    // next second while epoch-style conversions count it at or past it,
415    // breaking the monotonicity contracts filter pushdown relies on.
416    let (extra_sec, nanos) = match time.nanosecond().checked_sub(1_000_000_000) {
417        Some(nanos) => (1, nanos),
418        None => (0, time.nanosecond()),
419    };
420    let dt = NaiveDate::from(date)
421        .and_hms_nano_opt(time.hour(), time.minute(), time.second(), nanos)
422        .unwrap()
423        .checked_add_signed(chrono::Duration::try_seconds(extra_sec).unwrap())
424        .ok_or(EvalError::TimestampOutOfRange)?;
425    Ok(CheckedTimestamp::from_timestamplike(dt)?)
426}
427
428// Monotone in `date` (dates have no sub-day component, so day-clamping at month
429// boundaries only causes results to collapse, never to reverse), but not in
430// `interval`: e.g. `{0 months, 31 days}` is lex-less than `{1 month, 0 days}`,
431// but adding the former to `2024-01-31` gives `2024-03-02` while the latter
432// gives `2024-02-29`.
433#[sqlfunc(is_monotone = "(true, false)", is_infix_op = true, sqlname = "+")]
434fn add_date_interval(
435    date: Date,
436    interval: Interval,
437) -> Result<CheckedTimestamp<NaiveDateTime>, EvalError> {
438    let dt = NaiveDate::from(date).and_hms_opt(0, 0, 0).unwrap();
439    let dt = add_timestamp_months(&dt, interval.months)?;
440    let dt = dt
441        .checked_add_signed(interval.duration_as_chrono())
442        .ok_or(EvalError::TimestampOutOfRange)?;
443    Ok(CheckedTimestamp::from_timestamplike(dt)?)
444}
445
446#[sqlfunc(
447    // <time> + <interval> wraps!
448    is_monotone = "(false, false)",
449    is_infix_op = true,
450    sqlname = "+",
451    propagates_nulls = true
452)]
453fn add_time_interval(time: chrono::NaiveTime, interval: Interval) -> chrono::NaiveTime {
454    let (t, _) = time.overflowing_add_signed(interval.duration_as_chrono());
455    t
456}
457
458#[sqlfunc(
459    is_monotone = "(true, false)",
460    output_type = "Numeric",
461    sqlname = "round",
462    propagates_nulls = true
463)]
464fn round_numeric_binary(a: OrderedDecimal<Numeric>, mut b: i32) -> Result<Numeric, EvalError> {
465    let mut a = a.0;
466    let mut cx = numeric::cx_datum();
467    let a_scale = numeric::get_scale(&a);
468    if a.is_finite() && i64::from(b) > i64::from(a_scale) {
469        // Rounding at or past `a`'s scale cannot change the value: right-pad
470        // with zeroes via rescale. The rounding path below shifts left by `b`
471        // first and overflows for large `b`.
472        //
473        // NOTE: Equal values can reach here with different scales, since
474        // `Row` encoding folds trailing zeroes into the exponent. The result,
475        // value or error, must not depend on which representation arrives: the
476        // abstract interpreter reads its datums back out of a `Row`, and if it
477        // calls infallible what the evaluator fails on, persist filter
478        // pushdown discards parts it has to keep.
479        //
480        // `Infinity` and `NaN` report a scale of zero, but `rescale` on an
481        // infinity yields `NaN` via invalid_operation, not the overflow
482        // checked below, so the specials take the rounding path, which
483        // propagates them unchanged, as PostgreSQL does.
484
485        // Ensure rescale doesn't exceed max precision by putting a ceiling on
486        // b equal to the maximum remaining scale the value can support.
487        let max_remaining_scale = u32::from(numeric::NUMERIC_DATUM_MAX_PRECISION)
488            - (numeric::get_precision(&a) - a_scale);
489        b = match i32::try_from(max_remaining_scale) {
490            Ok(max_remaining_scale) => std::cmp::min(b, max_remaining_scale),
491            Err(_) => b,
492        };
493        cx.rescale(&mut a, &numeric::Numeric::from(-b));
494    } else {
495        // To avoid invalid operations, clamp b to be within 1 more than the
496        // precision limit.
497        const MAX_P_LIMIT: i32 = 1 + cast::u8_to_i32(numeric::NUMERIC_DATUM_MAX_PRECISION);
498        b = std::cmp::min(MAX_P_LIMIT, b);
499        b = std::cmp::max(-MAX_P_LIMIT, b);
500        let mut b = numeric::Numeric::from(b);
501        // Shift by 10^b; this put digit to round to in the one's place.
502        cx.scaleb(&mut a, &b);
503        cx.round(&mut a);
504        // Negate exponent for shift back
505        cx.neg(&mut b);
506        cx.scaleb(&mut a, &b);
507    }
508
509    if cx.status().overflow() {
510        Err(EvalError::FloatOverflow)
511    } else if a.is_zero() {
512        // simpler than handling cases where exponent has gotten set to some
513        // value greater than the max precision, but all significant digits
514        // were rounded away.
515        Ok(numeric::Numeric::zero())
516    } else {
517        numeric::munge_numeric(&mut a).unwrap();
518        Ok(a)
519    }
520}
521
522#[sqlfunc(sqlname = "convert_from", propagates_nulls = true)]
523fn convert_from<'a>(a: &'a [u8], b: &str) -> Result<&'a str, EvalError> {
524    // Convert PostgreSQL-style encoding names[1] to WHATWG-style encoding names[2],
525    // which the encoding library uses[3].
526    // [1]: https://www.postgresql.org/docs/9.5/multibyte.html
527    // [2]: https://encoding.spec.whatwg.org/
528    // [3]: https://github.com/lifthrasiir/rust-encoding/blob/4e79c35ab6a351881a86dbff565c4db0085cc113/src/label.rs
529    let encoding_name = b.to_lowercase().replace('_', "-").into_boxed_str();
530
531    // Supporting other encodings is tracked by database-issues#797.
532    if encoding_from_whatwg_label(&encoding_name).map(|e| e.name()) != Some("utf-8") {
533        return Err(EvalError::InvalidEncodingName(encoding_name));
534    }
535
536    match str::from_utf8(a) {
537        // Match PostgreSQL, which rejects NUL bytes because text values must
538        // never contain them.
539        Ok(from) if from.contains('\0') => Err(EvalError::InvalidByteSequence {
540            byte_sequence: "0x00".into(),
541            encoding_name,
542        }),
543        Ok(from) => Ok(from),
544        Err(e) => Err(EvalError::InvalidByteSequence {
545            byte_sequence: e.to_string().into(),
546            encoding_name,
547        }),
548    }
549}
550
551#[sqlfunc]
552fn encode(bytes: &[u8], format: &str) -> Result<String, EvalError> {
553    let format = encoding::lookup_format(format)?;
554    Ok(format.encode(bytes))
555}
556
557#[sqlfunc]
558fn decode(string: &str, format: &str, temp_storage: &RowArena) -> Result<Vec<u8>, EvalError> {
559    let format = encoding::lookup_format(format)?;
560    let out = format.decode(string)?;
561    if out.len() > max_string_func_result_bytes(temp_storage) {
562        Err(EvalError::LengthTooLarge)
563    } else {
564        Ok(out)
565    }
566}
567
568#[sqlfunc(sqlname = "length", propagates_nulls = true)]
569fn encoded_bytes_char_length(a: &[u8], b: &str) -> Result<i32, EvalError> {
570    // Convert PostgreSQL-style encoding names[1] to WHATWG-style encoding names[2],
571    // which the encoding library uses[3].
572    // [1]: https://www.postgresql.org/docs/9.5/multibyte.html
573    // [2]: https://encoding.spec.whatwg.org/
574    // [3]: https://github.com/lifthrasiir/rust-encoding/blob/4e79c35ab6a351881a86dbff565c4db0085cc113/src/label.rs
575    let encoding_name = b.to_lowercase().replace('_', "-").into_boxed_str();
576
577    let enc = match encoding_from_whatwg_label(&encoding_name) {
578        Some(enc) => enc,
579        None => return Err(EvalError::InvalidEncodingName(encoding_name)),
580    };
581
582    let decoded_string = match enc.decode(a, DecoderTrap::Strict) {
583        Ok(s) => s,
584        Err(e) => {
585            return Err(EvalError::InvalidByteSequence {
586                byte_sequence: e.into(),
587                encoding_name,
588            });
589        }
590    };
591
592    let count = decoded_string.chars().count();
593    i32::try_from(count).map_err(|_| EvalError::Int32OutOfRange(count.to_string().into()))
594}
595
596// TODO(benesch): remove potentially dangerous usage of `as`.
597#[allow(clippy::as_conversions)]
598pub fn add_timestamp_months<T: TimestampLike>(
599    dt: &T,
600    mut months: i32,
601) -> Result<CheckedTimestamp<T>, EvalError> {
602    if months == 0 {
603        return Ok(CheckedTimestamp::from_timestamplike(dt.clone())?);
604    }
605
606    let (mut year, mut month, mut day) = (dt.year(), dt.month0() as i32, dt.day());
607    let years = months / 12;
608    year = year
609        .checked_add(years)
610        .ok_or(EvalError::TimestampOutOfRange)?;
611
612    months %= 12;
613    // positive modulus is easier to reason about
614    if months < 0 {
615        year -= 1;
616        months += 12;
617    }
618    year += (month + months) / 12;
619    month = (month + months) % 12;
620    // account for dt.month0
621    month += 1;
622
623    // handle going from January 31st to February by saturation
624    let mut new_d = chrono::NaiveDate::from_ymd_opt(year, month as u32, day);
625    while new_d.is_none() {
626        // If we have decremented day past 28 and are still receiving `None`,
627        // then we have generally overflowed `NaiveDate`.
628        if day < 28 {
629            return Err(EvalError::TimestampOutOfRange);
630        }
631        day -= 1;
632        new_d = chrono::NaiveDate::from_ymd_opt(year, month as u32, day);
633    }
634    let new_d = new_d.unwrap();
635
636    // Neither postgres nor mysql support leap seconds, so this should be safe.
637    //
638    // Both my testing and https://dba.stackexchange.com/a/105829 support the
639    // idea that we should ignore leap seconds
640    let new_dt = new_d
641        .and_hms_nano_opt(dt.hour(), dt.minute(), dt.second(), dt.nanosecond())
642        .unwrap();
643    let new_dt = T::from_date_time(new_dt);
644    Ok(CheckedTimestamp::from_timestamplike(new_dt)?)
645}
646
647#[sqlfunc(
648    is_monotone = "(true, true)",
649    is_infix_op = true,
650    sqlname = "+",
651    propagates_nulls = true
652)]
653fn add_numeric(
654    a: OrderedDecimal<Numeric>,
655    b: OrderedDecimal<Numeric>,
656) -> Result<Numeric, EvalError> {
657    let mut cx = numeric::cx_datum();
658    let mut a = a.0;
659    cx.add(&mut a, &b.0);
660    if cx.status().overflow() {
661        Err(EvalError::FloatOverflow)
662    } else {
663        Ok(a)
664    }
665}
666
667#[sqlfunc(
668    is_monotone = "(true, true)",
669    is_infix_op = true,
670    sqlname = "+",
671    propagates_nulls = true
672)]
673fn add_interval(a: Interval, b: Interval) -> Result<Interval, EvalError> {
674    a.checked_add(&b)
675        .ok_or_else(|| EvalError::IntervalOutOfRange(format!("{a} + {b}").into()))
676}
677
678#[sqlfunc(is_infix_op = true, sqlname = "&", propagates_nulls = true)]
679fn bit_and_int16(a: i16, b: i16) -> i16 {
680    a & b
681}
682
683#[sqlfunc(is_infix_op = true, sqlname = "&", propagates_nulls = true)]
684fn bit_and_int32(a: i32, b: i32) -> i32 {
685    a & b
686}
687
688#[sqlfunc(is_infix_op = true, sqlname = "&", propagates_nulls = true)]
689fn bit_and_int64(a: i64, b: i64) -> i64 {
690    a & b
691}
692
693#[sqlfunc(is_infix_op = true, sqlname = "&", propagates_nulls = true)]
694fn bit_and_uint16(a: u16, b: u16) -> u16 {
695    a & b
696}
697
698#[sqlfunc(is_infix_op = true, sqlname = "&", propagates_nulls = true)]
699fn bit_and_uint32(a: u32, b: u32) -> u32 {
700    a & b
701}
702
703#[sqlfunc(is_infix_op = true, sqlname = "&", propagates_nulls = true)]
704fn bit_and_uint64(a: u64, b: u64) -> u64 {
705    a & b
706}
707
708#[sqlfunc(is_infix_op = true, sqlname = "|", propagates_nulls = true)]
709fn bit_or_int16(a: i16, b: i16) -> i16 {
710    a | b
711}
712
713#[sqlfunc(is_infix_op = true, sqlname = "|", propagates_nulls = true)]
714fn bit_or_int32(a: i32, b: i32) -> i32 {
715    a | b
716}
717
718#[sqlfunc(is_infix_op = true, sqlname = "|", propagates_nulls = true)]
719fn bit_or_int64(a: i64, b: i64) -> i64 {
720    a | b
721}
722
723#[sqlfunc(is_infix_op = true, sqlname = "|", propagates_nulls = true)]
724fn bit_or_uint16(a: u16, b: u16) -> u16 {
725    a | b
726}
727
728#[sqlfunc(is_infix_op = true, sqlname = "|", propagates_nulls = true)]
729fn bit_or_uint32(a: u32, b: u32) -> u32 {
730    a | b
731}
732
733#[sqlfunc(is_infix_op = true, sqlname = "|", propagates_nulls = true)]
734fn bit_or_uint64(a: u64, b: u64) -> u64 {
735    a | b
736}
737
738#[sqlfunc(is_infix_op = true, sqlname = "#", propagates_nulls = true)]
739fn bit_xor_int16(a: i16, b: i16) -> i16 {
740    a ^ b
741}
742
743#[sqlfunc(is_infix_op = true, sqlname = "#", propagates_nulls = true)]
744fn bit_xor_int32(a: i32, b: i32) -> i32 {
745    a ^ b
746}
747
748#[sqlfunc(is_infix_op = true, sqlname = "#", propagates_nulls = true)]
749fn bit_xor_int64(a: i64, b: i64) -> i64 {
750    a ^ b
751}
752
753#[sqlfunc(is_infix_op = true, sqlname = "#", propagates_nulls = true)]
754fn bit_xor_uint16(a: u16, b: u16) -> u16 {
755    a ^ b
756}
757
758#[sqlfunc(is_infix_op = true, sqlname = "#", propagates_nulls = true)]
759fn bit_xor_uint32(a: u32, b: u32) -> u32 {
760    a ^ b
761}
762
763#[sqlfunc(is_infix_op = true, sqlname = "#", propagates_nulls = true)]
764fn bit_xor_uint64(a: u64, b: u64) -> u64 {
765    a ^ b
766}
767
768#[sqlfunc(is_infix_op = true, sqlname = "<<", propagates_nulls = true)]
769// TODO(benesch): remove potentially dangerous usage of `as`.
770#[allow(clippy::as_conversions)]
771fn bit_shift_left_int16(a: i16, b: i32) -> i16 {
772    // widen to i32 and then cast back to i16 in order emulate the C promotion rules used in by Postgres
773    // when the rhs in the 16-31 range, e.g. (1 << 17 should evaluate to 0)
774    // see https://github.com/postgres/postgres/blob/REL_14_STABLE/src/backend/utils/adt/int.c#L1460-L1476
775    let lhs: i32 = a as i32;
776    let rhs: u32 = b as u32;
777    lhs.wrapping_shl(rhs) as i16
778}
779
780#[sqlfunc(is_infix_op = true, sqlname = "<<", propagates_nulls = true)]
781// TODO(benesch): remove potentially dangerous usage of `as`.
782#[allow(clippy::as_conversions)]
783fn bit_shift_left_int32(lhs: i32, rhs: i32) -> i32 {
784    let rhs = rhs as u32;
785    lhs.wrapping_shl(rhs)
786}
787
788#[sqlfunc(is_infix_op = true, sqlname = "<<", propagates_nulls = true)]
789// TODO(benesch): remove potentially dangerous usage of `as`.
790#[allow(clippy::as_conversions)]
791fn bit_shift_left_int64(lhs: i64, rhs: i32) -> i64 {
792    let rhs = rhs as u32;
793    lhs.wrapping_shl(rhs)
794}
795
796#[sqlfunc(is_infix_op = true, sqlname = "<<", propagates_nulls = true)]
797// TODO(benesch): remove potentially dangerous usage of `as`.
798#[allow(clippy::as_conversions)]
799fn bit_shift_left_uint16(a: u16, b: u32) -> u16 {
800    // widen to u32 and then cast back to u16 in order emulate the C promotion rules used in by Postgres
801    // when the rhs in the 16-31 range, e.g. (1 << 17 should evaluate to 0)
802    // see https://github.com/postgres/postgres/blob/REL_14_STABLE/src/backend/utils/adt/int.c#L1460-L1476
803    let lhs: u32 = a as u32;
804    let rhs: u32 = b;
805    lhs.wrapping_shl(rhs) as u16
806}
807
808#[sqlfunc(is_infix_op = true, sqlname = "<<", propagates_nulls = true)]
809fn bit_shift_left_uint32(a: u32, b: u32) -> u32 {
810    let lhs = a;
811    let rhs = b;
812    lhs.wrapping_shl(rhs)
813}
814
815#[sqlfunc(
816    output_type = "u64",
817    is_infix_op = true,
818    sqlname = "<<",
819    propagates_nulls = true
820)]
821fn bit_shift_left_uint64(lhs: u64, rhs: u32) -> u64 {
822    lhs.wrapping_shl(rhs)
823}
824
825#[sqlfunc(is_infix_op = true, sqlname = ">>", propagates_nulls = true)]
826// TODO(benesch): remove potentially dangerous usage of `as`.
827#[allow(clippy::as_conversions)]
828fn bit_shift_right_int16(lhs: i16, rhs: i32) -> i16 {
829    // widen to i32 and then cast back to i16 in order emulate the C promotion rules used in by Postgres
830    // when the rhs in the 16-31 range, e.g. (-32767 >> 17 should evaluate to -1)
831    // see https://github.com/postgres/postgres/blob/REL_14_STABLE/src/backend/utils/adt/int.c#L1460-L1476
832    let lhs = lhs as i32;
833    let rhs = rhs as u32;
834    lhs.wrapping_shr(rhs) as i16
835}
836
837#[sqlfunc(is_infix_op = true, sqlname = ">>", propagates_nulls = true)]
838// TODO(benesch): remove potentially dangerous usage of `as`.
839#[allow(clippy::as_conversions)]
840fn bit_shift_right_int32(lhs: i32, rhs: i32) -> i32 {
841    lhs.wrapping_shr(rhs as u32)
842}
843
844#[sqlfunc(is_infix_op = true, sqlname = ">>", propagates_nulls = true)]
845// TODO(benesch): remove potentially dangerous usage of `as`.
846#[allow(clippy::as_conversions)]
847fn bit_shift_right_int64(lhs: i64, rhs: i32) -> i64 {
848    lhs.wrapping_shr(rhs as u32)
849}
850
851#[sqlfunc(is_infix_op = true, sqlname = ">>", propagates_nulls = true)]
852// TODO(benesch): remove potentially dangerous usage of `as`.
853#[allow(clippy::as_conversions)]
854fn bit_shift_right_uint16(lhs: u16, rhs: u32) -> u16 {
855    // widen to u32 and then cast back to u16 in order emulate the C promotion rules used in by Postgres
856    // when the rhs in the 16-31 range, e.g. (-32767 >> 17 should evaluate to -1)
857    // see https://github.com/postgres/postgres/blob/REL_14_STABLE/src/backend/utils/adt/int.c#L1460-L1476
858    let lhs = lhs as u32;
859    lhs.wrapping_shr(rhs) as u16
860}
861
862#[sqlfunc(is_infix_op = true, sqlname = ">>", propagates_nulls = true)]
863fn bit_shift_right_uint32(lhs: u32, rhs: u32) -> u32 {
864    lhs.wrapping_shr(rhs)
865}
866
867#[sqlfunc(is_infix_op = true, sqlname = ">>", propagates_nulls = true)]
868fn bit_shift_right_uint64(lhs: u64, rhs: u32) -> u64 {
869    lhs.wrapping_shr(rhs)
870}
871
872#[sqlfunc(
873    is_monotone = "(true, true)",
874    is_infix_op = true,
875    sqlname = "-",
876    propagates_nulls = true
877)]
878fn sub_int16(a: i16, b: i16) -> Result<i16, EvalError> {
879    a.checked_sub(b).ok_or(EvalError::NumericFieldOverflow)
880}
881
882#[sqlfunc(
883    is_monotone = "(true, true)",
884    is_infix_op = true,
885    sqlname = "-",
886    propagates_nulls = true
887)]
888fn sub_int32(a: i32, b: i32) -> Result<i32, EvalError> {
889    a.checked_sub(b).ok_or(EvalError::NumericFieldOverflow)
890}
891
892#[sqlfunc(
893    is_monotone = "(true, true)",
894    is_infix_op = true,
895    sqlname = "-",
896    propagates_nulls = true
897)]
898fn sub_int64(a: i64, b: i64) -> Result<i64, EvalError> {
899    a.checked_sub(b).ok_or(EvalError::NumericFieldOverflow)
900}
901
902#[sqlfunc(
903    is_monotone = "(true, true)",
904    is_infix_op = true,
905    sqlname = "-",
906    propagates_nulls = true
907)]
908fn sub_uint16(a: u16, b: u16) -> Result<u16, EvalError> {
909    a.checked_sub(b)
910        .ok_or_else(|| EvalError::UInt16OutOfRange(format!("{a} - {b}").into()))
911}
912
913#[sqlfunc(
914    is_monotone = "(true, true)",
915    is_infix_op = true,
916    sqlname = "-",
917    propagates_nulls = true
918)]
919fn sub_uint32(a: u32, b: u32) -> Result<u32, EvalError> {
920    a.checked_sub(b)
921        .ok_or_else(|| EvalError::UInt32OutOfRange(format!("{a} - {b}").into()))
922}
923
924#[sqlfunc(
925    is_monotone = "(true, true)",
926    is_infix_op = true,
927    sqlname = "-",
928    propagates_nulls = true
929)]
930fn sub_uint64(a: u64, b: u64) -> Result<u64, EvalError> {
931    a.checked_sub(b)
932        .ok_or_else(|| EvalError::UInt64OutOfRange(format!("{a} - {b}").into()))
933}
934
935#[sqlfunc(
936    is_monotone = "(true, true)",
937    is_infix_op = true,
938    sqlname = "-",
939    propagates_nulls = true
940)]
941fn sub_float32(a: f32, b: f32) -> Result<f32, EvalError> {
942    let difference = a - b;
943    if difference.is_infinite() && !a.is_infinite() && !b.is_infinite() {
944        Err(EvalError::FloatOverflow)
945    } else {
946        Ok(difference)
947    }
948}
949
950#[sqlfunc(
951    is_monotone = "(true, true)",
952    is_infix_op = true,
953    sqlname = "-",
954    propagates_nulls = true
955)]
956fn sub_float64(a: f64, b: f64) -> Result<f64, EvalError> {
957    let difference = a - b;
958    if difference.is_infinite() && !a.is_infinite() && !b.is_infinite() {
959        Err(EvalError::FloatOverflow)
960    } else {
961        Ok(difference)
962    }
963}
964
965#[sqlfunc(
966    is_monotone = "(true, true)",
967    is_infix_op = true,
968    sqlname = "-",
969    propagates_nulls = true
970)]
971fn sub_numeric(
972    a: OrderedDecimal<Numeric>,
973    b: OrderedDecimal<Numeric>,
974) -> Result<Numeric, EvalError> {
975    let mut cx = numeric::cx_datum();
976    let mut a = a.0;
977    cx.sub(&mut a, &b.0);
978    if cx.status().overflow() {
979        Err(EvalError::FloatOverflow)
980    } else {
981        Ok(a)
982    }
983}
984
985// `age(a, b)` is non-monotone in *both* arguments:
986//
987// * Lex order on `Interval` is `(months, days, micros)`, but the Postgres
988//   `age` algorithm independently subtracts year/month/day/... fields and
989//   then *borrows* across boundaries when a lower field goes negative. With
990//   `b = 2024-02-15` fixed:
991//     a = 2024-03-31  →  age = {1 month, 16 days}
992//     a = 2024-04-01  →  age = {1 month, 15 days}
993//     a = 2024-05-01  →  age = {2 months, 15 days}
994//   As `a` increases past a month boundary, `months` jumps by 1 and `days`
995//   drops, producing a lex-smaller interval than the previous step.
996//
997// * Holding `a` fixed and varying `b`, the result has a V-shape at `a == b`
998//   (sign is flipped when `a < b`):
999//     a = 2024-02-15, b = 2024-02-14  →  age = {0 months, 1 day}
1000//     a = 2024-02-15, b = 2024-02-15  →  age = {0 months, 0 days}
1001//     a = 2024-02-15, b = 2024-02-16  →  age = {0 months, 1 day}
1002#[sqlfunc(sqlname = "age")]
1003fn age_timestamp(
1004    a: CheckedTimestamp<chrono::NaiveDateTime>,
1005    b: CheckedTimestamp<chrono::NaiveDateTime>,
1006) -> Result<Interval, EvalError> {
1007    Ok(a.age(&b)?)
1008}
1009
1010// See `age_timestamp` for why this is not monotone in either argument.
1011#[sqlfunc(sqlname = "age")]
1012fn age_timestamp_tz(
1013    a: CheckedTimestamp<chrono::DateTime<Utc>>,
1014    b: CheckedTimestamp<chrono::DateTime<Utc>>,
1015) -> Result<Interval, EvalError> {
1016    Ok(a.age(&b)?)
1017}
1018
1019#[sqlfunc(is_monotone = "(true, true)", is_infix_op = true, sqlname = "-")]
1020fn sub_timestamp(
1021    a: CheckedTimestamp<NaiveDateTime>,
1022    b: CheckedTimestamp<NaiveDateTime>,
1023) -> Result<Interval, EvalError> {
1024    Interval::from_chrono_duration(a - b)
1025        .map_err(|e| EvalError::IntervalOutOfRange(e.to_string().into()))
1026}
1027
1028#[sqlfunc(is_monotone = "(true, true)", is_infix_op = true, sqlname = "-")]
1029fn sub_timestamp_tz(
1030    a: CheckedTimestamp<chrono::DateTime<Utc>>,
1031    b: CheckedTimestamp<chrono::DateTime<Utc>>,
1032) -> Result<Interval, EvalError> {
1033    Interval::from_chrono_duration(a - b)
1034        .map_err(|e| EvalError::IntervalOutOfRange(e.to_string().into()))
1035}
1036
1037#[sqlfunc(
1038    is_monotone = "(true, true)",
1039    is_infix_op = true,
1040    sqlname = "-",
1041    propagates_nulls = true
1042)]
1043fn sub_date(a: Date, b: Date) -> i32 {
1044    a - b
1045}
1046
1047#[sqlfunc(is_monotone = "(true, true)", is_infix_op = true, sqlname = "-")]
1048fn sub_time(a: chrono::NaiveTime, b: chrono::NaiveTime) -> Result<Interval, EvalError> {
1049    Interval::from_chrono_duration(a - b)
1050        .map_err(|e| EvalError::IntervalOutOfRange(e.to_string().into()))
1051}
1052
1053#[sqlfunc(
1054    is_monotone = "(true, true)",
1055    output_type = "Interval",
1056    is_infix_op = true,
1057    sqlname = "-",
1058    propagates_nulls = true
1059)]
1060fn sub_interval(a: Interval, b: Interval) -> Result<Interval, EvalError> {
1061    b.checked_neg()
1062        .and_then(|b| b.checked_add(&a))
1063        .ok_or_else(|| EvalError::IntervalOutOfRange(format!("{a} - {b}").into()))
1064}
1065
1066// See `add_date_interval` for why this is not monotone in `interval`.
1067#[sqlfunc(
1068    is_monotone = "(true, false)",
1069    is_infix_op = true,
1070    sqlname = "-",
1071    propagates_nulls = true
1072)]
1073fn sub_date_interval(
1074    date: Date,
1075    interval: Interval,
1076) -> Result<CheckedTimestamp<NaiveDateTime>, EvalError> {
1077    let dt = NaiveDate::from(date).and_hms_opt(0, 0, 0).unwrap();
1078    let dt = interval
1079        .months
1080        .checked_neg()
1081        .ok_or_else(|| EvalError::IntervalOutOfRange(interval.months.to_string().into()))
1082        .and_then(|months| add_timestamp_months(&dt, months))?;
1083    let dt = dt
1084        .checked_sub_signed(interval.duration_as_chrono())
1085        .ok_or(EvalError::TimestampOutOfRange)?;
1086    Ok(dt.try_into()?)
1087}
1088
1089#[sqlfunc(
1090    is_monotone = "(false, false)",
1091    is_infix_op = true,
1092    sqlname = "-",
1093    propagates_nulls = true
1094)]
1095fn sub_time_interval(time: chrono::NaiveTime, interval: Interval) -> chrono::NaiveTime {
1096    let (t, _) = time.overflowing_sub_signed(interval.duration_as_chrono());
1097    t
1098}
1099
1100#[sqlfunc(
1101    is_monotone = "(true, true)",
1102    is_infix_op = true,
1103    sqlname = "*",
1104    propagates_nulls = true
1105)]
1106fn mul_int16(a: i16, b: i16) -> Result<i16, EvalError> {
1107    a.checked_mul(b).ok_or(EvalError::NumericFieldOverflow)
1108}
1109
1110#[sqlfunc(
1111    is_monotone = "(true, true)",
1112    is_infix_op = true,
1113    sqlname = "*",
1114    propagates_nulls = true
1115)]
1116fn mul_int32(a: i32, b: i32) -> Result<i32, EvalError> {
1117    a.checked_mul(b).ok_or(EvalError::NumericFieldOverflow)
1118}
1119
1120#[sqlfunc(
1121    is_monotone = "(true, true)",
1122    is_infix_op = true,
1123    sqlname = "*",
1124    propagates_nulls = true
1125)]
1126fn mul_int64(a: i64, b: i64) -> Result<i64, EvalError> {
1127    a.checked_mul(b).ok_or(EvalError::NumericFieldOverflow)
1128}
1129
1130#[sqlfunc(
1131    is_monotone = "(true, true)",
1132    is_infix_op = true,
1133    sqlname = "*",
1134    propagates_nulls = true
1135)]
1136fn mul_uint16(a: u16, b: u16) -> Result<u16, EvalError> {
1137    a.checked_mul(b)
1138        .ok_or_else(|| EvalError::UInt16OutOfRange(format!("{a} * {b}").into()))
1139}
1140
1141#[sqlfunc(
1142    is_monotone = "(true, true)",
1143    is_infix_op = true,
1144    sqlname = "*",
1145    propagates_nulls = true
1146)]
1147fn mul_uint32(a: u32, b: u32) -> Result<u32, EvalError> {
1148    a.checked_mul(b)
1149        .ok_or_else(|| EvalError::UInt32OutOfRange(format!("{a} * {b}").into()))
1150}
1151
1152#[sqlfunc(
1153    is_monotone = "(true, true)",
1154    is_infix_op = true,
1155    sqlname = "*",
1156    propagates_nulls = true
1157)]
1158fn mul_uint64(a: u64, b: u64) -> Result<u64, EvalError> {
1159    a.checked_mul(b)
1160        .ok_or_else(|| EvalError::UInt64OutOfRange(format!("{a} * {b}").into()))
1161}
1162
1163#[sqlfunc(
1164    is_monotone = (true, true),
1165    is_infinity_monotone = false,
1166    is_infix_op = true,
1167    sqlname = "*",
1168    propagates_nulls = true
1169)]
1170fn mul_float32(a: f32, b: f32) -> Result<f32, EvalError> {
1171    let product = a * b;
1172    if product.is_infinite() && !a.is_infinite() && !b.is_infinite() {
1173        Err(EvalError::FloatOverflow)
1174    } else if product == 0.0f32 && a != 0.0f32 && b != 0.0f32 {
1175        Err(EvalError::FloatUnderflow)
1176    } else {
1177        Ok(product)
1178    }
1179}
1180
1181#[sqlfunc(
1182    is_monotone = "(true, true)",
1183    is_infinity_monotone = false,
1184    is_infix_op = true,
1185    sqlname = "*",
1186    propagates_nulls = true
1187)]
1188fn mul_float64(a: f64, b: f64) -> Result<f64, EvalError> {
1189    let product = a * b;
1190    if product.is_infinite() && !a.is_infinite() && !b.is_infinite() {
1191        Err(EvalError::FloatOverflow)
1192    } else if product == 0.0f64 && a != 0.0f64 && b != 0.0f64 {
1193        Err(EvalError::FloatUnderflow)
1194    } else {
1195        Ok(product)
1196    }
1197}
1198
1199#[sqlfunc(
1200    is_monotone = "(true, true)",
1201    is_infinity_monotone = false,
1202    is_infix_op = true,
1203    sqlname = "*",
1204    propagates_nulls = true
1205)]
1206fn mul_numeric(mut a: Numeric, b: Numeric) -> Result<Numeric, EvalError> {
1207    let mut cx = numeric::cx_datum();
1208    cx.mul(&mut a, &b);
1209    let cx_status = cx.status();
1210    if cx_status.overflow() {
1211        Err(EvalError::FloatOverflow)
1212    } else if cx_status.subnormal() {
1213        Err(EvalError::FloatUnderflow)
1214    } else {
1215        numeric::munge_numeric(&mut a).unwrap();
1216        Ok(a)
1217    }
1218}
1219
1220#[sqlfunc(
1221    is_monotone = "(false, false)",
1222    is_infix_op = true,
1223    sqlname = "*",
1224    propagates_nulls = true
1225)]
1226fn mul_interval(a: Interval, b: f64) -> Result<Interval, EvalError> {
1227    a.checked_mul(b)
1228        .ok_or_else(|| EvalError::IntervalOutOfRange(format!("{a} * {b}").into()))
1229}
1230
1231#[sqlfunc(
1232    is_monotone = "(true, false)",
1233    is_infix_op = true,
1234    sqlname = "/",
1235    propagates_nulls = true
1236)]
1237fn div_int16(a: i16, b: i16) -> Result<i16, EvalError> {
1238    if b == 0 {
1239        Err(EvalError::DivisionByZero)
1240    } else {
1241        a.checked_div(b)
1242            .ok_or_else(|| EvalError::Int16OutOfRange(format!("{a} / {b}").into()))
1243    }
1244}
1245
1246#[sqlfunc(
1247    is_monotone = "(true, false)",
1248    is_infix_op = true,
1249    sqlname = "/",
1250    propagates_nulls = true
1251)]
1252fn div_int32(a: i32, b: i32) -> Result<i32, EvalError> {
1253    if b == 0 {
1254        Err(EvalError::DivisionByZero)
1255    } else {
1256        a.checked_div(b)
1257            .ok_or_else(|| EvalError::Int32OutOfRange(format!("{a} / {b}").into()))
1258    }
1259}
1260
1261#[sqlfunc(
1262    is_monotone = "(true, false)",
1263    is_infix_op = true,
1264    sqlname = "/",
1265    propagates_nulls = true
1266)]
1267fn div_int64(a: i64, b: i64) -> Result<i64, EvalError> {
1268    if b == 0 {
1269        Err(EvalError::DivisionByZero)
1270    } else {
1271        a.checked_div(b)
1272            .ok_or_else(|| EvalError::Int64OutOfRange(format!("{a} / {b}").into()))
1273    }
1274}
1275
1276#[sqlfunc(
1277    is_monotone = "(true, false)",
1278    is_infix_op = true,
1279    sqlname = "/",
1280    propagates_nulls = true
1281)]
1282fn div_uint16(a: u16, b: u16) -> Result<u16, EvalError> {
1283    if b == 0 {
1284        Err(EvalError::DivisionByZero)
1285    } else {
1286        Ok(a / b)
1287    }
1288}
1289
1290#[sqlfunc(
1291    is_monotone = "(true, false)",
1292    is_infix_op = true,
1293    sqlname = "/",
1294    propagates_nulls = true
1295)]
1296fn div_uint32(a: u32, b: u32) -> Result<u32, EvalError> {
1297    if b == 0 {
1298        Err(EvalError::DivisionByZero)
1299    } else {
1300        Ok(a / b)
1301    }
1302}
1303
1304#[sqlfunc(
1305    is_monotone = "(true, false)",
1306    is_infix_op = true,
1307    sqlname = "/",
1308    propagates_nulls = true
1309)]
1310fn div_uint64(a: u64, b: u64) -> Result<u64, EvalError> {
1311    if b == 0 {
1312        Err(EvalError::DivisionByZero)
1313    } else {
1314        Ok(a / b)
1315    }
1316}
1317
1318#[sqlfunc(
1319    is_monotone = "(true, false)",
1320    is_infinity_monotone = false,
1321    is_infix_op = true,
1322    sqlname = "/",
1323    propagates_nulls = true
1324)]
1325fn div_float32(a: f32, b: f32) -> Result<f32, EvalError> {
1326    if b == 0.0f32 && !a.is_nan() {
1327        Err(EvalError::DivisionByZero)
1328    } else {
1329        let quotient = a / b;
1330        if quotient.is_infinite() && !a.is_infinite() {
1331            Err(EvalError::FloatOverflow)
1332        } else if quotient == 0.0f32 && a != 0.0f32 && !b.is_infinite() {
1333            Err(EvalError::FloatUnderflow)
1334        } else {
1335            Ok(quotient)
1336        }
1337    }
1338}
1339
1340#[sqlfunc(
1341    is_monotone = "(true, false)",
1342    is_infinity_monotone = false,
1343    is_infix_op = true,
1344    sqlname = "/",
1345    propagates_nulls = true
1346)]
1347fn div_float64(a: f64, b: f64) -> Result<f64, EvalError> {
1348    if b == 0.0f64 && !a.is_nan() {
1349        Err(EvalError::DivisionByZero)
1350    } else {
1351        let quotient = a / b;
1352        if quotient.is_infinite() && !a.is_infinite() {
1353            Err(EvalError::FloatOverflow)
1354        } else if quotient == 0.0f64 && a != 0.0f64 && !b.is_infinite() {
1355            Err(EvalError::FloatUnderflow)
1356        } else {
1357            Ok(quotient)
1358        }
1359    }
1360}
1361
1362#[sqlfunc(
1363    is_monotone = "(true, false)",
1364    is_infinity_monotone = false,
1365    is_infix_op = true,
1366    sqlname = "/",
1367    propagates_nulls = true
1368)]
1369fn div_numeric(mut a: Numeric, b: Numeric) -> Result<Numeric, EvalError> {
1370    let mut cx = numeric::cx_datum();
1371
1372    cx.div(&mut a, &b);
1373    let cx_status = cx.status();
1374
1375    // checking the status for division by zero errors is insufficient because
1376    // the underlying library treats 0/0 as undefined and not division by zero.
1377    if b.is_zero() {
1378        Err(EvalError::DivisionByZero)
1379    } else if cx_status.overflow() {
1380        Err(EvalError::FloatOverflow)
1381    } else if cx_status.subnormal() {
1382        Err(EvalError::FloatUnderflow)
1383    } else {
1384        numeric::munge_numeric(&mut a).unwrap();
1385        Ok(a)
1386    }
1387}
1388
1389#[sqlfunc(
1390    is_monotone = "(false, false)",
1391    is_infix_op = true,
1392    sqlname = "/",
1393    propagates_nulls = true
1394)]
1395fn div_interval(a: Interval, b: f64) -> Result<Interval, EvalError> {
1396    if b == 0.0 {
1397        Err(EvalError::DivisionByZero)
1398    } else {
1399        a.checked_div(b)
1400            .ok_or_else(|| EvalError::IntervalOutOfRange(format!("{a} / {b}").into()))
1401    }
1402}
1403
1404#[sqlfunc(is_infix_op = true, sqlname = "%", propagates_nulls = true)]
1405fn mod_int16(a: i16, b: i16) -> Result<i16, EvalError> {
1406    if b == 0 {
1407        Err(EvalError::DivisionByZero)
1408    } else {
1409        Ok(a.checked_rem(b).unwrap_or(0))
1410    }
1411}
1412
1413#[sqlfunc(is_infix_op = true, sqlname = "%", propagates_nulls = true)]
1414fn mod_int32(a: i32, b: i32) -> Result<i32, EvalError> {
1415    if b == 0 {
1416        Err(EvalError::DivisionByZero)
1417    } else {
1418        Ok(a.checked_rem(b).unwrap_or(0))
1419    }
1420}
1421
1422#[sqlfunc(is_infix_op = true, sqlname = "%", propagates_nulls = true)]
1423fn mod_int64(a: i64, b: i64) -> Result<i64, EvalError> {
1424    if b == 0 {
1425        Err(EvalError::DivisionByZero)
1426    } else {
1427        Ok(a.checked_rem(b).unwrap_or(0))
1428    }
1429}
1430
1431#[sqlfunc(is_infix_op = true, sqlname = "%", propagates_nulls = true)]
1432fn mod_uint16(a: u16, b: u16) -> Result<u16, EvalError> {
1433    if b == 0 {
1434        Err(EvalError::DivisionByZero)
1435    } else {
1436        Ok(a % b)
1437    }
1438}
1439
1440#[sqlfunc(is_infix_op = true, sqlname = "%", propagates_nulls = true)]
1441fn mod_uint32(a: u32, b: u32) -> Result<u32, EvalError> {
1442    if b == 0 {
1443        Err(EvalError::DivisionByZero)
1444    } else {
1445        Ok(a % b)
1446    }
1447}
1448
1449#[sqlfunc(is_infix_op = true, sqlname = "%", propagates_nulls = true)]
1450fn mod_uint64(a: u64, b: u64) -> Result<u64, EvalError> {
1451    if b == 0 {
1452        Err(EvalError::DivisionByZero)
1453    } else {
1454        Ok(a % b)
1455    }
1456}
1457
1458#[sqlfunc(is_infix_op = true, sqlname = "%", propagates_nulls = true)]
1459fn mod_float32(a: f32, b: f32) -> Result<f32, EvalError> {
1460    if b == 0.0 {
1461        Err(EvalError::DivisionByZero)
1462    } else {
1463        Ok(a % b)
1464    }
1465}
1466
1467#[sqlfunc(is_infix_op = true, sqlname = "%", propagates_nulls = true)]
1468fn mod_float64(a: f64, b: f64) -> Result<f64, EvalError> {
1469    if b == 0.0 {
1470        Err(EvalError::DivisionByZero)
1471    } else {
1472        Ok(a % b)
1473    }
1474}
1475
1476#[sqlfunc(is_infix_op = true, sqlname = "%", propagates_nulls = true)]
1477fn mod_numeric(mut a: Numeric, b: Numeric) -> Result<Numeric, EvalError> {
1478    if b.is_zero() {
1479        return Err(EvalError::DivisionByZero);
1480    }
1481    let mut cx = numeric::cx_datum();
1482    // Postgres does _not_ use IEEE 754-style remainder
1483    cx.rem(&mut a, &b);
1484    numeric::munge_numeric(&mut a).unwrap();
1485    Ok(a)
1486}
1487
1488fn neg_interval_inner(a: Interval) -> Result<Interval, EvalError> {
1489    a.checked_neg()
1490        .ok_or_else(|| EvalError::IntervalOutOfRange(a.to_string().into()))
1491}
1492
1493fn log_guard_numeric(val: &Numeric, function_name: &str) -> Result<(), EvalError> {
1494    if val.is_negative() {
1495        return Err(EvalError::NegativeOutOfDomain(function_name.into()));
1496    }
1497    if val.is_zero() {
1498        return Err(EvalError::ZeroOutOfDomain(function_name.into()));
1499    }
1500    Ok(())
1501}
1502
1503#[sqlfunc(sqlname = "log", propagates_nulls = true)]
1504fn log_base_numeric(mut a: Numeric, mut b: Numeric) -> Result<Numeric, EvalError> {
1505    log_guard_numeric(&a, "log")?;
1506    log_guard_numeric(&b, "log")?;
1507    let mut cx = numeric::cx_datum();
1508    cx.ln(&mut a);
1509    cx.ln(&mut b);
1510    cx.div(&mut b, &a);
1511    if a.is_zero() {
1512        Err(EvalError::DivisionByZero)
1513    } else {
1514        // This division can result in slightly wrong answers due to the
1515        // limitation of dividing irrational numbers. To correct that, see if
1516        // rounding off the value from its `numeric::NUMERIC_DATUM_MAX_PRECISION
1517        // - 1`th position results in an integral value.
1518        cx.set_precision(usize::from(numeric::NUMERIC_DATUM_MAX_PRECISION - 1))
1519            .expect("reducing precision below max always succeeds");
1520        let mut integral_check = b.clone();
1521
1522        // `reduce` rounds to the context's final digit when the number of
1523        // digits in its argument exceeds its precision. We've contrived that to
1524        // happen by shrinking the context's precision by 1.
1525        cx.reduce(&mut integral_check);
1526
1527        // Reduced integral values always have a non-negative exponent.
1528        let mut b = if integral_check.exponent() >= 0 {
1529            // We believe our result should have been an integral
1530            integral_check
1531        } else {
1532            b
1533        };
1534
1535        numeric::munge_numeric(&mut b).unwrap();
1536        Ok(b)
1537    }
1538}
1539
1540#[sqlfunc(propagates_nulls = true)]
1541fn power(a: f64, b: f64) -> Result<f64, EvalError> {
1542    if a == 0.0 && b.is_sign_negative() {
1543        return Err(EvalError::Undefined(
1544            "zero raised to a negative power".into(),
1545        ));
1546    }
1547    if a.is_sign_negative() && b.fract() != 0.0 {
1548        // Equivalent to PG error:
1549        // > a negative number raised to a non-integer power yields a complex result
1550        return Err(EvalError::ComplexOutOfRange("pow".into()));
1551    }
1552    let res = a.powf(b);
1553    if res.is_infinite() {
1554        return Err(EvalError::FloatOverflow);
1555    }
1556    if res == 0.0 && a != 0.0 {
1557        return Err(EvalError::FloatUnderflow);
1558    }
1559    Ok(res)
1560}
1561
1562#[sqlfunc(propagates_nulls = true)]
1563fn uuid_generate_v5(a: uuid::Uuid, b: &str) -> uuid::Uuid {
1564    uuid::Uuid::new_v5(&a, b.as_bytes())
1565}
1566
1567#[sqlfunc(output_type = "Numeric", propagates_nulls = true)]
1568fn power_numeric(mut a: Numeric, b: Numeric) -> Result<Numeric, EvalError> {
1569    if a.is_zero() {
1570        if b.is_zero() {
1571            return Ok(Numeric::from(1));
1572        }
1573        if b.is_negative() {
1574            return Err(EvalError::Undefined(
1575                "zero raised to a negative power".into(),
1576            ));
1577        }
1578    }
1579    if a.is_negative() && b.exponent() < 0 {
1580        // Equivalent to PG error:
1581        // > a negative number raised to a non-integer power yields a complex result
1582        return Err(EvalError::ComplexOutOfRange("pow".into()));
1583    }
1584    let mut cx = numeric::cx_datum();
1585    cx.pow(&mut a, &b);
1586    let cx_status = cx.status();
1587    if cx_status.overflow() || (cx_status.invalid_operation() && !b.is_negative()) {
1588        Err(EvalError::FloatOverflow)
1589    } else if cx_status.subnormal() || cx_status.invalid_operation() {
1590        Err(EvalError::FloatUnderflow)
1591    } else {
1592        numeric::munge_numeric(&mut a).unwrap();
1593        Ok(a)
1594    }
1595}
1596
1597#[sqlfunc(propagates_nulls = true)]
1598fn get_bit(bytes: &[u8], index: i32) -> Result<i32, EvalError> {
1599    let err = EvalError::IndexOutOfRange {
1600        provided: index,
1601        valid_end: i32::try_from(bytes.len().saturating_mul(8)).unwrap_or(i32::MAX) - 1,
1602    };
1603
1604    let index = usize::try_from(index).map_err(|_| err.clone())?;
1605
1606    let byte_index = index / 8;
1607    let bit_index = index % 8;
1608
1609    let i = bytes
1610        .get(byte_index)
1611        .map(|b| (*b >> bit_index) & 1)
1612        .ok_or(err)?;
1613    assert!(i == 0 || i == 1);
1614    Ok(i32::from(i))
1615}
1616
1617#[sqlfunc(propagates_nulls = true)]
1618fn get_byte(bytes: &[u8], index: i32) -> Result<i32, EvalError> {
1619    let err = EvalError::IndexOutOfRange {
1620        provided: index,
1621        valid_end: i32::try_from(bytes.len()).unwrap_or(i32::MAX) - 1,
1622    };
1623    let i: &u8 = bytes
1624        .get(usize::try_from(index).map_err(|_| err.clone())?)
1625        .ok_or(err)?;
1626    Ok(i32::from(*i))
1627}
1628
1629#[sqlfunc(sqlname = "constant_time_compare_bytes", propagates_nulls = true)]
1630pub fn constant_time_eq_bytes(a: &[u8], b: &[u8]) -> bool {
1631    verify_slices_are_equal(a, b).is_ok()
1632}
1633
1634#[sqlfunc(sqlname = "constant_time_compare_strings", propagates_nulls = true)]
1635pub fn constant_time_eq_string(a: &str, b: &str) -> bool {
1636    verify_slices_are_equal(a.as_bytes(), b.as_bytes()).is_ok()
1637}
1638
1639#[sqlfunc(is_infix_op = true, sqlname = "@>", propagates_nulls = true)]
1640fn range_contains_i32<'a>(a: Range<Datum<'a>>, b: i32) -> bool {
1641    a.contains_elem(&b)
1642}
1643
1644#[sqlfunc(is_infix_op = true, sqlname = "@>", propagates_nulls = true)]
1645fn range_contains_i64<'a>(a: Range<Datum<'a>>, elem: i64) -> bool {
1646    a.contains_elem(&elem)
1647}
1648
1649#[sqlfunc(is_infix_op = true, sqlname = "@>", propagates_nulls = true)]
1650fn range_contains_date<'a>(a: Range<Datum<'a>>, elem: Date) -> bool {
1651    a.contains_elem(&elem)
1652}
1653
1654#[sqlfunc(is_infix_op = true, sqlname = "@>", propagates_nulls = true)]
1655fn range_contains_numeric<'a>(a: Range<Datum<'a>>, elem: OrderedDecimal<Numeric>) -> bool {
1656    a.contains_elem(&elem)
1657}
1658
1659#[sqlfunc(is_infix_op = true, sqlname = "@>", propagates_nulls = true)]
1660fn range_contains_timestamp<'a>(
1661    a: Range<Datum<'a>>,
1662    elem: CheckedTimestamp<NaiveDateTime>,
1663) -> bool {
1664    a.contains_elem(&elem)
1665}
1666
1667#[sqlfunc(is_infix_op = true, sqlname = "@>", propagates_nulls = true)]
1668fn range_contains_timestamp_tz<'a>(
1669    a: Range<Datum<'a>>,
1670    elem: CheckedTimestamp<DateTime<Utc>>,
1671) -> bool {
1672    a.contains_elem(&elem)
1673}
1674
1675#[sqlfunc(is_infix_op = true, sqlname = "<@", propagates_nulls = true)]
1676fn range_contains_i32_rev<'a>(a: Range<Datum<'a>>, b: i32) -> bool {
1677    a.contains_elem(&b)
1678}
1679
1680#[sqlfunc(is_infix_op = true, sqlname = "<@", propagates_nulls = true)]
1681fn range_contains_i64_rev<'a>(a: Range<Datum<'a>>, elem: i64) -> bool {
1682    a.contains_elem(&elem)
1683}
1684
1685#[sqlfunc(is_infix_op = true, sqlname = "<@", propagates_nulls = true)]
1686fn range_contains_date_rev<'a>(a: Range<Datum<'a>>, elem: Date) -> bool {
1687    a.contains_elem(&elem)
1688}
1689
1690#[sqlfunc(is_infix_op = true, sqlname = "<@", propagates_nulls = true)]
1691fn range_contains_numeric_rev<'a>(a: Range<Datum<'a>>, elem: OrderedDecimal<Numeric>) -> bool {
1692    a.contains_elem(&elem)
1693}
1694
1695#[sqlfunc(is_infix_op = true, sqlname = "<@", propagates_nulls = true)]
1696fn range_contains_timestamp_rev<'a>(
1697    a: Range<Datum<'a>>,
1698    elem: CheckedTimestamp<NaiveDateTime>,
1699) -> bool {
1700    a.contains_elem(&elem)
1701}
1702
1703#[sqlfunc(is_infix_op = true, sqlname = "<@", propagates_nulls = true)]
1704fn range_contains_timestamp_tz_rev<'a>(
1705    a: Range<Datum<'a>>,
1706    elem: CheckedTimestamp<DateTime<Utc>>,
1707) -> bool {
1708    a.contains_elem(&elem)
1709}
1710
1711/// Macro to define binary function for various range operations.
1712/// Parameters:
1713/// 1. Unique binary function symbol.
1714/// 2. Range function symbol.
1715/// 3. SQL name for the function.
1716macro_rules! range_fn {
1717    ($fn:expr, $range_fn:expr, $sqlname:expr) => {
1718        paste::paste! {
1719
1720            #[sqlfunc(
1721                output_type = "bool",
1722                is_infix_op = true,
1723                sqlname = $sqlname,
1724                propagates_nulls = true
1725            )]
1726            fn [< range_ $fn >]<'a>(a: Datum<'a>, b: Datum<'a>) -> Datum<'a>
1727            {
1728                if a.is_null() || b.is_null() { return Datum::Null }
1729                let l = a.unwrap_range();
1730                let r = b.unwrap_range();
1731                Datum::from(Range::<Datum<'a>>::$range_fn(&l, &r))
1732            }
1733        }
1734    };
1735}
1736
1737// RangeContainsRange is either @> or <@ depending on the order of the arguments.
1738// It doesn't influence the result, but it does influence the display string.
1739range_fn!(contains_range, contains_range, "@>");
1740range_fn!(contains_range_rev, contains_range, "<@");
1741range_fn!(overlaps, overlaps, "&&");
1742range_fn!(after, after, ">>");
1743range_fn!(before, before, "<<");
1744range_fn!(overleft, overleft, "&<");
1745range_fn!(overright, overright, "&>");
1746range_fn!(adjacent, adjacent, "-|-");
1747
1748#[sqlfunc(is_infix_op = true, sqlname = "+")]
1749fn range_union<T: Copy + Ord>(l: Range<T>, r: Range<T>) -> Result<Range<T>, EvalError> {
1750    Ok(l.union(&r)?)
1751}
1752
1753#[sqlfunc(is_infix_op = true, sqlname = "*")]
1754fn range_intersection<T: Copy + Ord>(l: Range<T>, r: Range<T>) -> Range<T> {
1755    l.intersection(&r)
1756}
1757
1758#[sqlfunc(
1759    output_type_expr = "input_types[0].scalar_type.without_modifiers().nullable(true)",
1760    is_infix_op = true,
1761    sqlname = "-",
1762    propagates_nulls = true
1763)]
1764fn range_difference<'a>(
1765    l: Range<Datum<'a>>,
1766    r: Range<Datum<'a>>,
1767) -> Result<Range<Datum<'a>>, EvalError> {
1768    Ok(l.difference(&r)?)
1769}
1770
1771#[sqlfunc(is_infix_op = true, sqlname = "=", negate = NotEq)]
1772fn eq<'a>(a: ExcludeNull<Datum<'a>>, b: ExcludeNull<Datum<'a>>) -> bool {
1773    // SQL equality demands that if either input is null, then the result should be null. However,
1774    // we don't need to handle this case here; it is handled when `BinaryFunc::eval` checks
1775    // `propagates_nulls`.
1776    a == b
1777}
1778
1779#[sqlfunc(is_infix_op = true, sqlname = "!=", negate = Eq)]
1780fn not_eq<'a>(a: ExcludeNull<Datum<'a>>, b: ExcludeNull<Datum<'a>>) -> bool {
1781    a != b
1782}
1783
1784#[sqlfunc(
1785    is_monotone = "(true, true)",
1786    is_infix_op = true,
1787    sqlname = "<",
1788    negate = Gte
1789)]
1790fn lt<'a>(a: ExcludeNull<Datum<'a>>, b: ExcludeNull<Datum<'a>>) -> bool {
1791    a < b
1792}
1793
1794#[sqlfunc(
1795    is_monotone = "(true, true)",
1796    is_infix_op = true,
1797    sqlname = "<=",
1798    negate = Gt
1799)]
1800fn lte<'a>(a: ExcludeNull<Datum<'a>>, b: ExcludeNull<Datum<'a>>) -> bool {
1801    a <= b
1802}
1803
1804#[sqlfunc(
1805    is_monotone = "(true, true)",
1806    is_infix_op = true,
1807    sqlname = ">",
1808    negate = Lte
1809)]
1810fn gt<'a>(a: ExcludeNull<Datum<'a>>, b: ExcludeNull<Datum<'a>>) -> bool {
1811    a > b
1812}
1813
1814#[sqlfunc(
1815    is_monotone = "(true, true)",
1816    is_infix_op = true,
1817    sqlname = ">=",
1818    negate = Lt
1819)]
1820fn gte<'a>(a: ExcludeNull<Datum<'a>>, b: ExcludeNull<Datum<'a>>) -> bool {
1821    a >= b
1822}
1823
1824#[sqlfunc(sqlname = "tocharts", propagates_nulls = true)]
1825fn to_char_timestamp_format(ts: CheckedTimestamp<chrono::NaiveDateTime>, format: &str) -> String {
1826    let fmt = DateTimeFormat::compile(format);
1827    fmt.render(&*ts)
1828}
1829
1830#[sqlfunc(sqlname = "tochartstz", propagates_nulls = true)]
1831fn to_char_timestamp_tz_format(
1832    ts: CheckedTimestamp<chrono::DateTime<Utc>>,
1833    format: &str,
1834) -> String {
1835    let fmt = DateTimeFormat::compile(format);
1836    fmt.render(&*ts)
1837}
1838
1839#[sqlfunc(sqlname = "->", is_infix_op = true)]
1840fn jsonb_get_int64<'a>(a: JsonbRef<'a>, i: i64) -> Option<JsonbRef<'a>> {
1841    match a.into_datum() {
1842        Datum::List(list) => {
1843            let i = if i >= 0 {
1844                usize::cast_from(i.unsigned_abs())
1845            } else {
1846                // index backwards from the end
1847                let i = usize::cast_from(i.unsigned_abs());
1848                (list.iter().count()).wrapping_sub(i)
1849            };
1850            let v = list.iter().nth(i)?;
1851            // `v` should be valid jsonb because it came from a jsonb list, but we don't
1852            // panic on mismatch to avoid bringing down the whole system on corrupt data.
1853            // Instead, we'll return None.
1854            JsonbRef::try_from_result(Ok::<_, ()>(v)).ok()
1855        }
1856        Datum::Map(_) => None,
1857        _ => {
1858            // I have no idea why postgres does this, but we're stuck with it
1859            (i == 0 || i == -1).then_some(a)
1860        }
1861    }
1862}
1863
1864#[sqlfunc(sqlname = "->>", is_infix_op = true)]
1865fn jsonb_get_int64_stringify<'a>(
1866    a: JsonbRef<'a>,
1867    i: i64,
1868    temp_storage: &'a RowArena,
1869) -> Option<&'a str> {
1870    let json = jsonb_get_int64(a, i)?;
1871    jsonb_stringify(json.into_datum(), temp_storage)
1872}
1873
1874#[sqlfunc(sqlname = "->", is_infix_op = true)]
1875fn jsonb_get_string<'a>(a: JsonbRef<'a>, k: &str) -> Option<JsonbRef<'a>> {
1876    let dict = DatumMap::try_from_result(Ok::<_, ()>(a.into_datum())).ok()?;
1877    let v = dict.iter().find(|(k2, _v)| k == *k2).map(|(_k, v)| v)?;
1878    JsonbRef::try_from_result(Ok::<_, ()>(v)).ok()
1879}
1880
1881#[sqlfunc(sqlname = "->>", is_infix_op = true)]
1882fn jsonb_get_string_stringify<'a>(
1883    a: JsonbRef<'a>,
1884    k: &str,
1885    temp_storage: &'a RowArena,
1886) -> Option<&'a str> {
1887    let v = jsonb_get_string(a, k)?;
1888    jsonb_stringify(v.into_datum(), temp_storage)
1889}
1890
1891#[sqlfunc(sqlname = "#>", is_infix_op = true)]
1892fn jsonb_get_path<'a>(mut json: JsonbRef<'a>, b: Array<'a>) -> Option<JsonbRef<'a>> {
1893    let path = b.elements();
1894    for key in path.iter() {
1895        let key = match key {
1896            Datum::String(s) => s,
1897            Datum::Null => return None,
1898            _ => unreachable!("keys in jsonb_get_path known to be strings"),
1899        };
1900        let v = match json.into_datum() {
1901            Datum::Map(map) => map.iter().find(|(k, _)| key == *k).map(|(_k, v)| v),
1902            Datum::List(list) => {
1903                let i = strconv::parse_int64(key).ok()?;
1904                let i = if i >= 0 {
1905                    usize::cast_from(i.unsigned_abs())
1906                } else {
1907                    // index backwards from the end
1908                    let i = usize::cast_from(i.unsigned_abs());
1909                    (list.iter().count()).wrapping_sub(i)
1910                };
1911                list.iter().nth(i)
1912            }
1913            _ => return None,
1914        }?;
1915        json = JsonbRef::try_from_result(Ok::<_, ()>(v)).ok()?;
1916    }
1917    Some(json)
1918}
1919
1920#[sqlfunc(sqlname = "#>>", is_infix_op = true)]
1921fn jsonb_get_path_stringify<'a>(
1922    a: JsonbRef<'a>,
1923    b: Array<'a>,
1924    temp_storage: &'a RowArena,
1925) -> Option<&'a str> {
1926    let json = jsonb_get_path(a, b)?;
1927    jsonb_stringify(json.into_datum(), temp_storage)
1928}
1929
1930#[sqlfunc(is_infix_op = true, sqlname = "?")]
1931fn jsonb_contains_string<'a>(a: JsonbRef<'a>, k: &str) -> bool {
1932    // https://www.postgresql.org/docs/current/datatype-json.html#JSON-CONTAINMENT
1933    // When the left operand is SQL NULL (NULL::jsonb), JsonbRef::try_from_result rejects it,
1934    // so the binary evaluator never calls this function and returns NULL (see binary.rs).
1935    // So, this function only runs for non-null jsonb; a.into_datum() never sees Datum::Null.
1936    match a.into_datum() {
1937        Datum::List(list) => list.iter().any(|k2| Datum::from(k) == k2),
1938        Datum::Map(dict) => dict.iter().any(|(k2, _v)| k == k2),
1939        Datum::String(string) => string == k,
1940        _ => false,
1941    }
1942}
1943
1944#[sqlfunc(is_infix_op = true, sqlname = "?", propagates_nulls = true)]
1945// Map keys are always text.
1946fn map_contains_key<'a>(map: DatumMap<'a>, k: &str) -> bool {
1947    map.iter().any(|(k2, _v)| k == k2)
1948}
1949
1950#[sqlfunc(is_infix_op = true, sqlname = "?&")]
1951fn map_contains_all_keys<'a>(map: DatumMap<'a>, keys: Array<'a>) -> bool {
1952    keys.elements()
1953        .iter()
1954        .all(|key| !key.is_null() && map.iter().any(|(k, _v)| k == key.unwrap_str()))
1955}
1956
1957#[sqlfunc(is_infix_op = true, sqlname = "?|", propagates_nulls = true)]
1958fn map_contains_any_keys<'a>(map: DatumMap<'a>, keys: Array<'a>) -> bool {
1959    keys.elements()
1960        .iter()
1961        .any(|key| !key.is_null() && map.iter().any(|(k, _v)| k == key.unwrap_str()))
1962}
1963
1964#[sqlfunc(is_infix_op = true, sqlname = "@>", propagates_nulls = true)]
1965fn map_contains_map<'a>(map_a: DatumMap<'a>, b: DatumMap<'a>) -> bool {
1966    b.iter().all(|(b_key, b_val)| {
1967        map_a
1968            .iter()
1969            .any(|(a_key, a_val)| (a_key == b_key) && (a_val == b_val))
1970    })
1971}
1972
1973#[sqlfunc(is_infix_op = true, sqlname = "->", propagates_nulls = true)]
1974fn map_get_value<'a, T: FromDatum<'a>>(a: DatumMap<'a, T>, target_key: &str) -> Option<T> {
1975    a.typed_iter()
1976        .find(|(key, _v)| target_key == *key)
1977        .map(|(_k, v)| v)
1978}
1979
1980#[sqlfunc(is_infix_op = true, sqlname = "@>")]
1981fn list_contains_list<'a>(a: ExcludeNull<DatumList<'a>>, b: ExcludeNull<DatumList<'a>>) -> bool {
1982    // NULL is never equal to NULL. If NULL is an element of b, b cannot be contained in a, even if a contains NULL.
1983    if b.iter().contains(&Datum::Null) {
1984        false
1985    } else {
1986        b.iter()
1987            .all(|item_b| a.iter().any(|item_a| item_a == item_b))
1988    }
1989}
1990
1991#[sqlfunc(is_infix_op = true, sqlname = "<@")]
1992fn list_contains_list_rev<'a>(
1993    a: ExcludeNull<DatumList<'a>>,
1994    b: ExcludeNull<DatumList<'a>>,
1995) -> bool {
1996    list_contains_list(b, a)
1997}
1998
1999// TODO(jamii) nested loops are possibly not the fastest way to do this
2000#[sqlfunc(is_infix_op = true, sqlname = "@>")]
2001fn jsonb_contains_jsonb<'a>(a: JsonbRef<'a>, b: JsonbRef<'a>) -> bool {
2002    // https://www.postgresql.org/docs/current/datatype-json.html#JSON-CONTAINMENT
2003    fn contains(a: Datum, b: Datum, at_top_level: bool) -> bool {
2004        match (a, b) {
2005            (Datum::JsonNull, Datum::JsonNull) => true,
2006            (Datum::False, Datum::False) => true,
2007            (Datum::True, Datum::True) => true,
2008            (Datum::Numeric(a), Datum::Numeric(b)) => a == b,
2009            (Datum::String(a), Datum::String(b)) => a == b,
2010            (Datum::List(a), Datum::List(b)) => b
2011                .iter()
2012                .all(|b_elem| a.iter().any(|a_elem| contains(a_elem, b_elem, false))),
2013            (Datum::Map(a), Datum::Map(b)) => b.iter().all(|(b_key, b_val)| {
2014                a.iter()
2015                    .any(|(a_key, a_val)| (a_key == b_key) && contains(a_val, b_val, false))
2016            }),
2017
2018            // fun special case
2019            (Datum::List(a), b) => {
2020                at_top_level && a.iter().any(|a_elem| contains(a_elem, b, false))
2021            }
2022
2023            _ => false,
2024        }
2025    }
2026    contains(a.into_datum(), b.into_datum(), true)
2027}
2028
2029#[sqlfunc(is_infix_op = true, sqlname = "||")]
2030fn jsonb_concat<'a>(
2031    a: JsonbRef<'a>,
2032    b: JsonbRef<'a>,
2033    temp_storage: &'a RowArena,
2034) -> Option<JsonbRef<'a>> {
2035    let res = match (a.into_datum(), b.into_datum()) {
2036        (Datum::Map(dict_a), Datum::Map(dict_b)) => {
2037            let mut pairs = dict_b.iter().chain(dict_a.iter()).collect::<Vec<_>>();
2038            // stable sort, so if keys collide dedup prefers dict_b
2039            pairs.sort_by(|(k1, _v1), (k2, _v2)| k1.cmp(k2));
2040            pairs.dedup_by(|(k1, _v1), (k2, _v2)| k1 == k2);
2041            temp_storage.make_datum(|packer| packer.push_dict(pairs))
2042        }
2043        (Datum::List(list_a), Datum::List(list_b)) => {
2044            let elems = list_a.iter().chain(list_b.iter());
2045            temp_storage.make_datum(|packer| packer.push_list(elems))
2046        }
2047        (Datum::List(list_a), b) => {
2048            let elems = list_a.iter().chain(Some(b));
2049            temp_storage.make_datum(|packer| packer.push_list(elems))
2050        }
2051        (a, Datum::List(list_b)) => {
2052            let elems = Some(a).into_iter().chain(list_b.iter());
2053            temp_storage.make_datum(|packer| packer.push_list(elems))
2054        }
2055        _ => return None,
2056    };
2057    Some(JsonbRef::from_datum(res))
2058}
2059
2060#[sqlfunc(is_infix_op = true, sqlname = "-", propagates_nulls = true)]
2061fn jsonb_delete_int64<'a>(
2062    a: Datum<'a>,
2063    i: i64,
2064    temp_storage: &'a RowArena,
2065) -> Option<JsonbRef<'a>> {
2066    match a {
2067        Datum::List(list) => {
2068            let i = if i >= 0 {
2069                usize::cast_from(i.unsigned_abs())
2070            } else {
2071                // index backwards from the end
2072                let i = usize::cast_from(i.unsigned_abs());
2073                (list.iter().count()).wrapping_sub(i)
2074            };
2075            let elems = list
2076                .iter()
2077                .enumerate()
2078                .filter(|(i2, _e)| i != *i2)
2079                .map(|(_, e)| e);
2080            let datum = temp_storage.make_datum(|packer| packer.push_list(elems));
2081            Some(JsonbRef::from_datum(datum))
2082        }
2083        _ => None,
2084    }
2085}
2086
2087#[sqlfunc(is_infix_op = true, sqlname = "-", propagates_nulls = true)]
2088fn jsonb_delete_string<'a>(
2089    a: Datum<'a>,
2090    k: &str,
2091    temp_storage: &'a RowArena,
2092) -> Option<JsonbRef<'a>> {
2093    let datum = match a {
2094        Datum::List(list) => {
2095            let elems = list.iter().filter(|e| Datum::from(k) != *e);
2096            temp_storage.make_datum(|packer| packer.push_list(elems))
2097        }
2098        Datum::Map(dict) => {
2099            let pairs = dict.iter().filter(|(k2, _v)| k != *k2);
2100            temp_storage.make_datum(|packer| packer.push_dict(pairs))
2101        }
2102        _ => return None,
2103    };
2104    Some(JsonbRef::from_datum(datum))
2105}
2106
2107#[sqlfunc(sqlname = "extractiv", propagates_nulls = true)]
2108fn date_part_interval_numeric(units: &str, b: Interval) -> Result<Numeric, EvalError> {
2109    match units.parse() {
2110        Ok(units) => Ok(date_part_interval_inner::<Numeric>(units, b)?),
2111        Err(_) => Err(EvalError::UnknownUnits(units.into())),
2112    }
2113}
2114
2115#[sqlfunc(sqlname = "date_partiv", propagates_nulls = true)]
2116fn date_part_interval_f64(units: &str, b: Interval) -> Result<f64, EvalError> {
2117    match units.parse() {
2118        Ok(units) => Ok(date_part_interval_inner::<f64>(units, b)?),
2119        Err(_) => Err(EvalError::UnknownUnits(units.into())),
2120    }
2121}
2122
2123#[sqlfunc(sqlname = "extractt", propagates_nulls = true)]
2124fn date_part_time_numeric(units: &str, b: chrono::NaiveTime) -> Result<Numeric, EvalError> {
2125    match units.parse() {
2126        Ok(units) => Ok(date_part_time_inner::<Numeric>(units, b)?),
2127        Err(_) => Err(EvalError::UnknownUnits(units.into())),
2128    }
2129}
2130
2131#[sqlfunc(sqlname = "date_partt", propagates_nulls = true)]
2132fn date_part_time_f64(units: &str, b: chrono::NaiveTime) -> Result<f64, EvalError> {
2133    match units.parse() {
2134        Ok(units) => Ok(date_part_time_inner::<f64>(units, b)?),
2135        Err(_) => Err(EvalError::UnknownUnits(units.into())),
2136    }
2137}
2138
2139#[sqlfunc(sqlname = "extractts", propagates_nulls = true)]
2140fn date_part_timestamp_timestamp_numeric(
2141    units: &str,
2142    ts: CheckedTimestamp<NaiveDateTime>,
2143) -> Result<Numeric, EvalError> {
2144    match units.parse() {
2145        Ok(units) => Ok(date_part_timestamp_inner::<_, Numeric>(units, &*ts)?),
2146        Err(_) => Err(EvalError::UnknownUnits(units.into())),
2147    }
2148}
2149
2150#[sqlfunc(sqlname = "extracttstz", propagates_nulls = true)]
2151fn date_part_timestamp_timestamp_tz_numeric(
2152    units: &str,
2153    ts: CheckedTimestamp<DateTime<Utc>>,
2154) -> Result<Numeric, EvalError> {
2155    match units.parse() {
2156        Ok(units) => Ok(date_part_timestamp_inner::<_, Numeric>(units, &*ts)?),
2157        Err(_) => Err(EvalError::UnknownUnits(units.into())),
2158    }
2159}
2160
2161#[sqlfunc(sqlname = "date_partts", propagates_nulls = true)]
2162fn date_part_timestamp_timestamp_f64(
2163    units: &str,
2164    ts: CheckedTimestamp<NaiveDateTime>,
2165) -> Result<f64, EvalError> {
2166    match units.parse() {
2167        Ok(units) => date_part_timestamp_inner(units, &*ts),
2168        Err(_) => Err(EvalError::UnknownUnits(units.into())),
2169    }
2170}
2171
2172#[sqlfunc(sqlname = "date_parttstz", propagates_nulls = true)]
2173fn date_part_timestamp_timestamp_tz_f64(
2174    units: &str,
2175    ts: CheckedTimestamp<DateTime<Utc>>,
2176) -> Result<f64, EvalError> {
2177    match units.parse() {
2178        Ok(units) => date_part_timestamp_inner(units, &*ts),
2179        Err(_) => Err(EvalError::UnknownUnits(units.into())),
2180    }
2181}
2182
2183#[sqlfunc(sqlname = "extractd", propagates_nulls = true)]
2184fn extract_date_units(units: &str, b: Date) -> Result<Numeric, EvalError> {
2185    match units.parse() {
2186        Ok(units) => Ok(extract_date_inner(units, b.into())?),
2187        Err(_) => Err(EvalError::UnknownUnits(units.into())),
2188    }
2189}
2190
2191pub fn date_bin<T>(
2192    stride: Interval,
2193    source: CheckedTimestamp<T>,
2194    origin: CheckedTimestamp<T>,
2195) -> Result<CheckedTimestamp<T>, EvalError>
2196where
2197    T: TimestampLike,
2198{
2199    if stride.months != 0 {
2200        return Err(EvalError::DateBinOutOfRange(
2201            "timestamps cannot be binned into intervals containing months or years".into(),
2202        ));
2203    }
2204
2205    let stride_ns = match stride.duration_as_chrono().num_nanoseconds() {
2206        Some(ns) if ns <= 0 => Err(EvalError::DateBinOutOfRange(
2207            "stride must be greater than zero".into(),
2208        )),
2209        Some(ns) => Ok(ns),
2210        None => Err(EvalError::DateBinOutOfRange(
2211            format!("stride cannot exceed {}/{} nanoseconds", i64::MAX, i64::MIN,).into(),
2212        )),
2213    }?;
2214
2215    // Make sure the returned timestamp is at the start of the bin, even if the
2216    // origin is in the future. We do this here because `T` is not `Copy` and
2217    // gets moved by its subtraction operation.
2218    let sub_stride = origin > source;
2219
2220    let tm_diff = (source - origin.clone()).num_nanoseconds().ok_or_else(|| {
2221        EvalError::DateBinOutOfRange(
2222            "source and origin must not differ more than 2^63 nanoseconds".into(),
2223        )
2224    })?;
2225
2226    let remainder = tm_diff % stride_ns;
2227    let mut tm_delta = tm_diff - remainder;
2228
2229    if sub_stride && remainder != 0 {
2230        tm_delta = tm_delta.checked_sub(stride_ns).ok_or_else(|| {
2231            EvalError::DateBinOutOfRange(
2232                "source and origin must not differ more than 2^63 nanoseconds".into(),
2233            )
2234        })?;
2235    }
2236
2237    let res = origin
2238        .checked_add_signed(Duration::nanoseconds(tm_delta))
2239        .ok_or(EvalError::TimestampOutOfRange)?;
2240    Ok(CheckedTimestamp::from_timestamplike(res)?)
2241}
2242
2243// Non-monotone in `stride`: the result is `origin + floor((source - origin) /
2244// stride) * stride`. For a fixed source like `2024-01-01 12:00:00`, a 1-day
2245// stride bins to `2024-01-01 00:00:00`, but a 2-day stride bins to
2246// `2023-12-31 00:00:00` — i.e. the lex-larger interval produces an earlier
2247// timestamp. Monotone in `source`.
2248#[sqlfunc(is_monotone = "(false, true)", sqlname = "bin_unix_epoch_timestamp")]
2249fn date_bin_timestamp(
2250    stride: Interval,
2251    source: CheckedTimestamp<NaiveDateTime>,
2252) -> Result<CheckedTimestamp<NaiveDateTime>, EvalError> {
2253    let origin =
2254        CheckedTimestamp::from_timestamplike(DateTime::from_timestamp(0, 0).unwrap().naive_utc())
2255            .expect("must fit");
2256    date_bin(stride, source, origin)
2257}
2258
2259// See `date_bin_timestamp` for why this is not monotone in `stride`.
2260#[sqlfunc(is_monotone = "(false, true)", sqlname = "bin_unix_epoch_timestamptz")]
2261fn date_bin_timestamp_tz(
2262    stride: Interval,
2263    source: CheckedTimestamp<DateTime<Utc>>,
2264) -> Result<CheckedTimestamp<DateTime<Utc>>, EvalError> {
2265    let origin = CheckedTimestamp::from_timestamplike(DateTime::from_timestamp(0, 0).unwrap())
2266        .expect("must fit");
2267    date_bin(stride, source, origin)
2268}
2269
2270#[sqlfunc(sqlname = "date_truncts", propagates_nulls = true)]
2271fn date_trunc_units_timestamp(
2272    units: &str,
2273    ts: CheckedTimestamp<NaiveDateTime>,
2274) -> Result<CheckedTimestamp<NaiveDateTime>, EvalError> {
2275    match units.parse() {
2276        Ok(units) => Ok(date_trunc_inner(units, &*ts)?.try_into()?),
2277        Err(_) => Err(EvalError::UnknownUnits(units.into())),
2278    }
2279}
2280
2281#[sqlfunc(sqlname = "date_trunctstz", propagates_nulls = true)]
2282fn date_trunc_units_timestamp_tz(
2283    units: &str,
2284    ts: CheckedTimestamp<DateTime<Utc>>,
2285) -> Result<CheckedTimestamp<DateTime<Utc>>, EvalError> {
2286    match units.parse() {
2287        Ok(units) => Ok(date_trunc_inner(units, &*ts)?.try_into()?),
2288        Err(_) => Err(EvalError::UnknownUnits(units.into())),
2289    }
2290}
2291
2292#[sqlfunc(sqlname = "date_trunciv", propagates_nulls = true)]
2293fn date_trunc_interval(units: &str, mut interval: Interval) -> Result<Interval, EvalError> {
2294    let dtf = units
2295        .parse()
2296        .map_err(|_| EvalError::UnknownUnits(units.into()))?;
2297
2298    interval
2299        .truncate_low_fields(dtf, Some(0), RoundBehavior::Truncate)
2300        .expect(
2301            "truncate_low_fields should not fail with max_precision 0 and RoundBehavior::Truncate",
2302        );
2303    Ok(interval)
2304}
2305
2306/// Parses a named timezone like `EST` or `America/New_York`, or a fixed-offset timezone like `-05:00`.
2307///
2308/// The interpretation of fixed offsets depend on whether the POSIX or ISO 8601 standard is being
2309/// used.
2310pub(crate) fn parse_timezone(tz: &str, spec: TimezoneSpec) -> Result<Timezone, EvalError> {
2311    Timezone::parse(tz, spec).map_err(|_| EvalError::InvalidTimezone(tz.into()))
2312}
2313
2314/// Converts the time datum `b`, which is assumed to be in UTC, to the timezone that the interval datum `a` is assumed
2315/// to represent. The interval is not allowed to hold months, but there are no limits on the amount of seconds.
2316/// The interval acts like a `chrono::FixedOffset`, without the `-86,400 < x < 86,400` limitation.
2317#[sqlfunc(sqlname = "timezoneit")]
2318fn timezone_interval_time_binary(
2319    interval: Interval,
2320    time: chrono::NaiveTime,
2321) -> Result<chrono::NaiveTime, EvalError> {
2322    if interval.months != 0 {
2323        Err(EvalError::InvalidTimezoneInterval)
2324    } else {
2325        Ok(time.overflowing_add_signed(interval.duration_as_chrono()).0)
2326    }
2327}
2328
2329/// Converts the timestamp datum `b`, which is assumed to be in the time of the timezone datum `a` to a timestamptz
2330/// in UTC. The interval is not allowed to hold months, but there are no limits on the amount of seconds.
2331/// The interval acts like a `chrono::FixedOffset`, without the `-86,400 < x < 86,400` limitation.
2332#[sqlfunc(sqlname = "timezoneits")]
2333fn timezone_interval_timestamp_binary(
2334    interval: Interval,
2335    ts: CheckedTimestamp<NaiveDateTime>,
2336) -> Result<CheckedTimestamp<DateTime<Utc>>, EvalError> {
2337    if interval.months != 0 {
2338        Err(EvalError::InvalidTimezoneInterval)
2339    } else {
2340        match ts.checked_sub_signed(interval.duration_as_chrono()) {
2341            Some(sub) => Ok(DateTime::from_naive_utc_and_offset(sub, Utc).try_into()?),
2342            None => Err(EvalError::TimestampOutOfRange),
2343        }
2344    }
2345}
2346
2347/// Converts the UTC timestamptz datum `b`, to the local timestamp of the timezone datum `a`.
2348/// The interval is not allowed to hold months, but there are no limits on the amount of seconds.
2349/// The interval acts like a `chrono::FixedOffset`, without the `-86,400 < x < 86,400` limitation.
2350#[sqlfunc(sqlname = "timezoneitstz")]
2351fn timezone_interval_timestamp_tz_binary(
2352    interval: Interval,
2353    tstz: CheckedTimestamp<DateTime<Utc>>,
2354) -> Result<CheckedTimestamp<NaiveDateTime>, EvalError> {
2355    if interval.months != 0 {
2356        return Err(EvalError::InvalidTimezoneInterval);
2357    }
2358    match tstz
2359        .naive_utc()
2360        .checked_add_signed(interval.duration_as_chrono())
2361    {
2362        Some(dt) => Ok(dt.try_into()?),
2363        None => Err(EvalError::TimestampOutOfRange),
2364    }
2365}
2366
2367#[sqlfunc(
2368    output_type_expr = r#"SqlScalarType::Record {
2369                fields: [
2370                    ("abbrev".into(), SqlScalarType::String.nullable(false)),
2371                    ("base_utc_offset".into(), SqlScalarType::Interval.nullable(false)),
2372                    ("dst_offset".into(), SqlScalarType::Interval.nullable(false)),
2373                ].into(),
2374                custom_id: None,
2375            }.nullable(true)"#,
2376    propagates_nulls = true
2377)]
2378fn timezone_offset<'a>(
2379    tz_str: &str,
2380    b: CheckedTimestamp<chrono::DateTime<Utc>>,
2381    temp_storage: &'a RowArena,
2382) -> Result<DatumList<'a>, EvalError> {
2383    let tz = match Tz::from_str_insensitive(tz_str) {
2384        Ok(tz) => tz,
2385        Err(_) => return Err(EvalError::InvalidIanaTimezoneId(tz_str.into())),
2386    };
2387    let offset = tz.offset_from_utc_datetime(&b.naive_utc());
2388    // Zones without an alphabetic abbreviation get the numeric form rendered
2389    // from the offset, e.g. "+05". This matches PostgreSQL, whose tzdata files
2390    // have the same rendering applied by zic's %z expansion.
2391    let abbrev = match offset.abbreviation() {
2392        Some(abbrev) => abbrev.to_string(),
2393        None => {
2394            const SECONDS_PER_MINUTE: i64 = 60;
2395            const MINUTES_PER_HOUR: i64 = 60;
2396            let secs = (offset.base_utc_offset() + offset.dst_offset()).num_seconds();
2397            let sign = if secs < 0 { '-' } else { '+' };
2398            let (mins, s) = (
2399                secs.abs() / SECONDS_PER_MINUTE,
2400                secs.abs() % SECONDS_PER_MINUTE,
2401            );
2402            let (h, m) = (mins / MINUTES_PER_HOUR, mins % MINUTES_PER_HOUR);
2403            if s != 0 {
2404                // Unreachable for current tzdata: sub-minute offsets exist
2405                // only for pre-standardization history (e.g. Africa/Monrovia
2406                // until 1972), and tzdata gives all of them alphabetic names.
2407                // We render rather than trust that invariant forever.
2408                // chrono-tz's own Display impl instead asserts, and a scalar
2409                // function must not panic.
2410                format!("{sign}{h:02}{m:02}{s:02}")
2411            } else if m != 0 {
2412                // Fractional-hour zones, e.g. Asia/Kathmandu renders "+0545".
2413                format!("{sign}{h:02}{m:02}")
2414            } else {
2415                // Whole-hour zones, e.g. Asia/Almaty renders "+05".
2416                format!("{sign}{h:02}")
2417            }
2418        }
2419    };
2420    Ok(temp_storage
2421        .make_datum(|packer| {
2422            packer.push_list_with(|packer| {
2423                packer.push(Datum::from(abbrev.as_str()));
2424                packer.push(Datum::from(offset.base_utc_offset()));
2425                packer.push(Datum::from(offset.dst_offset()));
2426            });
2427        })
2428        .unwrap_list())
2429}
2430
2431/// Determines if an mz_aclitem contains one of the specified privileges. This will return true if
2432/// any of the listed privileges are contained in the mz_aclitem.
2433#[sqlfunc(
2434    sqlname = "mz_aclitem_contains_privilege",
2435    output_type = "bool",
2436    propagates_nulls = true
2437)]
2438fn mz_acl_item_contains_privilege(
2439    mz_acl_item: MzAclItem,
2440    privileges: &str,
2441) -> Result<bool, EvalError> {
2442    let acl_mode = AclMode::parse_multiple_privileges(privileges)
2443        .map_err(|e: anyhow::Error| EvalError::InvalidPrivileges(e.to_string().into()))?;
2444    let contains = !mz_acl_item.acl_mode.intersection(acl_mode).is_empty();
2445    Ok(contains)
2446}
2447
2448#[sqlfunc]
2449// transliterated from postgres/src/backend/utils/adt/misc.c
2450fn parse_ident<'a>(ident: &'a str, strict: bool) -> Result<ArrayRustType<Cow<'a, str>>, EvalError> {
2451    fn is_ident_start(c: char) -> bool {
2452        matches!(c, 'A'..='Z' | 'a'..='z' | '_' | '\u{80}'..=char::MAX)
2453    }
2454
2455    fn is_ident_cont(c: char) -> bool {
2456        matches!(c, '0'..='9' | '$') || is_ident_start(c)
2457    }
2458
2459    let mut elems = vec![];
2460    let buf = &mut LexBuf::new(ident);
2461
2462    let mut after_dot = false;
2463
2464    buf.take_while(|ch| ch.is_ascii_whitespace());
2465
2466    loop {
2467        let mut missing_ident = true;
2468
2469        let c = buf.next();
2470
2471        if c == Some('"') {
2472            let s = buf.take_while(|ch| !matches!(ch, '"'));
2473
2474            if buf.next() != Some('"') {
2475                return Err(EvalError::InvalidIdentifier {
2476                    ident: ident.into(),
2477                    detail: Some("String has unclosed double quotes.".into()),
2478                });
2479            }
2480            elems.push(Cow::Borrowed(s));
2481            missing_ident = false;
2482        } else if c.map(is_ident_start).unwrap_or(false) {
2483            buf.prev();
2484            let s = buf.take_while(is_ident_cont);
2485            elems.push(Cow::Owned(s.to_ascii_lowercase()));
2486            missing_ident = false;
2487        }
2488
2489        if missing_ident {
2490            if c == Some('.') {
2491                return Err(EvalError::InvalidIdentifier {
2492                    ident: ident.into(),
2493                    detail: Some("No valid identifier before \".\".".into()),
2494                });
2495            } else if after_dot {
2496                return Err(EvalError::InvalidIdentifier {
2497                    ident: ident.into(),
2498                    detail: Some("No valid identifier after \".\".".into()),
2499                });
2500            } else {
2501                return Err(EvalError::InvalidIdentifier {
2502                    ident: ident.into(),
2503                    detail: None,
2504                });
2505            }
2506        }
2507
2508        buf.take_while(|ch| ch.is_ascii_whitespace());
2509
2510        match buf.next() {
2511            Some('.') => {
2512                after_dot = true;
2513
2514                buf.take_while(|ch| ch.is_ascii_whitespace());
2515            }
2516            Some(_) if strict => {
2517                return Err(EvalError::InvalidIdentifier {
2518                    ident: ident.into(),
2519                    detail: None,
2520                });
2521            }
2522            _ => break,
2523        }
2524    }
2525
2526    Ok(elems.into())
2527}
2528
2529fn regexp_split_to_array_re<'a>(
2530    text: &str,
2531    regexp: &Regex,
2532    temp_storage: &'a RowArena,
2533) -> Result<Array<'a>, EvalError> {
2534    // Bound the transient `Vec<&str>` before the split builds it. The count follows the split's own
2535    // zero-length-match rule, so it refuses exactly the calls the split would build.
2536    check_build_fits_budget(
2537        || mz_regexp::regexp_split_to_array_count(text, regexp),
2538        std::mem::size_of::<&str>(),
2539        temp_storage,
2540    )?;
2541    let found = mz_regexp::regexp_split_to_array(text, regexp);
2542    // Splitting amplifies: each chunk is packed with its own tag and length, so a pattern that
2543    // splits per character costs several times the input.
2544    check_datums_fit_budget(found.iter().copied().map(Datum::String), temp_storage)?;
2545    let mut row = Row::default();
2546    let mut packer = row.packer();
2547    packer.try_push_array(
2548        &[ArrayDimension {
2549            lower_bound: 1,
2550            length: found.len(),
2551        }],
2552        found.into_iter().map(Datum::String),
2553    )?;
2554    Ok(temp_storage.push_unary_row(row).unwrap_array())
2555}
2556
2557// NOTE: no budget pre-check, see the exception on `check_build_fits_budget`.
2558#[sqlfunc(propagates_nulls = true)]
2559fn pretty_sql<'a>(sql: &str, width: i32, temp_storage: &'a RowArena) -> Result<&'a str, EvalError> {
2560    let width =
2561        usize::try_from(width).map_err(|_| EvalError::PrettyError("invalid width".into()))?;
2562    let pretty = pretty_str(
2563        sql,
2564        PrettyConfig {
2565            width,
2566            format_mode: FormatMode::Simple,
2567        },
2568    )
2569    .map_err(|e| EvalError::PrettyError(e.to_string().into()))?;
2570    let pretty = temp_storage.push_string(pretty);
2571    Ok(pretty)
2572}
2573
2574// NOTE: no budget pre-check, see the exception on `check_build_fits_budget`.
2575#[sqlfunc]
2576fn redact_sql(sql: &str) -> Result<String, EvalError> {
2577    let stmts = mz_sql_parser::parser::parse_statements(sql)
2578        .map_err(|e| EvalError::RedactError(e.to_string().into()))?;
2579    match stmts.len() {
2580        1 => Ok(stmts[0].ast.to_ast_string_redacted()),
2581        n => Err(EvalError::RedactError(
2582            format!("expected a single statement, found {n}").into(),
2583        )),
2584    }
2585}
2586
2587#[sqlfunc(propagates_nulls = true)]
2588fn starts_with(a: &str, b: &str) -> bool {
2589    a.starts_with(b)
2590}
2591
2592#[sqlfunc(
2593    sqlname = "||",
2594    is_infix_op = true,
2595    propagates_nulls = true,
2596    // Text concatenation is monotonic in its second argument, because if I change the
2597    // second argument but don't change the first argument, then we won't find a difference
2598    // in that part of the concatenation result that came from the first argument, so we'll
2599    // find the difference that comes from changing the second argument.
2600    // (It's not monotonic in its first argument, because e.g.,
2601    // 'A' < 'AA' but 'AZ' > 'AAZ'.)
2602    is_monotone = (false, true),
2603)]
2604fn text_concat_binary(a: &str, b: &str, temp_storage: &RowArena) -> Result<String, EvalError> {
2605    if a.len() + b.len() > max_string_func_result_bytes(temp_storage) {
2606        return Err(EvalError::LengthTooLarge);
2607    }
2608    let mut buf = String::with_capacity(a.len() + b.len());
2609    buf.push_str(a);
2610    buf.push_str(b);
2611    Ok(buf)
2612}
2613
2614#[sqlfunc(propagates_nulls = true)]
2615fn like_escape<'a>(
2616    pattern: &str,
2617    b: &str,
2618    temp_storage: &'a RowArena,
2619) -> Result<&'a str, EvalError> {
2620    let escape = like_pattern::EscapeBehavior::from_str(b)?;
2621    let normalized = like_pattern::normalize_pattern(pattern, escape)?;
2622    Ok(temp_storage.push_string(normalized))
2623}
2624
2625#[sqlfunc(is_infix_op = true, sqlname = "like")]
2626fn is_like_match_case_sensitive(haystack: &str, pattern: &str) -> Result<bool, EvalError> {
2627    like_pattern::compile(pattern, false).map(|needle| needle.is_match(haystack))
2628}
2629
2630#[sqlfunc(is_infix_op = true, sqlname = "ilike")]
2631fn is_like_match_case_insensitive(haystack: &str, pattern: &str) -> Result<bool, EvalError> {
2632    like_pattern::compile(pattern, true).map(|needle| needle.is_match(haystack))
2633}
2634
2635#[sqlfunc(is_infix_op = true, sqlname = "~")]
2636fn is_regexp_match_case_sensitive(haystack: &str, needle: &str) -> Result<bool, EvalError> {
2637    let regex = build_regex(needle, "")?;
2638    Ok(regex.is_match(haystack))
2639}
2640
2641#[sqlfunc(is_infix_op = true, sqlname = "~*")]
2642fn is_regexp_match_case_insensitive(haystack: &str, needle: &str) -> Result<bool, EvalError> {
2643    let regex = build_regex(needle, "i")?;
2644    Ok(regex.is_match(haystack))
2645}
2646
2647fn regexp_match_static<'a>(
2648    haystack: &'a str,
2649    temp_storage: &'a RowArena,
2650    needle: &regex::Regex,
2651) -> Result<Option<Array<'a>>, EvalError> {
2652    let mut row = Row::default();
2653    let mut packer = row.packer();
2654    if needle.captures_len() > 1 {
2655        // The regex contains capture groups, so return an array containing the
2656        // matched text in each capture group, unless the entire match fails.
2657        // Individual capture groups may also be null if that group did not
2658        // participate in the match.
2659        match needle.captures(haystack) {
2660            None => packer.push(Datum::Null),
2661            Some(captures) => packer.try_push_array(
2662                &[ArrayDimension {
2663                    lower_bound: 1,
2664                    length: captures.len() - 1,
2665                }],
2666                // Skip the 0th capture group, which is the whole match.
2667                captures.iter().skip(1).map(|mtch| match mtch {
2668                    None => Datum::Null,
2669                    Some(mtch) => Datum::String(mtch.as_str()),
2670                }),
2671            )?,
2672        }
2673    } else {
2674        // The regex contains no capture groups, so return a one-element array
2675        // containing the match, or null if there is no match.
2676        match needle.find(haystack) {
2677            None => packer.push(Datum::Null),
2678            Some(mtch) => packer.try_push_array(
2679                &[ArrayDimension {
2680                    lower_bound: 1,
2681                    length: 1,
2682                }],
2683                iter::once(Datum::String(mtch.as_str())),
2684            )?,
2685        };
2686    };
2687    let datum = temp_storage.push_unary_row(row);
2688    Ok((!datum.is_null()).then(|| datum.unwrap_array()))
2689}
2690
2691/// Sets `limit` based on the presence of 'g' in `flags` for use in `Regex::replacen`,
2692/// and removes 'g' from `flags` if present.
2693pub(crate) fn regexp_replace_parse_flags(flags: &str) -> (usize, Cow<'_, str>) {
2694    // 'g' means to replace all instead of the first. Use a Cow to avoid allocating in the fast
2695    // path. We could switch build_regex to take an iter which would also achieve that.
2696    let (limit, flags) = if flags.contains('g') {
2697        let flags = flags.replace('g', "");
2698        (0, Cow::Owned(flags))
2699    } else {
2700        (1, Cow::Borrowed(flags))
2701    };
2702    (limit, flags)
2703}
2704
2705pub fn build_regex(needle: &str, flags: &str) -> Result<Regex, EvalError> {
2706    let mut case_insensitive = false;
2707    // Note: Postgres accepts it when both flags are present, taking the last one. We do the same.
2708    for f in flags.chars() {
2709        match f {
2710            'i' => {
2711                case_insensitive = true;
2712            }
2713            'c' => {
2714                case_insensitive = false;
2715            }
2716            _ => return Err(EvalError::InvalidRegexFlag(f)),
2717        }
2718    }
2719    Ok(Regex::new(needle, case_insensitive)?)
2720}
2721
2722#[sqlfunc(sqlname = "repeat")]
2723fn repeat_string(string: &str, count: i32, temp_storage: &RowArena) -> Result<String, EvalError> {
2724    let len = usize::try_from(count).unwrap_or(0);
2725    if len.saturating_mul(string.len()) > max_string_func_result_bytes(temp_storage) {
2726        return Err(EvalError::LengthTooLarge);
2727    }
2728    Ok(string.repeat(len))
2729}
2730
2731/// Constructs a new zero or one dimensional array out of an arbitrary number of
2732/// scalars.
2733///
2734/// If `datums` is empty, constructs a zero-dimensional array. Otherwise,
2735/// constructs a one dimensional array whose lower bound is one and whose length
2736/// is equal to `datums.len()`.
2737fn array_create_scalar<'a>(
2738    datums: &[Datum<'a>],
2739    temp_storage: &'a RowArena,
2740) -> Result<Array<'a>, EvalError> {
2741    let mut dims = &[ArrayDimension {
2742        lower_bound: 1,
2743        length: datums.len(),
2744    }][..];
2745    if datums.is_empty() {
2746        // Per PostgreSQL, empty arrays are represented with zero dimensions,
2747        // not one dimension of zero length. We write this condition a little
2748        // strangely to satisfy the borrow checker while avoiding an allocation.
2749        dims = &[];
2750    }
2751    check_datums_fit_budget(datums.iter().copied(), temp_storage)?;
2752    let datum = temp_storage.try_make_datum(|packer| packer.try_push_array(dims, datums))?;
2753    Ok(datum.unwrap_array())
2754}
2755
2756fn stringify_datum<'a, B>(
2757    buf: &mut B,
2758    d: Datum<'a>,
2759    ty: &SqlScalarType,
2760) -> Result<strconv::Nestable, EvalError>
2761where
2762    B: FormatBuffer,
2763{
2764    use SqlScalarType::*;
2765    match &ty {
2766        AclItem => Ok(strconv::format_acl_item(buf, d.unwrap_acl_item())),
2767        Bool => Ok(strconv::format_bool(buf, d.unwrap_bool())),
2768        Int16 => Ok(strconv::format_int16(buf, d.unwrap_int16())),
2769        Int32 => Ok(strconv::format_int32(buf, d.unwrap_int32())),
2770        Int64 => Ok(strconv::format_int64(buf, d.unwrap_int64())),
2771        UInt16 => Ok(strconv::format_uint16(buf, d.unwrap_uint16())),
2772        UInt32 | Oid | RegClass | RegProc | RegType => {
2773            Ok(strconv::format_uint32(buf, d.unwrap_uint32()))
2774        }
2775        UInt64 => Ok(strconv::format_uint64(buf, d.unwrap_uint64())),
2776        Float32 => Ok(strconv::format_float32(buf, d.unwrap_float32())),
2777        Float64 => Ok(strconv::format_float64(buf, d.unwrap_float64())),
2778        Numeric { .. } => Ok(strconv::format_numeric(buf, &d.unwrap_numeric())),
2779        Date => Ok(strconv::format_date(buf, d.unwrap_date())),
2780        Time => Ok(strconv::format_time(buf, d.unwrap_time())),
2781        Timestamp { .. } => Ok(strconv::format_timestamp(buf, &d.unwrap_timestamp())),
2782        TimestampTz { .. } => Ok(strconv::format_timestamptz(buf, &d.unwrap_timestamptz())),
2783        Interval => Ok(strconv::format_interval(buf, d.unwrap_interval())),
2784        Bytes => Ok(strconv::format_bytes(buf, d.unwrap_bytes())),
2785        String | VarChar { .. } | PgLegacyName => Ok(strconv::format_string(buf, d.unwrap_str())),
2786        Char { length } => Ok(strconv::format_string(
2787            buf,
2788            &mz_repr::adt::char::format_str_pad(d.unwrap_str(), *length),
2789        )),
2790        PgLegacyChar => {
2791            format_pg_legacy_char(buf, d.unwrap_uint8())?;
2792            Ok(strconv::Nestable::MayNeedEscaping)
2793        }
2794        Jsonb => Ok(strconv::format_jsonb(buf, JsonbRef::from_datum(d))),
2795        Uuid => Ok(strconv::format_uuid(buf, d.unwrap_uuid())),
2796        Record { fields, .. } => {
2797            let mut fields = fields.iter();
2798            strconv::format_record(buf, d.unwrap_list(), |buf, d| {
2799                let (_name, ty) = fields.next().unwrap();
2800                if d.is_null() {
2801                    Ok(buf.write_null())
2802                } else {
2803                    stringify_datum(buf.nonnull_buffer(), d, &ty.scalar_type)
2804                }
2805            })
2806        }
2807        Array(elem_type) => strconv::format_array(
2808            buf,
2809            &d.unwrap_array().dims().into_iter().collect::<Vec<_>>(),
2810            d.unwrap_array().elements(),
2811            |buf, d| {
2812                if d.is_null() {
2813                    Ok(buf.write_null())
2814                } else {
2815                    stringify_datum(buf.nonnull_buffer(), d, elem_type)
2816                }
2817            },
2818        ),
2819        List { element_type, .. } => strconv::format_list(buf, d.unwrap_list(), |buf, d| {
2820            if d.is_null() {
2821                Ok(buf.write_null())
2822            } else {
2823                stringify_datum(buf.nonnull_buffer(), d, element_type)
2824            }
2825        }),
2826        Map { value_type, .. } => strconv::format_map(buf, &d.unwrap_map(), |buf, d| {
2827            if d.is_null() {
2828                Ok(buf.write_null())
2829            } else {
2830                stringify_datum(buf.nonnull_buffer(), d, value_type)
2831            }
2832        }),
2833        Int2Vector => strconv::format_legacy_vector(buf, d.unwrap_array().elements(), |buf, d| {
2834            stringify_datum(buf.nonnull_buffer(), d, &SqlScalarType::Int16)
2835        }),
2836        MzTimestamp { .. } => Ok(strconv::format_mz_timestamp(buf, d.unwrap_mz_timestamp())),
2837        Range { element_type } => strconv::format_range(buf, &d.unwrap_range(), |buf, d| match d {
2838            Some(d) => stringify_datum(buf.nonnull_buffer(), *d, element_type),
2839            None => Ok::<_, EvalError>(buf.write_null()),
2840        }),
2841        MzAclItem => Ok(strconv::format_mz_acl_item(buf, d.unwrap_mz_acl_item())),
2842    }
2843}
2844
2845#[sqlfunc]
2846fn position(substring: &str, string: &str) -> Result<i32, EvalError> {
2847    let char_index = string.find(substring);
2848
2849    if let Some(char_index) = char_index {
2850        // find the index in char space
2851        let string_prefix = &string[0..char_index];
2852
2853        let num_prefix_chars = string_prefix.chars().count();
2854        let num_prefix_chars = i32::try_from(num_prefix_chars)
2855            .map_err(|_| EvalError::Int32OutOfRange(num_prefix_chars.to_string().into()))?;
2856
2857        Ok(num_prefix_chars + 1)
2858    } else {
2859        Ok(0)
2860    }
2861}
2862
2863#[sqlfunc]
2864fn strpos(string: &str, substring: &str) -> Result<i32, EvalError> {
2865    position(substring, string)
2866}
2867
2868#[sqlfunc(
2869    propagates_nulls = true,
2870    // `left` is unfortunately not monotonic (at least for negative second arguments),
2871    // because 'aa' < 'z', but `left(_, -1)` makes 'a' > ''.
2872    is_monotone = (false, false)
2873)]
2874fn left<'a>(string: &'a str, b: i32) -> Result<&'a str, EvalError> {
2875    let n = i64::from(b);
2876
2877    let mut byte_indices = string.char_indices().map(|(i, _)| i);
2878
2879    let end_in_bytes = match n.cmp(&0) {
2880        Ordering::Equal => 0,
2881        Ordering::Greater => {
2882            let n = usize::try_from(n).map_err(|_| {
2883                EvalError::InvalidParameterValue(format!("invalid parameter n: {:?}", n).into())
2884            })?;
2885            // nth from the back
2886            byte_indices.nth(n).unwrap_or(string.len())
2887        }
2888        Ordering::Less => {
2889            let n = usize::try_from(n.abs() - 1).map_err(|_| {
2890                EvalError::InvalidParameterValue(format!("invalid parameter n: {:?}", n).into())
2891            })?;
2892            byte_indices.rev().nth(n).unwrap_or(0)
2893        }
2894    };
2895
2896    Ok(&string[..end_in_bytes])
2897}
2898
2899#[sqlfunc(propagates_nulls = true)]
2900fn right<'a>(string: &'a str, n: i32) -> Result<&'a str, EvalError> {
2901    let mut byte_indices = string.char_indices().map(|(i, _)| i);
2902
2903    let start_in_bytes = if n == 0 {
2904        string.len()
2905    } else if n > 0 {
2906        let n = usize::try_from(n - 1).map_err(|_| {
2907            EvalError::InvalidParameterValue(format!("invalid parameter n: {:?}", n).into())
2908        })?;
2909        // nth from the back
2910        byte_indices.rev().nth(n).unwrap_or(0)
2911    } else if n == i32::MIN {
2912        // this seems strange but Postgres behaves like this
2913        0
2914    } else {
2915        let n = n.abs();
2916        let n = usize::try_from(n).map_err(|_| {
2917            EvalError::InvalidParameterValue(format!("invalid parameter n: {:?}", n).into())
2918        })?;
2919        byte_indices.nth(n).unwrap_or(string.len())
2920    };
2921
2922    Ok(&string[start_in_bytes..])
2923}
2924
2925#[sqlfunc(sqlname = "btrim", propagates_nulls = true)]
2926fn trim<'a>(a: &'a str, trim_chars: &str) -> &'a str {
2927    a.trim_matches(|c| trim_chars.contains(c))
2928}
2929
2930#[sqlfunc(sqlname = "ltrim", propagates_nulls = true)]
2931fn trim_leading<'a>(a: &'a str, trim_chars: &str) -> &'a str {
2932    a.trim_start_matches(|c| trim_chars.contains(c))
2933}
2934
2935#[sqlfunc(sqlname = "rtrim", propagates_nulls = true)]
2936fn trim_trailing<'a>(a: &'a str, trim_chars: &str) -> &'a str {
2937    a.trim_end_matches(|c| trim_chars.contains(c))
2938}
2939
2940#[sqlfunc(sqlname = "array_length", propagates_nulls = true)]
2941fn array_length<'a>(a: Array<'a>, b: i64) -> Result<Option<i32>, EvalError> {
2942    let i = match usize::try_from(b) {
2943        Ok(0) | Err(_) => return Ok(None),
2944        Ok(n) => n - 1,
2945    };
2946    Ok(match a.dims().into_iter().nth(i) {
2947        None => None,
2948        Some(dim) => Some(
2949            dim.length
2950                .try_into()
2951                .map_err(|_| EvalError::Int32OutOfRange(dim.length.to_string().into()))?,
2952        ),
2953    })
2954}
2955
2956#[sqlfunc(is_infix_op = true)]
2957// TODO(benesch): remove potentially dangerous usage of `as`.
2958#[allow(clippy::as_conversions)]
2959fn array_lower<'a>(a: Array<'a>, i: i64) -> Result<Option<i32>, EvalError> {
2960    if i < 1 {
2961        return Ok(None);
2962    }
2963    a.dims()
2964        .into_iter()
2965        .nth(i as usize - 1)
2966        .map(|dim| {
2967            let (lower, _upper) = dim.dimension_bounds();
2968            lower
2969                .try_into()
2970                .map_err(|_| EvalError::Int32OutOfRange(lower.to_string().into()))
2971        })
2972        .transpose()
2973}
2974
2975#[sqlfunc(
2976    output_type_expr = "input_types[0].scalar_type.without_modifiers().nullable(true)",
2977    sqlname = "array_remove",
2978    propagates_nulls = false
2979)]
2980fn array_remove<'a>(
2981    arr: Array<'a>,
2982    b: Datum<'a>,
2983    temp_storage: &'a RowArena,
2984) -> Result<Array<'a>, EvalError> {
2985    // Zero-dimensional arrays are empty by definition
2986    if arr.dims().len() == 0 {
2987        return Ok(arr);
2988    }
2989
2990    // array_remove only supports one-dimensional arrays
2991    if arr.dims().len() > 1 {
2992        return Err(EvalError::MultidimensionalArrayRemovalNotSupported);
2993    }
2994
2995    let mut dims = arr.dims().into_iter().collect::<Vec<_>>();
2996    // Removal can't grow the result, but the transient `Vec<Datum>` it filters into is a fresh
2997    // input-scaled allocation. One-dimensional by the check above, so dim 0's length is the count.
2998    check_build_fits_budget(
2999        || dims[0].length,
3000        std::mem::size_of::<Datum<'a>>(),
3001        temp_storage,
3002    )?;
3003
3004    let elems: Vec<_> = arr.elements().iter().filter(|v| v != &b).collect();
3005    // This access is safe because `dims` is guaranteed to be non-empty
3006    dims[0] = ArrayDimension {
3007        lower_bound: 1,
3008        length: elems.len(),
3009    };
3010
3011    let datum = temp_storage.try_make_datum(|packer| packer.try_push_array(&dims, elems))?;
3012    Ok(datum.unwrap_array())
3013}
3014
3015#[sqlfunc(is_infix_op = true)]
3016// TODO(benesch): remove potentially dangerous usage of `as`.
3017#[allow(clippy::as_conversions)]
3018fn array_upper<'a>(a: Array<'a>, i: i64) -> Result<Option<i32>, EvalError> {
3019    if i < 1 {
3020        return Ok(None);
3021    }
3022    a.dims()
3023        .into_iter()
3024        .nth(i as usize - 1)
3025        .map(|dim| {
3026            let (_lower, upper) = dim.dimension_bounds();
3027            upper
3028                .try_into()
3029                .map_err(|_| EvalError::Int32OutOfRange(upper.to_string().into()))
3030        })
3031        .transpose()
3032}
3033
3034#[sqlfunc(
3035    is_infix_op = true,
3036    sqlname = "array_contains",
3037    propagates_nulls = true
3038)]
3039fn array_contains<'a>(a: Datum<'a>, array: Array<'a>) -> bool {
3040    array.elements().iter().any(|e| e == a)
3041}
3042
3043#[sqlfunc(is_infix_op = true, sqlname = "@>")]
3044fn array_contains_array<'a>(a: Array<'a>, b: Array<'a>) -> bool {
3045    let a = a.elements();
3046    let b = b.elements();
3047
3048    // NULL is never equal to NULL. If NULL is an element of b, b cannot be contained in a, even if a contains NULL.
3049    if b.iter().contains(&Datum::Null) {
3050        false
3051    } else {
3052        b.iter()
3053            .all(|item_b| a.iter().any(|item_a| item_a == item_b))
3054    }
3055}
3056
3057#[sqlfunc(is_infix_op = true, sqlname = "<@")]
3058fn array_contains_array_rev<'a>(a: Array<'a>, b: Array<'a>) -> bool {
3059    array_contains_array(b, a)
3060}
3061
3062#[sqlfunc(
3063    output_type_expr = "input_types[0].scalar_type.without_modifiers().nullable(true)",
3064    is_infix_op = true,
3065    sqlname = "||",
3066    propagates_nulls = false,
3067    introduces_nulls = false
3068)]
3069fn array_array_concat<'a>(
3070    a: Option<Array<'a>>,
3071    b: Option<Array<'a>>,
3072    temp_storage: &'a RowArena,
3073) -> Result<Option<Array<'a>>, EvalError> {
3074    let Some(a_array) = a else {
3075        return Ok(b);
3076    };
3077    let Some(b_array) = b else {
3078        return Ok(a);
3079    };
3080
3081    let a_dims: Vec<ArrayDimension> = a_array.dims().into_iter().collect();
3082    let b_dims: Vec<ArrayDimension> = b_array.dims().into_iter().collect();
3083
3084    let a_ndims = a_dims.len();
3085    let b_ndims = b_dims.len();
3086
3087    // Per PostgreSQL, if either of the input arrays is zero dimensional,
3088    // the output is the other array, no matter their dimensions.
3089    if a_ndims == 0 {
3090        return Ok(b);
3091    } else if b_ndims == 0 {
3092        return Ok(a);
3093    }
3094
3095    // Postgres supports concatenating arrays of different dimensions,
3096    // as long as one of the arrays has the same type as an element of
3097    // the other array, i.e. `int[2][4] || int[4]` (or `int[4] || int[2][4]`)
3098    // works, because each element of `int[2][4]` is an `int[4]`.
3099    // This check is separate from the one below because Postgres gives a
3100    // specific error message if the number of dimensions differs by more
3101    // than one.
3102    // This cast is safe since MAX_ARRAY_DIMENSIONS is 6
3103    // Can be replaced by .abs_diff once it is stabilized
3104    // TODO(benesch): remove potentially dangerous usage of `as`.
3105    #[allow(clippy::as_conversions)]
3106    if (a_ndims as isize - b_ndims as isize).abs() > 1 {
3107        return Err(EvalError::IncompatibleArrayDimensions {
3108            dims: Some((a_ndims, b_ndims)),
3109        });
3110    }
3111
3112    let mut dims;
3113
3114    // After the checks above, we are certain that:
3115    // - neither array is zero dimensional nor empty
3116    // - both arrays have the same number of dimensions, or differ
3117    //   at most by one.
3118    match a_ndims.cmp(&b_ndims) {
3119        // If both arrays have the same number of dimensions, validate
3120        // that their inner dimensions are the same and concatenate the
3121        // arrays.
3122        Ordering::Equal => {
3123            if &a_dims[1..] != &b_dims[1..] {
3124                return Err(EvalError::IncompatibleArrayDimensions { dims: None });
3125            }
3126            dims = vec![ArrayDimension {
3127                lower_bound: a_dims[0].lower_bound,
3128                length: a_dims[0].length + b_dims[0].length,
3129            }];
3130            dims.extend(&a_dims[1..]);
3131        }
3132        // If `a` has less dimensions than `b`, this is an element-array
3133        // concatenation, which requires that `a` has the same dimensions
3134        // as an element of `b`.
3135        Ordering::Less => {
3136            if &a_dims[..] != &b_dims[1..] {
3137                return Err(EvalError::IncompatibleArrayDimensions { dims: None });
3138            }
3139            dims = vec![ArrayDimension {
3140                lower_bound: b_dims[0].lower_bound,
3141                // Since `a` is treated as an element of `b`, the length of
3142                // the first dimension of `b` is incremented by one, as `a` is
3143                // non-empty.
3144                length: b_dims[0].length + 1,
3145            }];
3146            dims.extend(a_dims);
3147        }
3148        // If `a` has more dimensions than `b`, this is an array-element
3149        // concatenation, which requires that `b` has the same dimensions
3150        // as an element of `a`.
3151        Ordering::Greater => {
3152            if &a_dims[1..] != &b_dims[..] {
3153                return Err(EvalError::IncompatibleArrayDimensions { dims: None });
3154            }
3155            dims = vec![ArrayDimension {
3156                lower_bound: a_dims[0].lower_bound,
3157                // Since `b` is treated as an element of `a`, the length of
3158                // the first dimension of `a` is incremented by one, as `b`
3159                // is non-empty.
3160                length: a_dims[0].length + 1,
3161            }];
3162            dims.extend(b_dims);
3163        }
3164    }
3165
3166    let elems = a_array.elements().iter().chain(b_array.elements().iter());
3167
3168    let datum = temp_storage.try_make_datum(|packer| packer.try_push_array(&dims, elems))?;
3169    Ok(Some(datum.unwrap_array()))
3170}
3171
3172#[sqlfunc(
3173    is_infix_op = true,
3174    sqlname = "||",
3175    propagates_nulls = false,
3176    introduces_nulls = false
3177)]
3178fn list_list_concat<'a, T: FromDatum<'a>>(
3179    a: Option<DatumList<'a, T>>,
3180    b: Option<DatumList<'a, T>>,
3181    temp_storage: &'a RowArena,
3182) -> Option<DatumList<'a, T>> {
3183    let Some(a) = a else {
3184        return b;
3185    };
3186    let Some(b) = b else {
3187        return Some(a);
3188    };
3189
3190    Some(temp_storage.make_datum_list(a.typed_iter().chain(b.typed_iter())))
3191}
3192
3193#[sqlfunc(is_infix_op = true, sqlname = "||", propagates_nulls = false)]
3194fn list_element_concat<'a, T: FromDatum<'a>>(
3195    a: Option<DatumList<'a, T>>,
3196    b: T,
3197    temp_storage: &'a RowArena,
3198) -> DatumList<'a, T> {
3199    let a_elems = a.into_iter().flat_map(|a| a.typed_iter());
3200    temp_storage.make_datum_list(a_elems.chain(std::iter::once(b)))
3201}
3202
3203// Note that the output type corresponds to the _second_ parameter's input type.
3204#[sqlfunc(is_infix_op = true, sqlname = "||", propagates_nulls = false)]
3205fn element_list_concat<'a, T: FromDatum<'a>>(
3206    a: T,
3207    b: Option<DatumList<'a, T>>,
3208    temp_storage: &'a RowArena,
3209) -> DatumList<'a, T> {
3210    let b_elems = b.into_iter().flat_map(|b| b.typed_iter());
3211    temp_storage.make_datum_list(std::iter::once(a).chain(b_elems))
3212}
3213
3214#[sqlfunc(sqlname = "list_remove")]
3215fn list_remove<'a, T: FromDatum<'a>>(
3216    a: DatumList<'a, T>,
3217    b: T,
3218    temp_storage: &'a RowArena,
3219) -> DatumList<'a, T> {
3220    temp_storage.make_datum_list(a.typed_iter().filter(|elem| *elem != b))
3221}
3222
3223#[sqlfunc(sqlname = "digest")]
3224fn digest_string(to_digest: &str, digest_fn: &str) -> Result<Vec<u8>, EvalError> {
3225    digest_inner(to_digest.as_bytes(), digest_fn)
3226}
3227
3228#[sqlfunc(sqlname = "digest")]
3229fn digest_bytes(to_digest: &[u8], digest_fn: &str) -> Result<Vec<u8>, EvalError> {
3230    digest_inner(to_digest, digest_fn)
3231}
3232
3233fn digest_inner(bytes: &[u8], digest_fn: &str) -> Result<Vec<u8>, EvalError> {
3234    match digest_fn {
3235        "md5" => Ok(Md5::digest(bytes).to_vec()),
3236        "sha1" => Ok(digest::digest(&digest::SHA1_FOR_LEGACY_USE_ONLY, bytes)
3237            .as_ref()
3238            .to_vec()),
3239        "sha224" => Ok(digest::digest(&digest::SHA224, bytes).as_ref().to_vec()),
3240        "sha256" => Ok(digest::digest(&digest::SHA256, bytes).as_ref().to_vec()),
3241        "sha384" => Ok(digest::digest(&digest::SHA384, bytes).as_ref().to_vec()),
3242        "sha512" => Ok(digest::digest(&digest::SHA512, bytes).as_ref().to_vec()),
3243        other => Err(EvalError::InvalidHashAlgorithm(other.into())),
3244    }
3245}
3246
3247#[sqlfunc]
3248fn mz_render_typmod(oid: u32, typmod: i32) -> String {
3249    match Type::from_oid_and_typmod(oid, typmod) {
3250        Ok(typ) => typ.constraint().display_or("").to_string(),
3251        // Match dubious PostgreSQL behavior of outputting the unmodified
3252        // `typmod` when positive if the type OID/typmod is invalid.
3253        Err(_) if typmod >= 0 => format!("({typmod})"),
3254        Err(_) => "".into(),
3255    }
3256}
3257
3258#[cfg(test)]
3259mod test {
3260    use mz_repr::PropDatum;
3261    use proptest::prelude::*;
3262
3263    use super::*;
3264    use crate::{Eval, MirScalarExpr};
3265
3266    #[mz_ore::test]
3267    fn variant_names_unique() {
3268        // `from_variant_name` resolves the first variant with a matching
3269        // canonical name, so a duplicate name (from a typo in `func_name!` or
3270        // colliding function names) would silently shadow a variant.
3271        fn assert_unique(enum_name: &str, names: impl Iterator<Item = &'static str>) {
3272            let mut seen = std::collections::BTreeSet::new();
3273            for name in names {
3274                assert!(seen.insert(name), "duplicate {enum_name} name: {name}");
3275            }
3276        }
3277        assert_unique("UnaryFunc", UnaryFunc::variant_names());
3278        assert_unique("BinaryFunc", BinaryFunc::variant_names());
3279        assert_unique("VariadicFunc", VariadicFunc::variant_names());
3280    }
3281
3282    #[mz_ore::test]
3283    fn add_interval_months() {
3284        let dt = ym(2000, 1);
3285
3286        assert_eq!(add_timestamp_months(&*dt, 0).unwrap(), dt);
3287        assert_eq!(add_timestamp_months(&*dt, 1).unwrap(), ym(2000, 2));
3288        assert_eq!(add_timestamp_months(&*dt, 12).unwrap(), ym(2001, 1));
3289        assert_eq!(add_timestamp_months(&*dt, 13).unwrap(), ym(2001, 2));
3290        assert_eq!(add_timestamp_months(&*dt, 24).unwrap(), ym(2002, 1));
3291        assert_eq!(add_timestamp_months(&*dt, 30).unwrap(), ym(2002, 7));
3292
3293        // and negatives
3294        assert_eq!(add_timestamp_months(&*dt, -1).unwrap(), ym(1999, 12));
3295        assert_eq!(add_timestamp_months(&*dt, -12).unwrap(), ym(1999, 1));
3296        assert_eq!(add_timestamp_months(&*dt, -13).unwrap(), ym(1998, 12));
3297        assert_eq!(add_timestamp_months(&*dt, -24).unwrap(), ym(1998, 1));
3298        assert_eq!(add_timestamp_months(&*dt, -30).unwrap(), ym(1997, 7));
3299
3300        // and going over a year boundary by less than a year
3301        let dt = ym(1999, 12);
3302        assert_eq!(add_timestamp_months(&*dt, 1).unwrap(), ym(2000, 1));
3303        let end_of_month_dt = NaiveDate::from_ymd_opt(1999, 12, 31)
3304            .unwrap()
3305            .and_hms_opt(9, 9, 9)
3306            .unwrap();
3307        assert_eq!(
3308            // leap year
3309            add_timestamp_months(&end_of_month_dt, 2).unwrap(),
3310            NaiveDate::from_ymd_opt(2000, 2, 29)
3311                .unwrap()
3312                .and_hms_opt(9, 9, 9)
3313                .unwrap()
3314                .try_into()
3315                .unwrap(),
3316        );
3317        assert_eq!(
3318            // not leap year
3319            add_timestamp_months(&end_of_month_dt, 14).unwrap(),
3320            NaiveDate::from_ymd_opt(2001, 2, 28)
3321                .unwrap()
3322                .and_hms_opt(9, 9, 9)
3323                .unwrap()
3324                .try_into()
3325                .unwrap(),
3326        );
3327    }
3328
3329    fn ym(year: i32, month: u32) -> CheckedTimestamp<NaiveDateTime> {
3330        NaiveDate::from_ymd_opt(year, month, 1)
3331            .unwrap()
3332            .and_hms_opt(9, 9, 9)
3333            .unwrap()
3334            .try_into()
3335            .unwrap()
3336    }
3337
3338    #[mz_ore::test]
3339    fn array_lower_upper_respect_lower_bound() {
3340        use mz_repr::adt::array::ArrayDimension;
3341        use mz_repr::{Datum, RowArena};
3342
3343        let arena = RowArena::new();
3344
3345        // Builds a one-dimensional array with the given lower bound and length,
3346        // then returns (array_lower(_, 1), array_upper(_, 1)).
3347        let bounds = |lower_bound: isize, length: usize| {
3348            let dims = [ArrayDimension {
3349                lower_bound,
3350                length,
3351            }];
3352            let elems = vec![Datum::Int32(0); length];
3353            let datum = arena.make_datum(|packer| packer.try_push_array(&dims, elems).unwrap());
3354            let arr = match datum {
3355                Datum::Array(arr) => arr,
3356                other => panic!("expected array, got {other:?}"),
3357            };
3358            (array_lower(arr, 1).unwrap(), array_upper(arr, 1).unwrap())
3359        };
3360
3361        // Default lower bound of 1: array_fill(0, ARRAY[3]).
3362        assert_eq!(bounds(1, 3), (Some(1), Some(3)));
3363        // Lower bound of 5: array_fill(0, ARRAY[3], ARRAY[5]) => [5:7].
3364        assert_eq!(bounds(5, 3), (Some(5), Some(7)));
3365        // Negative lower bound: array_fill(0, ARRAY[3], ARRAY[-3]) => [-3:-1].
3366        assert_eq!(bounds(-3, 3), (Some(-3), Some(-1)));
3367
3368        // Out-of-range dimensions return None rather than the bound.
3369        let dims = [ArrayDimension {
3370            lower_bound: 5,
3371            length: 3,
3372        }];
3373        let elems = vec![Datum::Int32(0); 3];
3374        let datum = arena.make_datum(|packer| packer.try_push_array(&dims, elems).unwrap());
3375        let arr = match datum {
3376            Datum::Array(arr) => arr,
3377            other => panic!("expected array, got {other:?}"),
3378        };
3379        assert_eq!(array_lower(arr, 0).unwrap(), None);
3380        assert_eq!(array_upper(arr, 0).unwrap(), None);
3381        assert_eq!(array_lower(arr, 2).unwrap(), None);
3382        assert_eq!(array_upper(arr, 2).unwrap(), None);
3383    }
3384
3385    #[mz_ore::test]
3386    #[cfg_attr(miri, ignore)] // unsupported operation: can't call foreign function `decNumberFromInt32` on OS `linux`
3387    fn test_is_monotone() {
3388        use proptest::prelude::*;
3389
3390        /// Asserts that the function is either monotonically increasing or decreasing over
3391        /// the given sets of arguments.
3392        fn assert_monotone<'a, const N: usize>(
3393            expr: &MirScalarExpr,
3394            arena: &'a RowArena,
3395            datums: &[[Datum<'a>; N]],
3396        ) {
3397            // TODO: assertions for nulls, errors
3398            let Ok(results) = datums
3399                .iter()
3400                .map(|args| expr.eval(args.as_slice(), arena))
3401                .collect::<Result<Vec<_>, _>>()
3402            else {
3403                return;
3404            };
3405
3406            let forward = results.iter().tuple_windows().all(|(a, b)| a <= b);
3407            let reverse = results.iter().tuple_windows().all(|(a, b)| a >= b);
3408            assert!(
3409                forward || reverse,
3410                "expected {expr} to be monotone, but passing {datums:?} returned {results:?}"
3411            );
3412        }
3413
3414        fn proptest_binary<'a>(
3415            func: BinaryFunc,
3416            arena: &'a RowArena,
3417            left: impl Strategy<Value = PropDatum>,
3418            right: impl Strategy<Value = PropDatum>,
3419        ) {
3420            let (left_monotone, right_monotone) = func.is_monotone();
3421            let expr = MirScalarExpr::CallBinary {
3422                func,
3423                expr1: Box::new(MirScalarExpr::column(0)),
3424                expr2: Box::new(MirScalarExpr::column(1)),
3425            };
3426            proptest!(|(
3427                mut left in proptest::array::uniform3(left),
3428                mut right in proptest::array::uniform3(right),
3429            )| {
3430                left.sort();
3431                right.sort();
3432                if left_monotone {
3433                    for r in &right {
3434                        let args: Vec<[_; 2]> = left
3435                            .iter()
3436                            .map(|l| [Datum::from(l), Datum::from(r)])
3437                            .collect();
3438                        assert_monotone(&expr, arena, &args);
3439                    }
3440                }
3441                if right_monotone {
3442                    for l in &left {
3443                        let args: Vec<[_; 2]> = right
3444                            .iter()
3445                            .map(|r| [Datum::from(l), Datum::from(r)])
3446                            .collect();
3447                        assert_monotone(&expr, arena, &args);
3448                    }
3449                }
3450            });
3451        }
3452
3453        let interesting_strs: Vec<_> = SqlScalarType::String.interesting_datums().collect();
3454        let str_datums = proptest::strategy::Union::new([
3455            proptest::string::string_regex("[A-Z]{0,10}")
3456                .expect("valid regex")
3457                .prop_map(|s| PropDatum::String(s.to_string()))
3458                .boxed(),
3459            (0..interesting_strs.len())
3460                .prop_map(move |i| {
3461                    let Datum::String(val) = interesting_strs[i] else {
3462                        unreachable!("interesting strings has non-strings")
3463                    };
3464                    PropDatum::String(val.to_string())
3465                })
3466                .boxed(),
3467        ]);
3468
3469        let interesting_i32s: Vec<Datum<'static>> =
3470            SqlScalarType::Int32.interesting_datums().collect();
3471        let i32_datums = proptest::strategy::Union::new([
3472            any::<i32>().prop_map(PropDatum::Int32).boxed(),
3473            (0..interesting_i32s.len())
3474                .prop_map(move |i| {
3475                    let Datum::Int32(val) = interesting_i32s[i] else {
3476                        unreachable!("interesting int32 has non-i32s")
3477                    };
3478                    PropDatum::Int32(val)
3479                })
3480                .boxed(),
3481            (-10i32..10).prop_map(PropDatum::Int32).boxed(),
3482        ]);
3483
3484        let arena = RowArena::new();
3485
3486        // It would be interesting to test all funcs here, but we currently need to hardcode
3487        // the generators for the argument types, which makes this tedious. Choose an interesting
3488        // subset for now.
3489        proptest_binary(
3490            BinaryFunc::AddInt32(AddInt32),
3491            &arena,
3492            &i32_datums,
3493            &i32_datums,
3494        );
3495        proptest_binary(SubInt32.into(), &arena, &i32_datums, &i32_datums);
3496        proptest_binary(MulInt32.into(), &arena, &i32_datums, &i32_datums);
3497        proptest_binary(DivInt32.into(), &arena, &i32_datums, &i32_datums);
3498        proptest_binary(TextConcatBinary.into(), &arena, &str_datums, &str_datums);
3499        proptest_binary(Left.into(), &arena, &str_datums, &i32_datums);
3500    }
3501}