Skip to main content

mz_expr/scalar/
func.rs

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