use rudb_common::{Error, LogicalType, Result, Value, civil_from_days, days_from_civil};
use rudb_vector::{Data, Form, StringColumn, Validity, Vector};
use crate::cast;
use crate::compare::{self, Comparison};
use crate::datetime::Part;
use crate::fallback::{self, Kernel};
use crate::number::{approximate, digits, fit, integral, pow10, rescale};
use crate::regexp;
use crate::shape::{first, identity, nulls_of, single};
use crate::subscript;
use crate::text;
pub type Written<'a> = Option<&'a dyn Fn() -> String>;
pub fn call<V: AsRef<Vector>>(
name: &str,
args: &[V],
returns: &LogicalType,
written: Written<'_>,
) -> Result<Vector> {
let rows = args.first().map_or(0, |arg| arg.as_ref().len());
for (at, arg) in args.iter().enumerate() {
if arg.as_ref().len() != rows {
return Err(Error::internal(format!(
"argument {at} of {name} is {} rows and argument 0 is {rows}",
arg.as_ref().len()
)));
}
}
if rows > 0 && !args.is_empty() && args.iter().all(|arg| arg.as_ref().form() == Form::Constant)
{
let row: Vec<Value> = args.iter().map(|arg| arg.as_ref().value_at(0)).collect();
return Ok(Vector::constant(
returns.clone(),
call_values(name, &row, returns, written)?,
rows,
));
}
if let Some(vector) = specialized(name, args, returns, rows, written)? {
return Ok(vector);
}
let left = args.first().map_or(Form::Flat, |arg| arg.as_ref().form());
fallback::record(Kernel::Scalar, left, args.get(1).map_or(left, |arg| arg.as_ref().form()));
let mut row = Vec::with_capacity(args.len());
let mut values = Vec::with_capacity(rows);
for index in 0..rows {
row.clear();
row.extend(args.iter().map(|arg| arg.as_ref().value_at(index)));
values.push(call_values(name, &row, returns, written)?);
}
Vector::from_values(returns.clone(), &values)
}
fn specialized<V: AsRef<Vector>>(
name: &str,
args: &[V],
returns: &LogicalType,
rows: usize,
written: Written<'_>,
) -> Result<Option<Vector>> {
if regexp::is_regexp(name) {
return regexp::vectorized(name, args, returns, rows);
}
match args {
[only] => unary(name, only.as_ref(), returns, rows),
[left, right] => binary(name, left.as_ref(), right.as_ref(), returns, rows, written),
_ => Ok(None),
}
}
pub(crate) fn over_valid(
len: usize,
base: Validity,
mut body: impl FnMut(usize) -> Result<()>,
) -> Result<Validity> {
match &base {
Validity::AllValid => {
for index in 0..len {
body(index)?;
}
}
Validity::AllInvalid => {}
Validity::Mask(mask) => {
for index in 0..len {
if mask.get(index) {
body(index)?;
}
}
}
}
Ok(if len == 0 { Validity::AllValid } else { base.normalize(len) })
}
pub(crate) fn finish(
returns: &LogicalType,
data: Data,
validity: Validity,
) -> Result<Option<Vector>> {
Ok(Some(Vector::flat(returns.clone(), data)?.with_validity(validity)))
}
fn unary(name: &str, arg: &Vector, returns: &LogicalType, rows: usize) -> Result<Option<Vector>> {
let base = nulls_of(arg);
match arg.form() {
Form::Flat => {
let Some(data) = arg.data() else {
return Ok(None);
};
one_of(name, data, identity, base, rows, returns, arg)
}
Form::Dictionary => {
let Some((codes, values)) = arg.dictionary_parts() else {
return Ok(None);
};
if codes.len() < rows {
return Ok(None);
}
let Some(data) = values.data() else {
return Ok(None);
};
one_of(name, data, move |index| codes[index] as usize, base, rows, returns, arg)
}
_ => Ok(None),
}
}
fn one_of<A: Fn(usize) -> usize>(
name: &str,
data: &Data,
at: A,
base: Validity,
rows: usize,
returns: &LogicalType,
arg: &Vector,
) -> Result<Option<Vector>> {
match name {
"not" => not_of(data, at, base, rows, returns),
"-" | "abs" if arg.logical_type() == returns => {
sign_of(name, data, at, base, rows, returns, arg)
}
"length" => length_of(data, at, base, rows, returns),
"strlen" => bytes_of(data, at, base, rows, returns),
"lower" | "upper" => fold_of(name, data, at, base, rows, returns),
"make_date" => made_date(data, at, base, rows, returns),
"epoch_ms" => made_timestamp(data, at, base, rows, returns),
_ => Ok(None),
}
}
fn made_date<A: Fn(usize) -> usize>(
data: &Data,
at: A,
base: Validity,
rows: usize,
returns: &LogicalType,
) -> Result<Option<Vector>> {
let (Data::Int32(days), LogicalType::Date) = (data, returns) else {
return Ok(None);
};
let out: Vec<i32> = (0..rows).map(|index| days[at(index)]).collect();
finish(returns, Data::Int32(out.into()), base.normalize(rows))
}
fn made_timestamp<A: Fn(usize) -> usize>(
data: &Data,
at: A,
base: Validity,
rows: usize,
returns: &LogicalType,
) -> Result<Option<Vector>> {
let (Data::Int64(millis), LogicalType::Timestamp) = (data, returns) else {
return Ok(None);
};
let mut out = vec![0i64; rows];
let validity = over_valid(rows, base, |index| {
out[index] = micros_of_millis(millis[at(index)])?;
Ok(())
})?;
finish(returns, Data::Int64(out.into()), validity)
}
fn micros_of_millis(millis: i64) -> Result<i64> {
millis.checked_mul(1_000).ok_or_else(|| {
Error::conversion("Could not convert Timestamp(MS) to Timestamp(US)".to_owned())
})
}
fn not_of<A: Fn(usize) -> usize>(
data: &Data,
at: A,
base: Validity,
rows: usize,
returns: &LogicalType,
) -> Result<Option<Vector>> {
let Data::Bool(held) = data else {
return Ok(None);
};
let mut out = vec![false; rows];
let validity = over_valid(rows, base, |index| {
out[index] = !held[at(index)];
Ok(())
})?;
finish(returns, Data::Bool(out.into()), validity)
}
fn sign_of<A: Fn(usize) -> usize>(
name: &str,
data: &Data,
at: A,
base: Validity,
rows: usize,
returns: &LogicalType,
arg: &Vector,
) -> Result<Option<Vector>> {
let negating = name == "-";
macro_rules! runs {
($(($variant:ident, $native:ty, $zero:expr)),+ $(,)?) => {
match data {
$(
Data::$variant(held) => {
let mut out = vec![0; rows];
let validity = over_valid(rows, base, |index| {
let value = held[at(index)];
let computed =
if negating { value.checked_neg() } else { value.checked_abs() };
match computed {
Some(answer) => {
out[index] = answer;
Ok(())
}
None if negating => Err(negation_overflow()),
None => Err(abs_overflow(&arg.value_at(index))),
}
})?;
finish(returns, Data::$variant(out.into()), validity)
}
)+
Data::Float32(held) => {
let mut out = vec![0.0f32; rows];
let validity = over_valid(rows, base, |index| {
let value = held[at(index)];
out[index] = if negating { -value } else { value.abs() };
Ok(())
})?;
finish(returns, Data::Float32(out.into()), validity)
}
Data::Float64(held) => {
let mut out = vec![0.0f64; rows];
let validity = over_valid(rows, base, |index| {
let value = held[at(index)];
out[index] = if negating { -value } else { value.abs() };
Ok(())
})?;
finish(returns, Data::Float64(out.into()), validity)
}
_ => Ok(None),
}
};
}
rudb_vector::for_each_layout!(signed, runs)
}
fn length_of<A: Fn(usize) -> usize>(
data: &Data,
at: A,
base: Validity,
rows: usize,
returns: &LogicalType,
) -> Result<Option<Vector>> {
let (Data::Varlen(column), LogicalType::BigInt) = (data, returns) else {
return Ok(None);
};
let mut out = vec![0i64; rows];
let validity = over_valid(rows, base, |index| {
let bytes = column.bytes(at(index)).unwrap_or_default();
let characters = bytes.iter().filter(|byte| (**byte as i8) >= -0x40).count();
out[index] = i64::try_from(characters).unwrap_or(i64::MAX);
Ok(())
})?;
finish(returns, Data::Int64(out.into()), validity)
}
fn bytes_of<A: Fn(usize) -> usize>(
data: &Data,
at: A,
base: Validity,
rows: usize,
returns: &LogicalType,
) -> Result<Option<Vector>> {
let (Data::Varlen(column), LogicalType::BigInt) = (data, returns) else {
return Ok(None);
};
let mut out = vec![0i64; rows];
let validity = over_valid(rows, base, |index| {
let bytes = column.bytes(at(index)).unwrap_or_default();
out[index] = i64::try_from(bytes.len()).unwrap_or(i64::MAX);
Ok(())
})?;
finish(returns, Data::Int64(out.into()), validity)
}
fn fold_of<A: Fn(usize) -> usize>(
name: &str,
data: &Data,
at: A,
base: Validity,
rows: usize,
returns: &LogicalType,
) -> Result<Option<Vector>> {
let (Data::Varlen(column), LogicalType::Varchar) = (data, returns) else {
return Ok(None);
};
let lowering = name == "lower";
let out = each_string(rows, &base, |index, into| {
let text = column.get(at(index)).unwrap_or_default();
let folded = if lowering { text.to_lowercase() } else { text.to_uppercase() };
into.push(&folded);
});
finish(returns, Data::Varlen(out), base.normalize(rows))
}
pub(crate) fn each_string(
rows: usize,
base: &Validity,
mut body: impl FnMut(usize, &mut StringColumn),
) -> StringColumn {
let mut out = StringColumn::with_capacity(rows);
for index in 0..rows {
if base.is_valid(index) {
body(index, &mut out);
} else {
out.push("");
}
}
out
}
fn binary(
name: &str,
left: &Vector,
right: &Vector,
returns: &LogicalType,
rows: usize,
written: Written<'_>,
) -> Result<Option<Vector>> {
if let Some(op) = arithmetic_op(name) {
return arithmetic_of(op, left, right, returns, written);
}
match name {
"/" => slash_of(left, right, returns),
"||" => concat_of(left, right, returns),
"~~" => like_of(left, right, returns, rows, false, false),
"!~~" => like_of(left, right, returns, rows, false, true),
"~~*" => like_of(left, right, returns, rows, true, false),
"!~~*" => like_of(left, right, returns, rows, true, true),
"date_part" | "date_trunc" => date_of(name, left, right, returns, rows),
_ => Ok(None),
}
}
fn arithmetic_op(name: &str) -> Option<Op> {
match name {
"+" => Some(Op::Add),
"-" => Some(Op::Subtract),
"*" => Some(Op::Multiply),
"//" => Some(Op::Divide),
"%" => Some(Op::Modulo),
_ => None,
}
}
macro_rules! by_form {
($left:ident, $right:ident, $body:ident, $($rest:expr),* $(,)?) => {{
if let (Some(one), Some(other)) = ($left.data(), $right.data()) {
return $body(one, identity, other, identity, $($rest),*);
}
if let (Some(one), Some(value)) = ($left.data(), $right.constant_value()) {
let Some(held) = single($right.logical_type(), value) else { return Ok(None) };
let Some(other) = held.data() else { return Ok(None) };
return $body(one, identity, other, first, $($rest),*);
}
if let (Some(value), Some(other)) = ($left.constant_value(), $right.data()) {
let Some(held) = single($left.logical_type(), value) else { return Ok(None) };
let Some(one) = held.data() else { return Ok(None) };
return $body(one, first, other, identity, $($rest),*);
}
if let (Some((codes, values)), Some(other)) = ($left.dictionary_parts(), $right.data()) {
let Some(one) = values.data() else { return Ok(None) };
let at = move |index: usize| codes[index] as usize;
return $body(one, at, other, identity, $($rest),*);
}
if let (Some(one), Some((codes, values))) = ($left.data(), $right.dictionary_parts()) {
let Some(other) = values.data() else { return Ok(None) };
let at = move |index: usize| codes[index] as usize;
return $body(one, identity, other, at, $($rest),*);
}
if let (Some((codes, values)), Some(value)) =
($left.dictionary_parts(), $right.constant_value())
{
let Some(one) = values.data() else { return Ok(None) };
let Some(held) = single($right.logical_type(), value) else { return Ok(None) };
let Some(other) = held.data() else { return Ok(None) };
let at = move |index: usize| codes[index] as usize;
return $body(one, at, other, first, $($rest),*);
}
if let (Some(value), Some((codes, values))) =
($left.constant_value(), $right.dictionary_parts())
{
let Some(other) = values.data() else { return Ok(None) };
let Some(held) = single($left.logical_type(), value) else { return Ok(None) };
let Some(one) = held.data() else { return Ok(None) };
let at = move |index: usize| codes[index] as usize;
return $body(one, first, other, at, $($rest),*);
}
Ok(None)
}};
}
fn arithmetic_of(
op: Op,
left: &Vector,
right: &Vector,
returns: &LogicalType,
written: Written<'_>,
) -> Result<Option<Vector>> {
let lined_up = match (returns, left.logical_type(), right.logical_type()) {
(
LogicalType::Decimal { width, .. },
LogicalType::Decimal { width: one, .. },
LogicalType::Decimal { width: other, .. },
) => one == width && other == width,
(_, one, other) => one == returns && other == returns,
};
if !lined_up {
return Ok(None);
}
by_form!(left, right, arithmetic_runs, op, left, right, returns, written)
}
fn sweep<T, L, R, S>(out: &mut [T], a: &[T], at_left: L, b: &[T], at_right: R, step: S) -> bool
where
T: Copy,
L: Fn(usize) -> usize,
R: Fn(usize) -> usize,
S: Fn(T, T) -> (T, bool),
{
let mut trouble = false;
for (index, slot) in out.iter_mut().enumerate() {
let (value, overflowed) = step(a[at_left(index)], b[at_right(index)]);
*slot = value;
trouble |= overflowed;
}
trouble
}
fn blank<T: Copy + Default>(out: &mut [T], validity: &Validity) {
match validity {
Validity::AllValid => {}
Validity::AllInvalid => out.fill(T::default()),
Validity::Mask(mask) => {
for (index, slot) in out.iter_mut().enumerate() {
if !mask.get(index) {
*slot = T::default();
}
}
}
}
}
#[expect(
clippy::too_many_arguments,
reason = "two sides with an index each, the operator, the two vectors the error message needs \
and the type of the answer, none of which is worth a struct that exists for three \
calls"
)]
fn arithmetic_runs<L, R>(
one: &Data,
at_left: L,
other: &Data,
at_right: R,
op: Op,
left: &Vector,
right: &Vector,
returns: &LogicalType,
written: Written<'_>,
) -> Result<Option<Vector>>
where
L: Fn(usize) -> usize,
R: Fn(usize) -> usize,
{
let rows = left.len();
let base = nulls_of(left).and(&nulls_of(right), rows);
if matches!(returns, LogicalType::Decimal { .. }) {
return decimal_runs(
one, at_left, other, at_right, op, &base, left, right, returns, written,
);
}
if matches!(op, Op::Divide | Op::Modulo) {
return guarded_runs(
one, at_left, other, at_right, op, base, left, right, returns, written,
);
}
fast_runs(one, at_left, other, at_right, op, &base, returns, rows)
}
#[expect(
clippy::too_many_arguments,
reason = "two sides with an index each, the operator, the nulls and the type and length of the \
answer, none of which is worth a struct that exists for three calls"
)]
fn fast_runs<L, R>(
one: &Data,
at_left: L,
other: &Data,
at_right: R,
op: Op,
base: &Validity,
returns: &LogicalType,
rows: usize,
) -> Result<Option<Vector>>
where
L: Fn(usize) -> usize,
R: Fn(usize) -> usize,
{
macro_rules! integers {
($(($variant:ident, $native:ty, $zero:expr)),+ $(,)?) => {
$(
if let (Data::$variant(a), Data::$variant(b)) = (one, other) {
let mut out = vec![0 as $native; rows];
let trouble = match op {
Op::Add => {
sweep(&mut out, a, &at_left, b, &at_right, <$native>::overflowing_add)
}
Op::Subtract => {
sweep(&mut out, a, &at_left, b, &at_right, <$native>::overflowing_sub)
}
Op::Multiply => {
sweep(&mut out, a, &at_left, b, &at_right, <$native>::overflowing_mul)
}
Op::Divide | Op::Modulo => true,
};
if trouble {
return Ok(None);
}
blank(&mut out, base);
return finish(returns, Data::$variant(out.into()), base.clone());
}
)+
};
}
macro_rules! floats {
($variant:ident, $native:ty, $widen:expr, $narrow:expr) => {
if let (Data::$variant(a), Data::$variant(b)) = (one, other) {
let step = |x: $native, y: $native| {
let (x, y) = ($widen(x), $widen(y));
($narrow(float_step(op, x, y)), false)
};
let mut out = vec![0 as $native; rows];
let _ = sweep(&mut out, a, &at_left, b, &at_right, step);
blank(&mut out, base);
return finish(returns, Data::$variant(out.into()), base.clone());
}
};
}
if returns.is_integer() {
rudb_vector::for_each_layout!(integer, integers);
}
floats!(Float64, f64, |x| x, |x| x);
#[expect(
clippy::cast_possible_truncation,
reason = "arithmetic on a FLOAT column produces a FLOAT"
)]
{
floats!(Float32, f32, f64::from, |x| x as f32);
}
Ok(None)
}
#[expect(
clippy::too_many_arguments,
reason = "two sides with an index each, the operator, the nulls, the two vectors the error \
message needs and the type of the answer, none of which is worth a struct that \
exists for three calls"
)]
fn guarded_runs<L, R>(
one: &Data,
at_left: L,
other: &Data,
at_right: R,
op: Op,
base: Validity,
left: &Vector,
right: &Vector,
returns: &LogicalType,
written: Written<'_>,
) -> Result<Option<Vector>>
where
L: Fn(usize) -> usize,
R: Fn(usize) -> usize,
{
let rows = left.len();
macro_rules! integers {
($(($variant:ident, $native:ty, $zero:expr)),+ $(,)?) => {
$(
if let (Data::$variant(a), Data::$variant(b)) = (one, other) {
let mut out = vec![0; rows];
let validity = over_valid(rows, base, |index| {
let (x, y) = (a[at_left(index)], b[at_right(index)]);
if y == 0 {
return Err(divided_by_zero(
written,
op,
&left.value_at(index),
&right.value_at(index),
));
}
let computed = if matches!(op, Op::Divide) {
x.checked_div(y)
} else {
Some(x.wrapping_rem(y))
};
match computed {
Some(answer) => {
out[index] = answer;
Ok(())
}
None => Err(overflow(
op,
returns,
&left.value_at(index),
&right.value_at(index),
)),
}
})?;
return finish(returns, Data::$variant(out.into()), validity);
}
)+
};
}
macro_rules! floats {
($variant:ident, $native:ty, $widen:expr, $narrow:expr) => {
if let (Data::$variant(a), Data::$variant(b)) = (one, other) {
let mut out = vec![0 as $native; rows];
let validity = over_valid(rows, base, |index| {
let (x, y) = (a[at_left(index)], b[at_right(index)]);
if y == 0.0 && matches!(op, Op::Divide) {
return Err(divided_by_zero(
written,
op,
&left.value_at(index),
&right.value_at(index),
));
}
out[index] = $narrow(float_step(op, $widen(x), $widen(y)));
Ok(())
})?;
return finish(returns, Data::$variant(out.into()), validity);
}
};
}
if returns.is_integer() {
rudb_vector::for_each_layout!(integer, integers);
}
floats!(Float64, f64, |x| x, |x| x);
#[expect(
clippy::cast_possible_truncation,
reason = "arithmetic on a FLOAT column produces a FLOAT"
)]
{
floats!(Float32, f32, f64::from, |x| x as f32);
}
Ok(None)
}
#[expect(
clippy::too_many_arguments,
reason = "two sides with an index each, the operator, the nulls, the type of the answer and \
the two vectors the error message needs, none of which is worth a struct that \
exists for three calls"
)]
fn decimal_runs<L, R>(
one: &Data,
at_left: L,
other: &Data,
at_right: R,
op: Op,
base: &Validity,
left: &Vector,
right: &Vector,
returns: &LogicalType,
written: Written<'_>,
) -> Result<Option<Vector>>
where
L: Fn(usize) -> usize,
R: Fn(usize) -> usize,
{
let LogicalType::Decimal { width, scale } = *returns else {
return Ok(None);
};
let (Some((_, left_scale)), Some((_, right_scale))) =
(left.logical_type().decimal_shape(), right.logical_type().decimal_shape())
else {
return Ok(None);
};
let held = left_scale.saturating_add(right_scale);
if !matches!(op, Op::Multiply) && (left_scale != scale || right_scale != scale) {
return Ok(None);
}
let rows = left.len();
let guarding = matches!(op, Op::Divide | Op::Modulo);
macro_rules! runs {
($($variant:ident => $native:ty),+ $(,)?) => {
$(
if let (Data::$variant(a), Data::$variant(b)) = (one, other) {
let mut out = vec![0 as $native; rows];
let validity = over_valid(rows, base.clone(), |index| {
let x = i128::from(a[at_left(index)]);
let y = i128::from(b[at_right(index)]);
if guarding && y == 0 {
return Err(divided_by_zero(
written,
op,
&left.value_at(index),
&right.value_at(index),
));
}
let unscaled = match op {
Op::Add => x.checked_add(y),
Op::Subtract => x.checked_sub(y),
Op::Multiply => {
x.checked_mul(y).and_then(|wide| rescale(wide, held, scale))
}
Op::Modulo => x.checked_rem(y),
Op::Divide => {
x.checked_div(y).and_then(|whole| whole.checked_mul(pow10(scale)))
}
};
let fits = unscaled
.filter(|value| digits(*value) <= width)
.and_then(|value| <$native>::try_from(value).ok());
match fits {
Some(answer) => {
out[index] = answer;
Ok(())
}
None => Err(overflow(
op,
returns,
&left.value_at(index),
&right.value_at(index),
)),
}
})?;
return finish(returns, Data::$variant(out.into()), validity);
}
)+
};
}
runs!(Int16 => i16, Int32 => i32, Int64 => i64, Int128 => i128);
Ok(None)
}
fn float_step(op: Op, x: f64, y: f64) -> f64 {
match op {
Op::Add => x + y,
Op::Subtract => x - y,
Op::Multiply => x * y,
Op::Divide => x / y,
Op::Modulo => x % y,
}
}
fn slash_of(left: &Vector, right: &Vector, returns: &LogicalType) -> Result<Option<Vector>> {
if !matches!(returns, LogicalType::Double)
|| left.logical_type() != returns
|| right.logical_type() != returns
{
return Ok(None);
}
by_form!(left, right, slash_runs, left, right, returns)
}
fn slash_runs<L, R>(
one: &Data,
at_left: L,
other: &Data,
at_right: R,
left: &Vector,
right: &Vector,
returns: &LogicalType,
) -> Result<Option<Vector>>
where
L: Fn(usize) -> usize,
R: Fn(usize) -> usize,
{
let (Data::Float64(a), Data::Float64(b)) = (one, other) else {
return Ok(None);
};
let rows = left.len();
let base = nulls_of(left).and(&nulls_of(right), rows);
let mut out = vec![0.0f64; rows];
let validity = over_valid(rows, base, |index| {
let (x, y) = (a[at_left(index)], b[at_right(index)]);
out[index] = x / y;
Ok(())
})?;
finish(returns, Data::Float64(out.into()), validity)
}
fn concat_of(left: &Vector, right: &Vector, returns: &LogicalType) -> Result<Option<Vector>> {
if !matches!(returns, LogicalType::Varchar)
|| !matches!(left.logical_type(), LogicalType::Varchar)
|| !matches!(right.logical_type(), LogicalType::Varchar)
{
return Ok(None);
}
by_form!(left, right, concat_runs, left, right, returns)
}
fn concat_runs<L, R>(
one: &Data,
at_left: L,
other: &Data,
at_right: R,
left: &Vector,
right: &Vector,
returns: &LogicalType,
) -> Result<Option<Vector>>
where
L: Fn(usize) -> usize,
R: Fn(usize) -> usize,
{
let (Data::Varlen(a), Data::Varlen(b)) = (one, other) else {
return Ok(None);
};
let rows = left.len();
let base = nulls_of(left).and(&nulls_of(right), rows);
let mut joined = String::new();
let out = each_string(rows, &base, |index, into| {
joined.clear();
joined.push_str(a.get(at_left(index)).unwrap_or_default());
joined.push_str(b.get(at_right(index)).unwrap_or_default());
into.push(&joined);
});
finish(returns, Data::Varlen(out), base.normalize(rows))
}
fn like_of(
text: &Vector,
pattern: &Vector,
returns: &LogicalType,
rows: usize,
fold_case: bool,
negated: bool,
) -> Result<Option<Vector>> {
if !matches!(returns, LogicalType::Boolean) {
return Ok(None);
}
let Some(Value::Varchar(spelling)) = pattern.constant_value() else {
return Ok(None);
};
let spelling = if fold_case { spelling.to_lowercase() } else { spelling.clone() };
let compiled = Pattern::compile(&spelling);
let base = nulls_of(text).and(&nulls_of(pattern), rows);
match text.form() {
Form::Flat => {
let Some(Data::Varlen(column)) = text.data() else {
return Ok(None);
};
like_run(column, identity, &compiled, base, rows, returns, fold_case, negated)
}
Form::Dictionary => {
let Some((codes, values)) = text.dictionary_parts() else {
return Ok(None);
};
if codes.len() < rows {
return Ok(None);
}
let Some(Data::Varlen(column)) = values.data() else {
return Ok(None);
};
let at = move |index: usize| codes[index] as usize;
like_run(column, at, &compiled, base, rows, returns, fold_case, negated)
}
_ => Ok(None),
}
}
#[expect(clippy::too_many_arguments, reason = "one loop, and every argument is what it needs")]
fn like_run<A: Fn(usize) -> usize>(
column: &StringColumn,
at: A,
compiled: &Pattern,
base: Validity,
rows: usize,
returns: &LogicalType,
fold_case: bool,
negated: bool,
) -> Result<Option<Vector>> {
let mut out = vec![false; rows];
let mut characters: Vec<char> = Vec::new();
let validity = over_valid(rows, base, |index| {
let text = column.get(at(index)).unwrap_or_default();
let folded = if fold_case { Some(text.to_lowercase()) } else { None };
let text = folded.as_deref().unwrap_or(text);
out[index] = compiled.holds(text, &mut characters) != negated;
Ok(())
})?;
finish(returns, Data::Bool(out.into()), validity)
}
#[derive(Debug)]
enum Pattern {
Exact(String),
Prefix(String),
Suffix(String),
Contains(String),
General(Vec<char>),
}
impl Pattern {
fn compile(spelling: &str) -> Self {
let plain = |text: &str| !text.contains('%') && !text.contains('_');
if plain(spelling) {
return Self::Exact(spelling.to_owned());
}
if let Some(inner) = spelling.strip_prefix('%').and_then(|rest| rest.strip_suffix('%')) {
if plain(inner) {
return Self::Contains(inner.to_owned());
}
}
if let Some(rest) = spelling.strip_prefix('%') {
if plain(rest) {
return Self::Suffix(rest.to_owned());
}
}
if let Some(head) = spelling.strip_suffix('%') {
if plain(head) {
return Self::Prefix(head.to_owned());
}
}
Self::General(spelling.chars().collect())
}
fn holds(&self, text: &str, characters: &mut Vec<char>) -> bool {
match self {
Self::Exact(against) => text == against,
Self::Prefix(against) => text.starts_with(against.as_str()),
Self::Suffix(against) => text.ends_with(against.as_str()),
Self::Contains(against) => text.contains(against.as_str()),
Self::General(against) => {
characters.clear();
characters.extend(text.chars());
like(characters, against)
}
}
}
}
fn date_of(
name: &str,
spec: &Vector,
when: &Vector,
returns: &LogicalType,
rows: usize,
) -> Result<Option<Vector>> {
let Some(Value::Varchar(spelling)) = spec.constant_value() else {
return Ok(None);
};
let truncating = name == "date_trunc";
if truncating {
if returns != when.logical_type() {
return Ok(None);
}
} else if *returns != LogicalType::BigInt {
return Ok(None);
}
let part = Part::parse(spelling)?;
let base = nulls_of(when).and(&nulls_of(spec), rows);
match when.form() {
Form::Flat => {
let Some(data) = when.data() else {
return Ok(None);
};
date_runs(part, data, identity, base, rows, returns, when, truncating)
}
Form::Dictionary => {
let Some((codes, values)) = when.dictionary_parts() else {
return Ok(None);
};
if codes.len() < rows {
return Ok(None);
}
let Some(data) = values.data() else {
return Ok(None);
};
let at = move |index: usize| codes[index] as usize;
date_runs(part, data, at, base, rows, returns, when, truncating)
}
_ => Ok(None),
}
}
#[expect(
clippy::too_many_arguments,
reason = "the part, the days or microseconds and their mapping, the nulls, the row count, the \
type of the answer, the vector whose type picks the arm and which of the two \
functions this is"
)]
fn date_runs<A: Fn(usize) -> usize>(
part: Part,
data: &Data,
at: A,
base: Validity,
rows: usize,
returns: &LogicalType,
when: &Vector,
truncating: bool,
) -> Result<Option<Vector>> {
match (when.logical_type(), data, truncating) {
(LogicalType::Date, Data::Int32(days), false) => {
let mut out = vec![0i64; rows];
let validity = over_valid(rows, base, |index| {
out[index] = part.of_days(days[at(index)])?;
Ok(())
})?;
finish(returns, Data::Int64(out.into()), validity)
}
(LogicalType::Date, Data::Int32(days), true) => {
let mut out = vec![0i32; rows];
let validity = over_valid(rows, base, |index| {
out[index] = part.truncate_days(days[at(index)])?;
Ok(())
})?;
finish(returns, Data::Int32(out.into()), validity)
}
(LogicalType::Timestamp, Data::Int64(micros), false) => {
let mut out = vec![0i64; rows];
let validity = over_valid(rows, base, |index| {
out[index] = part.of_micros(micros[at(index)])?;
Ok(())
})?;
finish(returns, Data::Int64(out.into()), validity)
}
(LogicalType::Timestamp, Data::Int64(micros), true) => {
let mut out = vec![0i64; rows];
let validity = over_valid(rows, base, |index| {
out[index] = part.truncate_micros(micros[at(index)])?;
Ok(())
})?;
finish(returns, Data::Int64(out.into()), validity)
}
_ => Ok(None),
}
}
fn date_value(name: &str, spec: &Value, when: &Value) -> Result<Value> {
let Value::Varchar(spelling) = spec else {
return Err(Error::internal(format!("{name} of a {} part", spec.logical_type())));
};
let part = Part::parse(spelling)?;
match (name == "date_trunc", when) {
(false, Value::Date(days)) => part.of_days(*days).map(Value::BigInt),
(false, Value::Timestamp(micros)) => part.of_micros(*micros).map(Value::BigInt),
(true, Value::Date(days)) => part.truncate_days(*days).map(Value::Date),
(true, Value::Timestamp(micros)) => part.truncate_micros(*micros).map(Value::Timestamp),
_ => Err(Error::binder(format!(
"No function matches the given name and argument types '{name}(VARCHAR, {})'. You might need to add explicit type casts.",
when.logical_type()
))),
}
}
fn made_date_value(days: &Value) -> Result<Value> {
let Some(days) = days.as_i64() else {
return Err(Error::internal(format!("make_date of a {}", days.logical_type())));
};
let fitted = i32::try_from(days)
.map_err(|_| Error::conversion(format!("Date out of range: {days} days")))?;
Ok(Value::Date(fitted))
}
fn made_civil_value(year: &Value, month: &Value, day: &Value) -> Result<Value> {
let (Some(year), Some(month), Some(day)) = (year.as_i64(), month.as_i64(), day.as_i64()) else {
return Err(Error::internal("make_date of something that is not three numbers"));
};
let out_of_range = || Error::conversion(format!("Date out of range: {year}-{month}-{day}"));
let (fitted, month, day) = match (i32::try_from(year), u32::try_from(month), u32::try_from(day))
{
(Ok(year), Ok(month), Ok(day)) => (year, month, day),
_ => return Err(out_of_range()),
};
if !(1..=12).contains(&month) || day == 0 {
return Err(out_of_range());
}
let days = days_from_civil(fitted, month, day);
if civil_from_days(days) != (fitted, month, day) {
return Err(out_of_range());
}
Ok(Value::Date(days))
}
fn made_timestamp_value(millis: &Value) -> Result<Value> {
let Some(millis) = millis.as_i64() else {
return Err(Error::internal(format!("epoch_ms of a {}", millis.logical_type())));
};
micros_of_millis(millis).map(Value::Timestamp)
}
pub fn call_values(
name: &str,
args: &[Value],
returns: &LogicalType,
written: Written<'_>,
) -> Result<Value> {
if name == "coalesce" {
let found = args.iter().find(|value| !value.is_null());
return Ok(found.cloned().unwrap_or(Value::Null));
}
if let ("nullif", [left, right]) = (name, args) {
if compare::compare_values(Comparison::Equal, left, right)?.as_bool() == Some(true) {
return Ok(Value::Null);
}
return cast::cast_value(left, returns, false);
}
if args.iter().any(Value::is_null) {
return Ok(Value::Null);
}
match (name, args) {
("+", [only]) => Ok(only.clone()),
("-", [only]) => negate(only, returns),
("abs", [only]) => absolute(only, returns),
("not", [only]) => match only.as_bool() {
Some(held) => Ok(Value::Boolean(!held)),
None => Err(Error::internal(format!("not of a {}", only.logical_type()))),
},
("+", [left, right]) => arithmetic(Op::Add, left, right, returns, written),
("-", [left, right]) => arithmetic(Op::Subtract, left, right, returns, written),
("*", [left, right]) => arithmetic(Op::Multiply, left, right, returns, written),
("%", [left, right]) => arithmetic(Op::Modulo, left, right, returns, written),
("//", [left, right]) => arithmetic(Op::Divide, left, right, returns, written),
("/", [left, right]) => divide(left, right),
("||", [left, right]) => Ok(Value::Varchar(format!("{left}{right}"))),
("lower", [only]) => Ok(Value::Varchar(only.to_string().to_lowercase())),
("upper", [only]) => Ok(Value::Varchar(only.to_string().to_uppercase())),
("length", [only]) => Ok(Value::BigInt(count_characters(only))),
("strlen", [only]) => Ok(Value::BigInt(count_bytes(only))),
("substring" | "substr", [held, start]) => text::substring(held, start, None),
("substring" | "substr", [held, start, length]) => {
text::substring(held, start, Some(length))
}
("position" | "strpos" | "instr", [haystack, needle]) => text::position(haystack, needle),
("trim" | "ltrim" | "rtrim", [only]) => text::trim(name, only, None),
("trim" | "ltrim" | "rtrim", [only, characters]) => {
text::trim(name, only, Some(characters))
}
("overlay", [held, replacement, start]) => text::overlay(held, replacement, start, None),
("overlay", [held, replacement, start, length]) => {
text::overlay(held, replacement, start, Some(length))
}
("~~", [text, pattern]) => Ok(Value::Boolean(matches(text, pattern, false))),
("!~~", [text, pattern]) => Ok(Value::Boolean(!matches(text, pattern, false))),
("~~*", [text, pattern]) => Ok(Value::Boolean(matches(text, pattern, true))),
("!~~*", [text, pattern]) => Ok(Value::Boolean(!matches(text, pattern, true))),
("date_part" | "date_trunc", [spec, when]) => date_value(name, spec, when),
("make_date", [days]) => made_date_value(days),
("make_date", [year, month, day]) => made_civil_value(year, month, day),
("epoch_ms", [millis]) => made_timestamp_value(millis),
("array_extract", [target, index]) => subscript::extract(target, index),
("array_slice", [target, begin, end]) => subscript::slice(target, begin, end, None),
("array_slice", [target, begin, end, step]) => {
subscript::slice(target, begin, end, Some(step))
}
(_, [_, _, ..]) if regexp::is_regexp(name) => regexp::value(name, args),
_ => Err(Error::not_implemented(format!(
"the {name} function with {} arguments",
args.len()
))),
}
}
#[derive(Debug, Clone, Copy)]
enum Op {
Add,
Subtract,
Multiply,
Divide,
Modulo,
}
impl Op {
fn word(self) -> &'static str {
match self {
Self::Add => "addition",
Self::Subtract => "subtraction",
Self::Multiply => "multiplication",
Self::Divide => "division",
Self::Modulo => "modulo",
}
}
fn symbol(self) -> &'static str {
match self {
Self::Add => "+",
Self::Subtract => "-",
Self::Multiply => "*",
Self::Divide => "//",
Self::Modulo => "%",
}
}
}
fn overflow(op: Op, ty: &LogicalType, left: &Value, right: &Value) -> Error {
let decimal = matches!(ty, LogicalType::Decimal { .. });
let (left, right) = if decimal {
(unscaled(left), unscaled(right))
} else {
(left.to_string(), right.to_string())
};
let word = if decimal && matches!(op, Op::Subtract) { "subtract" } else { op.word() };
Error::out_of_range(format!(
"Overflow in {word} of {} ({left} {} {right}){}",
physical(ty),
op.symbol(),
ending(op, ty)
))
}
fn divided_by_zero(written: Written<'_>, op: Op, left: &Value, right: &Value) -> Error {
let quoted =
written.map_or_else(|| format!("({left} {} {right})", op.symbol()), |render| render());
Error::invalid_input(format!(
"Division by zero in expression {quoted}. Use TRY(...) to return NULL for this expression, \
or SET null_on_division_by_zero=true to return NULL for all divisions by zero."
))
}
fn abs_overflow(value: &Value) -> Error {
Error::out_of_range(format!("Overflow on abs({value})"))
}
fn negation_overflow() -> Error {
Error::out_of_range("Overflow in negation of numeric value!")
}
fn unscaled(value: &Value) -> String {
match value {
Value::Decimal { unscaled, .. } => unscaled.to_string(),
other => other.to_string(),
}
}
fn physical(ty: &LogicalType) -> String {
let name = match ty {
LogicalType::TinyInt => "INT8",
LogicalType::SmallInt => "INT16",
LogicalType::Integer => "INT32",
LogicalType::BigInt => "INT64",
LogicalType::HugeInt => "INT128",
LogicalType::UTinyInt => "UINT8",
LogicalType::USmallInt => "UINT16",
LogicalType::UInteger => "UINT32",
LogicalType::UBigInt => "UINT64",
LogicalType::UHugeInt => "UINT128",
LogicalType::Decimal { width, .. } => return format!("DECIMAL({})", storage_width(*width)),
other => return other.to_string(),
};
name.to_string()
}
fn storage_width(width: u8) -> u8 {
match width {
0..=4 => 4,
5..=9 => 9,
10..=18 => 18,
_ => 38,
}
}
fn ending(op: Op, ty: &LogicalType) -> &'static str {
let width = match ty {
LogicalType::Decimal { width, .. } => storage_width(*width),
_ => return "!",
};
match op {
Op::Multiply if width == 38 => {
". You might want to add an explicit cast to a decimal with a smaller scale."
}
Op::Multiply => ". You might want to add an explicit cast to a bigger decimal.",
_ => ";",
}
}
fn arithmetic(
op: Op,
left: &Value,
right: &Value,
ty: &LogicalType,
written: Written<'_>,
) -> Result<Value> {
match ty {
LogicalType::Float | LogicalType::Double => float_arithmetic(op, left, right, ty, written),
LogicalType::Decimal { width, scale } => {
decimal_arithmetic(op, left, right, *width, *scale, written)
}
other if other.is_integer() => integer_arithmetic(op, left, right, ty, written),
other => Err(Error::not_implemented(format!("{} on {other}", op.word()))),
}
}
fn integer_arithmetic(
op: Op,
left: &Value,
right: &Value,
ty: &LogicalType,
written: Written<'_>,
) -> Result<Value> {
let (a, b) = match (integral(left), integral(right)) {
(Some(a), Some(b)) => (a, b),
_ => {
return Err(Error::not_implemented(format!(
"{} on {} and {}",
op.word(),
left.logical_type(),
right.logical_type()
)));
}
};
if matches!(op, Op::Divide | Op::Modulo) && b == 0 {
return Err(divided_by_zero(written, op, left, right));
}
let wide = match op {
Op::Add => a.checked_add(b),
Op::Subtract => a.checked_sub(b),
Op::Multiply => a.checked_mul(b),
Op::Divide => a.checked_div(b),
Op::Modulo => a.checked_rem(b),
};
wide.and_then(|whole| fit(whole, ty)).ok_or_else(|| overflow(op, ty, left, right))
}
fn float_arithmetic(
op: Op,
left: &Value,
right: &Value,
ty: &LogicalType,
written: Written<'_>,
) -> Result<Value> {
let (a, b) = match (approximate(left), approximate(right)) {
(Some(a), Some(b)) => (a, b),
_ => {
return Err(Error::not_implemented(format!(
"{} on {} and {}",
op.word(),
left.logical_type(),
right.logical_type()
)));
}
};
if matches!(op, Op::Divide) && b == 0.0 {
return Err(divided_by_zero(written, op, left, right));
}
let result = float_step(op, a, b);
if matches!(ty, LogicalType::Float) {
#[expect(
clippy::cast_possible_truncation,
reason = "arithmetic on a FLOAT column produces a FLOAT"
)]
return Ok(Value::Float(result as f32));
}
Ok(Value::Double(result))
}
fn decimal_arithmetic(
op: Op,
left: &Value,
right: &Value,
width: u8,
scale: u8,
written: Written<'_>,
) -> Result<Value> {
let ty = LogicalType::Decimal { width, scale };
if matches!(op, Op::Multiply) {
return decimal_product(left, right, width, scale, &ty);
}
let (a, b) = match (unscaled_at(left, scale), unscaled_at(right, scale)) {
(Some(a), Some(b)) => (a, b),
_ => {
return Err(Error::not_implemented(format!(
"{} on {} and {}",
op.word(),
left.logical_type(),
right.logical_type()
)));
}
};
if matches!(op, Op::Divide | Op::Modulo) && b == 0 {
return Err(divided_by_zero(written, op, left, right));
}
let unscaled = match op {
Op::Add => a.checked_add(b),
Op::Subtract => a.checked_sub(b),
Op::Multiply => unreachable!("a product is handled above"),
Op::Modulo => a.checked_rem(b),
Op::Divide => a.checked_div(b).and_then(|whole| whole.checked_mul(pow10(scale))),
};
let unscaled = unscaled.ok_or_else(|| overflow(op, &ty, left, right))?;
if digits(unscaled) > width {
return Err(overflow(op, &ty, left, right));
}
Ok(Value::Decimal { unscaled, width, scale })
}
fn decimal_product(
left: &Value,
right: &Value,
width: u8,
scale: u8,
ty: &LogicalType,
) -> Result<Value> {
let (a, b) = match (unscaled_and_scale(left), unscaled_and_scale(right)) {
(Some(a), Some(b)) => (a, b),
_ => {
return Err(Error::not_implemented(format!(
"multiplication on {} and {}",
left.logical_type(),
right.logical_type()
)));
}
};
let held = a.1.saturating_add(b.1);
let unscaled =
a.0.checked_mul(b.0)
.and_then(|wide| rescale(wide, held, scale))
.ok_or_else(|| overflow(Op::Multiply, ty, left, right))?;
if digits(unscaled) > width {
return Err(overflow(Op::Multiply, ty, left, right));
}
Ok(Value::Decimal { unscaled, width, scale })
}
fn unscaled_and_scale(value: &Value) -> Option<(i128, u8)> {
match *value {
Value::Decimal { unscaled, scale, .. } => Some((unscaled, scale)),
_ => integral(value).map(|whole| (whole, 0)),
}
}
fn unscaled_at(value: &Value, scale: u8) -> Option<i128> {
match *value {
Value::Decimal { unscaled, scale: held, .. } => rescale(unscaled, held, scale),
_ => integral(value).and_then(|whole| whole.checked_mul(pow10(scale))),
}
}
fn divide(left: &Value, right: &Value) -> Result<Value> {
let (a, b) = match (approximate(left), approximate(right)) {
(Some(a), Some(b)) => (a, b),
_ => {
return Err(Error::not_implemented(format!(
"division on {} and {}",
left.logical_type(),
right.logical_type()
)));
}
};
Ok(Value::Double(a / b))
}
fn negate(value: &Value, ty: &LogicalType) -> Result<Value> {
match value {
Value::Float(v) => Ok(Value::Float(-v)),
Value::Double(v) => Ok(Value::Double(-v)),
Value::Decimal { unscaled, width, scale } => {
Ok(Value::Decimal { unscaled: -unscaled, width: *width, scale: *scale })
}
_ => match integral(value) {
Some(whole) => whole
.checked_neg()
.and_then(|negated| fit(negated, ty))
.ok_or_else(negation_overflow),
None => Err(Error::not_implemented(format!("negating a {}", value.logical_type()))),
},
}
}
fn absolute(value: &Value, ty: &LogicalType) -> Result<Value> {
match value {
Value::Float(v) => Ok(Value::Float(v.abs())),
Value::Double(v) => Ok(Value::Double(v.abs())),
Value::Decimal { unscaled, width, scale } => {
Ok(Value::Decimal { unscaled: unscaled.abs(), width: *width, scale: *scale })
}
_ => match integral(value) {
Some(whole) => whole
.checked_abs()
.and_then(|positive| fit(positive, ty))
.ok_or_else(|| abs_overflow(value)),
None => Err(Error::not_implemented(format!("abs of a {}", value.logical_type()))),
},
}
}
fn count_characters(value: &Value) -> i64 {
let text = match value.as_str() {
Some(text) => text.chars().count(),
None => value.to_string().chars().count(),
};
i64::try_from(text).unwrap_or(i64::MAX)
}
fn count_bytes(value: &Value) -> i64 {
let bytes = match value.as_str() {
Some(text) => text.len(),
None => value.to_string().len(),
};
i64::try_from(bytes).unwrap_or(i64::MAX)
}
fn matches(text: &Value, pattern: &Value, fold_case: bool) -> bool {
let (text, pattern) = if fold_case {
(text.to_string().to_lowercase(), pattern.to_string().to_lowercase())
} else {
(text.to_string(), pattern.to_string())
};
let text: Vec<char> = text.chars().collect();
let pattern: Vec<char> = pattern.chars().collect();
like(&text, &pattern)
}
fn like(text: &[char], pattern: &[char]) -> bool {
let (mut at, mut against) = (0usize, 0usize);
let (mut star, mut resume) = (None, 0usize);
while at < text.len() {
if against < pattern.len() && pattern[against] == '%' {
star = Some(against);
resume = at;
against += 1;
} else if against < pattern.len()
&& (pattern[against] == '_' || pattern[against] == text[at])
{
at += 1;
against += 1;
} else if let Some(back) = star {
against = back + 1;
resume += 1;
at = resume;
} else {
return false;
}
}
while against < pattern.len() && pattern[against] == '%' {
against += 1;
}
against == pattern.len()
}
#[cfg(test)]
mod tests {
use super::*;
fn called(name: &str, args: &[Value], returns: &LogicalType) -> Value {
call_values(name, args, returns, None).expect("this call is written")
}
#[test]
fn nullif_blanks_a_pair_that_matches_and_keeps_the_left_one_otherwise() {
let integer = LogicalType::Integer;
assert_eq!(
called("nullif", &[Value::Integer(2), Value::Integer(2)], &integer),
Value::Null
);
assert_eq!(
called("nullif", &[Value::Integer(1), Value::Integer(2)], &integer),
Value::Integer(1)
);
assert_eq!(
called("nullif", &[Value::Integer(1), Value::Null], &integer),
Value::Integer(1)
);
assert_eq!(called("nullif", &[Value::Null, Value::Integer(1)], &integer), Value::Null);
let decimal = |unscaled| Value::Decimal { unscaled, width: 11, scale: 1 };
assert_eq!(
called("nullif", &[decimal(20), decimal(25)], &integer),
Value::Integer(2),
"2 is not 2.5, and what comes back is the 2 the query wrote"
);
assert_eq!(called("nullif", &[decimal(20), decimal(20)], &integer), Value::Null);
}
#[test]
fn null_in_is_null_out_for_everything_but_coalesce() {
assert_eq!(
called("+", &[Value::Integer(1), Value::Null], &LogicalType::Integer),
Value::Null
);
assert_eq!(
called("coalesce", &[Value::Null, Value::Integer(2)], &LogicalType::Integer),
Value::Integer(2)
);
assert_eq!(
called("coalesce", &[Value::Null, Value::Null], &LogicalType::Integer),
Value::Null
);
}
#[test]
fn arithmetic_that_overflows_says_so_rather_than_wrapping() {
let error = call_values(
"+",
&[Value::Integer(i32::MAX), Value::Integer(1)],
&LogicalType::Integer,
None,
)
.expect_err("2147483647 + 1 is not an integer");
assert_eq!(error.message(), "Overflow in addition of INT32 (2147483647 + 1)!");
}
#[test]
fn a_zero_divisor_is_an_infinity_on_slash_and_an_error_on_the_other_two() {
assert_eq!(
called("/", &[Value::Double(1.0), Value::Double(0.0)], &LogicalType::Double),
Value::Double(f64::INFINITY)
);
let error =
call_values("//", &[Value::Integer(1), Value::Integer(0)], &LogicalType::Integer, None)
.expect_err("1 // 0 raises");
assert_eq!(
error.message(),
"Division by zero in expression (1 // 0). Use TRY(...) to return NULL for this \
expression, or SET null_on_division_by_zero=true to return NULL for all divisions by \
zero."
);
let error =
call_values("%", &[Value::Integer(1), Value::Integer(0)], &LogicalType::Integer, None)
.expect_err("1 % 0 raises");
assert!(error.message().starts_with("Division by zero in expression (1 % 0)."), "{error}");
let remainder =
called("%", &[Value::Double(1.0), Value::Double(0.0)], &LogicalType::Double);
assert!(matches!(remainder, Value::Double(answer) if answer.is_nan()), "{remainder}");
}
#[test]
fn a_named_expression_is_what_the_message_quotes() {
let written = || "(a // 0)".to_string();
let error = call_values(
"//",
&[Value::Integer(7), Value::Integer(0)],
&LogicalType::Integer,
Some(&written),
)
.expect_err("7 // 0 raises");
assert!(error.message().starts_with("Division by zero in expression (a // 0)."), "{error}");
}
#[test]
fn a_division_is_a_double_even_when_both_sides_are_whole() {
assert_eq!(
called("/", &[Value::Integer(7), Value::Integer(2)], &LogicalType::Double),
Value::Double(3.5)
);
assert_eq!(
called("//", &[Value::Integer(7), Value::Integer(2)], &LogicalType::Integer),
Value::Integer(3)
);
}
#[test]
fn integer_division_over_floats_divides_and_does_not_truncate() {
let slash = |left, right| {
called("//", &[Value::Double(left), Value::Double(right)], &LogicalType::Double)
};
assert_eq!(slash(7.0, 2.0), Value::Double(3.5));
assert_eq!(slash(7.5, 2.0), Value::Double(3.75));
assert_eq!(slash(7.9, 1.0), Value::Double(7.9));
assert_eq!(slash(-7.9, 1.0), Value::Double(-7.9));
}
#[test]
fn decimals_add_at_their_own_scale_and_multiply_back_down_to_it() {
let ty = LogicalType::decimal(10, 2).expect("a legal decimal");
let two_fifty = Value::Decimal { unscaled: 250, width: 10, scale: 2 };
let four = Value::Decimal { unscaled: 400, width: 10, scale: 2 };
assert_eq!(
called("+", &[two_fifty.clone(), four.clone()], &ty),
Value::Decimal { unscaled: 650, width: 10, scale: 2 }
);
assert_eq!(
called("*", &[two_fifty, four], &ty),
Value::Decimal { unscaled: 1000, width: 10, scale: 2 }
);
}
#[test]
fn strings_join_and_fold() {
assert_eq!(
called(
"||",
&[Value::Varchar("ab".into()), Value::Varchar("cd".into())],
&LogicalType::Varchar
),
Value::Varchar("abcd".into())
);
assert_eq!(
called("upper", &[Value::Varchar("aB".into())], &LogicalType::Varchar),
Value::Varchar("AB".into())
);
assert_eq!(
called("length", &[Value::Varchar("héllo".into())], &LogicalType::BigInt),
Value::BigInt(5)
);
assert_eq!(
called("strlen", &[Value::Varchar("héllo".into())], &LogicalType::BigInt),
Value::BigInt(6)
);
}
#[test]
fn like_matches_the_way_sql_says_it_does() {
let text = Value::Varchar("google.com".into());
for (pattern, expected) in [
("%google%", true),
("google%", true),
("%com", true),
("g_ogle.com", true),
("g__gle.com", true),
("goggle%", false),
("%GOOGLE%", false),
("google.com", true),
("%", true),
] {
let held = called(
"~~",
&[text.clone(), Value::Varchar(pattern.into())],
&LogicalType::Boolean,
);
assert_eq!(held, Value::Boolean(expected), "{pattern}");
}
}
#[test]
fn like_backtracks_rather_than_giving_up_at_the_first_star() {
let text = Value::Varchar("aaaaaaab".into());
let held = called("~~", &[text, Value::Varchar("%a%a%b".into())], &LogicalType::Boolean);
assert_eq!(held, Value::Boolean(true));
}
#[test]
fn a_percent_sign_in_the_string_is_a_character_and_not_a_wildcard() {
for (text, pattern, expected) in [
("a%b", "%a%", true),
("ax%b", "%a%", true),
("a%%b", "%a%", true),
("a%b", "a%", true),
("a%", "a%", true),
("%a", "%", true),
("%%", "%", true),
("%", "%", true),
("a%b", "%b%", true),
("a%b", "%a%b%", true),
("a%b", "_%_", true),
("http://x/google%2F12.15", "%google%", true),
("amalgama-lab.com.ua/google%2F12.15&he=900&Select", "%google%", true),
("a%", "%a", false),
("a%b", "%a", false),
("%b", "a%", false),
("goo%gle", "google", false),
("goo%gle", "%google%", false),
] {
let held = called(
"~~",
&[Value::Varchar(text.into()), Value::Varchar(pattern.into())],
&LogicalType::Boolean,
);
assert_eq!(held, Value::Boolean(expected), "{text} LIKE {pattern}");
}
}
#[test]
fn the_compiled_shapes_and_the_walk_answer_the_same_question() {
let mut characters = Vec::new();
for text in ["a%b", "ax%b", "%ab", "ab%", "a%", "%", "ab", ""] {
for (fast, slow) in [("%a%", "%a%%"), ("a%", "a%%"), ("%b", "%%b"), ("ab", "ab")] {
assert_eq!(
Pattern::compile(fast).holds(text, &mut characters),
Pattern::compile(slow).holds(text, &mut characters),
"{text:?} against {fast} and {slow}"
);
}
}
}
#[test]
fn the_case_folding_like_ignores_case_and_the_negated_ones_invert() {
let text = Value::Varchar("Google".into());
let pattern = Value::Varchar("%GOOGLE%".into());
assert_eq!(
called("~~*", &[text.clone(), pattern.clone()], &LogicalType::Boolean),
Value::Boolean(true)
);
assert_eq!(called("!~~", &[text, pattern], &LogicalType::Boolean), Value::Boolean(true));
}
#[test]
fn a_dictionary_or_constant_text_reaches_the_compiled_like() {
let values = Vector::from_values(
LogicalType::Varchar,
&[
Value::Varchar("a google search".into()),
Value::Varchar("goggle".into()),
Value::Null,
],
)
.expect("builds");
let text = Vector::dictionary(vec![0, 1, 2, 0], values).expect("codes are in range");
let pattern = Vector::constant(LogicalType::Varchar, Value::Varchar("%google%".into()), 4);
let answer = binary("~~", &text, &pattern, &LogicalType::Boolean, 4, None)
.expect("the call is written")
.expect("a dictionary text has a loop of its own");
let rows: Vec<Value> = (0..4).map(|row| answer.value_at(row)).collect();
assert_eq!(
rows,
[Value::Boolean(true), Value::Boolean(false), Value::Null, Value::Boolean(true)]
);
let text = Vector::constant(LogicalType::Varchar, Value::Varchar("google".into()), 4);
assert!(
binary("~~", &text, &pattern, &LogicalType::Boolean, 4, None)
.expect("the call is written")
.is_none()
);
assert_eq!(
call("~~", &[text, pattern], &LogicalType::Boolean, None)
.expect("the call is written")
.value_at(0),
Value::Boolean(true)
);
}
#[test]
fn a_dictionary_argument_reaches_the_one_argument_loops() {
let values = Vector::from_values(
LogicalType::Varchar,
&[Value::Varchar("héllo".into()), Value::Varchar(String::new()), Value::Null],
)
.expect("builds");
let arg = Vector::dictionary(vec![0, 1, 2, 0], values).expect("codes are in range");
let read = |name: &str| {
let answer = unary(name, &arg, &LogicalType::BigInt, 4)
.expect("the call is written")
.expect("a dictionary argument has a loop of its own");
(0..4).map(|row| answer.value_at(row)).collect::<Vec<_>>()
};
assert_eq!(
read("length"),
[Value::BigInt(5), Value::BigInt(0), Value::Null, Value::BigInt(5)]
);
assert_eq!(
read("strlen"),
[Value::BigInt(6), Value::BigInt(0), Value::Null, Value::BigInt(6)]
);
let days = Vector::from_values(
LogicalType::Date,
&[Value::Date(0), Value::Date(16_000), Value::Null],
)
.expect("builds");
let when = Vector::dictionary(vec![0, 1, 2, 1], days).expect("codes are in range");
let part = Vector::constant(LogicalType::Varchar, Value::Varchar("year".into()), 4);
let years = date_of("date_part", &part, &when, &LogicalType::BigInt, 4)
.expect("the call is written")
.expect("a dictionary date has a loop of its own");
assert_eq!(
(0..4).map(|row| years.value_at(row)).collect::<Vec<_>>(),
[Value::BigInt(1970), Value::BigInt(2013), Value::Null, Value::BigInt(2013)]
);
}
#[test]
fn a_function_nobody_has_written_says_which_one() {
let error = call_values("sqrt", &[Value::Double(4.0)], &LogicalType::Double, None)
.expect_err("sqrt is not written yet");
assert!(error.message().contains("the sqrt function"), "{error}");
}
#[test]
fn a_batch_call_is_one_answer_per_row() {
let left = Vector::from_values(
LogicalType::Integer,
&[Value::Integer(1), Value::Integer(2), Value::Null],
)
.expect("three rows");
let right = Vector::constant(LogicalType::Integer, Value::Integer(10), 3);
let sum = call("+", &[left, right], &LogicalType::Integer, None).expect("adds");
assert_eq!(sum.value_at(0), Value::Integer(11));
assert_eq!(sum.value_at(1), Value::Integer(12));
assert_eq!(sum.value_at(2), Value::Null);
}
#[test]
fn arguments_of_different_lengths_are_caught() {
let left = Vector::constant(LogicalType::Integer, Value::Integer(1), 3);
let right = Vector::constant(LogicalType::Integer, Value::Integer(1), 4);
let error = call("+", &[left, right], &LogicalType::Integer, None).expect_err("ragged");
assert!(error.message().contains("argument 1"), "{error}");
}
fn oracle(name: &str, args: &[Vector], returns: &LogicalType) -> Result<Vector> {
let rows = args.first().map_or(0, Vector::len);
let mut row = Vec::with_capacity(args.len());
let mut values = Vec::with_capacity(rows);
for index in 0..rows {
row.clear();
row.extend(args.iter().map(|arg| arg.value_at(index)));
values.push(call_values(name, &row, returns, None)?);
}
Vector::from_values(returns.clone(), &values)
}
fn agrees(name: &str, args: &[Vector], returns: &LogicalType) {
let forms: Vec<Form> = args.iter().map(Vector::form).collect();
let what = format!("{name} on {forms:?} returning {returns}");
match (call(name, args, returns, None), oracle(name, args, returns)) {
(Ok(fast), Ok(slow)) => assert_eq!(format!("{fast:?}"), format!("{slow:?}"), "{what}"),
(Err(fast), Err(slow)) => assert_eq!(fast.message(), slow.message(), "{what}"),
(fast, slow) => panic!("{what}: one path gave {fast:?} and the other gave {slow:?}"),
}
}
struct Rng(u64);
impl Rng {
fn next(&mut self) -> u64 {
self.0 ^= self.0 << 13;
self.0 ^= self.0 >> 7;
self.0 ^= self.0 << 17;
self.0
}
fn below(&mut self, bound: u64) -> u64 {
self.next() % bound
}
}
fn sample(ty: &LogicalType, rows: usize, nulls: u64, rng: &mut Rng) -> Vector {
let mut values = Vec::with_capacity(rows);
for _ in 0..rows {
if nulls > 0 && rng.below(nulls) == 0 {
values.push(Value::Null);
continue;
}
let small = rng.below(9) as i64 - 4;
let edge = rng.below(32) == 0;
values.push(match ty {
LogicalType::TinyInt => Value::TinyInt(if edge { i8::MIN } else { small as i8 }),
LogicalType::Integer => Value::Integer(if edge { i32::MAX } else { small as i32 }),
LogicalType::BigInt => Value::BigInt(if edge { i64::MIN } else { small }),
LogicalType::HugeInt => Value::HugeInt(i128::from(small)),
LogicalType::UInteger => {
Value::UInteger(if edge { u32::MAX } else { small.unsigned_abs() as u32 })
}
LogicalType::Float => Value::Float(small as f32 / 2.0),
LogicalType::Double => Value::Double(small as f64 / 2.0),
LogicalType::Decimal { width, scale } => Value::Decimal {
unscaled: i128::from(small) * 37,
width: *width,
scale: *scale,
},
LogicalType::Boolean => Value::Boolean(small > 0),
LogicalType::Varchar => Value::Varchar(text(rng)),
LogicalType::Date => Value::Date(rng.below(1_000_000) as i32 - 500_000),
LogicalType::Timestamp => {
Value::Timestamp(rng.next() as i64 % 300_000_000_000_000_000)
}
other => panic!("the generator has nothing for a {other}"),
});
}
Vector::from_values(ty.clone(), &values).expect("the generator builds legal columns")
}
fn text(rng: &mut Rng) -> String {
let words = [
"",
"google",
"Google",
"a google search",
"GOOGLE",
"goggle",
"twelve bytes",
"thirteen bytes",
"Ï€ is two bytes and this string is not inline at all",
"g",
"google%2F12",
"goo_gle%",
"goo%gle",
];
words[rng.below(words.len() as u64) as usize].to_owned()
}
fn forms(arg: &Vector) -> Vec<Vector> {
let rows = arg.len();
let codes: Vec<u32> = (0..rows).map(|index| (rows - 1 - index) as u32 / 2).collect();
vec![arg.clone(), Vector::dictionary(codes, arg.clone()).expect("codes are in range")]
}
fn pairings(left: &Vector, right: &Vector) -> Vec<(Vector, Vector)> {
let rows = left.len();
let as_constant = |vector: &Vector| {
Vector::constant(vector.logical_type().clone(), vector.value_at(0), rows)
};
let as_dictionary = |vector: &Vector| {
let codes: Vec<u32> = (0..rows).map(|index| (rows - 1 - index) as u32 / 2).collect();
Vector::dictionary(codes, vector.clone()).expect("codes are in range")
};
vec![
(left.clone(), right.clone()),
(left.clone(), as_constant(right)),
(as_constant(left), right.clone()),
(as_dictionary(left), right.clone()),
(left.clone(), as_dictionary(right)),
(as_dictionary(left), as_constant(right)),
(as_constant(left), as_dictionary(right)),
]
}
#[test]
fn every_specialized_arithmetic_agrees_with_the_row_at_a_time_path() {
let mut rng = Rng(0x5eed_1234_9abc_def1);
let types = [
LogicalType::TinyInt,
LogicalType::Integer,
LogicalType::BigInt,
LogicalType::HugeInt,
LogicalType::UInteger,
LogicalType::Float,
LogicalType::Double,
LogicalType::decimal(10, 2).expect("a legal decimal"),
];
for ty in &types {
for nulls in [0, 7, 1] {
let left = sample(ty, 96, nulls, &mut rng);
let right = sample(ty, 96, nulls, &mut rng);
for name in ["+", "-", "*", "//", "%"] {
for (one, other) in pairings(&left, &right) {
agrees(name, &[one, other], ty);
}
}
for name in ["-", "abs"] {
for arg in forms(&left) {
agrees(name, std::slice::from_ref(&arg), ty);
}
}
if matches!(ty, LogicalType::Double) {
for (one, other) in pairings(&left, &right) {
agrees("/", &[one, other], ty);
}
}
}
}
}
#[test]
fn every_specialized_string_and_boolean_function_agrees_with_the_row_at_a_time_path() {
let mut rng = Rng(0x1234_5eed_dead_beef);
for nulls in [0, 7, 1] {
let left = sample(&LogicalType::Varchar, 96, nulls, &mut rng);
let right = sample(&LogicalType::Varchar, 96, nulls, &mut rng);
for arg in forms(&left) {
for name in ["length", "strlen"] {
agrees(name, std::slice::from_ref(&arg), &LogicalType::BigInt);
}
for name in ["lower", "upper"] {
agrees(name, std::slice::from_ref(&arg), &LogicalType::Varchar);
}
}
for (one, other) in pairings(&left, &right) {
agrees("||", &[one, other], &LogicalType::Varchar);
}
for spelling in ["google", "goo%", "%gle", "%oog%", "g_ogle", "%g%l%", "%", ""] {
let column = vec![Value::Varchar(spelling.into()); 96];
let pattern = Vector::from_values(LogicalType::Varchar, &column).expect("builds");
for name in ["~~", "!~~", "~~*", "!~~*"] {
for (text, pattern) in pairings(&left, &pattern) {
agrees(name, &[text, pattern], &LogicalType::Boolean);
}
}
}
let flags = sample(&LogicalType::Boolean, 96, nulls, &mut rng);
for arg in forms(&flags) {
agrees("not", std::slice::from_ref(&arg), &LogicalType::Boolean);
}
}
}
#[test]
fn every_part_of_a_date_agrees_with_the_row_at_a_time_path() {
const PARTS: &[&str] = &[
"year",
"month",
"day",
"hour",
"minute",
"second",
"millisecond",
"microsecond",
"week",
"quarter",
"dayofweek",
"isodow",
"dayofyear",
"decade",
"century",
"millennium",
"era",
"isoyear",
"yearweek",
];
let mut rng = Rng(0xdead_beef_1234_5eed);
for nulls in [0, 7, 1] {
for ty in [LogicalType::Date, LogicalType::Timestamp] {
let when = sample(&ty, 96, nulls, &mut rng);
for spelling in PARTS {
let part = Vector::constant(
LogicalType::Varchar,
Value::Varchar((*spelling).to_owned()),
96,
);
for arg in forms(&when) {
agrees("date_part", &[part.clone(), arg.clone()], &LogicalType::BigInt);
agrees("date_trunc", &[part.clone(), arg], &ty);
}
}
}
}
}
#[test]
fn a_part_that_varies_per_row_is_still_right() {
let part = Vector::from_values(
LogicalType::Varchar,
&[Value::Varchar("minute".into()), Value::Varchar("hour".into())],
)
.expect("two rows");
let when = Vector::from_values(
LogicalType::Timestamp,
&[Value::Timestamp(13 * 3_600_000_000 + 45 * 60_000_000), Value::Timestamp(0)],
)
.expect("two rows");
let found =
call("date_part", &[part, when], &LogicalType::BigInt, None).expect("two parts");
assert_eq!(found.value_at(0), Value::BigInt(45));
assert_eq!(found.value_at(1), Value::BigInt(0));
}
#[test]
fn a_pattern_that_varies_per_row_is_still_right_and_says_so() {
let text = Vector::from_values(
LogicalType::Varchar,
&[Value::Varchar("google".into()), Value::Varchar("goggle".into())],
)
.expect("two rows");
let pattern = Vector::from_values(
LogicalType::Varchar,
&[Value::Varchar("goo%".into()), Value::Varchar("goo%".into())],
)
.expect("two rows");
let before = fallback::count(Kernel::Scalar, Form::Flat, Form::Flat);
agrees("~~", &[text, pattern], &LogicalType::Boolean);
assert!(fallback::count(Kernel::Scalar, Form::Flat, Form::Flat) > before);
}
#[test]
fn a_call_where_every_argument_is_constant_costs_one_call() {
let left = Vector::constant(LogicalType::Integer, Value::Integer(3), 1024);
let right = Vector::constant(LogicalType::Integer, Value::Integer(4), 1024);
let sum = call("+", &[left, right], &LogicalType::Integer, None).expect("adds");
assert_eq!(sum.form(), Form::Constant);
assert_eq!(sum.len(), 1024);
assert_eq!(sum.value_at(1000), Value::Integer(7));
}
#[test]
fn a_dictionary_argument_reads_its_nulls_from_the_values() {
let values = Vector::from_values(
LogicalType::Integer,
&[Value::Null, Value::Integer(5), Value::Integer(6)],
)
.expect("three values");
let codes = Vector::dictionary(vec![0, 1, 2, 1, 0], values).expect("a dictionary");
let ten = Vector::constant(LogicalType::Integer, Value::Integer(10), 5);
let sum = call("+", &[codes, ten], &LogicalType::Integer, None).expect("adds");
assert_eq!(sum.value_at(0), Value::Null);
assert_eq!(sum.value_at(1), Value::Integer(15));
assert_eq!(sum.value_at(4), Value::Null);
}
#[test]
fn a_number_becomes_a_date_and_a_timestamp() {
assert_eq!(
called("make_date", &[Value::Integer(16_000)], &LogicalType::Date),
Value::Date(16_000)
);
assert_eq!(
called(
"make_date",
&[Value::Integer(2013), Value::Integer(7), Value::Integer(1)],
&LogicalType::Date
),
Value::Date(days_from_civil(2013, 7, 1))
);
assert_eq!(
called("epoch_ms", &[Value::BigInt(1_600_000_000_000)], &LogicalType::Timestamp),
Value::Timestamp(1_600_000_000_000_000)
);
assert_eq!(
called("epoch_ms", &[Value::BigInt(-1)], &LogicalType::Timestamp),
Value::Timestamp(-1_000)
);
}
#[test]
fn a_day_that_is_not_in_its_month_is_a_date_out_of_range() {
for (year, month, day, written) in [
(2013, 13, 1, "2013-13-1"),
(2013, 2, 30, "2013-2-30"),
(0, 0, 0, "0-0-0"),
(2013, 7, 0, "2013-7-0"),
] {
let error = call_values(
"make_date",
&[Value::Integer(year), Value::Integer(month), Value::Integer(day)],
&LogicalType::Date,
None,
)
.expect_err("a date that is not a date");
assert_eq!(error.message(), format!("Date out of range: {written}"));
}
assert_eq!(
called(
"make_date",
&[Value::Integer(2024), Value::Integer(2), Value::Integer(29)],
&LogicalType::Date
),
Value::Date(days_from_civil(2024, 2, 29))
);
}
#[test]
fn milliseconds_that_do_not_fit_in_microseconds_say_which_two_units_they_are() {
let error =
call_values("epoch_ms", &[Value::BigInt(i64::MAX)], &LogicalType::Timestamp, None)
.expect_err("that is not a timestamp");
assert_eq!(error.message(), "Could not convert Timestamp(MS) to Timestamp(US)");
}
#[test]
fn the_loops_for_the_two_constructors_agree_with_the_row_at_a_time_path() {
let days = Vector::from_values(
LogicalType::Integer,
&[Value::Integer(0), Value::Null, Value::Integer(16_000), Value::Integer(-1)],
)
.expect("four days");
for arg in forms(&days) {
agrees("make_date", &[arg], &LogicalType::Date);
}
let millis = Vector::from_values(
LogicalType::BigInt,
&[Value::BigInt(0), Value::Null, Value::BigInt(1_600_000_000_000), Value::BigInt(-1)],
)
.expect("four stamps");
for arg in forms(&millis) {
agrees("epoch_ms", &[arg], &LogicalType::Timestamp);
}
let overflowing =
Vector::from_values(LogicalType::BigInt, &[Value::BigInt(i64::MAX)]).expect("one row");
for arg in forms(&overflowing) {
agrees("epoch_ms", &[arg], &LogicalType::Timestamp);
}
}
#[test]
fn an_empty_call_is_an_empty_answer() {
let empty = Vector::from_values(LogicalType::Integer, &[]).expect("no rows");
let sum = call("+", &[empty.clone(), empty], &LogicalType::Integer, None).expect("adds");
assert_eq!(sum.len(), 0);
assert_eq!(sum.validity(), &Validity::AllValid);
}
#[test]
fn the_smallest_value_of_a_type_modulo_negative_one_is_zero_on_both_paths() {
let left =
Vector::from_values(LogicalType::TinyInt, &[Value::TinyInt(i8::MIN)]).expect("one row");
let right = Vector::constant(LogicalType::TinyInt, Value::TinyInt(-1), 1);
agrees("%", &[left.clone(), right.clone()], &LogicalType::TinyInt);
let answer = call("%", &[left, right], &LogicalType::TinyInt, None).expect("modulo");
assert_eq!(answer.value_at(0), Value::TinyInt(0));
}
}