mirror of
https://github.com/tursodatabase/limbo.git
synced 2025-08-04 18:18:03 +00:00
feat: abs func initial implementation
This commit is contained in:
parent
188ecf8593
commit
b7c08b8fd5
6 changed files with 172 additions and 61 deletions
118
COMPAT.md
118
COMPAT.md
|
@ -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
|
||||
|
||||
|
|
|
@ -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(()),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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!(),
|
||||
}
|
||||
}
|
||||
|
|
48
core/vdbe.rs
48
core/vdbe.rs
|
@ -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"));
|
||||
|
|
|
@ -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
|
||||
|
|
20
testing/scalar-functions.test
Normal file
20
testing/scalar-functions.test
Normal 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);
|
||||
} {}
|
Loading…
Add table
Add a link
Reference in a new issue