feat: abs func initial implementation

This commit is contained in:
Brayan Jules 2024-07-17 22:55:41 -04:00
parent 188ecf8593
commit b7c08b8fd5
6 changed files with 172 additions and 61 deletions

118
COMPAT.md
View file

@ -59,65 +59,65 @@ This document describes the SQLite compatibility status of Limbo:
### Scalar functions
| Function | Status | Comment |
|------------------------------|---------|---------|
| abs(X) | No | |
| changes() | No | |
| char(X1,X2,...,XN) | No | |
| coalesce(X,Y,...) | Yes | |
| concat(X,...) | No | |
| concat_ws(SEP,X,...) | No | |
| format(FORMAT,...) | No | |
| glob(X,Y) | No | |
| hex(X) | No | |
| ifnull(X,Y) | No | |
| iif(X,Y,Z) | No | |
| instr(X,Y) | No | |
| last_insert_rowid() | No | |
| length(X) | No | |
| like(X,Y) | No | |
| like(X,Y,Z) | No | |
| likelihood(X,Y) | No | |
| likely(X) | No | |
| load_extension(X) | No | |
| load_extension(X,Y) | No | |
| lower(X) | No | |
| ltrim(X) | No | |
| ltrim(X,Y) | No | |
| max(X,Y,...) | No | |
| min(X,Y,...) | No | |
| nullif(X,Y) | No | |
| octet_length(X) | No | |
| printf(FORMAT,...) | No | |
| quote(X) | No | |
| random() | No | |
| randomblob(N) | No | |
| replace(X,Y,Z) | No | |
| round(X) | No | |
| round(X,Y) | No | |
| rtrim(X) | No | |
| rtrim(X,Y) | No | |
| sign(X) | No | |
| soundex(X) | No | |
| sqlite_compileoption_get(N) | No | |
| sqlite_compileoption_used(X) | No | |
| sqlite_offset(X) | No | |
| sqlite_source_id() | No | |
| sqlite_version() | No | |
| substr(X,Y,Z) | No | |
| substr(X,Y) | No | |
| substring(X,Y,Z) | No | |
| substring(X,Y) | No | |
| total_changes() | No | |
| trim(X) | No | |
| trim(X,Y) | No | |
| typeof(X) | No | |
| unhex(X) | No | |
| unhex(X,Y) | No | |
| unicode(X) | No | |
| unlikely(X) | No | |
| upper(X) | No | |
| zeroblob(N) | No | |
| Function | Status | Comment |
|------------------------------|--------|---------|
| abs(X) | Yes | |
| changes() | No | |
| char(X1,X2,...,XN) | No | |
| coalesce(X,Y,...) | Yes | |
| concat(X,...) | No | |
| concat_ws(SEP,X,...) | No | |
| format(FORMAT,...) | No | |
| glob(X,Y) | No | |
| hex(X) | No | |
| ifnull(X,Y) | No | |
| iif(X,Y,Z) | No | |
| instr(X,Y) | No | |
| last_insert_rowid() | No | |
| length(X) | No | |
| like(X,Y) | No | |
| like(X,Y,Z) | No | |
| likelihood(X,Y) | No | |
| likely(X) | No | |
| load_extension(X) | No | |
| load_extension(X,Y) | No | |
| lower(X) | No | |
| ltrim(X) | No | |
| ltrim(X,Y) | No | |
| max(X,Y,...) | No | |
| min(X,Y,...) | No | |
| nullif(X,Y) | No | |
| octet_length(X) | No | |
| printf(FORMAT,...) | No | |
| quote(X) | No | |
| random() | No | |
| randomblob(N) | No | |
| replace(X,Y,Z) | No | |
| round(X) | No | |
| round(X,Y) | No | |
| rtrim(X) | No | |
| rtrim(X,Y) | No | |
| sign(X) | No | |
| soundex(X) | No | |
| sqlite_compileoption_get(N) | No | |
| sqlite_compileoption_used(X) | No | |
| sqlite_offset(X) | No | |
| sqlite_source_id() | No | |
| sqlite_version() | No | |
| substr(X,Y,Z) | No | |
| substr(X,Y) | No | |
| substring(X,Y,Z) | No | |
| substring(X,Y) | No | |
| total_changes() | No | |
| trim(X) | No | |
| trim(X,Y) | No | |
| typeof(X) | No | |
| unhex(X) | No | |
| unhex(X,Y) | No | |
| unicode(X) | No | |
| unlikely(X) | No | |
| upper(X) | No | |
| zeroblob(N) | No | |
### Aggregate functions

View file

@ -31,6 +31,7 @@ impl AggFunc {
pub enum SingleRowFunc {
Coalesce,
Like,
Abs,
}
impl ToString for SingleRowFunc {
@ -38,6 +39,7 @@ impl ToString for SingleRowFunc {
match self {
SingleRowFunc::Coalesce => "coalesce".to_string(),
SingleRowFunc::Like => "like(2)".to_string(),
SingleRowFunc::Abs => "abs".to_string(),
}
}
}
@ -62,6 +64,7 @@ impl FromStr for Func {
"total" => Ok(Func::Agg(AggFunc::Total)),
"coalesce" => Ok(Func::SingleRow(SingleRowFunc::Coalesce)),
"like" => Ok(Func::SingleRow(SingleRowFunc::Like)),
"abs" => Ok(Func::SingleRow(SingleRowFunc::Abs)),
_ => Err(()),
}
}

View file

@ -8,7 +8,7 @@ use crate::sqlite3_ondisk::{DatabaseHeader, MIN_PAGE_CACHE_SIZE};
use crate::util::normalize_ident;
use crate::vdbe::{BranchOffset, Insn, Program, ProgramBuilder};
use anyhow::Result;
use sqlite3_parser::ast::{self, Expr, Literal};
use sqlite3_parser::ast::{self, Expr, Literal, UnaryOperator};
struct Select<'a> {
columns: &'a Vec<ast::ResultColumn>,
@ -1024,6 +1024,28 @@ fn translate_expr(
});
Ok(target_register)
}
SingleRowFunc::Abs => {
let args = if let Some(args) = args {
if args.len() != 1 {
anyhow::bail!(
"Parse error: abs function with not exactly 1 argument"
);
}
args
} else {
anyhow::bail!("Parse error: abs function with no arguments");
};
let regs = program.alloc_register();
let _ = translate_expr(program, select, &args[0], regs)?;
program.emit_insn(Insn::Function {
start_reg: regs,
dest: target_register,
func: SingleRowFunc::Abs,
});
Ok(target_register)
}
}
}
None => {
@ -1113,7 +1135,24 @@ fn translate_expr(
}
ast::Expr::Raise(_, _) => todo!(),
ast::Expr::Subquery(_) => todo!(),
ast::Expr::Unary(_, _) => todo!(),
ast::Expr::Unary(op, expr) => match (op, expr.as_ref()) {
(UnaryOperator::Negative, ast::Expr::Literal(ast::Literal::Numeric(numeric_value))) => {
let maybe_int = numeric_value.parse::<i64>();
if let Ok(value) = maybe_int {
program.emit_insn(Insn::Integer {
value: -value,
dest: target_register,
});
} else {
program.emit_insn(Insn::Real {
value: -numeric_value.parse::<f64>()?,
dest: target_register,
});
}
Ok(target_register)
}
_ => todo!(),
},
ast::Expr::Variable(_) => todo!(),
}
}

View file

@ -1195,6 +1195,15 @@ impl Program {
state.registers[*dest] = result;
state.pc += 1;
}
SingleRowFunc::Abs => {
let reg_value = state.registers[*start_reg].borrow_mut();
if let Some(value) = exec_abs(reg_value) {
state.registers[*dest] = value;
} else {
state.registers[*dest] = OwnedValue::Null;
}
state.pc += 1;
}
},
}
}
@ -1708,6 +1717,27 @@ fn get_indent_count(indent_count: usize, curr_insn: &Insn, prev_insn: Option<&In
}
}
fn exec_abs(reg: &OwnedValue) -> Option<OwnedValue> {
match reg {
OwnedValue::Integer(x) => {
if x < &0 {
Some(OwnedValue::Integer(-x))
} else {
Some(OwnedValue::Integer(*x))
}
}
OwnedValue::Float(x) => {
if x < &0.0 {
Some(OwnedValue::Float(-x))
} else {
Some(OwnedValue::Float(*x))
}
}
OwnedValue::Null => Some(OwnedValue::Null),
_ => Some(OwnedValue::Float(0.0)),
}
}
// Implements LIKE pattern matching.
fn exec_like(pattern: &str, text: &str) -> bool {
let re = Regex::new(&format!("{}", pattern.replace("%", ".*").replace("_", "."))).unwrap();
@ -1731,6 +1761,24 @@ fn exec_if(reg: &OwnedValue, null_reg: &OwnedValue, not: bool) -> bool {
mod tests {
use super::*;
#[test]
fn test_abs() {
let int_positive_reg = OwnedValue::Integer(10);
let int_negative_reg = OwnedValue::Integer(-10);
assert_eq!(exec_abs(&int_positive_reg).unwrap(), int_positive_reg);
assert_eq!(exec_abs(&int_negative_reg).unwrap(), int_positive_reg);
let float_positive_reg = OwnedValue::Integer(10);
let float_negative_reg = OwnedValue::Integer(-10);
assert_eq!(exec_abs(&float_positive_reg).unwrap(), float_positive_reg);
assert_eq!(exec_abs(&float_negative_reg).unwrap(), float_positive_reg);
assert_eq!(
exec_abs(&OwnedValue::Text(Rc::new(String::from("a")))).unwrap(),
OwnedValue::Float(0.0)
);
assert_eq!(exec_abs(&OwnedValue::Null).unwrap(), OwnedValue::Null);
}
#[test]
fn test_like() {
assert!(exec_like("a%", "aaaa"));

View file

@ -9,3 +9,4 @@ source $testdir/pragma.test
source $testdir/select.test
source $testdir/where.test
source $testdir/like.test
source $testdir/scalar-functions.test

View file

@ -0,0 +1,20 @@
#!/usr/bin/env tclsh
set testdir [file dirname $argv0]
source $testdir/tester.tcl
do_execsql_test abs {
select abs(1);
} {1}
do_execsql_test abs-negative {
select abs(-1);
} {1}
do_execsql_test abs-char {
select abs('a');
} {0.0}
do_execsql_test abs-null {
select abs(null);
} {}