Merge 'Support math functions' from Lauri Virtanen

Add support for math scalar functions of SQLite:
https://sqlite.org/lang_mathfunc.html
Since SQLite CLI and Limbo CLI present floating point numbers with
different precision, I added `do_execsql_test_tolerance` which tests
that floating point results close enough of the expected value. However,
we probably could make Limbo's floating point presentation match to
SQLite.

Reviewed-by: Jussi Saurio <jussi.saurio@gmail.com>
Reviewed-by: Jussi Saurio <jussi.saurio@gmail.com>

Closes #460
This commit is contained in:
jussisaurio
2024-12-17 22:02:43 +02:00
6 changed files with 1167 additions and 2 deletions

View File

@@ -160,6 +160,41 @@ Feature support of [sqlite expr syntax](https://www.sqlite.org/lang_expr.html).
| upper(X) | Yes | |
| zeroblob(N) | Yes | |
### Mathematical functions
| Function | Status | Comment |
| ---------- | ------ | ------- |
| acos(X) | Yes | |
| acosh(X) | Yes | |
| asin(X) | Yes | |
| asinh(X) | Yes | |
| atan(X) | Yes | |
| atan2(Y,X) | Yes | |
| atanh(X) | Yes | |
| ceil(X) | Yes | |
| ceiling(X) | Yes | |
| cos(X) | Yes | |
| cosh(X) | Yes | |
| degrees(X) | Yes | |
| exp(X) | Yes | |
| floor(X) | Yes | |
| ln(X) | Yes | |
| log(B,X) | Yes | |
| log(X) | Yes | |
| log10(X) | Yes | |
| log2(X) | Yes | |
| mod(X,Y) | Yes | |
| pi() | Yes | |
| pow(X,Y) | Yes | |
| power(X,Y) | Yes | |
| radians(X) | Yes | |
| sin(X) | Yes | |
| sinh(X) | Yes | |
| sqrt(X) | Yes | |
| tan(X) | Yes | |
| tanh(X) | Yes | |
| trunc(X) | Yes | |
### Aggregate functions
| Function | Status | Comment |

View File

@@ -139,10 +139,126 @@ impl Display for ScalarFunc {
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum MathFunc {
Acos,
Acosh,
Asin,
Asinh,
Atan,
Atan2,
Atanh,
Ceil,
Ceiling,
Cos,
Cosh,
Degrees,
Exp,
Floor,
Ln,
Log,
Log10,
Log2,
Mod,
Pi,
Pow,
Power,
Radians,
Sin,
Sinh,
Sqrt,
Tan,
Tanh,
Trunc,
}
pub enum MathFuncArity {
Nullary,
Unary,
Binary,
UnaryOrBinary,
}
impl MathFunc {
pub fn arity(&self) -> MathFuncArity {
match self {
MathFunc::Pi => MathFuncArity::Nullary,
MathFunc::Acos
| MathFunc::Acosh
| MathFunc::Asin
| MathFunc::Asinh
| MathFunc::Atan
| MathFunc::Atanh
| MathFunc::Ceil
| MathFunc::Ceiling
| MathFunc::Cos
| MathFunc::Cosh
| MathFunc::Degrees
| MathFunc::Exp
| MathFunc::Floor
| MathFunc::Ln
| MathFunc::Log10
| MathFunc::Log2
| MathFunc::Radians
| MathFunc::Sin
| MathFunc::Sinh
| MathFunc::Sqrt
| MathFunc::Tan
| MathFunc::Tanh
| MathFunc::Trunc => MathFuncArity::Unary,
MathFunc::Atan2 | MathFunc::Mod | MathFunc::Pow | MathFunc::Power => {
MathFuncArity::Binary
}
MathFunc::Log => MathFuncArity::UnaryOrBinary,
}
}
}
impl Display for MathFunc {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let str = match self {
MathFunc::Acos => "acos".to_string(),
MathFunc::Acosh => "acosh".to_string(),
MathFunc::Asin => "asin".to_string(),
MathFunc::Asinh => "asinh".to_string(),
MathFunc::Atan => "atan".to_string(),
MathFunc::Atan2 => "atan2".to_string(),
MathFunc::Atanh => "atanh".to_string(),
MathFunc::Ceil => "ceil".to_string(),
MathFunc::Ceiling => "ceiling".to_string(),
MathFunc::Cos => "cos".to_string(),
MathFunc::Cosh => "cosh".to_string(),
MathFunc::Degrees => "degrees".to_string(),
MathFunc::Exp => "exp".to_string(),
MathFunc::Floor => "floor".to_string(),
MathFunc::Ln => "ln".to_string(),
MathFunc::Log => "log".to_string(),
MathFunc::Log10 => "log10".to_string(),
MathFunc::Log2 => "log2".to_string(),
MathFunc::Mod => "mod".to_string(),
MathFunc::Pi => "pi".to_string(),
MathFunc::Pow => "pow".to_string(),
MathFunc::Power => "power".to_string(),
MathFunc::Radians => "radians".to_string(),
MathFunc::Sin => "sin".to_string(),
MathFunc::Sinh => "sinh".to_string(),
MathFunc::Sqrt => "sqrt".to_string(),
MathFunc::Tan => "tan".to_string(),
MathFunc::Tanh => "tanh".to_string(),
MathFunc::Trunc => "trunc".to_string(),
};
write!(f, "{}", str)
}
}
#[derive(Debug)]
pub enum Func {
Agg(AggFunc),
Scalar(ScalarFunc),
Math(MathFunc),
#[cfg(feature = "json")]
Json(JsonFunc),
}
@@ -152,6 +268,7 @@ impl Display for Func {
match self {
Func::Agg(agg_func) => write!(f, "{}", agg_func.to_string()),
Func::Scalar(scalar_func) => write!(f, "{}", scalar_func),
Func::Math(math_func) => write!(f, "{}", math_func),
#[cfg(feature = "json")]
Func::Json(json_func) => write!(f, "{}", json_func),
}
@@ -216,6 +333,35 @@ impl Func {
"unhex" => Ok(Func::Scalar(ScalarFunc::Unhex)),
"zeroblob" => Ok(Func::Scalar(ScalarFunc::ZeroBlob)),
"soundex" => Ok(Func::Scalar(ScalarFunc::Soundex)),
"acos" => Ok(Func::Math(MathFunc::Acos)),
"acosh" => Ok(Func::Math(MathFunc::Acosh)),
"asin" => Ok(Func::Math(MathFunc::Asin)),
"asinh" => Ok(Func::Math(MathFunc::Asinh)),
"atan" => Ok(Func::Math(MathFunc::Atan)),
"atan2" => Ok(Func::Math(MathFunc::Atan2)),
"atanh" => Ok(Func::Math(MathFunc::Atanh)),
"ceil" => Ok(Func::Math(MathFunc::Ceil)),
"ceiling" => Ok(Func::Math(MathFunc::Ceiling)),
"cos" => Ok(Func::Math(MathFunc::Cos)),
"cosh" => Ok(Func::Math(MathFunc::Cosh)),
"degrees" => Ok(Func::Math(MathFunc::Degrees)),
"exp" => Ok(Func::Math(MathFunc::Exp)),
"floor" => Ok(Func::Math(MathFunc::Floor)),
"ln" => Ok(Func::Math(MathFunc::Ln)),
"log" => Ok(Func::Math(MathFunc::Log)),
"log10" => Ok(Func::Math(MathFunc::Log10)),
"log2" => Ok(Func::Math(MathFunc::Log2)),
"mod" => Ok(Func::Math(MathFunc::Mod)),
"pi" => Ok(Func::Math(MathFunc::Pi)),
"pow" => Ok(Func::Math(MathFunc::Pow)),
"power" => Ok(Func::Math(MathFunc::Power)),
"radians" => Ok(Func::Math(MathFunc::Radians)),
"sin" => Ok(Func::Math(MathFunc::Sin)),
"sinh" => Ok(Func::Math(MathFunc::Sinh)),
"sqrt" => Ok(Func::Math(MathFunc::Sqrt)),
"tan" => Ok(Func::Math(MathFunc::Tan)),
"tanh" => Ok(Func::Math(MathFunc::Tanh)),
"trunc" => Ok(Func::Math(MathFunc::Trunc)),
_ => Err(()),
}
}

View File

@@ -2,7 +2,7 @@ use sqlite3_parser::ast::{self, UnaryOperator};
#[cfg(feature = "json")]
use crate::function::JsonFunc;
use crate::function::{AggFunc, Func, FuncCtx, ScalarFunc};
use crate::function::{AggFunc, Func, FuncCtx, MathFuncArity, ScalarFunc};
use crate::schema::Type;
use crate::util::normalize_ident;
use crate::vdbe::{builder::ProgramBuilder, BranchOffset, Insn};
@@ -1603,6 +1603,134 @@ pub fn translate_expr(
}
}
}
Func::Math(math_func) => match math_func.arity() {
MathFuncArity::Nullary => {
if args.is_some() {
crate::bail_parse_error!("{} function with arguments", math_func);
}
program.emit_insn(Insn::Function {
constant_mask: 0,
start_reg: 0,
dest: target_register,
func: func_ctx,
});
Ok(target_register)
}
MathFuncArity::Unary => {
let args = if let Some(args) = args {
if args.len() != 1 {
crate::bail_parse_error!(
"{} function with not exactly 1 argument",
math_func
);
}
args
} else {
crate::bail_parse_error!("{} function with no arguments", math_func);
};
let reg = program.alloc_register();
translate_expr(
program,
referenced_tables,
&args[0],
reg,
precomputed_exprs_to_registers,
)?;
program.emit_insn(Insn::Function {
constant_mask: 0,
start_reg: reg,
dest: target_register,
func: func_ctx,
});
Ok(target_register)
}
MathFuncArity::Binary => {
let args = if let Some(args) = args {
if args.len() != 2 {
crate::bail_parse_error!(
"{} function with not exactly 2 arguments",
math_func
);
}
args
} else {
crate::bail_parse_error!("{} function with no arguments", math_func);
};
let reg1 = program.alloc_register();
let reg2 = program.alloc_register();
translate_expr(
program,
referenced_tables,
&args[0],
reg1,
precomputed_exprs_to_registers,
)?;
if let ast::Expr::Literal(_) = &args[0] {
program.mark_last_insn_constant();
}
translate_expr(
program,
referenced_tables,
&args[1],
reg2,
precomputed_exprs_to_registers,
)?;
if let ast::Expr::Literal(_) = &args[1] {
program.mark_last_insn_constant();
}
program.emit_insn(Insn::Function {
constant_mask: 0,
start_reg: target_register + 1,
dest: target_register,
func: func_ctx,
});
Ok(target_register)
}
MathFuncArity::UnaryOrBinary => {
let args = if let Some(args) = args {
if args.len() > 2 {
crate::bail_parse_error!(
"{} function with more than 2 arguments",
math_func
);
}
args
} else {
crate::bail_parse_error!("{} function with no arguments", math_func);
};
let regs = program.alloc_registers(args.len());
for (i, arg) in args.iter().enumerate() {
translate_expr(
program,
referenced_tables,
arg,
regs + i,
precomputed_exprs_to_registers,
)?;
}
program.emit_insn(Insn::Function {
constant_mask: 0,
start_reg: regs,
dest: target_register,
func: func_ctx,
});
Ok(target_register)
}
},
}
}
ast::Expr::FunctionCallStar { .. } => todo!(),

View File

@@ -24,7 +24,7 @@ pub mod sorter;
mod datetime;
use crate::error::{LimboError, SQLITE_CONSTRAINT_PRIMARYKEY};
use crate::function::{AggFunc, FuncCtx, ScalarFunc};
use crate::function::{AggFunc, FuncCtx, MathFunc, MathFuncArity, ScalarFunc};
use crate::pseudo::PseudoCursor;
use crate::schema::Table;
use crate::storage::sqlite3_ondisk::DatabaseHeader;
@@ -2491,6 +2491,58 @@ impl Program {
state.registers[*dest] = exec_replace(source, pattern, replacement);
}
},
crate::function::Func::Math(math_func) => match math_func.arity() {
MathFuncArity::Nullary => match math_func {
MathFunc::Pi => {
state.registers[*dest] =
OwnedValue::Float(std::f64::consts::PI);
}
_ => {
unreachable!(
"Unexpected mathematical Nullary function {:?}",
math_func
);
}
},
MathFuncArity::Unary => {
let reg_value = &state.registers[*start_reg];
let result = exec_math_unary(reg_value, math_func);
state.registers[*dest] = result;
}
MathFuncArity::Binary => {
let lhs = &state.registers[*start_reg];
let rhs = &state.registers[*start_reg + 1];
let result = exec_math_binary(lhs, rhs, math_func);
state.registers[*dest] = result;
}
MathFuncArity::UnaryOrBinary => match math_func {
MathFunc::Log => {
let result = match arg_count {
1 => {
let arg = &state.registers[*start_reg];
exec_math_log(arg, None)
}
2 => {
let base = &state.registers[*start_reg];
let arg = &state.registers[*start_reg + 1];
exec_math_log(arg, Some(base))
}
_ => unreachable!(
"{:?} function with unexpected number of arguments",
math_func
),
};
state.registers[*dest] = result;
}
_ => unreachable!(
"Unexpected mathematical UnaryOrBinary function {:?}",
math_func
),
},
},
crate::function::Func::Agg(_) => {
unreachable!("Aggregate functions should not be handled here")
}
@@ -3597,6 +3649,109 @@ fn execute_sqlite_version(version_integer: i64) -> String {
format!("{}.{}.{}", major, minor, release)
}
fn to_f64(reg: &OwnedValue) -> Option<f64> {
match reg {
OwnedValue::Integer(i) => Some(*i as f64),
OwnedValue::Float(f) => Some(*f),
OwnedValue::Text(t) => t.parse::<f64>().ok(),
OwnedValue::Agg(ctx) => to_f64(ctx.final_value()),
_ => None,
}
}
fn exec_math_unary(reg: &OwnedValue, function: &MathFunc) -> OwnedValue {
// In case of some functions and integer input, return the input as is
if let OwnedValue::Integer(_) = reg {
if matches! { function, MathFunc::Ceil | MathFunc::Ceiling | MathFunc::Floor | MathFunc::Trunc }
{
return reg.clone();
}
}
let f = match to_f64(reg) {
Some(f) => f,
None => return OwnedValue::Null,
};
let result = match function {
MathFunc::Acos => f.acos(),
MathFunc::Acosh => f.acosh(),
MathFunc::Asin => f.asin(),
MathFunc::Asinh => f.asinh(),
MathFunc::Atan => f.atan(),
MathFunc::Atanh => f.atanh(),
MathFunc::Ceil | MathFunc::Ceiling => f.ceil(),
MathFunc::Cos => f.cos(),
MathFunc::Cosh => f.cosh(),
MathFunc::Degrees => f.to_degrees(),
MathFunc::Exp => f.exp(),
MathFunc::Floor => f.floor(),
MathFunc::Ln => f.ln(),
MathFunc::Log10 => f.log10(),
MathFunc::Log2 => f.log2(),
MathFunc::Radians => f.to_radians(),
MathFunc::Sin => f.sin(),
MathFunc::Sinh => f.sinh(),
MathFunc::Sqrt => f.sqrt(),
MathFunc::Tan => f.tan(),
MathFunc::Tanh => f.tanh(),
MathFunc::Trunc => f.trunc(),
_ => unreachable!("Unexpected mathematical unary function {:?}", function),
};
if result.is_nan() {
OwnedValue::Null
} else {
OwnedValue::Float(result)
}
}
fn exec_math_binary(lhs: &OwnedValue, rhs: &OwnedValue, function: &MathFunc) -> OwnedValue {
let lhs = match to_f64(lhs) {
Some(f) => f,
None => return OwnedValue::Null,
};
let rhs = match to_f64(rhs) {
Some(f) => f,
None => return OwnedValue::Null,
};
let result = match function {
MathFunc::Atan2 => lhs.atan2(rhs),
MathFunc::Mod => lhs % rhs,
MathFunc::Pow | MathFunc::Power => lhs.powf(rhs),
_ => unreachable!("Unexpected mathematical binary function {:?}", function),
};
if result.is_nan() {
OwnedValue::Null
} else {
OwnedValue::Float(result)
}
}
fn exec_math_log(arg: &OwnedValue, base: Option<&OwnedValue>) -> OwnedValue {
let f = match to_f64(arg) {
Some(f) => f,
None => return OwnedValue::Null,
};
let base = match base {
Some(base) => match to_f64(base) {
Some(f) => f,
None => return OwnedValue::Null,
},
None => 10.0,
};
if f <= 0.0 || base <= 0.0 || base == 1.0 {
return OwnedValue::Null;
}
OwnedValue::Float(f.log(base))
}
#[cfg(test)]
mod tests {

View File

@@ -3,6 +3,10 @@
set testdir [file dirname $argv0]
source $testdir/tester.tcl
# Tolerance for floating point comparisons
# FIXME: When Limbo's floating point presentation matches to SQLite, this could/should be removed
set tolerance 1e-13
do_execsql_test add-int {
SELECT 10 + 1
} {11}
@@ -361,3 +365,663 @@ do_execsql_test bitwise-not-zero {
SELECT ~0
} {-1}
do_execsql_test_tolerance pi {
SELECT pi()
} {3.14159265358979} $tolerance
do_execsql_test_tolerance acos-int {
SELECT acos(1)
} {0.0} $tolerance
do_execsql_test_tolerance acos-float {
SELECT acos(-0.5)
} {2.0943951023931957} $tolerance
do_execsql_test_tolerance acos-str {
SELECT acos('-0.5')
} {2.0943951023931957} $tolerance
do_execsql_test_tolerance acos-null {
SELECT acos(null)
} {} $tolerance
do_execsql_test_tolerance acosh-int {
SELECT acosh(1)
} {0.0} $tolerance
do_execsql_test_tolerance acosh-float {
SELECT acosh(1.5)
} {0.962423650119207} $tolerance
do_execsql_test_tolerance acosh-str {
SELECT acosh('1.5')
} {0.962423650119207} $tolerance
do_execsql_test_tolerance acosh-invalid {
SELECT acosh(0.99)
} {} $tolerance
do_execsql_test_tolerance acosh-null {
SELECT acosh(null)
} {} $tolerance
do_execsql_test_tolerance asin-int {
SELECT asin(1)
} {1.5707963267948966} $tolerance
do_execsql_test_tolerance asin-float {
SELECT asin(-0.5)
} {-0.5235987755982989} $tolerance
do_execsql_test_tolerance asin-str {
SELECT asin('-0.5')
} {-0.5235987755982989} $tolerance
do_execsql_test_tolerance asin-null {
SELECT asin(null)
} {} $tolerance
do_execsql_test_tolerance sin-int {
SELECT sin(1)
} {0.841470984807897} $tolerance
do_execsql_test_tolerance sin-float {
SELECT sin(-0.5)
} {-0.479425538604203} $tolerance
do_execsql_test_tolerance sin-str {
SELECT sin('-0.5')
} {-0.479425538604203} $tolerance
do_execsql_test_tolerance sin-null {
SELECT sin(null)
} {} $tolerance
do_execsql_test_tolerance sin-products-id {
SELECT sin(id) from products limit 5
} {0.8414709848078965
0.9092974268256817
0.1411200080598672
-0.7568024953079282
-0.9589242746631385} $tolerance
do_execsql_test_tolerance asinh-int {
SELECT asinh(1)
} {0.881373587019543} $tolerance
do_execsql_test_tolerance asinh-float {
SELECT asinh(-0.5)
} {-0.48121182505960347} $tolerance
do_execsql_test_tolerance asinh-str {
SELECT asinh('-0.5')
} {-0.48121182505960347} $tolerance
do_execsql_test_tolerance asinh-null {
SELECT asinh(null)
} {} $tolerance
do_execsql_test_tolerance atan-int {
SELECT atan(1)
} {0.7853981633974483} $tolerance
do_execsql_test_tolerance atan-float {
SELECT atan(-0.5)
} {-0.4636476090008061} $tolerance
do_execsql_test_tolerance atan-str {
SELECT atan('-0.5')
} {-0.4636476090008061} $tolerance
do_execsql_test_tolerance atan-null {
SELECT atan(null)
} {} $tolerance
do_execsql_test_tolerance tan-int {
SELECT tan(1)
} {1.5574077246549} $tolerance
do_execsql_test_tolerance tan-float {
SELECT tan(-0.5)
} {-0.54630248984379} $tolerance
do_execsql_test_tolerance tan-str {
SELECT tan('-0.5')
} {-0.54630248984379} $tolerance
do_execsql_test_tolerance tan-null {
SELECT tan(null)
} {} $tolerance
do_execsql_test_tolerance atanh-int {
SELECT atanh(0)
} {0.0} $tolerance
do_execsql_test_tolerance atanh-float {
SELECT atanh(-0.5)
} {-0.5493061443340548} $tolerance
do_execsql_test_tolerance atanh-str {
SELECT atanh('-0.5')
} {-0.5493061443340548} $tolerance
do_execsql_test_tolerance atanh-null {
SELECT atanh(null)
} {} $tolerance
do_execsql_test ceil-int {
SELECT ceil(1)
} {1}
do_execsql_test ceil-float {
SELECT ceil(-1.5)
} {-1.0}
do_execsql_test ceil-str {
SELECT ceil('1.5')
} {2.0}
do_execsql_test ceil-null {
SELECT ceil(null)
} {}
do_execsql_test ceiling-int {
SELECT ceiling(1)
} {1}
do_execsql_test ceiling-float {
SELECT ceiling(-1.5)
} {-1.0}
do_execsql_test ceiling-str {
SELECT ceiling('1.5')
} {2.0}
do_execsql_test ceiling-null {
SELECT ceiling(null)
} {}
do_execsql_test_tolerance cos-int {
SELECT cos(1)
} {0.54030230586814} $tolerance
do_execsql_test_tolerance cos-float {
SELECT cos(-0.5)
} {0.877582561890373} $tolerance
do_execsql_test_tolerance cos-str {
SELECT cos('-0.5')
} {0.877582561890373} $tolerance
do_execsql_test_tolerance cos-null {
SELECT cos(null)
} {} $tolerance
do_execsql_test_tolerance cosh-int {
SELECT cosh(1)
} {1.54308063481524} $tolerance
do_execsql_test_tolerance cosh-float {
SELECT cosh(-0.5)
} {1.12762596520638} $tolerance
do_execsql_test_tolerance cosh-str {
SELECT cosh('-0.5')
} {1.12762596520638} $tolerance
do_execsql_test_tolerance cosh-null {
SELECT cosh(null)
} {} $tolerance
do_execsql_test_tolerance degrees-int {
SELECT degrees(1)
} {57.2957795130823} $tolerance
do_execsql_test_tolerance degrees-float {
SELECT degrees(-0.5)
} {-28.6478897565412} $tolerance
do_execsql_test_tolerance degrees-str {
SELECT degrees('-0.5')
} {-28.6478897565412} $tolerance
do_execsql_test_tolerance degrees-null {
SELECT degrees(null)
} {} $tolerance
do_execsql_test_tolerance exp-int {
SELECT exp(1)
} {2.71828182845905} $tolerance
do_execsql_test_tolerance exp-float {
SELECT exp(-0.5)
} {0.606530659712633} $tolerance
do_execsql_test_tolerance exp-str {
SELECT exp('-0.5')
} {0.606530659712633} $tolerance
do_execsql_test_tolerance exp-null {
SELECT exp(null)
} {} $tolerance
do_execsql_test floor-int {
SELECT floor(1)
} {1}
do_execsql_test floor-float {
SELECT floor(-1.5)
} {-2.0}
do_execsql_test floor-str {
SELECT floor('1.5')
} {1.0}
do_execsql_test floor-null {
SELECT floor(null)
} {}
do_execsql_test_tolerance ln-int {
SELECT ln(1)
} {0.0} $tolerance
do_execsql_test_tolerance ln-float {
SELECT ln(0.5)
} {-0.693147180559945} $tolerance
do_execsql_test_tolerance ln-str {
SELECT ln('0.5')
} {-0.693147180559945} $tolerance
do_execsql_test_tolerance ln-negative {
SELECT ln(-0.5)
} {} $tolerance
do_execsql_test_tolerance ln-null {
SELECT ln(null)
} {} $tolerance
do_execsql_test_tolerance log10-int {
SELECT log10(1)
} {0.0} $tolerance
do_execsql_test_tolerance log10-float {
SELECT log10(0.5)
} {-0.301029995663981} $tolerance
do_execsql_test_tolerance log10-str {
SELECT log10('0.5')
} {-0.301029995663981} $tolerance
do_execsql_test_tolerance log10-negative {
SELECT log10(-0.5)
} {} $tolerance
do_execsql_test_tolerance log10-null {
SELECT log10(null)
} {} $tolerance
do_execsql_test_tolerance log2-int {
SELECT log2(1)
} {0.0} $tolerance
do_execsql_test_tolerance log2-float {
SELECT log2(0.5)
} {-1.0} $tolerance
do_execsql_test_tolerance log2-str {
SELECT log2('0.5')
} {-1.0} $tolerance
do_execsql_test_tolerance log2-negative {
SELECT log2(-0.5)
} {} $tolerance
do_execsql_test_tolerance log2-null {
SELECT log2(null)
} {} $tolerance
do_execsql_test_tolerance radians-int {
SELECT radians(1)
} {0.0174532925199433} $tolerance
do_execsql_test_tolerance radians-float {
SELECT radians(-0.5)
} {-0.00872664625997165} $tolerance
do_execsql_test_tolerance radians-str {
SELECT radians('-0.5')
} {-0.00872664625997165} $tolerance
do_execsql_test_tolerance radians-null {
SELECT radians(null)
} {} $tolerance
do_execsql_test_tolerance sinh-int {
SELECT sinh(1)
} {1.1752011936438} $tolerance
do_execsql_test_tolerance sinh-float {
SELECT sinh(-0.5)
} {-0.521095305493747} $tolerance
do_execsql_test_tolerance sinh-str {
SELECT sinh('-0.5')
} {-0.521095305493747} $tolerance
do_execsql_test_tolerance sinh-null {
SELECT sinh(null)
} {} $tolerance
do_execsql_test_tolerance sqrt-int {
SELECT sqrt(1)
} {1.0} $tolerance
do_execsql_test_tolerance sqrt-float {
SELECT sqrt(0.5)
} {0.707106781186548} $tolerance
do_execsql_test_tolerance sqrt-str {
SELECT sqrt('0.5')
} {0.707106781186548} $tolerance
do_execsql_test_tolerance sqrt-negative {
SELECT sqrt(-0.5)
} {} $tolerance
do_execsql_test_tolerance sqrt-null {
SELECT sqrt(null)
} {} $tolerance
do_execsql_test_tolerance tanh-int {
SELECT tanh(1)
} {0.761594155955765} $tolerance
do_execsql_test_tolerance tanh-float {
SELECT tanh(-0.5)
} {-0.46211715726001} $tolerance
do_execsql_test_tolerance tanh-str {
SELECT tanh('-0.5')
} {-0.46211715726001} $tolerance
do_execsql_test_tolerance tanh-null {
SELECT tanh(null)
} {} $tolerance
do_execsql_test trunc-int {
SELECT trunc(1)
} {1}
do_execsql_test trunc-float {
SELECT trunc(2.5)
} {2.0}
do_execsql_test trunc-float-negative {
SELECT trunc(-2.5)
} {-2.0}
do_execsql_test trunc-str {
SELECT trunc('2.5')
} {2.0}
do_execsql_test trunc-null {
SELECT trunc(null)
} {}
do_execsql_test_tolerance atan2-int-int {
SELECT atan2(5, -1)
} {1.76819188664478} $tolerance
do_execsql_test_tolerance atan2-int-float {
SELECT atan2(5, -1.5)
} {1.86225312127276} $tolerance
do_execsql_test_tolerance atan2-int-str {
SELECT atan2(5, '-1.5')
} {1.86225312127276} $tolerance
do_execsql_test_tolerance atan2-float-int {
SELECT atan2(5.5, 10)
} {0.502843210927861} $tolerance
do_execsql_test_tolerance atan2-float-float {
SELECT atan2(5.5, -1.5)
} {1.83704837594582} $tolerance
do_execsql_test_tolerance atan2-float-str {
SELECT atan2(5.5, '-1.5')
} {1.83704837594582} $tolerance
do_execsql_test_tolerance atan2-str-str {
SELECT atan2('5.5', '-1.5')
} {1.83704837594582} $tolerance
do_execsql_test atan2-null-int {
SELECT atan2(null, 5)
} {}
do_execsql_test atan2-int-null {
SELECT atan2(5, null)
} {}
do_execsql_test_tolerance mod-int-int {
SELECT mod(10, -3)
} {1.0} $tolerance
do_execsql_test_tolerance mod-int-float {
SELECT mod(5, -1.5)
} {0.5} $tolerance
do_execsql_test_tolerance mod-int-str {
SELECT mod(5, '-1.5')
} {0.5} $tolerance
do_execsql_test_tolerance mod-float-int {
SELECT mod(5.5, 2)
} {1.5} $tolerance
do_execsql_test_tolerance mod-float-float {
SELECT mod(5.5, -1.5)
} {1.0} $tolerance
do_execsql_test_tolerance mod-float-str {
SELECT mod(5.5, '-1.5')
} {1.0} $tolerance
do_execsql_test_tolerance mod-str-str {
SELECT mod('5.5', '-1.5')
} {1.0} $tolerance
do_execsql_test mod-null-int {
SELECT mod(null, 5)
} {}
do_execsql_test mod-int-null {
SELECT mod(5, null)
} {}
do_execsql_test mod-float-zero {
SELECT mod(1.5, 0)
} {}
do_execsql_test mod-products-id {
SELECT mod(products.id, 3) from products limit 5
} {1.0
2.0
0.0
1.0
2.0}
do_execsql_test mod-products-price-id {
SELECT mod(products.price, products.id) from products limit 5
} {0.0
0.0
0.0
1.0
4.0}
do_execsql_test_tolerance pow-int-int {
SELECT pow(5, -1)
} {0.2} $tolerance
do_execsql_test_tolerance pow-int-float {
SELECT pow(5, -1.5)
} {0.0894427190999916} $tolerance
do_execsql_test_tolerance pow-int-str {
SELECT pow(5, '-1.5')
} {0.0894427190999916} $tolerance
do_execsql_test_tolerance pow-float-int {
SELECT pow(5.5, 2)
} {30.25} $tolerance
do_execsql_test_tolerance pow-float-float {
SELECT pow(5.5, -1.5)
} {0.077527533220222} $tolerance
do_execsql_test_tolerance pow-float-str {
SELECT pow(5.5, '-1.5')
} {0.077527533220222} $tolerance
do_execsql_test_tolerance pow-str-str {
SELECT pow('5.5', '-1.5')
} {0.077527533220222} $tolerance
do_execsql_test pow-null-int {
SELECT pow(null, 5)
} {}
do_execsql_test pow-int-null {
SELECT pow(5, null)
} {}
do_execsql_test_tolerance power-int-int {
SELECT power(5, -1)
} {0.2} $tolerance
do_execsql_test_tolerance power-int-float {
SELECT power(5, -1.5)
} {0.0894427190999916} $tolerance
do_execsql_test_tolerance power-int-str {
SELECT power(5, '-1.5')
} {0.0894427190999916} $tolerance
do_execsql_test_tolerance power-float-int {
SELECT power(5.5, 2)
} {30.25} $tolerance
do_execsql_test_tolerance power-float-float {
SELECT power(5.5, -1.5)
} {0.077527533220222} $tolerance
do_execsql_test_tolerance power-float-str {
SELECT power(5.5, '-1.5')
} {0.077527533220222} $tolerance
do_execsql_test_tolerance power-str-str {
SELECT power('5.5', '-1.5')
} {0.077527533220222} $tolerance
do_execsql_test power-null-int {
SELECT power(null, 5)
} {}
do_execsql_test power-int-null {
SELECT power(5, null)
} {}
do_execsql_test_tolerance log-int {
SELECT log(1)
} {0.0} $tolerance
do_execsql_test_tolerance log-float {
SELECT log(1.5)
} {0.176091259055681} $tolerance
do_execsql_test_tolerance log-str {
SELECT log('1.5')
} {0.176091259055681} $tolerance
do_execsql_test log-negative {
SELECT log(-1.5)
} {}
do_execsql_test log-null {
SELECT log(null)
} {}
do_execsql_test_tolerance log-int-int {
SELECT log(5, 1)
} {0.0} $tolerance
do_execsql_test_tolerance log-int-float {
SELECT log(5, 1.5)
} {0.251929636412592} $tolerance
do_execsql_test_tolerance log-int-str {
SELECT log(5, '1.5')
} {0.251929636412592} $tolerance
do_execsql_test_tolerance log-float-int {
SELECT log(5.5, 10)
} {1.35068935021985} $tolerance
do_execsql_test_tolerance log-float-float {
SELECT log(5.5, 1.5)
} {0.237844588273313} $tolerance
do_execsql_test_tolerance log-float-str {
SELECT log(5.5, '1.5')
} {0.237844588273313} $tolerance
do_execsql_test_tolerance log-str-str {
SELECT log('5.5', '1.5')
} {0.237844588273313} $tolerance
do_execsql_test log-negative-negative {
SELECT log(-1.5, -1.5)
} {}
do_execsql_test log-float-negative {
SELECT log(1.5, -1.5)
} {}
do_execsql_test log-null-int {
SELECT log(null, 5)
} {}
do_execsql_test log-int-null {
SELECT log(5, null)
} {}

View File

@@ -32,3 +32,40 @@ proc do_execsql_test_on_specific_db {db_name test_name sql_statements expected_o
set combined_expected_output [join $expected_outputs "\n"]
run_test $::sqlite_exec $db_name $combined_sql $combined_expected_output
}
proc within_tolerance {actual expected tolerance} {
expr {abs($actual - $expected) <= $tolerance}
}
# This function is used to test floating point values within a tolerance
# FIXME: When Limbo's floating point presentation matches to SQLite, this could/should be removed
proc do_execsql_test_tolerance {test_name sql_statements expected_outputs tolerance} {
foreach db $::test_dbs {
puts [format "(%s) %s Running test: %s" $db [string repeat " " [expr {40 - [string length $db]}]] $test_name]
set combined_sql [string trim $sql_statements]
set actual_output [evaluate_sql $::sqlite_exec $db $combined_sql]
set actual_values [split $actual_output "\n"]
set expected_values [split $expected_outputs "\n"]
if {[llength $actual_values] != [llength $expected_values]} {
puts "Test FAILED: '$sql_statements'"
puts "returned '$actual_output'"
puts "expected '$expected_outputs'"
exit 1
}
for {set i 0} {$i < [llength $actual_values]} {incr i} {
set actual [lindex $actual_values $i]
set expected [lindex $expected_values $i]
if {![within_tolerance $actual $expected $tolerance]} {
set lower_bound [expr {$expected - $tolerance}]
set upper_bound [expr {$expected + $tolerance}]
puts "Test FAILED: '$sql_statements'"
puts "returned '$actual'"
puts "expected a value within the range \[$lower_bound, $upper_bound\]"
exit 1
}
}
}
}