///|
/// A small, inspectable expression tree. Expressions are data, so plans can be
/// serialized, printed, rewritten and tested without closures.
pub(all) enum Expr {
Col(String)
Lit(Value)
Bin(String, Expr, Expr)
Not(Expr)
IsNull(Expr)
Cast(Expr, DataType)
Call(String, Array[Expr])
} derive(Eq)
///|
fn as_float(value : Value) -> Double? {
match value {
VInt(n) => Some(n.to_double())
VFloat(f) => Some(f)
_ => None
}
}
///|
fn as_text(value : Value) -> String {
value_text(value)
}
///|
fn numeric_binary(op : String, left : Value, right : Value) -> Value {
if left is VNull || right is VNull {
return VNull
}
match (left, right) {
(VInt(a), VInt(b)) =>
match op {
"+" => VInt(a + b)
"-" => VInt(a - b)
"*" => VInt(a * b)
"/" => if b == 0L { VNull } else { VInt(a / b) }
"%" => if b == 0L { VNull } else { VInt(a % b) }
_ => VNull
}
_ => {
let a = as_float(left)
let b = as_float(right)
match (a, b) {
(Some(x), Some(y)) =>
match op {
"+" => VFloat(x + y)
"-" => VFloat(x - y)
"*" => VFloat(x * y)
"/" => if y == 0.0 { VNull } else { VFloat(x / y) }
"%" => VNull
_ => VNull
}
_ => VNull
}
}
}
}
///|
fn bool_and(left : Value, right : Value) -> Value {
match (left, right) {
(VBool(false), _) | (_, VBool(false)) => VBool(false)
(VBool(true), VBool(true)) => VBool(true)
_ => VNull
}
}
///|
fn bool_or(left : Value, right : Value) -> Value {
match (left, right) {
(VBool(true), _) | (_, VBool(true)) => VBool(true)
(VBool(false), VBool(false)) => VBool(false)
_ => VNull
}
}
///|
fn eval_binary(op : String, left : Value, right : Value) -> Value {
match op {
"+" | "-" | "*" | "/" | "%" => numeric_binary(op, left, right)
"and" => bool_and(left, right)
"or" => bool_or(left, right)
"=" =>
if left is VNull || right is VNull {
VNull
} else {
VBool(value_compare(left, right) == 0)
}
"!=" =>
if left is VNull || right is VNull {
VNull
} else {
VBool(value_compare(left, right) != 0)
}
"<" =>
if left is VNull || right is VNull {
VNull
} else {
VBool(value_compare(left, right) < 0)
}
"<=" =>
if left is VNull || right is VNull {
VNull
} else {
VBool(value_compare(left, right) <= 0)
}
">" =>
if left is VNull || right is VNull {
VNull
} else {
VBool(value_compare(left, right) > 0)
}
">=" =>
if left is VNull || right is VNull {
VNull
} else {
VBool(value_compare(left, right) >= 0)
}
_ => VNull
}
}
///|
fn cast_value(value : Value, dtype : DataType) -> Value {
match dtype {
TNull => VNull
TText => VText(as_text(value))
TBool =>
match value {
VNull => VNull
VBool(_) => value
VInt(n) => VBool(n != 0L)
VFloat(f) => VBool(f != 0.0)
VText(s) =>
match parse_bool_opt(s) {
Some(b) => VBool(b)
None => VNull
}
}
TInt =>
match value {
VNull => VNull
VInt(_) => value
VBool(b) => VInt(if b { 1L } else { 0L })
VFloat(f) => VInt(f.to_int64())
VText(s) =>
match parse_int64_opt(s) {
Some(n) => VInt(n)
None => VNull
}
}
TFloat =>
match value {
VNull => VNull
VFloat(_) => value
VInt(n) => VFloat(n.to_double())
VBool(b) => VFloat(if b { 1.0 } else { 0.0 })
VText(s) =>
match parse_float_opt(s) {
Some(f) => VFloat(f)
None => VNull
}
}
}
}
///|
pub fn eval_expr(
expr : Expr,
batch : Batch,
row : Int32,
) -> Value raise EngineError {
match expr {
Col(name) => {
let index = batch_column_index(batch, name)
batch_cell(batch, index, row)
}
Lit(value) => value
Bin(op, left, right) => {
let a = eval_expr(left, batch, row)
let b = eval_expr(right, batch, row)
eval_binary(op, a, b)
}
Not(inner) =>
match eval_expr(inner, batch, row) {
VBool(b) => VBool(!b)
_ => VNull
}
IsNull(inner) => VBool(value_is_null(eval_expr(inner, batch, row)))
Cast(inner, dtype) => cast_value(eval_expr(inner, batch, row), dtype)
Call(name, args) => {
let values = []
for arg in args {
values.push(eval_expr(arg, batch, row))
}
match name {
"lower" =>
match values {
[VText(s), ..] => VText(s.to_lower())
_ => VNull
}
"upper" =>
match values {
[VText(s), ..] => VText(s.to_upper())
_ => VNull
}
"len" =>
match values {
[VText(s), ..] => VInt(s.length().to_int64())
_ => VNull
}
"coalesce" => {
let mut result = VNull
for value in values {
if !value_is_null(value) {
result = value
break
}
}
result
}
_ => VNull
}
}
}
}
///|
pub fn expr_columns(expr : Expr, out : Array[String]) -> Unit {
match expr {
Col(name) => out.push(name)
Lit(_) => ()
Bin(_, left, right) => {
expr_columns(left, out)
expr_columns(right, out)
}
Not(inner) | IsNull(inner) | Cast(inner, _) => expr_columns(inner, out)
Call(_, args) =>
for arg in args {
expr_columns(arg, out)
}
}
}
///|
pub fn expr_to_string(expr : Expr) -> String {
match expr {
Col(name) => name
Lit(value) => "'" + value_text(value) + "'"
Bin(op, left, right) =>
"(" + expr_to_string(left) + " " + op + " " + expr_to_string(right) + ")"
Not(inner) => "not(" + expr_to_string(inner) + ")"
IsNull(inner) => "is_null(" + expr_to_string(inner) + ")"
Cast(inner, dtype) =>
"cast(" + expr_to_string(inner) + ", " + dtype_name(dtype) + ")"
Call(name, args) => name + "(" + args.map(expr_to_string).join(", ") + ")"
}
}