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