use memchr::memmem;
use rudb_common::{Error, LogicalType, Result, Value, civil_from_days, days_from_civil};
use rudb_vector::{Data, Form, StringColumn, Validity, Vector};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, OnceLock};
use crate::aggregate::{Accumulator, divide_mean, exactly};
use crate::cast;
use crate::compare::{self, Comparison};
use crate::datetime::{self, Count, Part};
use crate::fallback::{self, Kernel};
use crate::lists;
use crate::maps;
use crate::number::{approximate, beyond, digits, fit, integral, pow10, rescale};
use crate::prepare::{Hoisted, Recipe};
use crate::regexp;
use crate::shape::{first, identity, nulls_of, single};
use crate::structs;
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> {
run(name, &Hoisted::Nothing, args, returns, written)
}
pub fn call_prepared<V: AsRef<Vector>>(
recipe: &Recipe,
args: &[V],
returns: &LogicalType,
written: Written<'_>,
) -> Result<Vector> {
run(recipe.name(), recipe.hoisted(), args, returns, written)
}
fn run<V: AsRef<Vector>>(
name: &str,
hoisted: &Hoisted,
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 {
return Vector::from_values(returns.clone(), &[]);
}
if let Some(vector) = crate::sequence::call(name, args, rows)? {
return Ok(vector);
}
if let ("enum_code", [only]) = (name, args) {
return only.as_ref().enum_codes();
}
if !args.is_empty() && args.iter().all(|arg| arg.as_ref().form() == Form::Constant) {
let row: Vec<Value> =
args.iter().map(|arg| arg.as_ref().try_value_at(0)).collect::<Result<_>>()?;
return Ok(Vector::constant(
returns.clone(),
call_values(name, &row, returns, written)?,
rows,
));
}
if let Some(vector) = specialized(name, hoisted, 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();
for arg in args {
row.push(arg.as_ref().try_value_at(index)?);
}
values.push(call_values(name, &row, returns, written)?);
}
Vector::from_values(returns.clone(), &values)
}
fn specialized<V: AsRef<Vector>>(
name: &str,
hoisted: &Hoisted,
args: &[V],
returns: &LogicalType,
rows: usize,
written: Written<'_>,
) -> Result<Option<Vector>> {
if regexp::is_regexp(name) {
return regexp::vectorized(name, hoisted.regexp(), args, returns, rows);
}
if matches!(name, "substring" | "substr") {
return substring_of(args, returns, rows);
}
if let Some(vector) = lists::vectorized(name, args, returns, rows)? {
return Ok(Some(vector));
}
if let Some(vector) = structs::vectorized(name, args, returns)? {
return Ok(Some(vector));
}
match args {
[only] => unary(name, only.as_ref(), returns, rows),
[left, right] => {
binary(name, hoisted, left.as_ref(), right.as_ref(), returns, rows, written)
}
_ => Ok(None),
}
}
pub(crate) fn hoist(name: &str, literals: &[Option<Value>]) -> Option<Hoisted> {
if regexp::is_regexp(name) {
return regexp::hoist(name, literals).map(|call| Hoisted::Regexp(Box::new(call)));
}
let [_, Some(Value::Varchar(spelling))] = literals else {
return None;
};
Like::of(name, spelling).map(Hoisted::Like)
}
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::StringView => read_text(name, arg, base, rows, returns),
Form::Dictionary | Form::Rle => {
let Some((codes, values)) = arg.positions() else {
return Ok(None);
};
if codes.len() < rows {
return Ok(None);
}
match values.data() {
Some(data) => {
one_of(name, data, move |index| codes[index] as usize, base, rows, returns, arg)
}
None => read_text(name, arg, base, rows, returns),
}
}
_ => 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 {
"__rudb_zero_to_null" => {
let validity = Validity::from_iter(rows, |index| {
base.is_valid(index) && approximate(&arg.value_at(index)) != Some(0.0)
});
Ok(Some(arg.clone().with_validity(validity)))
}
"not" => not_of(data, at, base, rows, returns),
"-" | "abs" if arg.logical_type() == returns => {
sign_of(name, data, at, base, rows, returns, arg)
}
"length" | "strlen" | "lower" | "upper" => match data {
Data::Varlen(column) => text_of(name, &Text::Held { column, at }, base, rows, returns),
_ => Ok(None),
},
"make_date" => made_date(data, at, base, rows, returns),
"epoch_ms" => made_timestamp(data, at, base, rows, returns),
name if datetime::is_interval(name) => made_interval(name, data, at, base, rows, returns),
_ => Ok(None),
}
}
enum Text<'a, A> {
Held { column: &'a StringColumn, at: A },
Read(&'a Vector),
}
impl<'a> Text<'a, fn(usize) -> usize> {
fn read(vector: &'a Vector) -> Option<Self> {
matches!(vector.logical_type(), LogicalType::Varchar).then_some(Text::Read(vector))
}
}
impl<A: Fn(usize) -> usize> Text<'_, A> {
fn bytes(&self, index: usize) -> Result<&[u8]> {
match self {
Text::Held { column, at } => Ok(column.bytes(at(index)).unwrap_or_default()),
Text::Read(vector) => Ok(vector.try_bytes_at(index)?.unwrap_or_default()),
}
}
fn get(&self, index: usize) -> Result<&str> {
Ok(std::str::from_utf8(self.bytes(index)?).unwrap_or_default())
}
fn len(&self, index: usize) -> Result<usize> {
match self {
Text::Held { column, at } => Ok(column.bytes(at(index)).unwrap_or_default().len()),
Text::Read(vector) => Ok(vector.try_bytes_len_at(index)?.unwrap_or_default()),
}
}
}
fn read_text(
name: &str,
arg: &Vector,
base: Validity,
rows: usize,
returns: &LogicalType,
) -> Result<Option<Vector>> {
match Text::read(arg) {
Some(text) => text_of(name, &text, base, rows, returns),
None => Ok(None),
}
}
fn text_of<A: Fn(usize) -> usize>(
name: &str,
text: &Text<'_, A>,
base: Validity,
rows: usize,
returns: &LogicalType,
) -> Result<Option<Vector>> {
match name {
"length" => length_of(text, base, rows, returns),
"strlen" => bytes_of(text, base, rows, returns),
"lower" | "upper" => fold_of(name, text, base, rows, returns),
_ => Ok(None),
}
}
fn substring_of<V: AsRef<Vector>>(
args: &[V],
returns: &LogicalType,
rows: usize,
) -> Result<Option<Vector>> {
let (held, start, length) = match args {
[held, start] => (held.as_ref(), start.as_ref(), None),
[held, start, length] => (held.as_ref(), start.as_ref(), Some(length.as_ref())),
_ => return Ok(None),
};
if returns != &LogicalType::Varchar {
return Ok(None);
}
let fixed = |arg: &Vector| -> Result<Option<i128>> {
if arg.form() != Form::Constant || arg.is_null_at(0) {
return Ok(None);
}
text::whole(&arg.try_value_at(0)?).map(Some)
};
let Some(start) = fixed(start)? else {
return Ok(None);
};
let length = match length {
Some(length) => match fixed(length)? {
Some(length) => Some(length),
None => return Ok(None),
},
None => None,
};
let base = nulls_of(held);
let text = match held.form() {
Form::Flat => match held.data() {
Some(Data::Varlen(column)) => {
return cut_each(&Text::Held { column, at: identity }, start, length, base, rows);
}
_ => return Ok(None),
},
Form::Dictionary | Form::Rle => match held.positions() {
Some((codes, values)) if codes.len() >= rows => match values.data() {
Some(Data::Varlen(column)) => {
let at = move |index: usize| codes[index] as usize;
return cut_each(&Text::Held { column, at }, start, length, base, rows);
}
_ => Text::read(held),
},
_ => None,
},
Form::StringView => Text::read(held),
_ => None,
};
match text {
Some(text) => cut_each(&text, start, length, base, rows),
None => Ok(None),
}
}
fn cut_each<A: Fn(usize) -> usize>(
text: &Text<'_, A>,
start: i128,
length: Option<i128>,
base: Validity,
rows: usize,
) -> Result<Option<Vector>> {
if let Some(length) = length {
if start >= 1 && length >= 0 {
let skip = usize::try_from(start - 1).unwrap_or(usize::MAX);
let take = usize::try_from(length).unwrap_or(usize::MAX);
if let Text::Read(vector) = text {
let visited = visited_bytes(vector, &base, rows, |value, into| {
into.extend_from_slice(text::cut_forward(value, skip, take));
})?;
if let Some(out) = visited {
return finish(&LogicalType::Varchar, Data::Varlen(out), base.normalize(rows));
}
}
let out = try_each_string(rows, &base, |index, into| {
into.push_bytes(text::cut_forward(text.bytes(index)?, skip, take));
Ok(())
})?;
return finish(&LogicalType::Varchar, Data::Varlen(out), base.normalize(rows));
}
}
if let Text::Read(vector) = text {
let visited = visited_strings(vector, &base, rows, |value, into| {
into.push_str(text::cut(value, start, length));
})?;
if let Some(out) = visited {
return finish(&LogicalType::Varchar, Data::Varlen(out), base.normalize(rows));
}
}
let out = try_each_string(rows, &base, |index, into| {
into.push(text::cut(text.get(index)?, start, length));
Ok(())
})?;
finish(&LogicalType::Varchar, Data::Varlen(out), base.normalize(rows))
}
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 made_interval<A: Fn(usize) -> usize>(
name: &str,
data: &Data,
at: A,
base: Validity,
rows: usize,
returns: &LogicalType,
) -> Result<Option<Vector>> {
if returns != &LogicalType::Interval {
return Ok(None);
}
let unit = datetime::Unit::of(name)?;
match data {
Data::Float64(counts) => {
counted(rows, base, returns, |index| unit.count(Count::Real(counts[at(index)])))
}
Data::Int64(counts) => counted(rows, base, returns, |index| {
unit.count(Count::Whole(i128::from(counts[at(index)])))
}),
Data::Int32(counts) => counted(rows, base, returns, |index| {
unit.count(Count::Whole(i128::from(counts[at(index)])))
}),
_ => Ok(None),
}
}
fn counted(
rows: usize,
base: Validity,
returns: &LogicalType,
make: impl Fn(usize) -> Result<(i32, i32, i64)>,
) -> Result<Option<Vector>> {
let mut out = vec![(0, 0, 0); rows];
let validity = over_valid(rows, base, |index| {
out[index] = make(index)?;
Ok(())
})?;
finish(returns, Data::Interval(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>(
text: &Text<'_, A>,
base: Validity,
rows: usize,
returns: &LogicalType,
) -> Result<Option<Vector>> {
if returns != &LogicalType::BigInt {
return Ok(None);
}
let mut out = Vec::new();
let whole = match text {
Text::Read(vector) if !matches!(base, Validity::AllInvalid) => {
vector.try_chars_lens(&mut out)?
}
_ => false,
};
if whole && out.len() == rows {
if let Validity::Mask(mask) = &base {
for (index, len) in out.iter_mut().enumerate() {
if !mask.get(index) {
*len = 0;
}
}
}
let validity = if rows == 0 { Validity::AllValid } else { base.normalize(rows) };
return finish(returns, Data::Int64(out.into()), validity);
}
out.clear();
out.resize(rows, 0);
let validity = over_valid(rows, base, |index| {
let bytes = text.bytes(index)?;
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>(
text: &Text<'_, A>,
base: Validity,
rows: usize,
returns: &LogicalType,
) -> Result<Option<Vector>> {
if returns != &LogicalType::BigInt {
return Ok(None);
}
let mut out = Vec::new();
let whole = match text {
Text::Read(vector) if matches!(base, Validity::AllValid) => {
vector.try_bytes_lens(&mut out)?
}
_ => false,
};
if whole {
return finish(returns, Data::Int64(out.into()), Validity::AllValid);
}
out.clear();
out.resize(rows, 0);
let validity = over_valid(rows, base, |index| {
out[index] = i64::try_from(text.len(index)?).unwrap_or(i64::MAX);
Ok(())
})?;
finish(returns, Data::Int64(out.into()), validity)
}
fn fold_of<A: Fn(usize) -> usize>(
name: &str,
text: &Text<'_, A>,
base: Validity,
rows: usize,
returns: &LogicalType,
) -> Result<Option<Vector>> {
if returns != &LogicalType::Varchar {
return Ok(None);
}
let lowering = name == "lower";
if let Text::Read(vector) = text {
let visited = visited_strings(vector, &base, rows, |value, into| {
into.push_str(&if lowering { value.to_lowercase() } else { value.to_uppercase() });
})?;
if let Some(out) = visited {
return finish(returns, Data::Varlen(out), base.normalize(rows));
}
}
let out = try_each_string(rows, &base, |index, into| {
let value = text.get(index)?;
let folded = if lowering { value.to_lowercase() } else { value.to_uppercase() };
into.push(&folded);
Ok(())
})?;
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 try_each_string(
rows: usize,
base: &Validity,
mut body: impl FnMut(usize, &mut StringColumn) -> Result<()>,
) -> Result<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("");
}
}
Ok(out)
}
fn visited_strings(
vector: &Vector,
base: &Validity,
rows: usize,
mut each: impl FnMut(&str, &mut String),
) -> Result<Option<StringColumn>> {
if vector.len() != rows {
return Ok(None);
}
let mut answers = String::new();
let mut spans = vec![(0, 0); rows];
let visited = vector.try_visit_text(&mut |row, bytes| {
let from = answers.len();
each(std::str::from_utf8(bytes).unwrap_or_default(), &mut answers);
if let Some(span) = spans.get_mut(row) {
*span = (from, answers.len());
}
Ok(())
})?;
if !visited {
return Ok(None);
}
Ok(Some(each_string(rows, base, |row, into| {
let (from, to) = spans[row];
into.push(&answers[from..to]);
})))
}
fn visited_bytes(
vector: &Vector,
base: &Validity,
rows: usize,
mut each: impl FnMut(&[u8], &mut Vec<u8>),
) -> Result<Option<StringColumn>> {
if vector.len() != rows {
return Ok(None);
}
let mut answers = Vec::new();
let mut spans = vec![(0, 0); rows];
let visited = vector.try_visit_text(&mut |row, bytes| {
let from = answers.len();
each(bytes, &mut answers);
if let Some(span) = spans.get_mut(row) {
*span = (from, answers.len());
}
Ok(())
})?;
if !visited {
return Ok(None);
}
Ok(Some(each_string(rows, base, |row, into| {
let (from, to) = spans[row];
into.push_bytes(&answers[from..to]);
})))
}
fn binary(
name: &str,
hoisted: &Hoisted,
left: &Vector,
right: &Vector,
returns: &LogicalType,
rows: usize,
written: Written<'_>,
) -> Result<Option<Vector>> {
if matches!(name, "+" | "-") {
if let Some(moved) = shift_of(name == "-", left, right, returns)? {
return Ok(Some(moved));
}
if let Some(moved) = count_of(name == "-", left, right, returns)? {
return Ok(Some(moved));
}
}
if name == "__rudb_stamp_seconds" {
return stamp_seconds_of(left, right, returns);
}
if name == "__rudb_mean" {
return mean_of(left, right, returns, rows);
}
if let Some((op, floating_zero_errors)) = arithmetic_op(name) {
return arithmetic_of(op, floating_zero_errors, left, right, returns, written);
}
match name {
"/" => slash_of(left, right, returns),
"||" => concat_of(left, right, returns),
"~~" | "!~~" | "~~*" | "!~~*" => like_of(name, hoisted.like(), left, right, returns, rows),
"date_part" | "date_trunc" => date_of(name, left, right, returns, rows),
_ => Ok(None),
}
}
fn arithmetic_op(name: &str) -> Option<(Op, bool)> {
Some(match name {
"+" => (Op::Add, false),
"-" => (Op::Subtract, false),
"*" => (Op::Multiply, false),
"//" | "__rudb_checked_slash" => (Op::Divide, true),
"%" => (Op::Modulo, false),
"__rudb_checked_remainder" => (Op::Modulo, true),
_ => return 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.positions(), $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.positions()) {
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.positions(), $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.positions())
{
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 shift_of(
subtract: bool,
left: &Vector,
right: &Vector,
returns: &LogicalType,
) -> Result<Option<Vector>> {
let stamped = |ty: &LogicalType| {
matches!(ty, LogicalType::Timestamp | LogicalType::TimestampTz) && ty == returns
};
let interval = |ty: &LogicalType| matches!(ty, LogicalType::Interval);
let (one, other) = (left.logical_type(), right.logical_type());
let stamp_first = if stamped(one) && interval(other) {
true
} else if !subtract && interval(one) && stamped(other) {
false
} else {
return Ok(None);
};
by_form!(left, right, shift_runs, subtract, stamp_first, left, right, returns)
}
fn stamp_seconds(stamp: i64, count: i64) -> Result<i64> {
const EXACT: u64 = (1 << 53) / datetime::MICROS_PER_SECOND.unsigned_abs();
if count.unsigned_abs() <= EXACT {
return datetime::nudged_stamp(stamp, count * datetime::MICROS_PER_SECOND);
}
#[expect(clippy::cast_precision_loss, reason = "the double is what the cast would have made")]
let (_, _, micros) = datetime::interval("to_seconds", Count::Real(count as f64))?;
datetime::nudged_stamp(stamp, micros)
}
fn stamp_seconds_of(
stamp: &Vector,
count: &Vector,
returns: &LogicalType,
) -> Result<Option<Vector>> {
if returns != &LogicalType::Timestamp || stamp.logical_type() != &LogicalType::Timestamp {
return Ok(None);
}
let opened = opened_integer(count)?;
let count = opened.as_ref().unwrap_or(count);
by_form!(stamp, count, stamp_seconds_runs, stamp, count, returns)
}
fn mean_of(
total: &Vector,
count: &Vector,
returns: &LogicalType,
rows: usize,
) -> Result<Option<Vector>> {
let Some(scale) = mean_scale(total.logical_type()) else { return Ok(None) };
let (Some(Data::Int128(totals)), Some(Data::Int64(counts))) = (total.data(), count.data())
else {
return Ok(None);
};
if returns != &LogicalType::Double || totals.len() < rows || counts.len() < rows {
return Ok(None);
}
let (total_nulls, count_nulls) = (nulls_of(total), nulls_of(count));
let mut valid = vec![true; rows];
let mut answers = Vec::with_capacity(rows);
for row in 0..rows {
let seen = counts[row];
if seen > 0 && total_nulls.is_valid(row) && count_nulls.is_valid(row) {
answers.push(divide_mean(exactly(totals[row]), seen, scale));
} else {
valid[row] = false;
answers.push(0.0);
}
}
finish(returns, Data::Float64(answers.into()), Validity::from_run(&valid))
}
fn mean_scale(ty: &LogicalType) -> Option<u8> {
match ty {
LogicalType::HugeInt => Some(0),
LogicalType::Decimal { scale, .. } => Some(*scale),
_ => None,
}
}
fn mean_total(value: &Value) -> Option<i128> {
match *value {
Value::HugeInt(total) => Some(total),
Value::Decimal { unscaled, .. } => Some(unscaled),
_ => None,
}
}
fn opened_integer(side: &Vector) -> Result<Option<Vector>> {
let readable = side.data().is_some()
|| side.constant_value().is_some()
|| side.positions().is_some_and(|(_, values)| values.data().is_some());
if readable || !side.logical_type().is_integer() {
return Ok(None);
}
side.opened().map(Some)
}
fn stamp_seconds_runs<S, C>(
stamps: &Data,
at_stamp: S,
counts: &Data,
at_count: C,
stamp: &Vector,
count: &Vector,
returns: &LogicalType,
) -> Result<Option<Vector>>
where
S: Fn(usize) -> usize,
C: Fn(usize) -> usize,
{
let Data::Int64(stamps) = stamps else {
return Ok(None);
};
let rows = stamp.len();
let base = nulls_of(stamp).and(&nulls_of(count), rows);
let mut out = vec![0i64; rows];
macro_rules! over {
($counts:expr) => {
over_valid(rows, base, |index| {
let seconds = i64::from($counts[at_count(index)]);
out[index] = stamp_seconds(stamps[at_stamp(index)], seconds)?;
Ok(())
})?
};
}
let validity = match counts {
Data::Int64(counts) => over!(counts),
Data::Int32(counts) => over!(counts),
Data::Int16(counts) => over!(counts),
Data::Int8(counts) => over!(counts),
Data::UInt32(counts) => over!(counts),
Data::UInt16(counts) => over!(counts),
Data::UInt8(counts) => over!(counts),
_ => return Ok(None),
};
finish(returns, Data::Int64(out.into()), validity)
}
fn count_of(
subtract: bool,
left: &Vector,
right: &Vector,
returns: &LogicalType,
) -> Result<Option<Vector>> {
if returns != &LogicalType::Date {
return Ok(None);
}
let date_first = match (left.logical_type(), right.logical_type()) {
(LogicalType::Date, LogicalType::Integer) => true,
(LogicalType::Integer, LogicalType::Date) if !subtract => false,
_ => return Ok(None),
};
let (opened_left, opened_right) = (opened_integer(left)?, opened_integer(right)?);
let left = opened_left.as_ref().unwrap_or(left);
let right = opened_right.as_ref().unwrap_or(right);
by_form!(left, right, count_runs, subtract, date_first, left, right, returns)
}
#[expect(
clippy::too_many_arguments,
reason = "two sides with an index each, the direction, which side is the date, and the \
vectors and type the answer is built from"
)]
fn count_runs<L, R>(
one: &Data,
at_left: L,
other: &Data,
at_right: R,
subtract: bool,
date_first: bool,
left: &Vector,
right: &Vector,
returns: &LogicalType,
) -> Result<Option<Vector>>
where
L: Fn(usize) -> usize,
R: Fn(usize) -> usize,
{
let (Data::Int32(one), Data::Int32(other)) = (one, other) else {
return Ok(None);
};
let rows = left.len();
let base = nulls_of(left).and(&nulls_of(right), rows);
let mut out = vec![0i32; rows];
let validity = over_valid(rows, base, |index| {
let (day, count) = if date_first {
(one[at_left(index)], other[at_right(index)])
} else {
(other[at_right(index)], one[at_left(index)])
};
let count = i64::from(count);
out[index] = datetime::shifted_days(day, 0, if subtract { -count } else { count })?;
Ok(())
})?;
finish(returns, Data::Int32(out.into()), validity)
}
#[expect(
clippy::too_many_arguments,
reason = "two sides with an index each, the direction, which side is the timestamp, and the \
vectors and type the answer is built from"
)]
fn shift_runs<L, R>(
one: &Data,
at_left: L,
other: &Data,
at_right: R,
subtract: bool,
stamp_first: bool,
left: &Vector,
right: &Vector,
returns: &LogicalType,
) -> 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);
let out = match (stamp_first, one, other) {
(true, Data::Int64(stamps), Data::Interval(intervals)) => {
shifted(stamps, at_left, intervals, at_right, subtract, base, rows)?
}
(false, Data::Interval(intervals), Data::Int64(stamps)) => {
shifted(stamps, at_right, intervals, at_left, subtract, base, rows)?
}
_ => return Ok(None),
};
let (out, validity) = out;
finish(returns, Data::Int64(out.into()), validity)
}
fn shifted<S, I>(
stamps: &[i64],
at_stamp: S,
intervals: &[(i32, i32, i64)],
at_interval: I,
subtract: bool,
base: Validity,
rows: usize,
) -> Result<(Vec<i64>, Validity)>
where
S: Fn(usize) -> usize,
I: Fn(usize) -> usize,
{
let sign = if subtract { -1 } else { 1 };
let mut out = vec![0i64; rows];
let validity = over_valid(rows, base, |index| {
let (months, days, micros) = intervals[at_interval(index)];
out[index] = datetime::shifted_stamp(
stamps[at_stamp(index)],
i64::from(months) * sign,
i64::from(days) * sign,
i128::from(micros) * i128::from(sign),
)?;
Ok(())
})?;
Ok((out, validity))
}
fn arithmetic_of(
op: Op,
floating_zero_errors: bool,
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);
}
let direct = |side: &Vector| side.data().is_some() || side.constant_value().is_some();
let mapped =
|side: &Vector| side.positions().is_some_and(|(_, values)| values.data().is_some());
let integer = |side: &Vector| side.logical_type().is_integer();
let open_left = integer(left) && !direct(left) && (!mapped(left) || !direct(right));
let open_right = integer(right) && !direct(right) && !mapped(right);
if open_left || open_right {
let opened_left = if open_left { Some(left.opened()?) } else { None };
let opened_right = if open_right { Some(right.opened()?) } else { None };
let left = opened_left.as_ref().unwrap_or(left);
let right = opened_right.as_ref().unwrap_or(right);
if (direct(left) || mapped(left)) && (direct(right) || mapped(right)) {
return arithmetic_of(op, floating_zero_errors, left, right, returns, written);
}
return Ok(None);
}
by_form!(left, right, arithmetic_runs, op, floating_zero_errors, 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,
floating_zero_errors: bool,
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,
floating_zero_errors,
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,
floating_zero_errors: bool,
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.symbol(),
&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 && floating_zero_errors {
return Err(divided_by_zero(
written,
op.symbol(),
&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);
if let Some(answer) =
decimal_sweep(one, &at_left, other, &at_right, op, base, returns, width, scale, held, rows)?
{
return Ok(Some(answer));
}
let limit = beyond(width);
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.symbol(),
&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| value.unsigned_abs() < limit)
.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)
}
#[expect(
clippy::too_many_arguments,
reason = "two sides with an index each, the operator, the nulls, and the four numbers \
describing the answer, none of which is worth a struct that exists for one call"
)]
fn decimal_sweep<L, R>(
one: &Data,
at_left: L,
other: &Data,
at_right: R,
op: Op,
base: &Validity,
returns: &LogicalType,
width: u8,
scale: u8,
held: u8,
rows: usize,
) -> Result<Option<Vector>>
where
L: Fn(usize) -> usize,
R: Fn(usize) -> usize,
{
match op {
Op::Add | Op::Subtract => {}
Op::Multiply if held == scale => {}
_ => return Ok(None),
}
macro_rules! runs {
($($variant:ident => $native:ty),+ $(,)?) => {
$(
if let (Data::$variant(a), Data::$variant(b)) = (one, other) {
let Some(cap) = <$native>::try_from(pow10(width)).ok() else {
return Ok(None);
};
let checked = |value: $native, overflowed: bool| {
(value, overflowed || value >= cap || value <= -cap)
};
let mut out = vec![0 as $native; rows];
let trouble = match op {
Op::Add => sweep(&mut out, a, &at_left, b, &at_right, |x, y| {
let (value, overflowed) = <$native>::overflowing_add(x, y);
checked(value, overflowed)
}),
Op::Subtract => sweep(&mut out, a, &at_left, b, &at_right, |x, y| {
let (value, overflowed) = <$native>::overflowing_sub(x, y);
checked(value, overflowed)
}),
Op::Multiply => sweep(&mut out, a, &at_left, b, &at_right, |x, y| {
let (value, overflowed) = <$native>::overflowing_mul(x, y);
checked(value, overflowed)
}),
Op::Divide | Op::Modulo => true,
};
if trouble {
return Ok(None);
}
blank(&mut out, base);
let validity = if rows == 0 {
Validity::AllValid
} else {
base.clone().normalize(rows)
};
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::Float | 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 rows = left.len();
let base = nulls_of(left).and(&nulls_of(right), rows);
macro_rules! slash {
($variant:ident, $native:ty) => {
if let (Data::$variant(a), Data::$variant(b)) = (one, other) {
let mut out = vec![0.0 as $native; rows];
let validity = over_valid(rows, base, |index| {
out[index] = a[at_left(index)] / b[at_right(index)];
Ok(())
})?;
return finish(returns, Data::$variant(out.into()), validity);
}
};
}
slash!(Float32, f32);
slash!(Float64, f64);
Ok(None)
}
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(
name: &str,
prepared: Option<&Like>,
text: &Vector,
pattern: &Vector,
returns: &LogicalType,
rows: usize,
) -> Result<Option<Vector>> {
if !matches!(returns, LogicalType::Boolean) {
return Ok(None);
}
let held;
let like = match prepared {
Some(like) => like,
None => {
let Some(Value::Varchar(spelling)) = pattern.constant_value() else {
return Ok(None);
};
let Some(built) = Like::of(name, spelling) else {
return Ok(None);
};
held = built;
&held
}
};
let base = nulls_of(text).and(&nulls_of(pattern), rows);
if let Some((codes, dictionary)) = text.stable_dictionary_parts() {
return like_stable(dictionary, codes, like, base, rows, returns);
}
match text.form() {
Form::Flat => {
let Some(Data::Varlen(column)) = text.data() else {
return Ok(None);
};
like_run(column, identity, like, base, rows, returns)
}
Form::Dictionary | Form::Rle => {
let Some((codes, values)) = text.positions() else {
return Ok(None);
};
if codes.len() < rows {
return Ok(None);
}
let Some(Data::Varlen(column)) = values.data() else {
return like_vector_run(values, &codes, like, base, rows, returns);
};
if column.len() < rows {
return like_over(column, &codes, like, base, rows, returns);
}
let at = move |index: usize| codes[index] as usize;
like_run(column, at, like, base, rows, returns)
}
_ => Ok(None),
}
}
#[derive(Debug)]
pub(crate) struct Like {
compiled: Pattern,
fold_case: bool,
negated: bool,
stable: OnceLock<StableLike>,
}
const LIKE_GROUP: usize = 1024;
#[derive(Debug)]
struct StableLike {
dictionary: Arc<Vector>,
state: Vec<AtomicU64>,
}
const MEMO_VALUES: usize = 32;
impl StableLike {
fn slot(code: usize) -> (usize, usize) {
(code / MEMO_VALUES, code % MEMO_VALUES * 2)
}
fn peek(&self, code: usize) -> Option<bool> {
let (index, shift) = Self::slot(code);
let word = self.state.get(index)?.load(Ordering::Acquire);
((word >> shift) & 1 == 1).then(|| (word >> (shift + 1)) & 1 == 1)
}
fn decide_group(&self, code: usize, like: &Like, characters: &mut Vec<char>) -> Result<()> {
let first = code / LIKE_GROUP * LIKE_GROUP;
let last = (first + LIKE_GROUP).min(self.dictionary.len());
if !like.fold_case {
if let Pattern::Contains(finder) = &like.compiled {
if finder.needle().len() >= 4
&& !self.dictionary.text_block_might_contain(first, finder.needle())?
{
let word = if like.negated { u64::MAX } else { 0x5555_5555_5555_5555 };
for step in 0..(last - first).div_ceil(MEMO_VALUES) {
let remaining = (last - first - step * MEMO_VALUES).min(MEMO_VALUES);
let mask = if remaining == MEMO_VALUES {
u64::MAX
} else {
(1_u64 << (remaining * 2)) - 1
};
self.word(first / MEMO_VALUES + step)?
.fetch_or(word & mask, Ordering::Release);
}
return Ok(());
}
}
}
let mut bits = [0_u64; LIKE_GROUP / MEMO_VALUES];
let mut at = first;
while at < last {
let stopped =
self.dictionary.sweep_text(at, last, &mut |index: usize, text: &[u8]| {
let held = like.holds_loan(text, characters)?;
let (word, shift) = Self::slot(index - first);
bits[word] |= (1 | u64::from(held) << 1) << shift;
Ok(())
})?;
if stopped <= at {
return Err(Error::internal("a dictionary sweep did not move"));
}
at = stopped;
}
for (step, word) in bits.iter().take((last - first).div_ceil(MEMO_VALUES)).enumerate() {
self.word(first / MEMO_VALUES + step)?.fetch_or(*word, Ordering::Release);
}
Ok(())
}
fn word(&self, index: usize) -> Result<&AtomicU64> {
self.state
.get(index)
.ok_or_else(|| Error::internal("a stable dictionary code is out of range"))
}
}
impl Like {
pub(crate) fn of(name: &str, spelling: &str) -> Option<Self> {
let (fold_case, negated) = match name {
"~~" => (false, false),
"!~~" => (false, true),
"~~*" => (true, false),
"!~~*" => (true, true),
_ => return None,
};
let folded;
let spelling = if fold_case {
folded = spelling.to_lowercase();
&folded
} else {
spelling
};
Some(Self {
compiled: Pattern::compile(spelling),
fold_case,
negated,
stable: OnceLock::new(),
})
}
fn holds_at(&self, column: &StringColumn, position: usize, characters: &mut Vec<char>) -> bool {
if !self.fold_case && !matches!(self.compiled, Pattern::General(_)) {
let text = column.bytes(position).unwrap_or_default();
return self.compiled.holds_bytes(text) != self.negated;
}
let text = column.get(position).unwrap_or_default();
let folded = if self.fold_case { Some(text.to_lowercase()) } else { None };
let text = folded.as_deref().unwrap_or(text);
self.compiled.holds(text, characters) != self.negated
}
fn holds_vector(
&self,
vector: &Vector,
position: usize,
characters: &mut Vec<char>,
) -> Result<bool> {
if !self.fold_case && !matches!(self.compiled, Pattern::General(_)) {
let text = vector.try_bytes_at(position)?.unwrap_or_default();
return Ok(self.compiled.holds_bytes(text) != self.negated);
}
let text = vector.try_text_at(position)?.unwrap_or_default();
let folded = if self.fold_case { Some(text.to_lowercase()) } else { None };
let text = folded.as_deref().unwrap_or(text);
Ok(self.compiled.holds(text, characters) != self.negated)
}
fn holds_loan(&self, text: &[u8], characters: &mut Vec<char>) -> Result<bool> {
if !self.fold_case && !matches!(self.compiled, Pattern::General(_)) {
return Ok(self.compiled.holds_bytes(text) != self.negated);
}
let text = std::str::from_utf8(text)
.map_err(|error| Error::conversion(format!("invalid UTF-8 in VARCHAR: {error}")))?;
let folded = if self.fold_case { Some(text.to_lowercase()) } else { None };
let text = folded.as_deref().unwrap_or(text);
Ok(self.compiled.holds(text, characters) != self.negated)
}
}
fn like_run<A: Fn(usize) -> usize>(
column: &StringColumn,
at: A,
like: &Like,
base: Validity,
rows: usize,
returns: &LogicalType,
) -> Result<Option<Vector>> {
let mut out = vec![false; rows];
let mut characters: Vec<char> = Vec::new();
let validity = over_valid(rows, base, |index| {
out[index] = like.holds_at(column, at(index), &mut characters);
Ok(())
})?;
finish(returns, Data::Bool(out.into()), validity)
}
fn like_vector_run(
values: &Vector,
codes: &[u32],
like: &Like,
base: Validity,
rows: usize,
returns: &LogicalType,
) -> Result<Option<Vector>> {
let mut out = vec![false; rows];
let mut characters = Vec::new();
let validity = over_valid(rows, base, |index| {
out[index] = like.holds_vector(values, codes[index] as usize, &mut characters)?;
Ok(())
})?;
finish(returns, Data::Bool(out.into()), validity)
}
fn like_stable(
dictionary: &Arc<Vector>,
codes: &[u32],
like: &Like,
base: Validity,
rows: usize,
returns: &LogicalType,
) -> Result<Option<Vector>> {
let cache = like.stable.get_or_init(|| {
let words = dictionary.len().div_ceil(MEMO_VALUES);
StableLike {
dictionary: Arc::clone(dictionary),
state: (0..words).map(|_| AtomicU64::new(0)).collect(),
}
});
if !Arc::ptr_eq(&cache.dictionary, dictionary) {
return like_vector_run(dictionary, codes, like, base, rows, returns);
}
let mut out = vec![false; rows];
let mut characters = Vec::new();
let validity = over_valid(rows, base, |index| {
let code = codes[index] as usize;
out[index] = match cache.peek(code) {
Some(held) => held,
None => {
cache.decide_group(code, like, &mut characters)?;
cache
.peek(code)
.ok_or_else(|| Error::internal("a stable dictionary code is out of range"))?
}
};
Ok(())
})?;
finish(returns, Data::Bool(out.into()), validity)
}
fn like_over(
column: &StringColumn,
codes: &[u32],
like: &Like,
base: Validity,
rows: usize,
returns: &LogicalType,
) -> Result<Option<Vector>> {
let mut characters: Vec<char> = Vec::new();
let answer: Vec<bool> =
(0..column.len()).map(|value| like.holds_at(column, value, &mut characters)).collect();
let mut out = vec![false; rows];
let validity = over_valid(rows, base, |index| {
out[index] = answer[codes[index] as usize];
Ok(())
})?;
finish(returns, Data::Bool(out.into()), validity)
}
#[derive(Debug)]
enum Pattern {
Exact(String),
Prefix(String),
Suffix(String),
Contains(Box<memmem::Finder<'static>>),
Segments(Box<Split>),
General(Vec<char>),
}
#[derive(Debug)]
struct Split {
prefix: String,
suffix: String,
middles: Vec<memmem::Finder<'static>>,
}
impl Split {
fn holds(&self, text: &[u8]) -> bool {
if !text.starts_with(self.prefix.as_bytes()) || !text.ends_with(self.suffix.as_bytes()) {
return false;
}
let Some(end) = text.len().checked_sub(self.suffix.len()) else {
return false;
};
if self.prefix.len() > end {
return false;
}
let mut rest = &text[self.prefix.len()..end];
for finder in &self.middles {
let Some(at) = finder.find(rest) else {
return false;
};
rest = &rest[at + finder.needle().len()..];
}
true
}
}
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(Box::new(memmem::Finder::new(inner).into_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());
}
}
if !spelling.contains('_') {
let mut pieces: Vec<&str> = spelling.split('%').collect();
if pieces.len() >= 2 {
let suffix = pieces.pop().unwrap_or_default().to_owned();
let prefix = pieces.remove(0).to_owned();
let middles = pieces
.into_iter()
.filter(|piece| !piece.is_empty())
.map(|piece| memmem::Finder::new(piece).into_owned())
.collect();
return Self::Segments(Box::new(Split { prefix, suffix, middles }));
}
}
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(finder) => finder.find(text.as_bytes()).is_some(),
Self::Segments(split) => split.holds(text.as_bytes()),
Self::General(against) => {
characters.clear();
characters.extend(text.chars());
like(characters, against)
}
}
}
fn holds_bytes(&self, text: &[u8]) -> bool {
match self {
Self::Exact(against) => text == against.as_bytes(),
Self::Prefix(against) => text.starts_with(against.as_bytes()),
Self::Suffix(against) => text.ends_with(against.as_bytes()),
Self::Contains(finder) => finder.find(text).is_some(),
Self::Segments(split) => split.holds(text),
Self::General(_) => false,
}
}
}
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 !matches!(returns, LogicalType::BigInt | LogicalType::Double) {
return Ok(None);
}
let part = Part::parse(spelling)?;
let part = match when.logical_type() {
LogicalType::Interval if !truncating => part.of_an_interval(spelling)?,
_ => part,
};
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 | Form::Rle => {
let Some((codes, values)) = when.positions() 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),
}
}
fn clock<const PERIOD: i64, const UNIT: i64>(
micros: &[i64],
at: &impl Fn(usize) -> usize,
base: Validity,
out: &mut [i64],
) -> Result<Validity> {
over_valid(out.len(), base, |index| {
out[index] = micros[at(index)].rem_euclid(PERIOD) / UNIT;
Ok(())
})
}
#[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>> {
let doubled = *returns == LogicalType::Double && !truncating;
match (when.logical_type(), data, truncating) {
(LogicalType::Date, Data::Int32(days), false) if doubled => {
let mut out = vec![0f64; rows];
let validity = over_valid(rows, base, |index| {
out[index] = part.double_of_days(days[at(index)])?;
Ok(())
})?;
finish(returns, Data::Float64(out.into()), validity)
}
(LogicalType::Timestamp, Data::Int64(micros), false) if doubled => {
let mut out = vec![0f64; rows];
let validity = over_valid(rows, base, |index| {
out[index] = part.double_of_micros(micros[at(index)])?;
Ok(())
})?;
finish(returns, Data::Float64(out.into()), validity)
}
(LogicalType::Interval, Data::Interval(fields), false) if doubled => {
let mut out = vec![0f64; rows];
let validity = over_valid(rows, base, |index| {
let (months, days, micros) = fields[at(index)];
out[index] = part.double_of_interval(months, days, micros)?;
Ok(())
})?;
finish(returns, Data::Float64(out.into()), validity)
}
(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 = match part {
Part::Hour => clock::<{ datetime::MICROS_PER_DAY }, { datetime::MICROS_PER_HOUR }>(
micros, &at, base, &mut out,
)?,
Part::Minute => clock::<
{ datetime::MICROS_PER_HOUR },
{ datetime::MICROS_PER_MINUTE },
>(micros, &at, base, &mut out)?,
Part::Second => clock::<
{ datetime::MICROS_PER_MINUTE },
{ datetime::MICROS_PER_SECOND },
>(micros, &at, base, &mut out)?,
_ => 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)
}
(LogicalType::Interval, Data::Interval(fields), false) => {
let mut out = vec![0i64; rows];
let validity = over_valid(rows, base, |index| {
let (months, days, micros) = fields[at(index)];
out[index] = part.of_interval(months, days, micros)?;
Ok(())
})?;
finish(returns, Data::Int64(out.into()), validity)
}
(LogicalType::Interval, Data::Interval(fields), true) => {
let mut out = vec![(0i32, 0i32, 0i64); rows];
let validity = over_valid(rows, base, |index| {
let (months, days, micros) = fields[at(index)];
out[index] = part.truncate_interval(months, days, micros)?;
Ok(())
})?;
finish(returns, Data::Interval(out.into()), validity)
}
_ => Ok(None),
}
}
fn date_value(name: &str, spec: &Value, when: &Value, returns: &LogicalType) -> 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)?;
let doubled = *returns == LogicalType::Double;
match (name == "date_trunc", when) {
(false, Value::Date(days)) if doubled => part.double_of_days(*days).map(Value::Double),
(false, Value::Timestamp(micros) | Value::TimestampTz(micros)) if doubled => {
part.double_of_micros(*micros).map(Value::Double)
}
(false, Value::Interval { months, days, micros }) if doubled => part
.of_an_interval(spelling)?
.double_of_interval(*months, *days, *micros)
.map(Value::Double),
(false, Value::Date(days)) => part.of_days(*days).map(Value::BigInt),
(false, Value::Timestamp(micros) | Value::TimestampTz(micros)) => {
part.of_micros(*micros).map(Value::BigInt)
}
(false, Value::Interval { months, days, micros }) => {
part.of_an_interval(spelling)?.of_interval(*months, *days, *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),
(true, Value::TimestampTz(micros)) => part.truncate_micros(*micros).map(Value::TimestampTz),
(true, Value::Interval { months, days, micros }) => {
let (months, days, micros) = part.truncate_interval(*months, *days, *micros)?;
Ok(Value::Interval { months, days, micros })
}
_ => 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 interval_value(name: &str, count: &Value) -> Result<Value> {
let count = match count {
Value::Double(real) => Count::Real(*real),
other => match integral(other) {
Some(whole) => Count::Whole(whole),
None => return Err(Error::internal(format!("{name} of a {}", other.logical_type()))),
},
};
let (months, days, micros) = datetime::interval(name, count)?;
Ok(Value::Interval { months, days, micros })
}
fn truncated(value: &Value) -> Result<Value> {
match value {
Value::Float(real) => Ok(Value::Float(real.trunc())),
Value::Double(real) => Ok(Value::Double(real.trunc())),
Value::Decimal { unscaled, width, scale } => {
let step = pow10(*scale);
Ok(Value::Decimal { unscaled: unscaled / step * step, width: *width, scale: *scale })
}
_ if integral(value).is_some() => Ok(value.clone()),
_ => Err(Error::not_implemented(format!("trunc of a {}", value.logical_type()))),
}
}
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 let ("__rudb_zero_to_null", [value]) = (name, args) {
return Ok(if approximate(value) == Some(0.0) { Value::Null } else { value.clone() });
}
if let ("__rudb_stamp_seconds", [stamp, count]) = (name, args) {
if stamp.is_null() || count.is_null() {
return Ok(Value::Null);
}
let (Value::Timestamp(stamp), Some(count)) = (stamp, count.as_i64()) else {
return Err(Error::internal(format!(
"{name} of a {} and a {}",
stamp.logical_type(),
count.logical_type()
)));
};
return stamp_seconds(*stamp, count).map(Value::Timestamp);
}
if let ("__rudb_mean", [total, count]) = (name, args) {
let (Some(total), Some(scale)) = (mean_total(total), mean_scale(&total.logical_type()))
else {
return Ok(Value::Null);
};
return Ok(match count.as_i64() {
Some(seen) if seen > 0 => Value::Double(divide_mean(exactly(total), seen, scale)),
_ => Value::Null,
});
}
if name == "coalesce" {
let found = args.iter().find(|value| !value.is_null());
return Ok(found.cloned().unwrap_or(Value::Null));
}
if let Some(value) = structs::value(name, args, returns)? {
return Ok(value);
}
if name == "list_value" {
let LogicalType::List(element) = returns else {
return Err(Error::internal(format!("list_value returning {returns}")));
};
return Ok(Value::List { element: (**element).clone(), values: args.to_vec() });
}
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 name == "concat" {
let mut out = String::new();
for value in args.iter().filter(|value| !value.is_null()) {
out.push_str(&value.to_string());
}
return Ok(Value::Varchar(out));
}
if name == "list_concat" {
let LogicalType::List(element) = returns else {
return Err(Error::internal(format!("list_concat returning {returns}")));
};
let mut values = Vec::new();
let mut seen = false;
for value in args {
let Value::List { values: held, .. } = value else {
continue;
};
seen = true;
values.extend(held.iter().cloned());
}
if !seen {
return Ok(Value::Null);
}
return Ok(Value::List { element: (**element).clone(), values });
}
if let Some(answer) = lists::before_nulls(name, args, returns) {
return answer;
}
if let Some(answer) = maps::before_nulls(name, args, returns) {
return answer;
}
if args.iter().any(Value::is_null) {
return Ok(Value::Null);
}
if let Some(answer) = lists::value(name, args, returns) {
return answer;
}
if let Some(answer) = maps::value(name, args, returns) {
return answer;
}
if let ("list_aggr", [Value::List { values, .. }, Value::Varchar(aggregate), extra @ ..]) =
(name, args)
{
let mut accumulator = Accumulator::new(aggregate, returns)?;
let mut row = Vec::with_capacity(1 + extra.len());
for value in values {
row.clear();
row.push(value.clone());
row.extend(extra.iter().cloned());
accumulator.update(&row)?;
}
return accumulator.finish();
}
match (name, args) {
("||", [Value::List { values: left, .. }, Value::List { values: right, .. }]) => {
let LogicalType::List(element) = returns else {
return Err(Error::internal(format!("|| returning {returns}")));
};
let mut values = left.clone();
values.extend(right.iter().cloned());
Ok(Value::List { element: (**element).clone(), values })
}
("+", [only]) => Ok(only.clone()),
("-", [only @ Value::Interval { .. }]) => datetime::negated(only),
("-", [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]) if datetime::is_shift(left, right) => {
datetime::shift(left, right, name == "-")
}
("+" | "-", [left @ Value::Interval { .. }, right @ Value::Interval { .. }]) => {
datetime::combine(left, right, name == "-")
}
("+" | "-", [left, right]) if datetime::is_counted(left, right) => {
datetime::counted(left, right, name == "-")
}
("-", [left @ Value::Date(_), right @ Value::Date(_)])
| ("-", [left @ Value::Timestamp(_), right @ Value::Timestamp(_)])
| ("-", [left @ Value::TimestampTz(_), right @ Value::TimestampTz(_)]) => {
datetime::apart(left, right)
}
("+", [left, right]) if datetime::is_joined(left, right) => datetime::joined(left, right),
("/", [left, right])
if datetime::is_scale(left, right) && approximate(right) == Some(0.0) =>
{
Err(divided_by_zero(written, "/", left, right))
}
("*" | "/", [left, right]) if datetime::is_scale(left, right) => {
datetime::scaled(left, right, name == "/")
}
("+", [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),
("__rudb_checked_slash", [left, right]) => {
arithmetic(Op::Divide, left, right, returns, written)
}
("__rudb_checked_remainder", [left, right]) => {
if approximate(right) == Some(0.0) {
Err(divided_by_zero(written, "%", left, right))
} else {
arithmetic(Op::Modulo, left, right, returns, written)
}
}
("/", [left, right]) => divide(left, right, returns),
("||", [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" | "array_length", [Value::List { values, .. }]) => {
Ok(Value::BigInt(elements(values)))
}
("array_length", [Value::List { values, .. }, dimension]) => {
if dimension.as_i64() != Some(1) {
return Err(Error::not_implemented(
"array_length for lists with dimensions other than 1 not implemented",
));
}
Ok(Value::BigInt(elements(values)))
}
("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),
("contains", [haystack, needle]) => text::contains(haystack, needle),
("left" | "right", [held, count]) => text::end(name, held, count),
("replace", [held, needle, replacement]) => text::replace(held, needle, replacement),
("chr", [code]) => text::chr(code),
("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, returns),
("age", [later, earlier]) => datetime::age(later, earlier),
("trunc", [only]) => truncated(only),
(_, [count]) if datetime::is_interval(name) => interval_value(name, count),
("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)]
pub(crate) 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 => "%",
}
}
}
pub(crate) 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}){}",
ty.physical_name(),
op.symbol(),
ending(op, ty)
))
}
fn divided_by_zero(written: Written<'_>, symbol: &str, left: &Value, right: &Value) -> Error {
let quoted = written.map_or_else(|| format!("({left} {symbol} {right})"), |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})"))
}
pub(crate) 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 ending(op: Op, ty: &LogicalType) -> &'static str {
let Some(width) = ty.decimal_storage() else {
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.symbol(), 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.symbol(), 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.symbol(), 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, returns: &LogicalType) -> 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()
)));
}
};
if returns == &LogicalType::Float {
#[expect(clippy::cast_possible_truncation, reason = "the resolved result is FLOAT")]
return Ok(Value::Float((a / b) as f32));
}
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 elements(values: &[Value]) -> i64 {
i64::try_from(values.len()).unwrap_or(i64::MAX)
}
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_segment_search_answers_what_the_backtracking_walk_answers() {
let mut alphabet = vec![String::new()];
let mut words = vec![String::new()];
for _ in 0..4 {
alphabet = alphabet
.iter()
.flat_map(|word| ['a', 'b', '%'].map(|letter| format!("{word}{letter}")))
.collect();
words.extend(alphabet.iter().cloned());
}
let mut characters = Vec::new();
let mut seen = 0_usize;
for pattern in &words {
let compiled = Pattern::compile(pattern);
if !matches!(compiled, Pattern::Segments(_)) {
continue;
}
seen += 1;
let spelling: Vec<char> = pattern.chars().collect();
for text in &words {
let walked = like(&text.chars().collect::<Vec<char>>(), &spelling);
assert_eq!(
compiled.holds(text, &mut characters),
walked,
"{text:?} LIKE {pattern:?}"
);
assert_eq!(
compiled.holds_bytes(text.as_bytes()),
walked,
"{text:?} LIKE {pattern:?} on bytes"
);
}
}
assert!(seen > 20, "the segment shape was reached {seen} times");
}
#[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("~~", &Hoisted::Nothing, &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("~~", &Hoisted::Nothing, &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 answering_a_like_per_distinct_value_agrees_with_answering_it_per_row() {
let seen = [
Value::Varchar("a google search".into()),
Value::Varchar("goggle".into()),
Value::Null,
Value::Varchar("GOOGLE".into()),
Value::Varchar("".into()),
Value::Varchar("google.com/google".into()),
];
for spelling in ["%google%", "%GOOGLE%", "goggle", "g%e", "%le", "go%"] {
for name in ["~~", "!~~", "~~*"] {
let pattern =
Vector::constant(LogicalType::Varchar, Value::Varchar(spelling.into()), 12);
let answer = |text: Vector| {
let out = binary(
name,
&Hoisted::Nothing,
&text,
&pattern,
&LogicalType::Boolean,
12,
None,
)
.expect("the call is written")
.expect("text in this form has a loop of its own");
(0..12).map(|row| out.value_at(row)).collect::<Vec<Value>>()
};
let codes: Vec<u32> = (0..12).map(|row| (row % seen.len()) as u32).collect();
let rows: Vec<Value> =
codes.iter().map(|&code| seen[code as usize].clone()).collect();
let flat = Vector::from_values(LogicalType::Varchar, &rows).expect("builds");
let values = Vector::from_values(LogicalType::Varchar, &seen).expect("builds");
let short = Vector::dictionary(codes, values).expect("codes are in range");
let long = Vector::dictionary(
(0..12).collect(),
Vector::from_values(LogicalType::Varchar, &rows).expect("builds"),
)
.expect("codes are in range");
let want = answer(flat);
assert_eq!(answer(short), want, "{name} {spelling} over a short dictionary");
assert_eq!(answer(long), want, "{name} {spelling} over a long one");
}
}
}
#[test]
fn a_stable_dictionary_like_agrees_whichever_way_the_memo_filled() {
let values: Vec<Value> = (0..2_500)
.map(|index| match index % 7 {
0 => Value::Null,
1 => Value::Varchar(format!("http://google.com/{index}")),
2 => Value::Varchar(format!("http://goggle.com/{index}")),
_ => Value::Varchar(format!("row {index}")),
})
.collect();
let dictionary =
Arc::new(Vector::from_values(LogicalType::Varchar, &values).expect("builds"));
let like = Like::of("~~", "%google%").expect("a literal pattern compiles");
for rows in [2_000_usize, 64] {
let codes: Vec<u32> =
(0..rows).map(|row| ((row * 991) % values.len()) as u32).collect();
let picked: Vec<Value> =
codes.iter().map(|&code| values[code as usize].clone()).collect();
let flat = Vector::from_values(LogicalType::Varchar, &picked).expect("builds");
let pattern =
Vector::constant(LogicalType::Varchar, Value::Varchar("%google%".into()), rows);
let answer = |text: &Vector| {
like_of("~~", Some(&like), text, &pattern, &LogicalType::Boolean, rows)
.expect("the call is written")
.expect("text in this form has a loop of its own")
};
let column = Vector::stable_dictionary(codes, Arc::clone(&dictionary))
.expect("codes are in range");
let want = answer(&flat);
let got = answer(&column);
for row in 0..rows {
assert_eq!(got.value_at(row), want.value_at(row), "{rows} rows, row {row}");
}
}
}
#[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: if edge {
(pow10(*width) - 1) * if small < 0 { -1 } else { 1 }
} else {
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(4, 2).expect("a legal decimal"),
LogicalType::decimal(9, 4).expect("a legal decimal"),
LogicalType::decimal(10, 2).expect("a legal decimal"),
LogicalType::decimal(18, 6).expect("a legal decimal"),
LogicalType::decimal(38, 4).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 let LogicalType::Decimal { width, scale } = ty {
let doubled = LogicalType::decimal(*width, scale.saturating_mul(2))
.expect("a scale of twice a legal one is inside the width");
for (one, other) in pairings(&left, &right) {
agrees("*", &[one, other], &doubled);
}
}
if matches!(ty, LogicalType::Double) {
for (one, other) in pairings(&left, &right) {
agrees("/", &[one, other], ty);
}
}
}
}
}
#[derive(Debug)]
struct Kept(Vec<Vec<u8>>);
impl rudb_vector::TextSource for Kept {
fn len(&self) -> usize {
self.0.len()
}
fn bytes_at(&self, index: usize) -> Result<Option<&[u8]>> {
Ok(self.0.get(index).map(Vec::as_slice))
}
fn footprint(&self) -> usize {
self.0.iter().map(Vec::len).sum()
}
}
#[test]
fn a_string_function_over_a_column_that_is_read_rather_than_held_stays_vectorized() {
let values = ["Ärger", "b", "", "Straße", "http://EXAMPLE.com/Q"];
let kept = Kept(values.iter().map(|text| text.as_bytes().to_vec()).collect());
let read = Vector::external_text(LogicalType::Varchar, Arc::new(kept))
.expect("the source is text");
let coded = Vector::dictionary(vec![4, 0, 2, 1, 3, 0, 4], read.clone())
.expect("every code names a value");
for arg in [read, coded] {
for (name, returns) in [
("lower", LogicalType::Varchar),
("upper", LogicalType::Varchar),
("length", LogicalType::BigInt),
("strlen", LogicalType::BigInt),
] {
let form = arg.form();
let taken = unary(name, &arg, &returns, arg.len())
.expect("the call is written")
.unwrap_or_else(|| panic!("{name} on {form:?} took the row at a time path"));
let want = oracle(name, std::slice::from_ref(&arg), &returns)
.expect("the row at a time path answers");
assert_eq!(format!("{taken:?}"), format!("{want:?}"), "{name} on {form:?}");
}
}
}
#[test]
fn substring_with_a_literal_start_and_length_agrees_with_the_row_at_a_time_path() {
let values = ["Ärger", "b", "", "Straße", "13-715-945-6730", "日本語のテキスト"];
let kept = Kept(values.iter().map(|text| text.as_bytes().to_vec()).collect());
let read = Vector::external_text(LogicalType::Varchar, Arc::new(kept))
.expect("the source is text");
let coded = Vector::dictionary(vec![4, 0, 2, 1, 3, 5, 4], read.clone())
.expect("every code names a value");
let held = Vector::from_values(
LogicalType::Varchar,
&values.iter().map(|text| Value::Varchar((*text).into())).collect::<Vec<_>>(),
)
.expect("builds");
let mut rng = Rng(0x5eed_0f5b_57e1_0001);
let sampled = sample(&LogicalType::Varchar, 96, 7, &mut rng);
let mut columns = vec![read, coded];
columns.extend(forms(&held));
columns.extend(forms(&sampled));
let whole =
|at: i64, rows: usize| Vector::constant(LogicalType::BigInt, Value::BigInt(at), rows);
for arg in columns {
let rows = arg.len();
for start in [-9, -2, -1, 0, 1, 2, 3, 7] {
let begin = whole(start, rows);
if arg.form() != Form::Constant {
let taken = substring_of(&[&arg, &begin], &LogicalType::Varchar, rows)
.expect("the call is written");
assert!(
taken.is_some(),
"substring on {:?} took the row at a time path",
arg.form()
);
}
agrees("substring", &[arg.clone(), begin.clone()], &LogicalType::Varchar);
for length in [-3, -1, 0, 1, 2, 5, 40] {
let args = [arg.clone(), begin.clone(), whole(length, rows)];
agrees("substring", &args, &LogicalType::Varchar);
}
}
}
}
#[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 integer_arithmetic_over_dictionaries_and_packed_runs_has_a_loop() {
let small = |values: &[i16]| {
Vector::from_values(
LogicalType::SmallInt,
&values.iter().map(|&value| Value::SmallInt(value)).collect::<Vec<_>>(),
)
.expect("small integers")
};
let one = Vector::dictionary(vec![0, 1, 1, 2], small(&[-1, 4, 7])).expect("codes");
let other = Vector::dictionary(vec![2, 0, 1, 1], small(&[3, 0, 9])).expect("codes");
let packed =
Vector::packed(LogicalType::SmallInt, vec![0b11_0100], 2, 900, 4).expect("packs");
assert_eq!(packed.form(), Form::BitPacked);
for args in [[one.clone(), other.clone()], [packed.clone(), other], [one, packed]] {
let forms = (args[0].form(), args[1].form());
let before = fallback::count(Kernel::Scalar, forms.0, forms.1);
agrees("+", &args, &LogicalType::SmallInt);
let sum = call("+", &args, &LogicalType::SmallInt, None).expect("adds");
assert_eq!(sum.len(), 4);
assert_eq!(fallback::count(Kernel::Scalar, forms.0, forms.1), before);
}
}
#[test]
fn a_date_with_days_added_has_a_loop_and_keeps_the_range_check() {
let epoch = Vector::constant(LogicalType::Date, Value::Date(0), 3);
let days = Vector::from_values(
LogicalType::Integer,
&[Value::Integer(15_887), Value::Null, Value::Integer(-3)],
)
.expect("days");
let before = fallback::count(Kernel::Scalar, Form::Constant, Form::Flat);
agrees("+", &[epoch.clone(), days.clone()], &LogicalType::Date);
agrees("+", &[days.clone(), epoch.clone()], &LogicalType::Date);
agrees("-", &[epoch.clone(), days], &LogicalType::Date);
assert_eq!(fallback::count(Kernel::Scalar, Form::Constant, Form::Flat), before);
let far = Vector::from_values(LogicalType::Integer, &[Value::Integer(i32::MAX)])
.expect("one day count");
let one = Vector::constant(LogicalType::Date, Value::Date(0), 1);
agrees("+", &[one, far], &LogicalType::Date);
}
#[test]
fn a_stamp_or_a_date_moved_by_a_packed_count_has_a_loop() {
for ty in [LogicalType::Integer, LogicalType::BigInt] {
let packed = Vector::packed(ty.clone(), vec![0b11_0100], 2, 900, 4).expect("packs");
assert_eq!(packed.form(), Form::BitPacked);
let stamp = Vector::constant(LogicalType::Timestamp, Value::Timestamp(7), 4);
let before = fallback::count(Kernel::Scalar, Form::Constant, Form::BitPacked);
let args = [stamp, packed.clone()];
agrees("__rudb_stamp_seconds", &args, &LogicalType::Timestamp);
let moved =
call("__rudb_stamp_seconds", &args, &LogicalType::Timestamp, None).expect("moves");
assert_eq!(moved.value_at(2), Value::Timestamp(7 + 903 * 1_000_000));
if ty == LogicalType::Integer {
let epoch = Vector::constant(LogicalType::Date, Value::Date(0), 4);
agrees("+", &[epoch, packed], &LogicalType::Date);
}
assert_eq!(fallback::count(Kernel::Scalar, Form::Constant, Form::BitPacked), 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_call_on_an_empty_batch_costs_nothing_and_says_so() {
for forms in [
vec![
Vector::constant(LogicalType::Integer, Value::Integer(3), 0),
Vector::constant(LogicalType::Integer, Value::Integer(4), 0),
],
vec![
Vector::from_values(LogicalType::Integer, &[]).expect("no rows"),
Vector::constant(LogicalType::Integer, Value::Integer(4), 0),
],
] {
let left = forms[0].form();
let right = forms[1].form();
let before = fallback::count(Kernel::Scalar, left, right);
let sum = call("+", &forms, &LogicalType::Integer, None).expect("adds nothing");
assert_eq!(sum.len(), 0);
assert_eq!(fallback::count(Kernel::Scalar, left, right), before);
}
}
#[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 the_loops_for_counted_intervals_and_moved_timestamps_agree_with_the_row_at_a_time_path() {
let seconds = Vector::from_values(
LogicalType::Double,
&[Value::Double(1_373_000_000.0), Value::Null, Value::Double(2.7), Value::Double(-0.5)],
)
.expect("four counts");
for arg in forms(&seconds) {
agrees("to_seconds", &[arg], &LogicalType::Interval);
}
let days = Vector::from_values(
LogicalType::BigInt,
&[Value::BigInt(3), Value::Null, Value::BigInt(-40)],
)
.expect("three counts");
for arg in forms(&days) {
agrees("to_days", std::slice::from_ref(&arg), &LogicalType::Interval);
agrees("to_months", &[arg], &LogicalType::Interval);
}
let huge = Vector::from_values(LogicalType::Double, &[Value::Double(1e300)]).expect("one");
for arg in forms(&huge) {
agrees("to_seconds", &[arg], &LogicalType::Interval);
}
let intervals = Vector::from_values(
LogicalType::Interval,
&[
Value::Interval { months: 0, days: 0, micros: 1_373_000_000_000_000 },
Value::Null,
Value::Interval { months: 1, days: 1, micros: -5 },
Value::Interval { months: 0, days: 0, micros: i64::MAX },
],
)
.expect("four intervals");
let stamps = Vector::from_values(
LogicalType::Timestamp,
&[
Value::Timestamp(0),
Value::Timestamp(86_400_000_000),
Value::Null,
Value::Timestamp(-1),
],
)
.expect("four stamps");
let epoch = Vector::constant(LogicalType::Timestamp, Value::Timestamp(0), 4);
for interval in forms(&intervals) {
for stamp in forms(&stamps).into_iter().chain([epoch.clone()]) {
let pair = [stamp.clone(), interval.clone()];
agrees("+", &pair, &LogicalType::Timestamp);
agrees("-", &pair, &LogicalType::Timestamp);
agrees("+", &[interval.clone(), stamp], &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));
}
}