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