diff --git a/core/vdbe/execute.rs b/core/vdbe/execute.rs index 1bfc278a9..344e641e9 100644 --- a/core/vdbe/execute.rs +++ b/core/vdbe/execute.rs @@ -2,7 +2,6 @@ use crate::error::SQLITE_CONSTRAINT_UNIQUE; use crate::function::AlterTableFunc; use crate::mvcc::database::CheckpointStateMachine; -use crate::numeric::{NullableInteger, Numeric}; use crate::schema::Table; use crate::state_machine::StateMachine; use crate::storage::btree::{ @@ -22,6 +21,7 @@ use crate::util::{ rewrite_fk_parent_table_if_needed, rewrite_inline_col_fk_target_if_needed, }; use crate::vdbe::insn::InsertFlags; +use crate::vdbe::value::{exec_char, exec_concat_strings, exec_concat_ws}; use crate::vdbe::{registers_to_ref_values, EndStatement, TxnCleanup}; use crate::vector::{vector32_sparse, vector_concat, vector_distance_jaccard, vector_slice}; use crate::{ @@ -51,7 +51,7 @@ use turso_macros::match_ignore_ascii_case; use crate::pseudo::PseudoCursor; use crate::{ - schema::{affinity, Affinity}, + schema::Affinity, storage::btree::{BTreeCursor, BTreeKey}, }; @@ -83,8 +83,6 @@ use super::{ likeop::{construct_like_escape_arg, exec_glob, exec_like_with_escape}, sorter::Sorter, }; -use regex::{Regex, RegexBuilder}; -use std::collections::HashMap; #[cfg(feature = "json")] use crate::{ @@ -8952,978 +8950,6 @@ pub fn op_fk_if_zero( Ok(InsnFunctionStepResult::Step) } -mod cmath { - extern "C" { - pub fn exp(x: f64) -> f64; - pub fn log(x: f64) -> f64; - pub fn log10(x: f64) -> f64; - pub fn log2(x: f64) -> f64; - pub fn pow(x: f64, y: f64) -> f64; - - pub fn sin(x: f64) -> f64; - pub fn sinh(x: f64) -> f64; - pub fn asin(x: f64) -> f64; - pub fn asinh(x: f64) -> f64; - - pub fn cos(x: f64) -> f64; - pub fn cosh(x: f64) -> f64; - pub fn acos(x: f64) -> f64; - pub fn acosh(x: f64) -> f64; - - pub fn tan(x: f64) -> f64; - pub fn tanh(x: f64) -> f64; - pub fn atan(x: f64) -> f64; - pub fn atanh(x: f64) -> f64; - pub fn atan2(x: f64, y: f64) -> f64; - } -} - -enum TrimType { - All, - Left, - Right, -} - -impl TrimType { - fn trim<'a>(&self, text: &'a str, pattern: &[char]) -> &'a str { - match self { - TrimType::All => text.trim_matches(pattern), - TrimType::Right => text.trim_end_matches(pattern), - TrimType::Left => text.trim_start_matches(pattern), - } - } -} - -impl Value { - pub fn exec_lower(&self) -> Option { - self.cast_text() - .map(|s| Value::build_text(s.to_ascii_lowercase())) - } - - pub fn exec_length(&self) -> Self { - match self { - Value::Text(t) => { - let s = t.as_str(); - let len_before_null = s.find('\0').map_or_else( - || s.chars().count(), - |null_pos| s[..null_pos].chars().count(), - ); - Value::Integer(len_before_null as i64) - } - Value::Integer(_) | Value::Float(_) => { - // For numbers, SQLite returns the length of the string representation - Value::Integer(self.to_string().chars().count() as i64) - } - Value::Blob(blob) => Value::Integer(blob.len() as i64), - _ => self.to_owned(), - } - } - - pub fn exec_octet_length(&self) -> Self { - match self { - Value::Text(_) | Value::Integer(_) | Value::Float(_) => { - Value::Integer(self.to_string().into_bytes().len() as i64) - } - Value::Blob(blob) => Value::Integer(blob.len() as i64), - _ => self.to_owned(), - } - } - - pub fn exec_upper(&self) -> Option { - self.cast_text() - .map(|s| Value::build_text(s.to_ascii_uppercase())) - } - - pub fn exec_sign(&self) -> Option { - let v = Numeric::from_value_strict(self).try_into_f64()?; - - Some(Value::Integer(if v > 0.0 { - 1 - } else if v < 0.0 { - -1 - } else { - 0 - })) - } - - /// Generates the Soundex code for a given word - pub fn exec_soundex(&self) -> Value { - let s = match self { - Value::Null => return Value::build_text("?000"), - Value::Text(s) => { - // return ?000 if non ASCII alphabet character is found - if !s.as_str().chars().all(|c| c.is_ascii_alphabetic()) { - return Value::build_text("?000"); - } - s.clone() - } - _ => return Value::build_text("?000"), // For unsupported types, return NULL - }; - - // Remove numbers and spaces - let word: String = s - .as_str() - .chars() - .filter(|c| !c.is_ascii_digit()) - .collect::() - .replace(" ", ""); - if word.is_empty() { - return Value::build_text("0000"); - } - - let soundex_code = |c| match c { - 'b' | 'f' | 'p' | 'v' => Some('1'), - 'c' | 'g' | 'j' | 'k' | 'q' | 's' | 'x' | 'z' => Some('2'), - 'd' | 't' => Some('3'), - 'l' => Some('4'), - 'm' | 'n' => Some('5'), - 'r' => Some('6'), - _ => None, - }; - - // Convert the word to lowercase for consistent lookups - let word = word.to_lowercase(); - let first_letter = word.chars().next().unwrap(); - - // Remove all occurrences of 'h' and 'w' except the first letter - let code: String = word - .chars() - .skip(1) - .filter(|&ch| ch != 'h' && ch != 'w') - .fold(first_letter.to_string(), |mut acc, ch| { - acc.push(ch); - acc - }); - - // Replace consonants with digits based on Soundex mapping - let tmp: String = code - .chars() - .map(|ch| match soundex_code(ch) { - Some(code) => code.to_string(), - None => ch.to_string(), - }) - .collect(); - - // Remove adjacent same digits - let tmp = tmp.chars().fold(String::new(), |mut acc, ch| { - if !acc.ends_with(ch) { - acc.push(ch); - } - acc - }); - - // Remove all occurrences of a, e, i, o, u, y except the first letter - let mut result = tmp - .chars() - .enumerate() - .filter(|(i, ch)| *i == 0 || !matches!(ch, 'a' | 'e' | 'i' | 'o' | 'u' | 'y')) - .map(|(_, ch)| ch) - .collect::(); - - // If the first symbol is a digit, replace it with the saved first letter - if let Some(first_digit) = result.chars().next() { - if first_digit.is_ascii_digit() { - result.replace_range(0..1, &first_letter.to_string()); - } - } - - // Append zeros if the result contains less than 4 characters - while result.len() < 4 { - result.push('0'); - } - - // Retain the first 4 characters and convert to uppercase - result.truncate(4); - Value::build_text(result.to_uppercase()) - } - - pub fn exec_abs(&self) -> Result { - Ok(match self { - Value::Null => Value::Null, - Value::Integer(v) => { - Value::Integer(v.checked_abs().ok_or(LimboError::IntegerOverflow)?) - } - Value::Float(non_nan) => Value::Float(non_nan.abs()), - _ => { - let s = match self { - Value::Text(text) => text.to_string(), - Value::Blob(blob) => String::from_utf8_lossy(blob).to_string(), - _ => unreachable!(), - }; - - crate::numeric::str_to_f64(s) - .map(|v| Value::Float(f64::from(v).abs())) - .unwrap_or(Value::Float(0.0)) - } - }) - } - - pub fn exec_random(generate_random_number: F) -> Self - where - F: Fn() -> i64, - { - Value::Integer(generate_random_number()) - } - - pub fn exec_randomblob(&self, fill_bytes: F) -> Value - where - F: Fn(&mut [u8]), - { - let length = match self { - Value::Integer(i) => *i, - Value::Float(f) => *f as i64, - Value::Text(t) => t.as_str().parse().unwrap_or(1), - _ => 1, - } - .max(1) as usize; - - let mut blob: Vec = vec![0; length]; - fill_bytes(&mut blob); - Value::Blob(blob) - } - - pub fn exec_quote(&self) -> Self { - match self { - Value::Null => Value::build_text("NULL"), - Value::Integer(_) | Value::Float(_) => self.to_owned(), - Value::Blob(_) => todo!(), - Value::Text(s) => { - let mut quoted = String::with_capacity(s.as_str().len() + 2); - quoted.push('\''); - for c in s.as_str().chars() { - if c == '\0' { - break; - } else if c == '\'' { - quoted.push('\''); - quoted.push(c); - } else { - quoted.push(c); - } - } - quoted.push('\''); - Value::build_text(quoted) - } - } - } - - pub fn exec_nullif(&self, second_value: &Self) -> Self { - if self != second_value { - self.clone() - } else { - Value::Null - } - } - - pub fn exec_substring( - value: &Value, - start_value: &Value, - length_value: Option<&Value>, - ) -> Value { - /// Function is stabilized but not released for version 1.88 \ - /// https://doc.rust-lang.org/src/core/str/mod.rs.html#453 - const fn ceil_char_boundary(s: &str, index: usize) -> usize { - const fn is_utf8_char_boundary(c: u8) -> bool { - // This is bit magic equivalent to: b < 128 || b >= 192 - (c as i8) >= -0x40 - } - - if index >= s.len() { - s.len() - } else { - let mut i = index; - while i < s.len() { - if is_utf8_char_boundary(s.as_bytes()[i]) { - break; - } - i += 1; - } - - // The character boundary will be within four bytes of the index - debug_assert!(i <= index + 3); - - i - } - } - - fn calculate_postions( - start: i64, - bytes_len: usize, - length_value: Option<&Value>, - ) -> (usize, usize) { - let bytes_len = bytes_len as i64; - - // The left-most character of X is number 1. - // If Y is negative then the first character of the substring is found by counting from the right rather than the left. - let first_position = if start < 0 { - bytes_len.saturating_sub((start).abs()) - } else { - start - 1 - }; - // If Z is negative then the abs(Z) characters preceding the Y-th character are returned. - let last_position = match length_value { - Some(Value::Integer(length)) => first_position + *length, - _ => bytes_len, - }; - - let (start, end) = if first_position <= last_position { - (first_position, last_position) - } else { - (last_position, first_position) - }; - - ( - start.clamp(-0, bytes_len) as usize, - end.clamp(0, bytes_len) as usize, - ) - } - - let start_value = start_value.exec_cast("INT"); - let length_value = length_value.map(|value| value.exec_cast("INT")); - - match (value, start_value) { - (Value::Blob(b), Value::Integer(start)) => { - let (start, end) = calculate_postions(start, b.len(), length_value.as_ref()); - Value::from_blob(b[start..end].to_vec()) - } - (value, Value::Integer(start)) => { - if let Some(text) = value.cast_text() { - let (mut start, mut end) = - calculate_postions(start, text.len(), length_value.as_ref()); - - // https://github.com/sqlite/sqlite/blob/a248d84f/src/func.c#L417 - let s = text.as_str(); - let mut start_byte_idx = 0; - end -= start; - while start > 0 { - start_byte_idx = ceil_char_boundary(s, start_byte_idx + 1); - start -= 1; - } - let mut end_byte_idx = start_byte_idx; - while end > 0 { - end_byte_idx = ceil_char_boundary(s, end_byte_idx + 1); - end -= 1; - } - Value::build_text(&s[start_byte_idx..end_byte_idx]) - } else { - Value::Null - } - } - _ => Value::Null, - } - } - - pub fn exec_instr(&self, pattern: &Value) -> Value { - if self == &Value::Null || pattern == &Value::Null { - return Value::Null; - } - - if let (Value::Blob(reg), Value::Blob(pattern)) = (self, pattern) { - let result = reg - .windows(pattern.len()) - .position(|window| window == *pattern) - .map_or(0, |i| i + 1); - return Value::Integer(result as i64); - } - - let reg_str; - let reg = match self { - Value::Text(s) => s.as_str(), - _ => { - reg_str = self.to_string(); - reg_str.as_str() - } - }; - - let pattern_str; - let pattern = match pattern { - Value::Text(s) => s.as_str(), - _ => { - pattern_str = pattern.to_string(); - pattern_str.as_str() - } - }; - - match reg.find(pattern) { - Some(position) => Value::Integer(position as i64 + 1), - None => Value::Integer(0), - } - } - - pub fn exec_typeof(&self) -> Value { - match self { - Value::Null => Value::build_text("null"), - Value::Integer(_) => Value::build_text("integer"), - Value::Float(_) => Value::build_text("real"), - Value::Text(_) => Value::build_text("text"), - Value::Blob(_) => Value::build_text("blob"), - } - } - - pub fn exec_hex(&self) -> Value { - match self { - Value::Text(_) | Value::Integer(_) | Value::Float(_) => { - let text = self.to_string(); - Value::build_text(hex::encode_upper(text)) - } - Value::Blob(blob_bytes) => Value::build_text(hex::encode_upper(blob_bytes)), - Value::Null => Value::build_text(""), - } - } - - pub fn exec_unhex(&self, ignored_chars: Option<&Value>) -> Value { - match self { - Value::Null => Value::Null, - _ => match ignored_chars { - None => match self - .cast_text() - .map(|s| hex::decode(&s[0..s.find('\0').unwrap_or(s.len())])) - { - Some(Ok(bytes)) => Value::Blob(bytes), - _ => Value::Null, - }, - Some(ignore) => match ignore { - Value::Text(_) => { - let pat = ignore.to_string(); - let trimmed = self - .to_string() - .trim_start_matches(|x| pat.contains(x)) - .trim_end_matches(|x| pat.contains(x)) - .to_string(); - match hex::decode(trimmed) { - Ok(bytes) => Value::Blob(bytes), - _ => Value::Null, - } - } - _ => Value::Null, - }, - }, - } - } - - pub fn exec_unicode(&self) -> Value { - match self { - Value::Text(_) | Value::Integer(_) | Value::Float(_) | Value::Blob(_) => { - let text = self.to_string(); - if let Some(first_char) = text.chars().next() { - Value::Integer(first_char as u32 as i64) - } else { - Value::Null - } - } - _ => Value::Null, - } - } - - pub fn exec_round(&self, precision: Option<&Value>) -> Value { - let Some(f) = Numeric::from(self).try_into_f64() else { - return Value::Null; - }; - - let precision = match precision.map(|v| Numeric::from(v).try_into_f64()) { - None => 0.0, - Some(Some(v)) => v, - Some(None) => return Value::Null, - }; - - if !(-4503599627370496.0..=4503599627370496.0).contains(&f) { - return Value::Float(f); - } - - let precision = if precision < 1.0 { 0.0 } else { precision }; - let precision = precision.clamp(0.0, 30.0) as usize; - - if precision == 0 { - return Value::Float(((f + if f < 0.0 { -0.5 } else { 0.5 }) as i64) as f64); - } - - let f: f64 = crate::numeric::str_to_f64(format!("{f:.precision$}")) - .unwrap() - .into(); - - Value::Float(f) - } - - fn _exec_trim(&self, pattern: Option<&Value>, trim_type: TrimType) -> Value { - match (self, pattern) { - (Value::Text(_) | Value::Integer(_) | Value::Float(_), Some(pattern)) => { - let pattern_chars: Vec = pattern.to_string().chars().collect(); - let text = self.to_string(); - Value::build_text(trim_type.trim(&text, &pattern_chars)) - } - (Value::Text(t), None) => Value::build_text(trim_type.trim(t.as_str(), &[' '])), - (reg, _) => reg.to_owned(), - } - } - - // Implements TRIM pattern matching. - pub fn exec_trim(&self, pattern: Option<&Value>) -> Value { - self._exec_trim(pattern, TrimType::All) - } - // Implements RTRIM pattern matching. - pub fn exec_rtrim(&self, pattern: Option<&Value>) -> Value { - self._exec_trim(pattern, TrimType::Right) - } - - // Implements LTRIM pattern matching. - pub fn exec_ltrim(&self, pattern: Option<&Value>) -> Value { - self._exec_trim(pattern, TrimType::Left) - } - - pub fn exec_zeroblob(&self) -> Value { - let length: i64 = match self { - Value::Integer(i) => *i, - Value::Float(f) => *f as i64, - Value::Text(s) => s.as_str().parse().unwrap_or(0), - _ => 0, - }; - Value::Blob(vec![0; length.max(0) as usize]) - } - - // exec_if returns whether you should jump - pub fn exec_if(&self, jump_if_null: bool, not: bool) -> bool { - Numeric::from(self) - .try_into_bool() - .map(|jump| if not { !jump } else { jump }) - .unwrap_or(jump_if_null) - } - - pub fn exec_cast(&self, datatype: &str) -> Value { - if matches!(self, Value::Null) { - return Value::Null; - } - match affinity(datatype) { - // NONE Casting a value to a type-name with no affinity causes the value to be converted into a BLOB. Casting to a BLOB consists of first casting the value to TEXT in the encoding of the database connection, then interpreting the resulting byte sequence as a BLOB instead of as TEXT. - // Historically called NONE, but it's the same as BLOB - Affinity::Blob => { - // Convert to TEXT first, then interpret as BLOB - // TODO: handle encoding - let text = self.to_string(); - Value::Blob(text.into_bytes()) - } - // TEXT To cast a BLOB value to TEXT, the sequence of bytes that make up the BLOB is interpreted as text encoded using the database encoding. - // Casting an INTEGER or REAL value into TEXT renders the value as if via sqlite3_snprintf() except that the resulting TEXT uses the encoding of the database connection. - Affinity::Text => { - // Convert everything to text representation - // TODO: handle encoding and whatever sqlite3_snprintf does - Value::build_text(self.to_string()) - } - Affinity::Real => match self { - Value::Blob(b) => { - let text = String::from_utf8_lossy(b); - Value::Float( - crate::numeric::str_to_f64(&text) - .map(f64::from) - .unwrap_or(0.0), - ) - } - Value::Text(t) => { - Value::Float(crate::numeric::str_to_f64(t).map(f64::from).unwrap_or(0.0)) - } - Value::Integer(i) => Value::Float(*i as f64), - Value::Float(f) => Value::Float(*f), - _ => Value::Float(0.0), - }, - Affinity::Integer => match self { - Value::Blob(b) => { - // Convert BLOB to TEXT first - let text = String::from_utf8_lossy(b); - Value::Integer(crate::numeric::str_to_i64(&text).unwrap_or(0)) - } - Value::Text(t) => Value::Integer(crate::numeric::str_to_i64(t).unwrap_or(0)), - Value::Integer(i) => Value::Integer(*i), - // A cast of a REAL value into an INTEGER results in the integer between the REAL value and zero - // that is closest to the REAL value. If a REAL is greater than the greatest possible signed integer (+9223372036854775807) - // then the result is the greatest possible signed integer and if the REAL is less than the least possible signed integer (-9223372036854775808) - // then the result is the least possible signed integer. - Value::Float(f) => { - let i = f.trunc() as i128; - if i > i64::MAX as i128 { - Value::Integer(i64::MAX) - } else if i < i64::MIN as i128 { - Value::Integer(i64::MIN) - } else { - Value::Integer(i as i64) - } - } - _ => Value::Integer(0), - }, - Affinity::Numeric => match self { - Value::Null => Value::Null, - Value::Integer(v) => Value::Integer(*v), - Value::Float(v) => Self::Float(*v), - _ => { - let s = match self { - Value::Text(text) => text.to_string(), - Value::Blob(blob) => String::from_utf8_lossy(blob.as_slice()).to_string(), - _ => unreachable!(), - }; - - match crate::numeric::str_to_f64(&s) { - Some(parsed) => { - let Some(int) = crate::numeric::str_to_i64(&s) else { - return Value::Integer(0); - }; - - if f64::from(parsed) == int as f64 { - return Value::Integer(int); - } - - Value::Float(parsed.into()) - } - None => Value::Integer(0), - } - } - }, - } - } - - pub fn exec_replace(source: &Value, pattern: &Value, replacement: &Value) -> Value { - // The replace(X,Y,Z) function returns a string formed by substituting string Z for every occurrence of - // string Y in string X. The BINARY collating sequence is used for comparisons. If Y is an empty string - // then return X unchanged. If Z is not initially a string, it is cast to a UTF-8 string prior to processing. - - // If any of the arguments is NULL, the result is NULL. - if matches!(source, Value::Null) - || matches!(pattern, Value::Null) - || matches!(replacement, Value::Null) - { - return Value::Null; - } - - let source = source.exec_cast("TEXT"); - let pattern = pattern.exec_cast("TEXT"); - let replacement = replacement.exec_cast("TEXT"); - - // If any of the casts failed, panic as text casting is not expected to fail. - match (&source, &pattern, &replacement) { - (Value::Text(source), Value::Text(pattern), Value::Text(replacement)) => { - if pattern.as_str().is_empty() { - return Value::Text(source.clone()); - } - - let result = source - .as_str() - .replace(pattern.as_str(), replacement.as_str()); - Value::build_text(result) - } - _ => unreachable!("text cast should never fail"), - } - } - - fn exec_math_unary(&self, function: &MathFunc) -> Value { - let v = Numeric::from_value_strict(self); - - // In case of some functions and integer input, return the input as is - if let Numeric::Integer(i) = v { - if matches! { function, MathFunc::Ceil | MathFunc::Ceiling | MathFunc::Floor | MathFunc::Trunc } - { - return Value::Integer(i); - } - } - - let Some(f) = v.try_into_f64() else { - return Value::Null; - }; - - if matches! { function, MathFunc::Ln | MathFunc::Log10 | MathFunc::Log2 } && f <= 0.0 { - return Value::Null; - } - - let result = match function { - MathFunc::Acos => unsafe { cmath::acos(f) }, - MathFunc::Acosh => unsafe { cmath::acosh(f) }, - MathFunc::Asin => unsafe { cmath::asin(f) }, - MathFunc::Asinh => unsafe { cmath::asinh(f) }, - MathFunc::Atan => unsafe { cmath::atan(f) }, - MathFunc::Atanh => unsafe { cmath::atanh(f) }, - MathFunc::Ceil | MathFunc::Ceiling => libm::ceil(f), - MathFunc::Cos => unsafe { cmath::cos(f) }, - MathFunc::Cosh => unsafe { cmath::cosh(f) }, - MathFunc::Degrees => f.to_degrees(), - MathFunc::Exp => unsafe { cmath::exp(f) }, - MathFunc::Floor => libm::floor(f), - MathFunc::Ln => unsafe { cmath::log(f) }, - MathFunc::Log10 => unsafe { cmath::log10(f) }, - MathFunc::Log2 => unsafe { cmath::log2(f) }, - MathFunc::Radians => f.to_radians(), - MathFunc::Sin => unsafe { cmath::sin(f) }, - MathFunc::Sinh => unsafe { cmath::sinh(f) }, - MathFunc::Sqrt => libm::sqrt(f), - MathFunc::Tan => unsafe { cmath::tan(f) }, - MathFunc::Tanh => unsafe { cmath::tanh(f) }, - MathFunc::Trunc => libm::trunc(f), - _ => unreachable!("Unexpected mathematical unary function {:?}", function), - }; - - if result.is_nan() { - Value::Null - } else { - Value::Float(result) - } - } - - fn exec_math_binary(&self, rhs: &Value, function: &MathFunc) -> Value { - let Some(lhs) = Numeric::from_value_strict(self).try_into_f64() else { - return Value::Null; - }; - - let Some(rhs) = Numeric::from_value_strict(rhs).try_into_f64() else { - return Value::Null; - }; - - let result = match function { - MathFunc::Atan2 => unsafe { cmath::atan2(lhs, rhs) }, - MathFunc::Mod => libm::fmod(lhs, rhs), - MathFunc::Pow | MathFunc::Power => unsafe { cmath::pow(lhs, rhs) }, - _ => unreachable!("Unexpected mathematical binary function {:?}", function), - }; - - if result.is_nan() { - Value::Null - } else { - Value::Float(result) - } - } - - fn exec_math_log(&self, base: Option<&Value>) -> Value { - let Some(f) = Numeric::from_value_strict(self).try_into_f64() else { - return Value::Null; - }; - - let base = match base.map(|value| Numeric::from_value_strict(value).try_into_f64()) { - Some(Some(f)) => f, - Some(None) => return Value::Null, - None => 10.0, - }; - - if f <= 0.0 || base <= 0.0 || base == 1.0 { - return Value::Null; - } - - if base == 2.0 { - return Value::Float(libm::log2(f)); - } else if base == 10.0 { - return Value::Float(libm::log10(f)); - }; - - let log_x = libm::log(f); - let log_base = libm::log(base); - - if log_base <= 0.0 { - return Value::Null; - } - - let result = log_x / log_base; - Value::Float(result) - } - - pub fn exec_add(&self, rhs: &Value) -> Value { - (Numeric::from(self) + Numeric::from(rhs)).into() - } - - pub fn exec_subtract(&self, rhs: &Value) -> Value { - (Numeric::from(self) - Numeric::from(rhs)).into() - } - - pub fn exec_multiply(&self, rhs: &Value) -> Value { - (Numeric::from(self) * Numeric::from(rhs)).into() - } - - pub fn exec_divide(&self, rhs: &Value) -> Value { - (Numeric::from(self) / Numeric::from(rhs)).into() - } - - pub fn exec_bit_and(&self, rhs: &Value) -> Value { - (NullableInteger::from(self) & NullableInteger::from(rhs)).into() - } - - pub fn exec_bit_or(&self, rhs: &Value) -> Value { - (NullableInteger::from(self) | NullableInteger::from(rhs)).into() - } - - pub fn exec_remainder(&self, rhs: &Value) -> Value { - let convert_to_float = matches!(Numeric::from(self), Numeric::Float(_)) - || matches!(Numeric::from(rhs), Numeric::Float(_)); - - match NullableInteger::from(self) % NullableInteger::from(rhs) { - NullableInteger::Null => Value::Null, - NullableInteger::Integer(v) => { - if convert_to_float { - Value::Float(v as f64) - } else { - Value::Integer(v) - } - } - } - } - - pub fn exec_bit_not(&self) -> Value { - (!NullableInteger::from(self)).into() - } - - pub fn exec_shift_left(&self, rhs: &Value) -> Value { - (NullableInteger::from(self) << NullableInteger::from(rhs)).into() - } - - pub fn exec_shift_right(&self, rhs: &Value) -> Value { - (NullableInteger::from(self) >> NullableInteger::from(rhs)).into() - } - - pub fn exec_boolean_not(&self) -> Value { - match Numeric::from(self).try_into_bool() { - None => Value::Null, - Some(v) => Value::Integer(!v as i64), - } - } - - pub fn exec_concat(&self, rhs: &Value) -> Value { - if let (Value::Blob(lhs), Value::Blob(rhs)) = (self, rhs) { - return Value::build_text(String::from_utf8_lossy( - &[lhs.as_slice(), rhs.as_slice()].concat(), - )); - } - - let Some(lhs) = self.cast_text() else { - return Value::Null; - }; - - let Some(rhs) = rhs.cast_text() else { - return Value::Null; - }; - - Value::build_text(lhs + &rhs) - } - - pub fn exec_and(&self, rhs: &Value) -> Value { - match ( - Numeric::from(self).try_into_bool(), - Numeric::from(rhs).try_into_bool(), - ) { - (Some(false), _) | (_, Some(false)) => Value::Integer(0), - (None, _) | (_, None) => Value::Null, - _ => Value::Integer(1), - } - } - - pub fn exec_or(&self, rhs: &Value) -> Value { - match ( - Numeric::from(self).try_into_bool(), - Numeric::from(rhs).try_into_bool(), - ) { - (Some(true), _) | (_, Some(true)) => Value::Integer(1), - (None, _) | (_, None) => Value::Null, - _ => Value::Integer(0), - } - } - - // Implements LIKE pattern matching. Caches the constructed regex if a cache is provided - pub fn exec_like( - regex_cache: Option<&mut HashMap>, - pattern: &str, - text: &str, - ) -> bool { - if let Some(cache) = regex_cache { - match cache.get(pattern) { - Some(re) => re.is_match(text), - None => { - let re = construct_like_regex(pattern); - let res = re.is_match(text); - cache.insert(pattern.to_string(), re); - res - } - } - } else { - let re = construct_like_regex(pattern); - re.is_match(text) - } - } - - pub fn exec_min<'a, T: Iterator>(regs: T) -> Value { - regs.min().map(|v| v.to_owned()).unwrap_or(Value::Null) - } - - pub fn exec_max<'a, T: Iterator>(regs: T) -> Value { - regs.max().map(|v| v.to_owned()).unwrap_or(Value::Null) - } -} - -fn exec_concat_strings(registers: &[Register]) -> Value { - let mut result = String::new(); - for reg in registers { - match reg.get_value() { - Value::Null => continue, - Value::Blob(_) => todo!("TODO concat blob"), - v => result.push_str(&format!("{v}")), - } - } - Value::build_text(result) -} - -fn exec_concat_ws(registers: &[Register]) -> Value { - if registers.is_empty() { - return Value::Null; - } - - let separator = match registers[0].get_value() { - Value::Null | Value::Blob(_) => return Value::Null, - v => format!("{v}"), - }; - - let parts = registers[1..] - .iter() - .filter_map(|reg| match reg.get_value() { - Value::Text(_) | Value::Integer(_) | Value::Float(_) => { - Some(format!("{}", reg.get_value())) - } - _ => None, - }); - - let result = parts.collect::>().join(&separator); - Value::build_text(result) -} - -fn exec_char(values: &[Register]) -> Value { - let result: String = values - .iter() - .filter_map(|x| { - if let Value::Integer(i) = x.get_value() { - Some(*i as u8 as char) - } else { - None - } - }) - .collect(); - Value::build_text(result) -} - -fn construct_like_regex(pattern: &str) -> Regex { - let mut regex_pattern = String::with_capacity(pattern.len() * 2); - - regex_pattern.push('^'); - - for c in pattern.chars() { - match c { - '\\' => regex_pattern.push_str("\\\\"), - '%' => regex_pattern.push_str(".*"), - '_' => regex_pattern.push('.'), - ch => { - if regex_syntax::is_meta_character(c) { - regex_pattern.push('\\'); - } - regex_pattern.push(ch); - } - } - } - - regex_pattern.push('$'); - - RegexBuilder::new(®ex_pattern) - .case_insensitive(true) - .dot_matches_new_line(true) - .build() - .unwrap() -} - fn apply_affinity_char(target: &mut Register, affinity: Affinity) -> bool { if let Register::Value(value) = target { if matches!(value, Value::Blob(_)) { @@ -10550,10 +9576,7 @@ fn get_schema_cookie( #[cfg(test)] mod tests { - use rand::{Rng, RngCore}; - use super::*; - use crate::types::Value; #[test] fn test_apply_numeric_affinity_partial_numbers() { @@ -10589,1274 +9612,10 @@ mod tests { assert_eq!(*reg.get_value(), Value::Integer(0)); } - #[test] - fn test_exec_add() { - let inputs = vec![ - (Value::Integer(3), Value::Integer(1)), - (Value::Float(3.0), Value::Float(1.0)), - (Value::Float(3.0), Value::Integer(1)), - (Value::Integer(3), Value::Float(1.0)), - (Value::Null, Value::Null), - (Value::Null, Value::Integer(1)), - (Value::Null, Value::Float(1.0)), - (Value::Null, Value::Text("2".into())), - (Value::Integer(1), Value::Null), - (Value::Float(1.0), Value::Null), - (Value::Text("1".into()), Value::Null), - (Value::Text("1".into()), Value::Text("3".into())), - (Value::Text("1.0".into()), Value::Text("3.0".into())), - (Value::Text("1.0".into()), Value::Float(3.0)), - (Value::Text("1.0".into()), Value::Integer(3)), - (Value::Float(1.0), Value::Text("3.0".into())), - (Value::Integer(1), Value::Text("3".into())), - ]; - - let outputs = [ - Value::Integer(4), - Value::Float(4.0), - Value::Float(4.0), - Value::Float(4.0), - Value::Null, - Value::Null, - Value::Null, - Value::Null, - Value::Null, - Value::Null, - Value::Null, - Value::Integer(4), - Value::Float(4.0), - Value::Float(4.0), - Value::Float(4.0), - Value::Float(4.0), - Value::Float(4.0), - ]; - - assert_eq!( - inputs.len(), - outputs.len(), - "Inputs and Outputs should have same size" - ); - for (i, (lhs, rhs)) in inputs.iter().enumerate() { - assert_eq!( - lhs.exec_add(rhs), - outputs[i], - "Wrong ADD for lhs: {lhs}, rhs: {rhs}" - ); - } - } - - #[test] - fn test_exec_subtract() { - let inputs = vec![ - (Value::Integer(3), Value::Integer(1)), - (Value::Float(3.0), Value::Float(1.0)), - (Value::Float(3.0), Value::Integer(1)), - (Value::Integer(3), Value::Float(1.0)), - (Value::Null, Value::Null), - (Value::Null, Value::Integer(1)), - (Value::Null, Value::Float(1.0)), - (Value::Null, Value::Text("1".into())), - (Value::Integer(1), Value::Null), - (Value::Float(1.0), Value::Null), - (Value::Text("4".into()), Value::Null), - (Value::Text("1".into()), Value::Text("3".into())), - (Value::Text("1.0".into()), Value::Text("3.0".into())), - (Value::Text("1.0".into()), Value::Float(3.0)), - (Value::Text("1.0".into()), Value::Integer(3)), - (Value::Float(1.0), Value::Text("3.0".into())), - (Value::Integer(1), Value::Text("3".into())), - ]; - - let outputs = [ - Value::Integer(2), - Value::Float(2.0), - Value::Float(2.0), - Value::Float(2.0), - Value::Null, - Value::Null, - Value::Null, - Value::Null, - Value::Null, - Value::Null, - Value::Null, - Value::Integer(-2), - Value::Float(-2.0), - Value::Float(-2.0), - Value::Float(-2.0), - Value::Float(-2.0), - Value::Float(-2.0), - ]; - - assert_eq!( - inputs.len(), - outputs.len(), - "Inputs and Outputs should have same size" - ); - for (i, (lhs, rhs)) in inputs.iter().enumerate() { - assert_eq!( - lhs.exec_subtract(rhs), - outputs[i], - "Wrong subtract for lhs: {lhs}, rhs: {rhs}" - ); - } - } - - #[test] - fn test_exec_multiply() { - let inputs = vec![ - (Value::Integer(3), Value::Integer(2)), - (Value::Float(3.0), Value::Float(2.0)), - (Value::Float(3.0), Value::Integer(2)), - (Value::Integer(3), Value::Float(2.0)), - (Value::Null, Value::Null), - (Value::Null, Value::Integer(1)), - (Value::Null, Value::Float(1.0)), - (Value::Null, Value::Text("1".into())), - (Value::Integer(1), Value::Null), - (Value::Float(1.0), Value::Null), - (Value::Text("4".into()), Value::Null), - (Value::Text("2".into()), Value::Text("3".into())), - (Value::Text("2.0".into()), Value::Text("3.0".into())), - (Value::Text("2.0".into()), Value::Float(3.0)), - (Value::Text("2.0".into()), Value::Integer(3)), - (Value::Float(2.0), Value::Text("3.0".into())), - (Value::Integer(2), Value::Text("3.0".into())), - ]; - - let outputs = [ - Value::Integer(6), - Value::Float(6.0), - Value::Float(6.0), - Value::Float(6.0), - Value::Null, - Value::Null, - Value::Null, - Value::Null, - Value::Null, - Value::Null, - Value::Null, - Value::Integer(6), - Value::Float(6.0), - Value::Float(6.0), - Value::Float(6.0), - Value::Float(6.0), - Value::Float(6.0), - ]; - - assert_eq!( - inputs.len(), - outputs.len(), - "Inputs and Outputs should have same size" - ); - for (i, (lhs, rhs)) in inputs.iter().enumerate() { - assert_eq!( - lhs.exec_multiply(rhs), - outputs[i], - "Wrong multiply for lhs: {lhs}, rhs: {rhs}" - ); - } - } - - #[test] - fn test_exec_divide() { - let inputs = vec![ - (Value::Integer(1), Value::Integer(0)), - (Value::Float(1.0), Value::Float(0.0)), - (Value::Integer(i64::MIN), Value::Integer(-1)), - (Value::Float(6.0), Value::Float(2.0)), - (Value::Float(6.0), Value::Integer(2)), - (Value::Integer(6), Value::Integer(2)), - (Value::Null, Value::Integer(2)), - (Value::Integer(2), Value::Null), - (Value::Null, Value::Null), - (Value::Text("6".into()), Value::Text("2".into())), - (Value::Text("6".into()), Value::Integer(2)), - ]; - - let outputs = [ - Value::Null, - Value::Null, - Value::Float(9.223372036854776e18), - Value::Float(3.0), - Value::Float(3.0), - Value::Float(3.0), - Value::Null, - Value::Null, - Value::Null, - Value::Float(3.0), - Value::Float(3.0), - ]; - - assert_eq!( - inputs.len(), - outputs.len(), - "Inputs and Outputs should have same size" - ); - for (i, (lhs, rhs)) in inputs.iter().enumerate() { - assert_eq!( - lhs.exec_divide(rhs), - outputs[i], - "Wrong divide for lhs: {lhs}, rhs: {rhs}" - ); - } - } - - #[test] - fn test_exec_remainder() { - let inputs = vec![ - (Value::Null, Value::Null), - (Value::Null, Value::Float(1.0)), - (Value::Null, Value::Integer(1)), - (Value::Null, Value::Text("1".into())), - (Value::Float(1.0), Value::Null), - (Value::Integer(1), Value::Null), - (Value::Integer(12), Value::Integer(0)), - (Value::Float(12.0), Value::Float(0.0)), - (Value::Float(12.0), Value::Integer(0)), - (Value::Integer(12), Value::Float(0.0)), - (Value::Integer(i64::MIN), Value::Integer(-1)), - (Value::Integer(12), Value::Integer(3)), - (Value::Float(12.0), Value::Float(3.0)), - (Value::Float(12.0), Value::Integer(3)), - (Value::Integer(12), Value::Float(3.0)), - (Value::Integer(12), Value::Integer(-3)), - (Value::Float(12.0), Value::Float(-3.0)), - (Value::Float(12.0), Value::Integer(-3)), - (Value::Integer(12), Value::Float(-3.0)), - (Value::Text("12.0".into()), Value::Text("3.0".into())), - (Value::Text("12.0".into()), Value::Float(3.0)), - (Value::Float(12.0), Value::Text("3.0".into())), - ]; - let outputs = vec![ - Value::Null, - Value::Null, - Value::Null, - Value::Null, - Value::Null, - Value::Null, - Value::Null, - Value::Null, - Value::Null, - Value::Null, - Value::Float(0.0), - Value::Integer(0), - Value::Float(0.0), - Value::Float(0.0), - Value::Float(0.0), - Value::Integer(0), - Value::Float(0.0), - Value::Float(0.0), - Value::Float(0.0), - Value::Float(0.0), - Value::Float(0.0), - Value::Float(0.0), - ]; - - assert_eq!( - inputs.len(), - outputs.len(), - "Inputs and Outputs should have same size" - ); - - for (i, (lhs, rhs)) in inputs.iter().enumerate() { - assert_eq!( - lhs.exec_remainder(rhs), - outputs[i], - "Wrong remainder for lhs: {lhs}, rhs: {rhs}" - ); - } - } - - #[test] - fn test_exec_and() { - let inputs = vec![ - (Value::Integer(0), Value::Null), - (Value::Null, Value::Integer(1)), - (Value::Null, Value::Null), - (Value::Float(0.0), Value::Null), - (Value::Integer(1), Value::Float(2.2)), - (Value::Integer(0), Value::Text("string".into())), - (Value::Integer(0), Value::Text("1".into())), - (Value::Integer(1), Value::Text("1".into())), - ]; - let outputs = [ - Value::Integer(0), - Value::Null, - Value::Null, - Value::Integer(0), - Value::Integer(1), - Value::Integer(0), - Value::Integer(0), - Value::Integer(1), - ]; - - assert_eq!( - inputs.len(), - outputs.len(), - "Inputs and Outputs should have same size" - ); - for (i, (lhs, rhs)) in inputs.iter().enumerate() { - assert_eq!( - lhs.exec_and(rhs), - outputs[i], - "Wrong AND for lhs: {lhs}, rhs: {rhs}" - ); - } - } - - #[test] - fn test_exec_or() { - let inputs = vec![ - (Value::Integer(0), Value::Null), - (Value::Null, Value::Integer(1)), - (Value::Null, Value::Null), - (Value::Float(0.0), Value::Null), - (Value::Integer(1), Value::Float(2.2)), - (Value::Float(0.0), Value::Integer(0)), - (Value::Integer(0), Value::Text("string".into())), - (Value::Integer(0), Value::Text("1".into())), - (Value::Integer(0), Value::Text("".into())), - ]; - let outputs = [ - Value::Null, - Value::Integer(1), - Value::Null, - Value::Null, - Value::Integer(1), - Value::Integer(0), - Value::Integer(0), - Value::Integer(1), - Value::Integer(0), - ]; - - assert_eq!( - inputs.len(), - outputs.len(), - "Inputs and Outputs should have same size" - ); - for (i, (lhs, rhs)) in inputs.iter().enumerate() { - assert_eq!( - lhs.exec_or(rhs), - outputs[i], - "Wrong OR for lhs: {lhs}, rhs: {rhs}" - ); - } - } - - use crate::vdbe::{Bitfield, Register}; - - use super::{exec_char, execute_turso_version}; - use std::collections::HashMap; - - #[test] - fn test_length() { - let input_str = Value::build_text("bob"); - let expected_len = Value::Integer(3); - assert_eq!(input_str.exec_length(), expected_len); - - let input_integer = Value::Integer(123); - let expected_len = Value::Integer(3); - assert_eq!(input_integer.exec_length(), expected_len); - - let input_float = Value::Float(123.456); - let expected_len = Value::Integer(7); - assert_eq!(input_float.exec_length(), expected_len); - - let expected_blob = Value::Blob("example".as_bytes().to_vec()); - let expected_len = Value::Integer(7); - assert_eq!(expected_blob.exec_length(), expected_len); - } - - #[test] - fn test_quote() { - let input = Value::build_text("abc\0edf"); - let expected = Value::build_text("'abc'"); - assert_eq!(input.exec_quote(), expected); - - let input = Value::Integer(123); - let expected = Value::Integer(123); - assert_eq!(input.exec_quote(), expected); - - let input = Value::build_text("hello''world"); - let expected = Value::build_text("'hello''''world'"); - assert_eq!(input.exec_quote(), expected); - } - - #[test] - fn test_typeof() { - let input = Value::Null; - let expected: Value = Value::build_text("null"); - assert_eq!(input.exec_typeof(), expected); - - let input = Value::Integer(123); - let expected: Value = Value::build_text("integer"); - assert_eq!(input.exec_typeof(), expected); - - let input = Value::Float(123.456); - let expected: Value = Value::build_text("real"); - assert_eq!(input.exec_typeof(), expected); - - let input = Value::build_text("hello"); - let expected: Value = Value::build_text("text"); - assert_eq!(input.exec_typeof(), expected); - - let input = Value::Blob("limbo".as_bytes().to_vec()); - let expected: Value = Value::build_text("blob"); - assert_eq!(input.exec_typeof(), expected); - } - - #[test] - fn test_unicode() { - assert_eq!(Value::build_text("a").exec_unicode(), Value::Integer(97)); - assert_eq!( - Value::build_text("😊").exec_unicode(), - Value::Integer(128522) - ); - assert_eq!(Value::build_text("").exec_unicode(), Value::Null); - assert_eq!(Value::Integer(23).exec_unicode(), Value::Integer(50)); - assert_eq!(Value::Integer(0).exec_unicode(), Value::Integer(48)); - assert_eq!(Value::Float(0.0).exec_unicode(), Value::Integer(48)); - assert_eq!(Value::Float(23.45).exec_unicode(), Value::Integer(50)); - assert_eq!(Value::Null.exec_unicode(), Value::Null); - assert_eq!( - Value::Blob("example".as_bytes().to_vec()).exec_unicode(), - Value::Integer(101) - ); - } - - #[test] - fn test_min_max() { - let input_int_vec = [ - Register::Value(Value::Integer(-1)), - Register::Value(Value::Integer(10)), - ]; - assert_eq!( - Value::exec_min(input_int_vec.iter().map(|v| v.get_value())), - Value::Integer(-1) - ); - assert_eq!( - Value::exec_max(input_int_vec.iter().map(|v| v.get_value())), - Value::Integer(10) - ); - - let str1 = Register::Value(Value::build_text("A")); - let str2 = Register::Value(Value::build_text("z")); - let input_str_vec = [str2, str1.clone()]; - assert_eq!( - Value::exec_min(input_str_vec.iter().map(|v| v.get_value())), - Value::build_text("A") - ); - assert_eq!( - Value::exec_max(input_str_vec.iter().map(|v| v.get_value())), - Value::build_text("z") - ); - - let input_null_vec = [Register::Value(Value::Null), Register::Value(Value::Null)]; - assert_eq!( - Value::exec_min(input_null_vec.iter().map(|v| v.get_value())), - Value::Null - ); - assert_eq!( - Value::exec_max(input_null_vec.iter().map(|v| v.get_value())), - Value::Null - ); - - let input_mixed_vec = [Register::Value(Value::Integer(10)), str1]; - assert_eq!( - Value::exec_min(input_mixed_vec.iter().map(|v| v.get_value())), - Value::Integer(10) - ); - assert_eq!( - Value::exec_max(input_mixed_vec.iter().map(|v| v.get_value())), - Value::build_text("A") - ); - } - - #[test] - fn test_trim() { - let input_str = Value::build_text(" Bob and Alice "); - let expected_str = Value::build_text("Bob and Alice"); - assert_eq!(input_str.exec_trim(None), expected_str); - - let input_str = Value::build_text(" Bob and Alice "); - let pattern_str = Value::build_text("Bob and"); - let expected_str = Value::build_text("Alice"); - assert_eq!(input_str.exec_trim(Some(&pattern_str)), expected_str); - - let input_str = Value::build_text("\ta"); - let expected_str = Value::build_text("\ta"); - assert_eq!(input_str.exec_trim(None), expected_str); - - let input_str = Value::build_text("\na"); - let expected_str = Value::build_text("\na"); - assert_eq!(input_str.exec_trim(None), expected_str); - } - - #[test] - fn test_ltrim() { - let input_str = Value::build_text(" Bob and Alice "); - let expected_str = Value::build_text("Bob and Alice "); - assert_eq!(input_str.exec_ltrim(None), expected_str); - - let input_str = Value::build_text(" Bob and Alice "); - let pattern_str = Value::build_text("Bob and"); - let expected_str = Value::build_text("Alice "); - assert_eq!(input_str.exec_ltrim(Some(&pattern_str)), expected_str); - } - - #[test] - fn test_rtrim() { - let input_str = Value::build_text(" Bob and Alice "); - let expected_str = Value::build_text(" Bob and Alice"); - assert_eq!(input_str.exec_rtrim(None), expected_str); - - let input_str = Value::build_text(" Bob and Alice "); - let pattern_str = Value::build_text("Bob and"); - let expected_str = Value::build_text(" Bob and Alice"); - assert_eq!(input_str.exec_rtrim(Some(&pattern_str)), expected_str); - - let input_str = Value::build_text(" Bob and Alice "); - let pattern_str = Value::build_text("and Alice"); - let expected_str = Value::build_text(" Bob"); - assert_eq!(input_str.exec_rtrim(Some(&pattern_str)), expected_str); - } - - #[test] - fn test_soundex() { - let input_str = Value::build_text("Pfister"); - let expected_str = Value::build_text("P236"); - assert_eq!(input_str.exec_soundex(), expected_str); - - let input_str = Value::build_text("husobee"); - let expected_str = Value::build_text("H210"); - assert_eq!(input_str.exec_soundex(), expected_str); - - let input_str = Value::build_text("Tymczak"); - let expected_str = Value::build_text("T522"); - assert_eq!(input_str.exec_soundex(), expected_str); - - let input_str = Value::build_text("Ashcraft"); - let expected_str = Value::build_text("A261"); - assert_eq!(input_str.exec_soundex(), expected_str); - - let input_str = Value::build_text("Robert"); - let expected_str = Value::build_text("R163"); - assert_eq!(input_str.exec_soundex(), expected_str); - - let input_str = Value::build_text("Rupert"); - let expected_str = Value::build_text("R163"); - assert_eq!(input_str.exec_soundex(), expected_str); - - let input_str = Value::build_text("Rubin"); - let expected_str = Value::build_text("R150"); - assert_eq!(input_str.exec_soundex(), expected_str); - - let input_str = Value::build_text("Kant"); - let expected_str = Value::build_text("K530"); - assert_eq!(input_str.exec_soundex(), expected_str); - - let input_str = Value::build_text("Knuth"); - let expected_str = Value::build_text("K530"); - assert_eq!(input_str.exec_soundex(), expected_str); - - let input_str = Value::build_text("x"); - let expected_str = Value::build_text("X000"); - assert_eq!(input_str.exec_soundex(), expected_str); - - let input_str = Value::build_text("闪电五连鞭"); - let expected_str = Value::build_text("?000"); - assert_eq!(input_str.exec_soundex(), expected_str); - } - - #[test] - fn test_upper_case() { - let input_str = Value::build_text("Limbo"); - let expected_str = Value::build_text("LIMBO"); - assert_eq!(input_str.exec_upper().unwrap(), expected_str); - - let input_int = Value::Integer(10); - assert_eq!(input_int.exec_upper().unwrap(), Value::build_text("10")); - assert_eq!(Value::Null.exec_upper(), None) - } - - #[test] - fn test_lower_case() { - let input_str = Value::build_text("Limbo"); - let expected_str = Value::build_text("limbo"); - assert_eq!(input_str.exec_lower().unwrap(), expected_str); - - let input_int = Value::Integer(10); - assert_eq!(input_int.exec_lower().unwrap(), Value::build_text("10")); - assert_eq!(Value::Null.exec_lower(), None) - } - - #[test] - fn test_hex() { - let input_str = Value::build_text("limbo"); - let expected_val = Value::build_text("6C696D626F"); - assert_eq!(input_str.exec_hex(), expected_val); - - let input_int = Value::Integer(100); - let expected_val = Value::build_text("313030"); - assert_eq!(input_int.exec_hex(), expected_val); - - let input_float = Value::Float(12.34); - let expected_val = Value::build_text("31322E3334"); - assert_eq!(input_float.exec_hex(), expected_val); - - let input_blob = Value::Blob(vec![0xff]); - let expected_val = Value::build_text("FF"); - assert_eq!(input_blob.exec_hex(), expected_val); - } - - #[test] - fn test_unhex() { - let input = Value::build_text("6f"); - let expected = Value::Blob(vec![0x6f]); - assert_eq!(input.exec_unhex(None), expected); - - let input = Value::build_text("6f"); - let expected = Value::Blob(vec![0x6f]); - assert_eq!(input.exec_unhex(None), expected); - - let input = Value::build_text("611"); - let expected = Value::Null; - assert_eq!(input.exec_unhex(None), expected); - - let input = Value::build_text(""); - let expected = Value::Blob(vec![]); - assert_eq!(input.exec_unhex(None), expected); - - let input = Value::build_text("61x"); - let expected = Value::Null; - assert_eq!(input.exec_unhex(None), expected); - - let input = Value::Null; - let expected = Value::Null; - assert_eq!(input.exec_unhex(None), expected); - } - - #[test] - fn test_abs() { - let int_positive_reg = Value::Integer(10); - let int_negative_reg = Value::Integer(-10); - assert_eq!(int_positive_reg.exec_abs().unwrap(), int_positive_reg); - assert_eq!(int_negative_reg.exec_abs().unwrap(), int_positive_reg); - - let float_positive_reg = Value::Integer(10); - let float_negative_reg = Value::Integer(-10); - assert_eq!(float_positive_reg.exec_abs().unwrap(), float_positive_reg); - assert_eq!(float_negative_reg.exec_abs().unwrap(), float_positive_reg); - - assert_eq!( - Value::build_text("a").exec_abs().unwrap(), - Value::Float(0.0) - ); - assert_eq!(Value::Null.exec_abs().unwrap(), Value::Null); - - // ABS(i64::MIN) should return RuntimeError - assert!(Value::Integer(i64::MIN).exec_abs().is_err()); - } - - #[test] - fn test_char() { - assert_eq!( - exec_char(&[ - Register::Value(Value::Integer(108)), - Register::Value(Value::Integer(105)) - ]), - Value::build_text("li") - ); - assert_eq!(exec_char(&[]), Value::build_text("")); - assert_eq!( - exec_char(&[Register::Value(Value::Null)]), - Value::build_text("") - ); - assert_eq!( - exec_char(&[Register::Value(Value::build_text("a"))]), - Value::build_text("") - ); - } - - #[test] - fn test_like_with_escape_or_regexmeta_chars() { - assert!(Value::exec_like(None, r#"\%A"#, r#"\A"#)); - assert!(Value::exec_like(None, "%a%a", "aaaa")); - } - - #[test] - fn test_like_no_cache() { - assert!(Value::exec_like(None, "a%", "aaaa")); - assert!(Value::exec_like(None, "%a%a", "aaaa")); - assert!(!Value::exec_like(None, "%a.a", "aaaa")); - assert!(!Value::exec_like(None, "a.a%", "aaaa")); - assert!(!Value::exec_like(None, "%a.ab", "aaaa")); - } - - #[test] - fn test_like_with_cache() { - let mut cache = HashMap::new(); - assert!(Value::exec_like(Some(&mut cache), "a%", "aaaa")); - assert!(Value::exec_like(Some(&mut cache), "%a%a", "aaaa")); - assert!(!Value::exec_like(Some(&mut cache), "%a.a", "aaaa")); - assert!(!Value::exec_like(Some(&mut cache), "a.a%", "aaaa")); - assert!(!Value::exec_like(Some(&mut cache), "%a.ab", "aaaa")); - - // again after values have been cached - assert!(Value::exec_like(Some(&mut cache), "a%", "aaaa")); - assert!(Value::exec_like(Some(&mut cache), "%a%a", "aaaa")); - assert!(!Value::exec_like(Some(&mut cache), "%a.a", "aaaa")); - assert!(!Value::exec_like(Some(&mut cache), "a.a%", "aaaa")); - assert!(!Value::exec_like(Some(&mut cache), "%a.ab", "aaaa")); - } - - #[test] - fn test_random() { - match Value::exec_random(|| rand::rng().random()) { - Value::Integer(value) => { - // Check that the value is within the range of i64 - assert!( - (i64::MIN..=i64::MAX).contains(&value), - "Random number out of range" - ); - } - _ => panic!("exec_random did not return an Integer variant"), - } - } - - #[test] - fn test_exec_randomblob() { - struct TestCase { - input: Value, - expected_len: usize, - } - - let test_cases = vec![ - TestCase { - input: Value::Integer(5), - expected_len: 5, - }, - TestCase { - input: Value::Integer(0), - expected_len: 1, - }, - TestCase { - input: Value::Integer(-1), - expected_len: 1, - }, - TestCase { - input: Value::build_text(""), - expected_len: 1, - }, - TestCase { - input: Value::build_text("5"), - expected_len: 5, - }, - TestCase { - input: Value::build_text("0"), - expected_len: 1, - }, - TestCase { - input: Value::build_text("-1"), - expected_len: 1, - }, - TestCase { - input: Value::Float(2.9), - expected_len: 2, - }, - TestCase { - input: Value::Float(-3.15), - expected_len: 1, - }, - TestCase { - input: Value::Null, - expected_len: 1, - }, - ]; - - for test_case in &test_cases { - let result = test_case.input.exec_randomblob(|dest| { - rand::rng().fill_bytes(dest); - }); - match result { - Value::Blob(blob) => { - assert_eq!(blob.len(), test_case.expected_len); - } - _ => panic!("exec_randomblob did not return a Blob variant"), - } - } - } - - #[test] - fn test_exec_round() { - let input_val = Value::Float(123.456); - let expected_val = Value::Float(123.0); - assert_eq!(input_val.exec_round(None), expected_val); - - let input_val = Value::Float(123.456); - let precision_val = Value::Integer(2); - let expected_val = Value::Float(123.46); - assert_eq!(input_val.exec_round(Some(&precision_val)), expected_val); - - let input_val = Value::Float(123.456); - let precision_val = Value::build_text("1"); - let expected_val = Value::Float(123.5); - assert_eq!(input_val.exec_round(Some(&precision_val)), expected_val); - - let input_val = Value::build_text("123.456"); - let precision_val = Value::Integer(2); - let expected_val = Value::Float(123.46); - assert_eq!(input_val.exec_round(Some(&precision_val)), expected_val); - - let input_val = Value::Integer(123); - let precision_val = Value::Integer(1); - let expected_val = Value::Float(123.0); - assert_eq!(input_val.exec_round(Some(&precision_val)), expected_val); - - let input_val = Value::Float(100.123); - let expected_val = Value::Float(100.0); - assert_eq!(input_val.exec_round(None), expected_val); - - let input_val = Value::Float(100.123); - let expected_val = Value::Null; - assert_eq!(input_val.exec_round(Some(&Value::Null)), expected_val); - } - - #[test] - fn test_exec_if() { - let reg = Value::Integer(0); - assert!(!reg.exec_if(false, false)); - assert!(reg.exec_if(false, true)); - - let reg = Value::Integer(1); - assert!(reg.exec_if(false, false)); - assert!(!reg.exec_if(false, true)); - - let reg = Value::Null; - assert!(!reg.exec_if(false, false)); - assert!(!reg.exec_if(false, true)); - - let reg = Value::Null; - assert!(reg.exec_if(true, false)); - assert!(reg.exec_if(true, true)); - - let reg = Value::Null; - assert!(!reg.exec_if(false, false)); - assert!(!reg.exec_if(false, true)); - } - - #[test] - fn test_nullif() { - assert_eq!( - Value::Integer(1).exec_nullif(&Value::Integer(1)), - Value::Null - ); - assert_eq!( - Value::Float(1.1).exec_nullif(&Value::Float(1.1)), - Value::Null - ); - assert_eq!( - Value::build_text("limbo").exec_nullif(&Value::build_text("limbo")), - Value::Null - ); - - assert_eq!( - Value::Integer(1).exec_nullif(&Value::Integer(2)), - Value::Integer(1) - ); - assert_eq!( - Value::Float(1.1).exec_nullif(&Value::Float(1.2)), - Value::Float(1.1) - ); - assert_eq!( - Value::build_text("limbo").exec_nullif(&Value::build_text("limb")), - Value::build_text("limbo") - ); - } - - #[test] - fn test_substring() { - let str_value = Value::build_text("limbo"); - let start_value = Value::Integer(1); - let length_value = Value::Integer(3); - let expected_val = Value::build_text("lim"); - assert_eq!( - Value::exec_substring(&str_value, &start_value, Some(&length_value)), - expected_val - ); - - let str_value = Value::build_text("limbo"); - let start_value = Value::Integer(1); - let length_value = Value::Integer(10); - let expected_val = Value::build_text("limbo"); - assert_eq!( - Value::exec_substring(&str_value, &start_value, Some(&length_value)), - expected_val - ); - - let str_value = Value::build_text("limbo"); - let start_value = Value::Integer(10); - let length_value = Value::Integer(3); - let expected_val = Value::build_text(""); - assert_eq!( - Value::exec_substring(&str_value, &start_value, Some(&length_value)), - expected_val - ); - - let str_value = Value::build_text("limbo"); - let start_value = Value::Integer(3); - let length_value = Value::Null; - let expected_val = Value::build_text("mbo"); - assert_eq!( - Value::exec_substring(&str_value, &start_value, Some(&length_value)), - expected_val - ); - - let str_value = Value::build_text("limbo"); - let start_value = Value::Integer(10); - let length_value = Value::Null; - let expected_val = Value::build_text(""); - assert_eq!( - Value::exec_substring(&str_value, &start_value, Some(&length_value)), - expected_val - ); - } - - #[test] - fn test_exec_instr() { - let input = Value::build_text("limbo"); - let pattern = Value::build_text("im"); - let expected = Value::Integer(2); - assert_eq!(input.exec_instr(&pattern), expected); - - let input = Value::build_text("limbo"); - let pattern = Value::build_text("limbo"); - let expected = Value::Integer(1); - assert_eq!(input.exec_instr(&pattern), expected); - - let input = Value::build_text("limbo"); - let pattern = Value::build_text("o"); - let expected = Value::Integer(5); - assert_eq!(input.exec_instr(&pattern), expected); - - let input = Value::build_text("liiiiimbo"); - let pattern = Value::build_text("ii"); - let expected = Value::Integer(2); - assert_eq!(input.exec_instr(&pattern), expected); - - let input = Value::build_text("limbo"); - let pattern = Value::build_text("limboX"); - let expected = Value::Integer(0); - assert_eq!(input.exec_instr(&pattern), expected); - - let input = Value::build_text("limbo"); - let pattern = Value::build_text(""); - let expected = Value::Integer(1); - assert_eq!(input.exec_instr(&pattern), expected); - - let input = Value::build_text(""); - let pattern = Value::build_text("limbo"); - let expected = Value::Integer(0); - assert_eq!(input.exec_instr(&pattern), expected); - - let input = Value::build_text(""); - let pattern = Value::build_text(""); - let expected = Value::Integer(1); - assert_eq!(input.exec_instr(&pattern), expected); - - let input = Value::Null; - let pattern = Value::Null; - let expected = Value::Null; - assert_eq!(input.exec_instr(&pattern), expected); - - let input = Value::build_text("limbo"); - let pattern = Value::Null; - let expected = Value::Null; - assert_eq!(input.exec_instr(&pattern), expected); - - let input = Value::Null; - let pattern = Value::build_text("limbo"); - let expected = Value::Null; - assert_eq!(input.exec_instr(&pattern), expected); - - let input = Value::Integer(123); - let pattern = Value::Integer(2); - let expected = Value::Integer(2); - assert_eq!(input.exec_instr(&pattern), expected); - - let input = Value::Integer(123); - let pattern = Value::Integer(5); - let expected = Value::Integer(0); - assert_eq!(input.exec_instr(&pattern), expected); - - let input = Value::Float(12.34); - let pattern = Value::Float(2.3); - let expected = Value::Integer(2); - assert_eq!(input.exec_instr(&pattern), expected); - - let input = Value::Float(12.34); - let pattern = Value::Float(5.6); - let expected = Value::Integer(0); - assert_eq!(input.exec_instr(&pattern), expected); - - let input = Value::Float(12.34); - let pattern = Value::build_text("."); - let expected = Value::Integer(3); - assert_eq!(input.exec_instr(&pattern), expected); - - let input = Value::Blob(vec![1, 2, 3, 4, 5]); - let pattern = Value::Blob(vec![3, 4]); - let expected = Value::Integer(3); - assert_eq!(input.exec_instr(&pattern), expected); - - let input = Value::Blob(vec![1, 2, 3, 4, 5]); - let pattern = Value::Blob(vec![3, 2]); - let expected = Value::Integer(0); - assert_eq!(input.exec_instr(&pattern), expected); - - let input = Value::Blob(vec![0x61, 0x62, 0x63, 0x64, 0x65]); - let pattern = Value::build_text("cd"); - let expected = Value::Integer(3); - assert_eq!(input.exec_instr(&pattern), expected); - - let input = Value::build_text("abcde"); - let pattern = Value::Blob(vec![0x63, 0x64]); - let expected = Value::Integer(3); - assert_eq!(input.exec_instr(&pattern), expected); - } - - #[test] - fn test_exec_sign() { - let input = Value::Integer(42); - let expected = Some(Value::Integer(1)); - assert_eq!(input.exec_sign(), expected); - - let input = Value::Integer(-42); - let expected = Some(Value::Integer(-1)); - assert_eq!(input.exec_sign(), expected); - - let input = Value::Integer(0); - let expected = Some(Value::Integer(0)); - assert_eq!(input.exec_sign(), expected); - - let input = Value::Float(0.0); - let expected = Some(Value::Integer(0)); - assert_eq!(input.exec_sign(), expected); - - let input = Value::Float(0.1); - let expected = Some(Value::Integer(1)); - assert_eq!(input.exec_sign(), expected); - - let input = Value::Float(42.0); - let expected = Some(Value::Integer(1)); - assert_eq!(input.exec_sign(), expected); - - let input = Value::Float(-42.0); - let expected = Some(Value::Integer(-1)); - assert_eq!(input.exec_sign(), expected); - - let input = Value::build_text("abc"); - let expected = None; - assert_eq!(input.exec_sign(), expected); - - let input = Value::build_text("42"); - let expected = Some(Value::Integer(1)); - assert_eq!(input.exec_sign(), expected); - - let input = Value::build_text("-42"); - let expected = Some(Value::Integer(-1)); - assert_eq!(input.exec_sign(), expected); - - let input = Value::build_text("0"); - let expected = Some(Value::Integer(0)); - assert_eq!(input.exec_sign(), expected); - - let input = Value::Blob(b"abc".to_vec()); - let expected = None; - assert_eq!(input.exec_sign(), expected); - - let input = Value::Blob(b"42".to_vec()); - let expected = None; - assert_eq!(input.exec_sign(), expected); - - let input = Value::Blob(b"-42".to_vec()); - let expected = None; - assert_eq!(input.exec_sign(), expected); - - let input = Value::Blob(b"0".to_vec()); - let expected = None; - assert_eq!(input.exec_sign(), expected); - - let input = Value::Null; - let expected = None; - assert_eq!(input.exec_sign(), expected); - } - - #[test] - fn test_exec_zeroblob() { - let input = Value::Integer(0); - let expected = Value::Blob(vec![]); - assert_eq!(input.exec_zeroblob(), expected); - - let input = Value::Null; - let expected = Value::Blob(vec![]); - assert_eq!(input.exec_zeroblob(), expected); - - let input = Value::Integer(4); - let expected = Value::Blob(vec![0; 4]); - assert_eq!(input.exec_zeroblob(), expected); - - let input = Value::Integer(-1); - let expected = Value::Blob(vec![]); - assert_eq!(input.exec_zeroblob(), expected); - - let input = Value::build_text("5"); - let expected = Value::Blob(vec![0; 5]); - assert_eq!(input.exec_zeroblob(), expected); - - let input = Value::build_text("-5"); - let expected = Value::Blob(vec![]); - assert_eq!(input.exec_zeroblob(), expected); - - let input = Value::build_text("text"); - let expected = Value::Blob(vec![]); - assert_eq!(input.exec_zeroblob(), expected); - - let input = Value::Float(2.6); - let expected = Value::Blob(vec![0; 2]); - assert_eq!(input.exec_zeroblob(), expected); - - let input = Value::Blob(vec![1]); - let expected = Value::Blob(vec![]); - assert_eq!(input.exec_zeroblob(), expected); - } - #[test] fn test_execute_sqlite_version() { let version_integer = 3046001; let expected = "3.46.1"; assert_eq!(execute_turso_version(version_integer), expected); } - - #[test] - fn test_replace() { - let input_str = Value::build_text("bob"); - let pattern_str = Value::build_text("b"); - let replace_str = Value::build_text("a"); - let expected_str = Value::build_text("aoa"); - assert_eq!( - Value::exec_replace(&input_str, &pattern_str, &replace_str), - expected_str - ); - - let input_str = Value::build_text("bob"); - let pattern_str = Value::build_text("b"); - let replace_str = Value::build_text(""); - let expected_str = Value::build_text("o"); - assert_eq!( - Value::exec_replace(&input_str, &pattern_str, &replace_str), - expected_str - ); - - let input_str = Value::build_text("bob"); - let pattern_str = Value::build_text("b"); - let replace_str = Value::build_text("abc"); - let expected_str = Value::build_text("abcoabc"); - assert_eq!( - Value::exec_replace(&input_str, &pattern_str, &replace_str), - expected_str - ); - - let input_str = Value::build_text("bob"); - let pattern_str = Value::build_text("a"); - let replace_str = Value::build_text("b"); - let expected_str = Value::build_text("bob"); - assert_eq!( - Value::exec_replace(&input_str, &pattern_str, &replace_str), - expected_str - ); - - let input_str = Value::build_text("bob"); - let pattern_str = Value::build_text(""); - let replace_str = Value::build_text("a"); - let expected_str = Value::build_text("bob"); - assert_eq!( - Value::exec_replace(&input_str, &pattern_str, &replace_str), - expected_str - ); - - let input_str = Value::build_text("bob"); - let pattern_str = Value::Null; - let replace_str = Value::build_text("a"); - let expected_str = Value::Null; - assert_eq!( - Value::exec_replace(&input_str, &pattern_str, &replace_str), - expected_str - ); - - let input_str = Value::build_text("bo5"); - let pattern_str = Value::Integer(5); - let replace_str = Value::build_text("a"); - let expected_str = Value::build_text("boa"); - assert_eq!( - Value::exec_replace(&input_str, &pattern_str, &replace_str), - expected_str - ); - - let input_str = Value::build_text("bo5.0"); - let pattern_str = Value::Float(5.0); - let replace_str = Value::build_text("a"); - let expected_str = Value::build_text("boa"); - assert_eq!( - Value::exec_replace(&input_str, &pattern_str, &replace_str), - expected_str - ); - - let input_str = Value::build_text("bo5"); - let pattern_str = Value::Float(5.0); - let replace_str = Value::build_text("a"); - let expected_str = Value::build_text("bo5"); - assert_eq!( - Value::exec_replace(&input_str, &pattern_str, &replace_str), - expected_str - ); - - let input_str = Value::build_text("bo5.0"); - let pattern_str = Value::Float(5.0); - let replace_str = Value::Float(6.0); - let expected_str = Value::build_text("bo6.0"); - assert_eq!( - Value::exec_replace(&input_str, &pattern_str, &replace_str), - expected_str - ); - - // todo: change this test to use (0.1 + 0.2) instead of 0.3 when decimals are implemented. - let input_str = Value::build_text("tes3"); - let pattern_str = Value::Integer(3); - let replace_str = Value::Float(0.3); - let expected_str = Value::build_text("tes0.3"); - assert_eq!( - Value::exec_replace(&input_str, &pattern_str, &replace_str), - expected_str - ); - } - - #[test] - fn test_bitfield() { - let mut bitfield = Bitfield::<4>::new(); - for i in 0..256 { - bitfield.set(i); - assert!(bitfield.get(i)); - for j in 0..i { - assert!(bitfield.get(j)); - } - for j in i + 1..256 { - assert!(!bitfield.get(j)); - } - } - for i in 0..256 { - bitfield.unset(i); - assert!(!bitfield.get(i)); - } - } } diff --git a/core/vdbe/mod.rs b/core/vdbe/mod.rs index 49979b68f..e77ab7e40 100644 --- a/core/vdbe/mod.rs +++ b/core/vdbe/mod.rs @@ -24,6 +24,7 @@ pub mod insn; pub mod likeop; pub mod metrics; pub mod sorter; +pub mod value; use crate::{ error::LimboError, diff --git a/core/vdbe/value.rs b/core/vdbe/value.rs new file mode 100644 index 000000000..8f59ea9c3 --- /dev/null +++ b/core/vdbe/value.rs @@ -0,0 +1,2253 @@ +use std::collections::HashMap; + +use regex::{Regex, RegexBuilder}; + +use crate::{ + function::MathFunc, + numeric::{NullableInteger, Numeric}, + schema::{affinity, Affinity}, + LimboError, Register, Result, Value, +}; + +mod cmath { + extern "C" { + pub fn exp(x: f64) -> f64; + pub fn log(x: f64) -> f64; + pub fn log10(x: f64) -> f64; + pub fn log2(x: f64) -> f64; + pub fn pow(x: f64, y: f64) -> f64; + + pub fn sin(x: f64) -> f64; + pub fn sinh(x: f64) -> f64; + pub fn asin(x: f64) -> f64; + pub fn asinh(x: f64) -> f64; + + pub fn cos(x: f64) -> f64; + pub fn cosh(x: f64) -> f64; + pub fn acos(x: f64) -> f64; + pub fn acosh(x: f64) -> f64; + + pub fn tan(x: f64) -> f64; + pub fn tanh(x: f64) -> f64; + pub fn atan(x: f64) -> f64; + pub fn atanh(x: f64) -> f64; + pub fn atan2(x: f64, y: f64) -> f64; + } +} + +enum TrimType { + All, + Left, + Right, +} + +impl TrimType { + fn trim<'a>(&self, text: &'a str, pattern: &[char]) -> &'a str { + match self { + TrimType::All => text.trim_matches(pattern), + TrimType::Right => text.trim_end_matches(pattern), + TrimType::Left => text.trim_start_matches(pattern), + } + } +} + +impl Value { + pub fn exec_lower(&self) -> Option { + self.cast_text() + .map(|s| Value::build_text(s.to_ascii_lowercase())) + } + + pub fn exec_length(&self) -> Self { + match self { + Value::Text(t) => { + let s = t.as_str(); + let len_before_null = s.find('\0').map_or_else( + || s.chars().count(), + |null_pos| s[..null_pos].chars().count(), + ); + Value::Integer(len_before_null as i64) + } + Value::Integer(_) | Value::Float(_) => { + // For numbers, SQLite returns the length of the string representation + Value::Integer(self.to_string().chars().count() as i64) + } + Value::Blob(blob) => Value::Integer(blob.len() as i64), + _ => self.to_owned(), + } + } + + pub fn exec_octet_length(&self) -> Self { + match self { + Value::Text(_) | Value::Integer(_) | Value::Float(_) => { + Value::Integer(self.to_string().into_bytes().len() as i64) + } + Value::Blob(blob) => Value::Integer(blob.len() as i64), + _ => self.to_owned(), + } + } + + pub fn exec_upper(&self) -> Option { + self.cast_text() + .map(|s| Value::build_text(s.to_ascii_uppercase())) + } + + pub fn exec_sign(&self) -> Option { + let v = Numeric::from_value_strict(self).try_into_f64()?; + + Some(Value::Integer(if v > 0.0 { + 1 + } else if v < 0.0 { + -1 + } else { + 0 + })) + } + + /// Generates the Soundex code for a given word + pub fn exec_soundex(&self) -> Value { + let s = match self { + Value::Null => return Value::build_text("?000"), + Value::Text(s) => { + // return ?000 if non ASCII alphabet character is found + if !s.as_str().chars().all(|c| c.is_ascii_alphabetic()) { + return Value::build_text("?000"); + } + s.clone() + } + _ => return Value::build_text("?000"), // For unsupported types, return NULL + }; + + // Remove numbers and spaces + let word: String = s + .as_str() + .chars() + .filter(|c| !c.is_ascii_digit()) + .collect::() + .replace(" ", ""); + if word.is_empty() { + return Value::build_text("0000"); + } + + let soundex_code = |c| match c { + 'b' | 'f' | 'p' | 'v' => Some('1'), + 'c' | 'g' | 'j' | 'k' | 'q' | 's' | 'x' | 'z' => Some('2'), + 'd' | 't' => Some('3'), + 'l' => Some('4'), + 'm' | 'n' => Some('5'), + 'r' => Some('6'), + _ => None, + }; + + // Convert the word to lowercase for consistent lookups + let word = word.to_lowercase(); + let first_letter = word.chars().next().unwrap(); + + // Remove all occurrences of 'h' and 'w' except the first letter + let code: String = word + .chars() + .skip(1) + .filter(|&ch| ch != 'h' && ch != 'w') + .fold(first_letter.to_string(), |mut acc, ch| { + acc.push(ch); + acc + }); + + // Replace consonants with digits based on Soundex mapping + let tmp: String = code + .chars() + .map(|ch| match soundex_code(ch) { + Some(code) => code.to_string(), + None => ch.to_string(), + }) + .collect(); + + // Remove adjacent same digits + let tmp = tmp.chars().fold(String::new(), |mut acc, ch| { + if !acc.ends_with(ch) { + acc.push(ch); + } + acc + }); + + // Remove all occurrences of a, e, i, o, u, y except the first letter + let mut result = tmp + .chars() + .enumerate() + .filter(|(i, ch)| *i == 0 || !matches!(ch, 'a' | 'e' | 'i' | 'o' | 'u' | 'y')) + .map(|(_, ch)| ch) + .collect::(); + + // If the first symbol is a digit, replace it with the saved first letter + if let Some(first_digit) = result.chars().next() { + if first_digit.is_ascii_digit() { + result.replace_range(0..1, &first_letter.to_string()); + } + } + + // Append zeros if the result contains less than 4 characters + while result.len() < 4 { + result.push('0'); + } + + // Retain the first 4 characters and convert to uppercase + result.truncate(4); + Value::build_text(result.to_uppercase()) + } + + pub fn exec_abs(&self) -> Result { + Ok(match self { + Value::Null => Value::Null, + Value::Integer(v) => { + Value::Integer(v.checked_abs().ok_or(LimboError::IntegerOverflow)?) + } + Value::Float(non_nan) => Value::Float(non_nan.abs()), + _ => { + let s = match self { + Value::Text(text) => text.to_string(), + Value::Blob(blob) => String::from_utf8_lossy(blob).to_string(), + _ => unreachable!(), + }; + + crate::numeric::str_to_f64(s) + .map(|v| Value::Float(f64::from(v).abs())) + .unwrap_or(Value::Float(0.0)) + } + }) + } + + pub fn exec_random(generate_random_number: F) -> Self + where + F: Fn() -> i64, + { + Value::Integer(generate_random_number()) + } + + pub fn exec_randomblob(&self, fill_bytes: F) -> Value + where + F: Fn(&mut [u8]), + { + let length = match self { + Value::Integer(i) => *i, + Value::Float(f) => *f as i64, + Value::Text(t) => t.as_str().parse().unwrap_or(1), + _ => 1, + } + .max(1) as usize; + + let mut blob: Vec = vec![0; length]; + fill_bytes(&mut blob); + Value::Blob(blob) + } + + pub fn exec_quote(&self) -> Self { + match self { + Value::Null => Value::build_text("NULL"), + Value::Integer(_) | Value::Float(_) => self.to_owned(), + Value::Blob(_) => todo!(), + Value::Text(s) => { + let mut quoted = String::with_capacity(s.as_str().len() + 2); + quoted.push('\''); + for c in s.as_str().chars() { + if c == '\0' { + break; + } else if c == '\'' { + quoted.push('\''); + quoted.push(c); + } else { + quoted.push(c); + } + } + quoted.push('\''); + Value::build_text(quoted) + } + } + } + + pub fn exec_nullif(&self, second_value: &Self) -> Self { + if self != second_value { + self.clone() + } else { + Value::Null + } + } + + pub fn exec_substring( + value: &Value, + start_value: &Value, + length_value: Option<&Value>, + ) -> Value { + /// Function is stabilized but not released for version 1.88 \ + /// https://doc.rust-lang.org/src/core/str/mod.rs.html#453 + const fn ceil_char_boundary(s: &str, index: usize) -> usize { + const fn is_utf8_char_boundary(c: u8) -> bool { + // This is bit magic equivalent to: b < 128 || b >= 192 + (c as i8) >= -0x40 + } + + if index >= s.len() { + s.len() + } else { + let mut i = index; + while i < s.len() { + if is_utf8_char_boundary(s.as_bytes()[i]) { + break; + } + i += 1; + } + + // The character boundary will be within four bytes of the index + debug_assert!(i <= index + 3); + + i + } + } + + fn calculate_postions( + start: i64, + bytes_len: usize, + length_value: Option<&Value>, + ) -> (usize, usize) { + let bytes_len = bytes_len as i64; + + // The left-most character of X is number 1. + // If Y is negative then the first character of the substring is found by counting from the right rather than the left. + let first_position = if start < 0 { + bytes_len.saturating_sub((start).abs()) + } else { + start - 1 + }; + // If Z is negative then the abs(Z) characters preceding the Y-th character are returned. + let last_position = match length_value { + Some(Value::Integer(length)) => first_position + *length, + _ => bytes_len, + }; + + let (start, end) = if first_position <= last_position { + (first_position, last_position) + } else { + (last_position, first_position) + }; + + ( + start.clamp(-0, bytes_len) as usize, + end.clamp(0, bytes_len) as usize, + ) + } + + let start_value = start_value.exec_cast("INT"); + let length_value = length_value.map(|value| value.exec_cast("INT")); + + match (value, start_value) { + (Value::Blob(b), Value::Integer(start)) => { + let (start, end) = calculate_postions(start, b.len(), length_value.as_ref()); + Value::from_blob(b[start..end].to_vec()) + } + (value, Value::Integer(start)) => { + if let Some(text) = value.cast_text() { + let (mut start, mut end) = + calculate_postions(start, text.len(), length_value.as_ref()); + + // https://github.com/sqlite/sqlite/blob/a248d84f/src/func.c#L417 + let s = text.as_str(); + let mut start_byte_idx = 0; + end -= start; + while start > 0 { + start_byte_idx = ceil_char_boundary(s, start_byte_idx + 1); + start -= 1; + } + let mut end_byte_idx = start_byte_idx; + while end > 0 { + end_byte_idx = ceil_char_boundary(s, end_byte_idx + 1); + end -= 1; + } + Value::build_text(&s[start_byte_idx..end_byte_idx]) + } else { + Value::Null + } + } + _ => Value::Null, + } + } + + pub fn exec_instr(&self, pattern: &Value) -> Value { + if self == &Value::Null || pattern == &Value::Null { + return Value::Null; + } + + if let (Value::Blob(reg), Value::Blob(pattern)) = (self, pattern) { + let result = reg + .windows(pattern.len()) + .position(|window| window == *pattern) + .map_or(0, |i| i + 1); + return Value::Integer(result as i64); + } + + let reg_str; + let reg = match self { + Value::Text(s) => s.as_str(), + _ => { + reg_str = self.to_string(); + reg_str.as_str() + } + }; + + let pattern_str; + let pattern = match pattern { + Value::Text(s) => s.as_str(), + _ => { + pattern_str = pattern.to_string(); + pattern_str.as_str() + } + }; + + match reg.find(pattern) { + Some(position) => Value::Integer(position as i64 + 1), + None => Value::Integer(0), + } + } + + pub fn exec_typeof(&self) -> Value { + match self { + Value::Null => Value::build_text("null"), + Value::Integer(_) => Value::build_text("integer"), + Value::Float(_) => Value::build_text("real"), + Value::Text(_) => Value::build_text("text"), + Value::Blob(_) => Value::build_text("blob"), + } + } + + pub fn exec_hex(&self) -> Value { + match self { + Value::Text(_) | Value::Integer(_) | Value::Float(_) => { + let text = self.to_string(); + Value::build_text(hex::encode_upper(text)) + } + Value::Blob(blob_bytes) => Value::build_text(hex::encode_upper(blob_bytes)), + Value::Null => Value::build_text(""), + } + } + + pub fn exec_unhex(&self, ignored_chars: Option<&Value>) -> Value { + match self { + Value::Null => Value::Null, + _ => match ignored_chars { + None => match self + .cast_text() + .map(|s| hex::decode(&s[0..s.find('\0').unwrap_or(s.len())])) + { + Some(Ok(bytes)) => Value::Blob(bytes), + _ => Value::Null, + }, + Some(ignore) => match ignore { + Value::Text(_) => { + let pat = ignore.to_string(); + let trimmed = self + .to_string() + .trim_start_matches(|x| pat.contains(x)) + .trim_end_matches(|x| pat.contains(x)) + .to_string(); + match hex::decode(trimmed) { + Ok(bytes) => Value::Blob(bytes), + _ => Value::Null, + } + } + _ => Value::Null, + }, + }, + } + } + + pub fn exec_unicode(&self) -> Value { + match self { + Value::Text(_) | Value::Integer(_) | Value::Float(_) | Value::Blob(_) => { + let text = self.to_string(); + if let Some(first_char) = text.chars().next() { + Value::Integer(first_char as u32 as i64) + } else { + Value::Null + } + } + _ => Value::Null, + } + } + + pub fn exec_round(&self, precision: Option<&Value>) -> Value { + let Some(f) = Numeric::from(self).try_into_f64() else { + return Value::Null; + }; + + let precision = match precision.map(|v| Numeric::from(v).try_into_f64()) { + None => 0.0, + Some(Some(v)) => v, + Some(None) => return Value::Null, + }; + + if !(-4503599627370496.0..=4503599627370496.0).contains(&f) { + return Value::Float(f); + } + + let precision = if precision < 1.0 { 0.0 } else { precision }; + let precision = precision.clamp(0.0, 30.0) as usize; + + if precision == 0 { + return Value::Float(((f + if f < 0.0 { -0.5 } else { 0.5 }) as i64) as f64); + } + + let f: f64 = crate::numeric::str_to_f64(format!("{f:.precision$}")) + .unwrap() + .into(); + + Value::Float(f) + } + + fn _exec_trim(&self, pattern: Option<&Value>, trim_type: TrimType) -> Value { + match (self, pattern) { + (Value::Text(_) | Value::Integer(_) | Value::Float(_), Some(pattern)) => { + let pattern_chars: Vec = pattern.to_string().chars().collect(); + let text = self.to_string(); + Value::build_text(trim_type.trim(&text, &pattern_chars)) + } + (Value::Text(t), None) => Value::build_text(trim_type.trim(t.as_str(), &[' '])), + (reg, _) => reg.to_owned(), + } + } + + // Implements TRIM pattern matching. + pub fn exec_trim(&self, pattern: Option<&Value>) -> Value { + self._exec_trim(pattern, TrimType::All) + } + // Implements RTRIM pattern matching. + pub fn exec_rtrim(&self, pattern: Option<&Value>) -> Value { + self._exec_trim(pattern, TrimType::Right) + } + + // Implements LTRIM pattern matching. + pub fn exec_ltrim(&self, pattern: Option<&Value>) -> Value { + self._exec_trim(pattern, TrimType::Left) + } + + pub fn exec_zeroblob(&self) -> Value { + let length: i64 = match self { + Value::Integer(i) => *i, + Value::Float(f) => *f as i64, + Value::Text(s) => s.as_str().parse().unwrap_or(0), + _ => 0, + }; + Value::Blob(vec![0; length.max(0) as usize]) + } + + // exec_if returns whether you should jump + pub fn exec_if(&self, jump_if_null: bool, not: bool) -> bool { + Numeric::from(self) + .try_into_bool() + .map(|jump| if not { !jump } else { jump }) + .unwrap_or(jump_if_null) + } + + pub fn exec_cast(&self, datatype: &str) -> Value { + if matches!(self, Value::Null) { + return Value::Null; + } + match affinity(datatype) { + // NONE Casting a value to a type-name with no affinity causes the value to be converted into a BLOB. Casting to a BLOB consists of first casting the value to TEXT in the encoding of the database connection, then interpreting the resulting byte sequence as a BLOB instead of as TEXT. + // Historically called NONE, but it's the same as BLOB + Affinity::Blob => { + // Convert to TEXT first, then interpret as BLOB + // TODO: handle encoding + let text = self.to_string(); + Value::Blob(text.into_bytes()) + } + // TEXT To cast a BLOB value to TEXT, the sequence of bytes that make up the BLOB is interpreted as text encoded using the database encoding. + // Casting an INTEGER or REAL value into TEXT renders the value as if via sqlite3_snprintf() except that the resulting TEXT uses the encoding of the database connection. + Affinity::Text => { + // Convert everything to text representation + // TODO: handle encoding and whatever sqlite3_snprintf does + Value::build_text(self.to_string()) + } + Affinity::Real => match self { + Value::Blob(b) => { + let text = String::from_utf8_lossy(b); + Value::Float( + crate::numeric::str_to_f64(&text) + .map(f64::from) + .unwrap_or(0.0), + ) + } + Value::Text(t) => { + Value::Float(crate::numeric::str_to_f64(t).map(f64::from).unwrap_or(0.0)) + } + Value::Integer(i) => Value::Float(*i as f64), + Value::Float(f) => Value::Float(*f), + _ => Value::Float(0.0), + }, + Affinity::Integer => match self { + Value::Blob(b) => { + // Convert BLOB to TEXT first + let text = String::from_utf8_lossy(b); + Value::Integer(crate::numeric::str_to_i64(&text).unwrap_or(0)) + } + Value::Text(t) => Value::Integer(crate::numeric::str_to_i64(t).unwrap_or(0)), + Value::Integer(i) => Value::Integer(*i), + // A cast of a REAL value into an INTEGER results in the integer between the REAL value and zero + // that is closest to the REAL value. If a REAL is greater than the greatest possible signed integer (+9223372036854775807) + // then the result is the greatest possible signed integer and if the REAL is less than the least possible signed integer (-9223372036854775808) + // then the result is the least possible signed integer. + Value::Float(f) => { + let i = f.trunc() as i128; + if i > i64::MAX as i128 { + Value::Integer(i64::MAX) + } else if i < i64::MIN as i128 { + Value::Integer(i64::MIN) + } else { + Value::Integer(i as i64) + } + } + _ => Value::Integer(0), + }, + Affinity::Numeric => match self { + Value::Null => Value::Null, + Value::Integer(v) => Value::Integer(*v), + Value::Float(v) => Self::Float(*v), + _ => { + let s = match self { + Value::Text(text) => text.to_string(), + Value::Blob(blob) => String::from_utf8_lossy(blob.as_slice()).to_string(), + _ => unreachable!(), + }; + + match crate::numeric::str_to_f64(&s) { + Some(parsed) => { + let Some(int) = crate::numeric::str_to_i64(&s) else { + return Value::Integer(0); + }; + + if f64::from(parsed) == int as f64 { + return Value::Integer(int); + } + + Value::Float(parsed.into()) + } + None => Value::Integer(0), + } + } + }, + } + } + + pub fn exec_replace(source: &Value, pattern: &Value, replacement: &Value) -> Value { + // The replace(X,Y,Z) function returns a string formed by substituting string Z for every occurrence of + // string Y in string X. The BINARY collating sequence is used for comparisons. If Y is an empty string + // then return X unchanged. If Z is not initially a string, it is cast to a UTF-8 string prior to processing. + + // If any of the arguments is NULL, the result is NULL. + if matches!(source, Value::Null) + || matches!(pattern, Value::Null) + || matches!(replacement, Value::Null) + { + return Value::Null; + } + + let source = source.exec_cast("TEXT"); + let pattern = pattern.exec_cast("TEXT"); + let replacement = replacement.exec_cast("TEXT"); + + // If any of the casts failed, panic as text casting is not expected to fail. + match (&source, &pattern, &replacement) { + (Value::Text(source), Value::Text(pattern), Value::Text(replacement)) => { + if pattern.as_str().is_empty() { + return Value::Text(source.clone()); + } + + let result = source + .as_str() + .replace(pattern.as_str(), replacement.as_str()); + Value::build_text(result) + } + _ => unreachable!("text cast should never fail"), + } + } + + pub fn exec_math_unary(&self, function: &MathFunc) -> Value { + let v = Numeric::from_value_strict(self); + + // In case of some functions and integer input, return the input as is + if let Numeric::Integer(i) = v { + if matches! { function, MathFunc::Ceil | MathFunc::Ceiling | MathFunc::Floor | MathFunc::Trunc } + { + return Value::Integer(i); + } + } + + let Some(f) = v.try_into_f64() else { + return Value::Null; + }; + + if matches! { function, MathFunc::Ln | MathFunc::Log10 | MathFunc::Log2 } && f <= 0.0 { + return Value::Null; + } + + let result = match function { + MathFunc::Acos => unsafe { cmath::acos(f) }, + MathFunc::Acosh => unsafe { cmath::acosh(f) }, + MathFunc::Asin => unsafe { cmath::asin(f) }, + MathFunc::Asinh => unsafe { cmath::asinh(f) }, + MathFunc::Atan => unsafe { cmath::atan(f) }, + MathFunc::Atanh => unsafe { cmath::atanh(f) }, + MathFunc::Ceil | MathFunc::Ceiling => libm::ceil(f), + MathFunc::Cos => unsafe { cmath::cos(f) }, + MathFunc::Cosh => unsafe { cmath::cosh(f) }, + MathFunc::Degrees => f.to_degrees(), + MathFunc::Exp => unsafe { cmath::exp(f) }, + MathFunc::Floor => libm::floor(f), + MathFunc::Ln => unsafe { cmath::log(f) }, + MathFunc::Log10 => unsafe { cmath::log10(f) }, + MathFunc::Log2 => unsafe { cmath::log2(f) }, + MathFunc::Radians => f.to_radians(), + MathFunc::Sin => unsafe { cmath::sin(f) }, + MathFunc::Sinh => unsafe { cmath::sinh(f) }, + MathFunc::Sqrt => libm::sqrt(f), + MathFunc::Tan => unsafe { cmath::tan(f) }, + MathFunc::Tanh => unsafe { cmath::tanh(f) }, + MathFunc::Trunc => libm::trunc(f), + _ => unreachable!("Unexpected mathematical unary function {:?}", function), + }; + + if result.is_nan() { + Value::Null + } else { + Value::Float(result) + } + } + + pub fn exec_math_binary(&self, rhs: &Value, function: &MathFunc) -> Value { + let Some(lhs) = Numeric::from_value_strict(self).try_into_f64() else { + return Value::Null; + }; + + let Some(rhs) = Numeric::from_value_strict(rhs).try_into_f64() else { + return Value::Null; + }; + + let result = match function { + MathFunc::Atan2 => unsafe { cmath::atan2(lhs, rhs) }, + MathFunc::Mod => libm::fmod(lhs, rhs), + MathFunc::Pow | MathFunc::Power => unsafe { cmath::pow(lhs, rhs) }, + _ => unreachable!("Unexpected mathematical binary function {:?}", function), + }; + + if result.is_nan() { + Value::Null + } else { + Value::Float(result) + } + } + + pub fn exec_math_log(&self, base: Option<&Value>) -> Value { + let Some(f) = Numeric::from_value_strict(self).try_into_f64() else { + return Value::Null; + }; + + let base = match base.map(|value| Numeric::from_value_strict(value).try_into_f64()) { + Some(Some(f)) => f, + Some(None) => return Value::Null, + None => 10.0, + }; + + if f <= 0.0 || base <= 0.0 || base == 1.0 { + return Value::Null; + } + + if base == 2.0 { + return Value::Float(libm::log2(f)); + } else if base == 10.0 { + return Value::Float(libm::log10(f)); + }; + + let log_x = libm::log(f); + let log_base = libm::log(base); + + if log_base <= 0.0 { + return Value::Null; + } + + let result = log_x / log_base; + Value::Float(result) + } + + pub fn exec_add(&self, rhs: &Value) -> Value { + (Numeric::from(self) + Numeric::from(rhs)).into() + } + + pub fn exec_subtract(&self, rhs: &Value) -> Value { + (Numeric::from(self) - Numeric::from(rhs)).into() + } + + pub fn exec_multiply(&self, rhs: &Value) -> Value { + (Numeric::from(self) * Numeric::from(rhs)).into() + } + + pub fn exec_divide(&self, rhs: &Value) -> Value { + (Numeric::from(self) / Numeric::from(rhs)).into() + } + + pub fn exec_bit_and(&self, rhs: &Value) -> Value { + (NullableInteger::from(self) & NullableInteger::from(rhs)).into() + } + + pub fn exec_bit_or(&self, rhs: &Value) -> Value { + (NullableInteger::from(self) | NullableInteger::from(rhs)).into() + } + + pub fn exec_remainder(&self, rhs: &Value) -> Value { + let convert_to_float = matches!(Numeric::from(self), Numeric::Float(_)) + || matches!(Numeric::from(rhs), Numeric::Float(_)); + + match NullableInteger::from(self) % NullableInteger::from(rhs) { + NullableInteger::Null => Value::Null, + NullableInteger::Integer(v) => { + if convert_to_float { + Value::Float(v as f64) + } else { + Value::Integer(v) + } + } + } + } + + pub fn exec_bit_not(&self) -> Value { + (!NullableInteger::from(self)).into() + } + + pub fn exec_shift_left(&self, rhs: &Value) -> Value { + (NullableInteger::from(self) << NullableInteger::from(rhs)).into() + } + + pub fn exec_shift_right(&self, rhs: &Value) -> Value { + (NullableInteger::from(self) >> NullableInteger::from(rhs)).into() + } + + pub fn exec_boolean_not(&self) -> Value { + match Numeric::from(self).try_into_bool() { + None => Value::Null, + Some(v) => Value::Integer(!v as i64), + } + } + + pub fn exec_concat(&self, rhs: &Value) -> Value { + if let (Value::Blob(lhs), Value::Blob(rhs)) = (self, rhs) { + return Value::build_text(String::from_utf8_lossy( + &[lhs.as_slice(), rhs.as_slice()].concat(), + )); + } + + let Some(lhs) = self.cast_text() else { + return Value::Null; + }; + + let Some(rhs) = rhs.cast_text() else { + return Value::Null; + }; + + Value::build_text(lhs + &rhs) + } + + pub fn exec_and(&self, rhs: &Value) -> Value { + match ( + Numeric::from(self).try_into_bool(), + Numeric::from(rhs).try_into_bool(), + ) { + (Some(false), _) | (_, Some(false)) => Value::Integer(0), + (None, _) | (_, None) => Value::Null, + _ => Value::Integer(1), + } + } + + pub fn exec_or(&self, rhs: &Value) -> Value { + match ( + Numeric::from(self).try_into_bool(), + Numeric::from(rhs).try_into_bool(), + ) { + (Some(true), _) | (_, Some(true)) => Value::Integer(1), + (None, _) | (_, None) => Value::Null, + _ => Value::Integer(0), + } + } + + // Implements LIKE pattern matching. Caches the constructed regex if a cache is provided + pub fn exec_like( + regex_cache: Option<&mut HashMap>, + pattern: &str, + text: &str, + ) -> bool { + if let Some(cache) = regex_cache { + match cache.get(pattern) { + Some(re) => re.is_match(text), + None => { + let re = construct_like_regex(pattern); + let res = re.is_match(text); + cache.insert(pattern.to_string(), re); + res + } + } + } else { + let re = construct_like_regex(pattern); + re.is_match(text) + } + } + + pub fn exec_min<'a, T: Iterator>(regs: T) -> Value { + regs.min().map(|v| v.to_owned()).unwrap_or(Value::Null) + } + + pub fn exec_max<'a, T: Iterator>(regs: T) -> Value { + regs.max().map(|v| v.to_owned()).unwrap_or(Value::Null) + } + + pub fn exec_comparison() {} +} + +pub fn exec_concat_strings(registers: &[Register]) -> Value { + let mut result = String::new(); + for reg in registers { + match reg.get_value() { + Value::Null => continue, + Value::Blob(_) => todo!("TODO concat blob"), + v => result.push_str(&format!("{v}")), + } + } + Value::build_text(result) +} + +pub fn exec_concat_ws(registers: &[Register]) -> Value { + if registers.is_empty() { + return Value::Null; + } + + let separator = match registers[0].get_value() { + Value::Null | Value::Blob(_) => return Value::Null, + v => format!("{v}"), + }; + + let parts = registers[1..] + .iter() + .filter_map(|reg| match reg.get_value() { + Value::Text(_) | Value::Integer(_) | Value::Float(_) => { + Some(format!("{}", reg.get_value())) + } + _ => None, + }); + + let result = parts.collect::>().join(&separator); + Value::build_text(result) +} + +pub fn exec_char(values: &[Register]) -> Value { + let result: String = values + .iter() + .filter_map(|x| { + if let Value::Integer(i) = x.get_value() { + Some(*i as u8 as char) + } else { + None + } + }) + .collect(); + Value::build_text(result) +} + +pub fn construct_like_regex(pattern: &str) -> Regex { + let mut regex_pattern = String::with_capacity(pattern.len() * 2); + + regex_pattern.push('^'); + + for c in pattern.chars() { + match c { + '\\' => regex_pattern.push_str("\\\\"), + '%' => regex_pattern.push_str(".*"), + '_' => regex_pattern.push('.'), + ch => { + if regex_syntax::is_meta_character(c) { + regex_pattern.push('\\'); + } + regex_pattern.push(ch); + } + } + } + + regex_pattern.push('$'); + + RegexBuilder::new(®ex_pattern) + .case_insensitive(true) + .dot_matches_new_line(true) + .build() + .unwrap() +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::types::Value; + use crate::vdbe::{Bitfield, Register}; + + use rand::{Rng, RngCore}; + use std::collections::HashMap; + + #[test] + fn test_exec_add() { + let inputs = vec![ + (Value::Integer(3), Value::Integer(1)), + (Value::Float(3.0), Value::Float(1.0)), + (Value::Float(3.0), Value::Integer(1)), + (Value::Integer(3), Value::Float(1.0)), + (Value::Null, Value::Null), + (Value::Null, Value::Integer(1)), + (Value::Null, Value::Float(1.0)), + (Value::Null, Value::Text("2".into())), + (Value::Integer(1), Value::Null), + (Value::Float(1.0), Value::Null), + (Value::Text("1".into()), Value::Null), + (Value::Text("1".into()), Value::Text("3".into())), + (Value::Text("1.0".into()), Value::Text("3.0".into())), + (Value::Text("1.0".into()), Value::Float(3.0)), + (Value::Text("1.0".into()), Value::Integer(3)), + (Value::Float(1.0), Value::Text("3.0".into())), + (Value::Integer(1), Value::Text("3".into())), + ]; + + let outputs = [ + Value::Integer(4), + Value::Float(4.0), + Value::Float(4.0), + Value::Float(4.0), + Value::Null, + Value::Null, + Value::Null, + Value::Null, + Value::Null, + Value::Null, + Value::Null, + Value::Integer(4), + Value::Float(4.0), + Value::Float(4.0), + Value::Float(4.0), + Value::Float(4.0), + Value::Float(4.0), + ]; + + assert_eq!( + inputs.len(), + outputs.len(), + "Inputs and Outputs should have same size" + ); + for (i, (lhs, rhs)) in inputs.iter().enumerate() { + assert_eq!( + lhs.exec_add(rhs), + outputs[i], + "Wrong ADD for lhs: {lhs}, rhs: {rhs}" + ); + } + } + + #[test] + fn test_exec_subtract() { + let inputs = vec![ + (Value::Integer(3), Value::Integer(1)), + (Value::Float(3.0), Value::Float(1.0)), + (Value::Float(3.0), Value::Integer(1)), + (Value::Integer(3), Value::Float(1.0)), + (Value::Null, Value::Null), + (Value::Null, Value::Integer(1)), + (Value::Null, Value::Float(1.0)), + (Value::Null, Value::Text("1".into())), + (Value::Integer(1), Value::Null), + (Value::Float(1.0), Value::Null), + (Value::Text("4".into()), Value::Null), + (Value::Text("1".into()), Value::Text("3".into())), + (Value::Text("1.0".into()), Value::Text("3.0".into())), + (Value::Text("1.0".into()), Value::Float(3.0)), + (Value::Text("1.0".into()), Value::Integer(3)), + (Value::Float(1.0), Value::Text("3.0".into())), + (Value::Integer(1), Value::Text("3".into())), + ]; + + let outputs = [ + Value::Integer(2), + Value::Float(2.0), + Value::Float(2.0), + Value::Float(2.0), + Value::Null, + Value::Null, + Value::Null, + Value::Null, + Value::Null, + Value::Null, + Value::Null, + Value::Integer(-2), + Value::Float(-2.0), + Value::Float(-2.0), + Value::Float(-2.0), + Value::Float(-2.0), + Value::Float(-2.0), + ]; + + assert_eq!( + inputs.len(), + outputs.len(), + "Inputs and Outputs should have same size" + ); + for (i, (lhs, rhs)) in inputs.iter().enumerate() { + assert_eq!( + lhs.exec_subtract(rhs), + outputs[i], + "Wrong subtract for lhs: {lhs}, rhs: {rhs}" + ); + } + } + + #[test] + fn test_exec_multiply() { + let inputs = vec![ + (Value::Integer(3), Value::Integer(2)), + (Value::Float(3.0), Value::Float(2.0)), + (Value::Float(3.0), Value::Integer(2)), + (Value::Integer(3), Value::Float(2.0)), + (Value::Null, Value::Null), + (Value::Null, Value::Integer(1)), + (Value::Null, Value::Float(1.0)), + (Value::Null, Value::Text("1".into())), + (Value::Integer(1), Value::Null), + (Value::Float(1.0), Value::Null), + (Value::Text("4".into()), Value::Null), + (Value::Text("2".into()), Value::Text("3".into())), + (Value::Text("2.0".into()), Value::Text("3.0".into())), + (Value::Text("2.0".into()), Value::Float(3.0)), + (Value::Text("2.0".into()), Value::Integer(3)), + (Value::Float(2.0), Value::Text("3.0".into())), + (Value::Integer(2), Value::Text("3.0".into())), + ]; + + let outputs = [ + Value::Integer(6), + Value::Float(6.0), + Value::Float(6.0), + Value::Float(6.0), + Value::Null, + Value::Null, + Value::Null, + Value::Null, + Value::Null, + Value::Null, + Value::Null, + Value::Integer(6), + Value::Float(6.0), + Value::Float(6.0), + Value::Float(6.0), + Value::Float(6.0), + Value::Float(6.0), + ]; + + assert_eq!( + inputs.len(), + outputs.len(), + "Inputs and Outputs should have same size" + ); + for (i, (lhs, rhs)) in inputs.iter().enumerate() { + assert_eq!( + lhs.exec_multiply(rhs), + outputs[i], + "Wrong multiply for lhs: {lhs}, rhs: {rhs}" + ); + } + } + + #[test] + fn test_exec_divide() { + let inputs = vec![ + (Value::Integer(1), Value::Integer(0)), + (Value::Float(1.0), Value::Float(0.0)), + (Value::Integer(i64::MIN), Value::Integer(-1)), + (Value::Float(6.0), Value::Float(2.0)), + (Value::Float(6.0), Value::Integer(2)), + (Value::Integer(6), Value::Integer(2)), + (Value::Null, Value::Integer(2)), + (Value::Integer(2), Value::Null), + (Value::Null, Value::Null), + (Value::Text("6".into()), Value::Text("2".into())), + (Value::Text("6".into()), Value::Integer(2)), + ]; + + let outputs = [ + Value::Null, + Value::Null, + Value::Float(9.223372036854776e18), + Value::Float(3.0), + Value::Float(3.0), + Value::Float(3.0), + Value::Null, + Value::Null, + Value::Null, + Value::Float(3.0), + Value::Float(3.0), + ]; + + assert_eq!( + inputs.len(), + outputs.len(), + "Inputs and Outputs should have same size" + ); + for (i, (lhs, rhs)) in inputs.iter().enumerate() { + assert_eq!( + lhs.exec_divide(rhs), + outputs[i], + "Wrong divide for lhs: {lhs}, rhs: {rhs}" + ); + } + } + + #[test] + fn test_exec_remainder() { + let inputs = vec![ + (Value::Null, Value::Null), + (Value::Null, Value::Float(1.0)), + (Value::Null, Value::Integer(1)), + (Value::Null, Value::Text("1".into())), + (Value::Float(1.0), Value::Null), + (Value::Integer(1), Value::Null), + (Value::Integer(12), Value::Integer(0)), + (Value::Float(12.0), Value::Float(0.0)), + (Value::Float(12.0), Value::Integer(0)), + (Value::Integer(12), Value::Float(0.0)), + (Value::Integer(i64::MIN), Value::Integer(-1)), + (Value::Integer(12), Value::Integer(3)), + (Value::Float(12.0), Value::Float(3.0)), + (Value::Float(12.0), Value::Integer(3)), + (Value::Integer(12), Value::Float(3.0)), + (Value::Integer(12), Value::Integer(-3)), + (Value::Float(12.0), Value::Float(-3.0)), + (Value::Float(12.0), Value::Integer(-3)), + (Value::Integer(12), Value::Float(-3.0)), + (Value::Text("12.0".into()), Value::Text("3.0".into())), + (Value::Text("12.0".into()), Value::Float(3.0)), + (Value::Float(12.0), Value::Text("3.0".into())), + ]; + let outputs = vec![ + Value::Null, + Value::Null, + Value::Null, + Value::Null, + Value::Null, + Value::Null, + Value::Null, + Value::Null, + Value::Null, + Value::Null, + Value::Float(0.0), + Value::Integer(0), + Value::Float(0.0), + Value::Float(0.0), + Value::Float(0.0), + Value::Integer(0), + Value::Float(0.0), + Value::Float(0.0), + Value::Float(0.0), + Value::Float(0.0), + Value::Float(0.0), + Value::Float(0.0), + ]; + + assert_eq!( + inputs.len(), + outputs.len(), + "Inputs and Outputs should have same size" + ); + + for (i, (lhs, rhs)) in inputs.iter().enumerate() { + assert_eq!( + lhs.exec_remainder(rhs), + outputs[i], + "Wrong remainder for lhs: {lhs}, rhs: {rhs}" + ); + } + } + + #[test] + fn test_exec_and() { + let inputs = vec![ + (Value::Integer(0), Value::Null), + (Value::Null, Value::Integer(1)), + (Value::Null, Value::Null), + (Value::Float(0.0), Value::Null), + (Value::Integer(1), Value::Float(2.2)), + (Value::Integer(0), Value::Text("string".into())), + (Value::Integer(0), Value::Text("1".into())), + (Value::Integer(1), Value::Text("1".into())), + ]; + let outputs = [ + Value::Integer(0), + Value::Null, + Value::Null, + Value::Integer(0), + Value::Integer(1), + Value::Integer(0), + Value::Integer(0), + Value::Integer(1), + ]; + + assert_eq!( + inputs.len(), + outputs.len(), + "Inputs and Outputs should have same size" + ); + for (i, (lhs, rhs)) in inputs.iter().enumerate() { + assert_eq!( + lhs.exec_and(rhs), + outputs[i], + "Wrong AND for lhs: {lhs}, rhs: {rhs}" + ); + } + } + + #[test] + fn test_exec_or() { + let inputs = vec![ + (Value::Integer(0), Value::Null), + (Value::Null, Value::Integer(1)), + (Value::Null, Value::Null), + (Value::Float(0.0), Value::Null), + (Value::Integer(1), Value::Float(2.2)), + (Value::Float(0.0), Value::Integer(0)), + (Value::Integer(0), Value::Text("string".into())), + (Value::Integer(0), Value::Text("1".into())), + (Value::Integer(0), Value::Text("".into())), + ]; + let outputs = [ + Value::Null, + Value::Integer(1), + Value::Null, + Value::Null, + Value::Integer(1), + Value::Integer(0), + Value::Integer(0), + Value::Integer(1), + Value::Integer(0), + ]; + + assert_eq!( + inputs.len(), + outputs.len(), + "Inputs and Outputs should have same size" + ); + for (i, (lhs, rhs)) in inputs.iter().enumerate() { + assert_eq!( + lhs.exec_or(rhs), + outputs[i], + "Wrong OR for lhs: {lhs}, rhs: {rhs}" + ); + } + } + + #[test] + fn test_length() { + let input_str = Value::build_text("bob"); + let expected_len = Value::Integer(3); + assert_eq!(input_str.exec_length(), expected_len); + + let input_integer = Value::Integer(123); + let expected_len = Value::Integer(3); + assert_eq!(input_integer.exec_length(), expected_len); + + let input_float = Value::Float(123.456); + let expected_len = Value::Integer(7); + assert_eq!(input_float.exec_length(), expected_len); + + let expected_blob = Value::Blob("example".as_bytes().to_vec()); + let expected_len = Value::Integer(7); + assert_eq!(expected_blob.exec_length(), expected_len); + } + + #[test] + fn test_quote() { + let input = Value::build_text("abc\0edf"); + let expected = Value::build_text("'abc'"); + assert_eq!(input.exec_quote(), expected); + + let input = Value::Integer(123); + let expected = Value::Integer(123); + assert_eq!(input.exec_quote(), expected); + + let input = Value::build_text("hello''world"); + let expected = Value::build_text("'hello''''world'"); + assert_eq!(input.exec_quote(), expected); + } + + #[test] + fn test_typeof() { + let input = Value::Null; + let expected: Value = Value::build_text("null"); + assert_eq!(input.exec_typeof(), expected); + + let input = Value::Integer(123); + let expected: Value = Value::build_text("integer"); + assert_eq!(input.exec_typeof(), expected); + + let input = Value::Float(123.456); + let expected: Value = Value::build_text("real"); + assert_eq!(input.exec_typeof(), expected); + + let input = Value::build_text("hello"); + let expected: Value = Value::build_text("text"); + assert_eq!(input.exec_typeof(), expected); + + let input = Value::Blob("limbo".as_bytes().to_vec()); + let expected: Value = Value::build_text("blob"); + assert_eq!(input.exec_typeof(), expected); + } + + #[test] + fn test_unicode() { + assert_eq!(Value::build_text("a").exec_unicode(), Value::Integer(97)); + assert_eq!( + Value::build_text("😊").exec_unicode(), + Value::Integer(128522) + ); + assert_eq!(Value::build_text("").exec_unicode(), Value::Null); + assert_eq!(Value::Integer(23).exec_unicode(), Value::Integer(50)); + assert_eq!(Value::Integer(0).exec_unicode(), Value::Integer(48)); + assert_eq!(Value::Float(0.0).exec_unicode(), Value::Integer(48)); + assert_eq!(Value::Float(23.45).exec_unicode(), Value::Integer(50)); + assert_eq!(Value::Null.exec_unicode(), Value::Null); + assert_eq!( + Value::Blob("example".as_bytes().to_vec()).exec_unicode(), + Value::Integer(101) + ); + } + + #[test] + fn test_min_max() { + let input_int_vec = [ + Register::Value(Value::Integer(-1)), + Register::Value(Value::Integer(10)), + ]; + assert_eq!( + Value::exec_min(input_int_vec.iter().map(|v| v.get_value())), + Value::Integer(-1) + ); + assert_eq!( + Value::exec_max(input_int_vec.iter().map(|v| v.get_value())), + Value::Integer(10) + ); + + let str1 = Register::Value(Value::build_text("A")); + let str2 = Register::Value(Value::build_text("z")); + let input_str_vec = [str2, str1.clone()]; + assert_eq!( + Value::exec_min(input_str_vec.iter().map(|v| v.get_value())), + Value::build_text("A") + ); + assert_eq!( + Value::exec_max(input_str_vec.iter().map(|v| v.get_value())), + Value::build_text("z") + ); + + let input_null_vec = [Register::Value(Value::Null), Register::Value(Value::Null)]; + assert_eq!( + Value::exec_min(input_null_vec.iter().map(|v| v.get_value())), + Value::Null + ); + assert_eq!( + Value::exec_max(input_null_vec.iter().map(|v| v.get_value())), + Value::Null + ); + + let input_mixed_vec = [Register::Value(Value::Integer(10)), str1]; + assert_eq!( + Value::exec_min(input_mixed_vec.iter().map(|v| v.get_value())), + Value::Integer(10) + ); + assert_eq!( + Value::exec_max(input_mixed_vec.iter().map(|v| v.get_value())), + Value::build_text("A") + ); + } + + #[test] + fn test_trim() { + let input_str = Value::build_text(" Bob and Alice "); + let expected_str = Value::build_text("Bob and Alice"); + assert_eq!(input_str.exec_trim(None), expected_str); + + let input_str = Value::build_text(" Bob and Alice "); + let pattern_str = Value::build_text("Bob and"); + let expected_str = Value::build_text("Alice"); + assert_eq!(input_str.exec_trim(Some(&pattern_str)), expected_str); + + let input_str = Value::build_text("\ta"); + let expected_str = Value::build_text("\ta"); + assert_eq!(input_str.exec_trim(None), expected_str); + + let input_str = Value::build_text("\na"); + let expected_str = Value::build_text("\na"); + assert_eq!(input_str.exec_trim(None), expected_str); + } + + #[test] + fn test_ltrim() { + let input_str = Value::build_text(" Bob and Alice "); + let expected_str = Value::build_text("Bob and Alice "); + assert_eq!(input_str.exec_ltrim(None), expected_str); + + let input_str = Value::build_text(" Bob and Alice "); + let pattern_str = Value::build_text("Bob and"); + let expected_str = Value::build_text("Alice "); + assert_eq!(input_str.exec_ltrim(Some(&pattern_str)), expected_str); + } + + #[test] + fn test_rtrim() { + let input_str = Value::build_text(" Bob and Alice "); + let expected_str = Value::build_text(" Bob and Alice"); + assert_eq!(input_str.exec_rtrim(None), expected_str); + + let input_str = Value::build_text(" Bob and Alice "); + let pattern_str = Value::build_text("Bob and"); + let expected_str = Value::build_text(" Bob and Alice"); + assert_eq!(input_str.exec_rtrim(Some(&pattern_str)), expected_str); + + let input_str = Value::build_text(" Bob and Alice "); + let pattern_str = Value::build_text("and Alice"); + let expected_str = Value::build_text(" Bob"); + assert_eq!(input_str.exec_rtrim(Some(&pattern_str)), expected_str); + } + + #[test] + fn test_soundex() { + let input_str = Value::build_text("Pfister"); + let expected_str = Value::build_text("P236"); + assert_eq!(input_str.exec_soundex(), expected_str); + + let input_str = Value::build_text("husobee"); + let expected_str = Value::build_text("H210"); + assert_eq!(input_str.exec_soundex(), expected_str); + + let input_str = Value::build_text("Tymczak"); + let expected_str = Value::build_text("T522"); + assert_eq!(input_str.exec_soundex(), expected_str); + + let input_str = Value::build_text("Ashcraft"); + let expected_str = Value::build_text("A261"); + assert_eq!(input_str.exec_soundex(), expected_str); + + let input_str = Value::build_text("Robert"); + let expected_str = Value::build_text("R163"); + assert_eq!(input_str.exec_soundex(), expected_str); + + let input_str = Value::build_text("Rupert"); + let expected_str = Value::build_text("R163"); + assert_eq!(input_str.exec_soundex(), expected_str); + + let input_str = Value::build_text("Rubin"); + let expected_str = Value::build_text("R150"); + assert_eq!(input_str.exec_soundex(), expected_str); + + let input_str = Value::build_text("Kant"); + let expected_str = Value::build_text("K530"); + assert_eq!(input_str.exec_soundex(), expected_str); + + let input_str = Value::build_text("Knuth"); + let expected_str = Value::build_text("K530"); + assert_eq!(input_str.exec_soundex(), expected_str); + + let input_str = Value::build_text("x"); + let expected_str = Value::build_text("X000"); + assert_eq!(input_str.exec_soundex(), expected_str); + + let input_str = Value::build_text("闪电五连鞭"); + let expected_str = Value::build_text("?000"); + assert_eq!(input_str.exec_soundex(), expected_str); + } + + #[test] + fn test_upper_case() { + let input_str = Value::build_text("Limbo"); + let expected_str = Value::build_text("LIMBO"); + assert_eq!(input_str.exec_upper().unwrap(), expected_str); + + let input_int = Value::Integer(10); + assert_eq!(input_int.exec_upper().unwrap(), Value::build_text("10")); + assert_eq!(Value::Null.exec_upper(), None) + } + + #[test] + fn test_lower_case() { + let input_str = Value::build_text("Limbo"); + let expected_str = Value::build_text("limbo"); + assert_eq!(input_str.exec_lower().unwrap(), expected_str); + + let input_int = Value::Integer(10); + assert_eq!(input_int.exec_lower().unwrap(), Value::build_text("10")); + assert_eq!(Value::Null.exec_lower(), None) + } + + #[test] + fn test_hex() { + let input_str = Value::build_text("limbo"); + let expected_val = Value::build_text("6C696D626F"); + assert_eq!(input_str.exec_hex(), expected_val); + + let input_int = Value::Integer(100); + let expected_val = Value::build_text("313030"); + assert_eq!(input_int.exec_hex(), expected_val); + + let input_float = Value::Float(12.34); + let expected_val = Value::build_text("31322E3334"); + assert_eq!(input_float.exec_hex(), expected_val); + + let input_blob = Value::Blob(vec![0xff]); + let expected_val = Value::build_text("FF"); + assert_eq!(input_blob.exec_hex(), expected_val); + } + + #[test] + fn test_unhex() { + let input = Value::build_text("6f"); + let expected = Value::Blob(vec![0x6f]); + assert_eq!(input.exec_unhex(None), expected); + + let input = Value::build_text("6f"); + let expected = Value::Blob(vec![0x6f]); + assert_eq!(input.exec_unhex(None), expected); + + let input = Value::build_text("611"); + let expected = Value::Null; + assert_eq!(input.exec_unhex(None), expected); + + let input = Value::build_text(""); + let expected = Value::Blob(vec![]); + assert_eq!(input.exec_unhex(None), expected); + + let input = Value::build_text("61x"); + let expected = Value::Null; + assert_eq!(input.exec_unhex(None), expected); + + let input = Value::Null; + let expected = Value::Null; + assert_eq!(input.exec_unhex(None), expected); + } + + #[test] + fn test_abs() { + let int_positive_reg = Value::Integer(10); + let int_negative_reg = Value::Integer(-10); + assert_eq!(int_positive_reg.exec_abs().unwrap(), int_positive_reg); + assert_eq!(int_negative_reg.exec_abs().unwrap(), int_positive_reg); + + let float_positive_reg = Value::Integer(10); + let float_negative_reg = Value::Integer(-10); + assert_eq!(float_positive_reg.exec_abs().unwrap(), float_positive_reg); + assert_eq!(float_negative_reg.exec_abs().unwrap(), float_positive_reg); + + assert_eq!( + Value::build_text("a").exec_abs().unwrap(), + Value::Float(0.0) + ); + assert_eq!(Value::Null.exec_abs().unwrap(), Value::Null); + + // ABS(i64::MIN) should return RuntimeError + assert!(Value::Integer(i64::MIN).exec_abs().is_err()); + } + + #[test] + fn test_char() { + assert_eq!( + exec_char(&[ + Register::Value(Value::Integer(108)), + Register::Value(Value::Integer(105)) + ]), + Value::build_text("li") + ); + assert_eq!(exec_char(&[]), Value::build_text("")); + assert_eq!( + exec_char(&[Register::Value(Value::Null)]), + Value::build_text("") + ); + assert_eq!( + exec_char(&[Register::Value(Value::build_text("a"))]), + Value::build_text("") + ); + } + + #[test] + fn test_like_with_escape_or_regexmeta_chars() { + assert!(Value::exec_like(None, r#"\%A"#, r#"\A"#)); + assert!(Value::exec_like(None, "%a%a", "aaaa")); + } + + #[test] + fn test_like_no_cache() { + assert!(Value::exec_like(None, "a%", "aaaa")); + assert!(Value::exec_like(None, "%a%a", "aaaa")); + assert!(!Value::exec_like(None, "%a.a", "aaaa")); + assert!(!Value::exec_like(None, "a.a%", "aaaa")); + assert!(!Value::exec_like(None, "%a.ab", "aaaa")); + } + + #[test] + fn test_like_with_cache() { + let mut cache = HashMap::new(); + assert!(Value::exec_like(Some(&mut cache), "a%", "aaaa")); + assert!(Value::exec_like(Some(&mut cache), "%a%a", "aaaa")); + assert!(!Value::exec_like(Some(&mut cache), "%a.a", "aaaa")); + assert!(!Value::exec_like(Some(&mut cache), "a.a%", "aaaa")); + assert!(!Value::exec_like(Some(&mut cache), "%a.ab", "aaaa")); + + // again after values have been cached + assert!(Value::exec_like(Some(&mut cache), "a%", "aaaa")); + assert!(Value::exec_like(Some(&mut cache), "%a%a", "aaaa")); + assert!(!Value::exec_like(Some(&mut cache), "%a.a", "aaaa")); + assert!(!Value::exec_like(Some(&mut cache), "a.a%", "aaaa")); + assert!(!Value::exec_like(Some(&mut cache), "%a.ab", "aaaa")); + } + + #[test] + fn test_random() { + match Value::exec_random(|| rand::rng().random()) { + Value::Integer(value) => { + // Check that the value is within the range of i64 + assert!( + (i64::MIN..=i64::MAX).contains(&value), + "Random number out of range" + ); + } + _ => panic!("exec_random did not return an Integer variant"), + } + } + + #[test] + fn test_exec_randomblob() { + struct TestCase { + input: Value, + expected_len: usize, + } + + let test_cases = vec![ + TestCase { + input: Value::Integer(5), + expected_len: 5, + }, + TestCase { + input: Value::Integer(0), + expected_len: 1, + }, + TestCase { + input: Value::Integer(-1), + expected_len: 1, + }, + TestCase { + input: Value::build_text(""), + expected_len: 1, + }, + TestCase { + input: Value::build_text("5"), + expected_len: 5, + }, + TestCase { + input: Value::build_text("0"), + expected_len: 1, + }, + TestCase { + input: Value::build_text("-1"), + expected_len: 1, + }, + TestCase { + input: Value::Float(2.9), + expected_len: 2, + }, + TestCase { + input: Value::Float(-3.15), + expected_len: 1, + }, + TestCase { + input: Value::Null, + expected_len: 1, + }, + ]; + + for test_case in &test_cases { + let result = test_case.input.exec_randomblob(|dest| { + rand::rng().fill_bytes(dest); + }); + match result { + Value::Blob(blob) => { + assert_eq!(blob.len(), test_case.expected_len); + } + _ => panic!("exec_randomblob did not return a Blob variant"), + } + } + } + + #[test] + fn test_exec_round() { + let input_val = Value::Float(123.456); + let expected_val = Value::Float(123.0); + assert_eq!(input_val.exec_round(None), expected_val); + + let input_val = Value::Float(123.456); + let precision_val = Value::Integer(2); + let expected_val = Value::Float(123.46); + assert_eq!(input_val.exec_round(Some(&precision_val)), expected_val); + + let input_val = Value::Float(123.456); + let precision_val = Value::build_text("1"); + let expected_val = Value::Float(123.5); + assert_eq!(input_val.exec_round(Some(&precision_val)), expected_val); + + let input_val = Value::build_text("123.456"); + let precision_val = Value::Integer(2); + let expected_val = Value::Float(123.46); + assert_eq!(input_val.exec_round(Some(&precision_val)), expected_val); + + let input_val = Value::Integer(123); + let precision_val = Value::Integer(1); + let expected_val = Value::Float(123.0); + assert_eq!(input_val.exec_round(Some(&precision_val)), expected_val); + + let input_val = Value::Float(100.123); + let expected_val = Value::Float(100.0); + assert_eq!(input_val.exec_round(None), expected_val); + + let input_val = Value::Float(100.123); + let expected_val = Value::Null; + assert_eq!(input_val.exec_round(Some(&Value::Null)), expected_val); + } + + #[test] + fn test_exec_if() { + let reg = Value::Integer(0); + assert!(!reg.exec_if(false, false)); + assert!(reg.exec_if(false, true)); + + let reg = Value::Integer(1); + assert!(reg.exec_if(false, false)); + assert!(!reg.exec_if(false, true)); + + let reg = Value::Null; + assert!(!reg.exec_if(false, false)); + assert!(!reg.exec_if(false, true)); + + let reg = Value::Null; + assert!(reg.exec_if(true, false)); + assert!(reg.exec_if(true, true)); + + let reg = Value::Null; + assert!(!reg.exec_if(false, false)); + assert!(!reg.exec_if(false, true)); + } + + #[test] + fn test_nullif() { + assert_eq!( + Value::Integer(1).exec_nullif(&Value::Integer(1)), + Value::Null + ); + assert_eq!( + Value::Float(1.1).exec_nullif(&Value::Float(1.1)), + Value::Null + ); + assert_eq!( + Value::build_text("limbo").exec_nullif(&Value::build_text("limbo")), + Value::Null + ); + + assert_eq!( + Value::Integer(1).exec_nullif(&Value::Integer(2)), + Value::Integer(1) + ); + assert_eq!( + Value::Float(1.1).exec_nullif(&Value::Float(1.2)), + Value::Float(1.1) + ); + assert_eq!( + Value::build_text("limbo").exec_nullif(&Value::build_text("limb")), + Value::build_text("limbo") + ); + } + + #[test] + fn test_substring() { + let str_value = Value::build_text("limbo"); + let start_value = Value::Integer(1); + let length_value = Value::Integer(3); + let expected_val = Value::build_text("lim"); + assert_eq!( + Value::exec_substring(&str_value, &start_value, Some(&length_value)), + expected_val + ); + + let str_value = Value::build_text("limbo"); + let start_value = Value::Integer(1); + let length_value = Value::Integer(10); + let expected_val = Value::build_text("limbo"); + assert_eq!( + Value::exec_substring(&str_value, &start_value, Some(&length_value)), + expected_val + ); + + let str_value = Value::build_text("limbo"); + let start_value = Value::Integer(10); + let length_value = Value::Integer(3); + let expected_val = Value::build_text(""); + assert_eq!( + Value::exec_substring(&str_value, &start_value, Some(&length_value)), + expected_val + ); + + let str_value = Value::build_text("limbo"); + let start_value = Value::Integer(3); + let length_value = Value::Null; + let expected_val = Value::build_text("mbo"); + assert_eq!( + Value::exec_substring(&str_value, &start_value, Some(&length_value)), + expected_val + ); + + let str_value = Value::build_text("limbo"); + let start_value = Value::Integer(10); + let length_value = Value::Null; + let expected_val = Value::build_text(""); + assert_eq!( + Value::exec_substring(&str_value, &start_value, Some(&length_value)), + expected_val + ); + } + + #[test] + fn test_exec_instr() { + let input = Value::build_text("limbo"); + let pattern = Value::build_text("im"); + let expected = Value::Integer(2); + assert_eq!(input.exec_instr(&pattern), expected); + + let input = Value::build_text("limbo"); + let pattern = Value::build_text("limbo"); + let expected = Value::Integer(1); + assert_eq!(input.exec_instr(&pattern), expected); + + let input = Value::build_text("limbo"); + let pattern = Value::build_text("o"); + let expected = Value::Integer(5); + assert_eq!(input.exec_instr(&pattern), expected); + + let input = Value::build_text("liiiiimbo"); + let pattern = Value::build_text("ii"); + let expected = Value::Integer(2); + assert_eq!(input.exec_instr(&pattern), expected); + + let input = Value::build_text("limbo"); + let pattern = Value::build_text("limboX"); + let expected = Value::Integer(0); + assert_eq!(input.exec_instr(&pattern), expected); + + let input = Value::build_text("limbo"); + let pattern = Value::build_text(""); + let expected = Value::Integer(1); + assert_eq!(input.exec_instr(&pattern), expected); + + let input = Value::build_text(""); + let pattern = Value::build_text("limbo"); + let expected = Value::Integer(0); + assert_eq!(input.exec_instr(&pattern), expected); + + let input = Value::build_text(""); + let pattern = Value::build_text(""); + let expected = Value::Integer(1); + assert_eq!(input.exec_instr(&pattern), expected); + + let input = Value::Null; + let pattern = Value::Null; + let expected = Value::Null; + assert_eq!(input.exec_instr(&pattern), expected); + + let input = Value::build_text("limbo"); + let pattern = Value::Null; + let expected = Value::Null; + assert_eq!(input.exec_instr(&pattern), expected); + + let input = Value::Null; + let pattern = Value::build_text("limbo"); + let expected = Value::Null; + assert_eq!(input.exec_instr(&pattern), expected); + + let input = Value::Integer(123); + let pattern = Value::Integer(2); + let expected = Value::Integer(2); + assert_eq!(input.exec_instr(&pattern), expected); + + let input = Value::Integer(123); + let pattern = Value::Integer(5); + let expected = Value::Integer(0); + assert_eq!(input.exec_instr(&pattern), expected); + + let input = Value::Float(12.34); + let pattern = Value::Float(2.3); + let expected = Value::Integer(2); + assert_eq!(input.exec_instr(&pattern), expected); + + let input = Value::Float(12.34); + let pattern = Value::Float(5.6); + let expected = Value::Integer(0); + assert_eq!(input.exec_instr(&pattern), expected); + + let input = Value::Float(12.34); + let pattern = Value::build_text("."); + let expected = Value::Integer(3); + assert_eq!(input.exec_instr(&pattern), expected); + + let input = Value::Blob(vec![1, 2, 3, 4, 5]); + let pattern = Value::Blob(vec![3, 4]); + let expected = Value::Integer(3); + assert_eq!(input.exec_instr(&pattern), expected); + + let input = Value::Blob(vec![1, 2, 3, 4, 5]); + let pattern = Value::Blob(vec![3, 2]); + let expected = Value::Integer(0); + assert_eq!(input.exec_instr(&pattern), expected); + + let input = Value::Blob(vec![0x61, 0x62, 0x63, 0x64, 0x65]); + let pattern = Value::build_text("cd"); + let expected = Value::Integer(3); + assert_eq!(input.exec_instr(&pattern), expected); + + let input = Value::build_text("abcde"); + let pattern = Value::Blob(vec![0x63, 0x64]); + let expected = Value::Integer(3); + assert_eq!(input.exec_instr(&pattern), expected); + } + + #[test] + fn test_exec_sign() { + let input = Value::Integer(42); + let expected = Some(Value::Integer(1)); + assert_eq!(input.exec_sign(), expected); + + let input = Value::Integer(-42); + let expected = Some(Value::Integer(-1)); + assert_eq!(input.exec_sign(), expected); + + let input = Value::Integer(0); + let expected = Some(Value::Integer(0)); + assert_eq!(input.exec_sign(), expected); + + let input = Value::Float(0.0); + let expected = Some(Value::Integer(0)); + assert_eq!(input.exec_sign(), expected); + + let input = Value::Float(0.1); + let expected = Some(Value::Integer(1)); + assert_eq!(input.exec_sign(), expected); + + let input = Value::Float(42.0); + let expected = Some(Value::Integer(1)); + assert_eq!(input.exec_sign(), expected); + + let input = Value::Float(-42.0); + let expected = Some(Value::Integer(-1)); + assert_eq!(input.exec_sign(), expected); + + let input = Value::build_text("abc"); + let expected = None; + assert_eq!(input.exec_sign(), expected); + + let input = Value::build_text("42"); + let expected = Some(Value::Integer(1)); + assert_eq!(input.exec_sign(), expected); + + let input = Value::build_text("-42"); + let expected = Some(Value::Integer(-1)); + assert_eq!(input.exec_sign(), expected); + + let input = Value::build_text("0"); + let expected = Some(Value::Integer(0)); + assert_eq!(input.exec_sign(), expected); + + let input = Value::Blob(b"abc".to_vec()); + let expected = None; + assert_eq!(input.exec_sign(), expected); + + let input = Value::Blob(b"42".to_vec()); + let expected = None; + assert_eq!(input.exec_sign(), expected); + + let input = Value::Blob(b"-42".to_vec()); + let expected = None; + assert_eq!(input.exec_sign(), expected); + + let input = Value::Blob(b"0".to_vec()); + let expected = None; + assert_eq!(input.exec_sign(), expected); + + let input = Value::Null; + let expected = None; + assert_eq!(input.exec_sign(), expected); + } + + #[test] + fn test_exec_zeroblob() { + let input = Value::Integer(0); + let expected = Value::Blob(vec![]); + assert_eq!(input.exec_zeroblob(), expected); + + let input = Value::Null; + let expected = Value::Blob(vec![]); + assert_eq!(input.exec_zeroblob(), expected); + + let input = Value::Integer(4); + let expected = Value::Blob(vec![0; 4]); + assert_eq!(input.exec_zeroblob(), expected); + + let input = Value::Integer(-1); + let expected = Value::Blob(vec![]); + assert_eq!(input.exec_zeroblob(), expected); + + let input = Value::build_text("5"); + let expected = Value::Blob(vec![0; 5]); + assert_eq!(input.exec_zeroblob(), expected); + + let input = Value::build_text("-5"); + let expected = Value::Blob(vec![]); + assert_eq!(input.exec_zeroblob(), expected); + + let input = Value::build_text("text"); + let expected = Value::Blob(vec![]); + assert_eq!(input.exec_zeroblob(), expected); + + let input = Value::Float(2.6); + let expected = Value::Blob(vec![0; 2]); + assert_eq!(input.exec_zeroblob(), expected); + + let input = Value::Blob(vec![1]); + let expected = Value::Blob(vec![]); + assert_eq!(input.exec_zeroblob(), expected); + } + + #[test] + fn test_replace() { + let input_str = Value::build_text("bob"); + let pattern_str = Value::build_text("b"); + let replace_str = Value::build_text("a"); + let expected_str = Value::build_text("aoa"); + assert_eq!( + Value::exec_replace(&input_str, &pattern_str, &replace_str), + expected_str + ); + + let input_str = Value::build_text("bob"); + let pattern_str = Value::build_text("b"); + let replace_str = Value::build_text(""); + let expected_str = Value::build_text("o"); + assert_eq!( + Value::exec_replace(&input_str, &pattern_str, &replace_str), + expected_str + ); + + let input_str = Value::build_text("bob"); + let pattern_str = Value::build_text("b"); + let replace_str = Value::build_text("abc"); + let expected_str = Value::build_text("abcoabc"); + assert_eq!( + Value::exec_replace(&input_str, &pattern_str, &replace_str), + expected_str + ); + + let input_str = Value::build_text("bob"); + let pattern_str = Value::build_text("a"); + let replace_str = Value::build_text("b"); + let expected_str = Value::build_text("bob"); + assert_eq!( + Value::exec_replace(&input_str, &pattern_str, &replace_str), + expected_str + ); + + let input_str = Value::build_text("bob"); + let pattern_str = Value::build_text(""); + let replace_str = Value::build_text("a"); + let expected_str = Value::build_text("bob"); + assert_eq!( + Value::exec_replace(&input_str, &pattern_str, &replace_str), + expected_str + ); + + let input_str = Value::build_text("bob"); + let pattern_str = Value::Null; + let replace_str = Value::build_text("a"); + let expected_str = Value::Null; + assert_eq!( + Value::exec_replace(&input_str, &pattern_str, &replace_str), + expected_str + ); + + let input_str = Value::build_text("bo5"); + let pattern_str = Value::Integer(5); + let replace_str = Value::build_text("a"); + let expected_str = Value::build_text("boa"); + assert_eq!( + Value::exec_replace(&input_str, &pattern_str, &replace_str), + expected_str + ); + + let input_str = Value::build_text("bo5.0"); + let pattern_str = Value::Float(5.0); + let replace_str = Value::build_text("a"); + let expected_str = Value::build_text("boa"); + assert_eq!( + Value::exec_replace(&input_str, &pattern_str, &replace_str), + expected_str + ); + + let input_str = Value::build_text("bo5"); + let pattern_str = Value::Float(5.0); + let replace_str = Value::build_text("a"); + let expected_str = Value::build_text("bo5"); + assert_eq!( + Value::exec_replace(&input_str, &pattern_str, &replace_str), + expected_str + ); + + let input_str = Value::build_text("bo5.0"); + let pattern_str = Value::Float(5.0); + let replace_str = Value::Float(6.0); + let expected_str = Value::build_text("bo6.0"); + assert_eq!( + Value::exec_replace(&input_str, &pattern_str, &replace_str), + expected_str + ); + + // todo: change this test to use (0.1 + 0.2) instead of 0.3 when decimals are implemented. + let input_str = Value::build_text("tes3"); + let pattern_str = Value::Integer(3); + let replace_str = Value::Float(0.3); + let expected_str = Value::build_text("tes0.3"); + assert_eq!( + Value::exec_replace(&input_str, &pattern_str, &replace_str), + expected_str + ); + } + + #[test] + fn test_bitfield() { + let mut bitfield = Bitfield::<4>::new(); + for i in 0..256 { + bitfield.set(i); + assert!(bitfield.get(i)); + for j in 0..i { + assert!(bitfield.get(j)); + } + for j in i + 1..256 { + assert!(!bitfield.get(j)); + } + } + for i in 0..256 { + bitfield.unset(i); + assert!(!bitfield.get(i)); + } + } +}