From b7c08b8fd5b9c31659fd8fc6520a6fefd1e3ca92 Mon Sep 17 00:00:00 2001 From: Brayan Jules Date: Wed, 17 Jul 2024 22:55:41 -0400 Subject: [PATCH] feat: abs func initial implementation --- COMPAT.md | 118 +++++++++++++++++----------------- core/function.rs | 3 + core/translate.rs | 43 ++++++++++++- core/vdbe.rs | 48 ++++++++++++++ testing/all.test | 1 + testing/scalar-functions.test | 20 ++++++ 6 files changed, 172 insertions(+), 61 deletions(-) create mode 100644 testing/scalar-functions.test diff --git a/COMPAT.md b/COMPAT.md index f0623434b..004021d8e 100644 --- a/COMPAT.md +++ b/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 diff --git a/core/function.rs b/core/function.rs index bccf53eb1..b8a49c247 100644 --- a/core/function.rs +++ b/core/function.rs @@ -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(()), } } diff --git a/core/translate.rs b/core/translate.rs index 63306a4dd..8f333d1d5 100644 --- a/core/translate.rs +++ b/core/translate.rs @@ -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, @@ -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::(); + 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::()?, + dest: target_register, + }); + } + Ok(target_register) + } + _ => todo!(), + }, ast::Expr::Variable(_) => todo!(), } } diff --git a/core/vdbe.rs b/core/vdbe.rs index 970d069e6..41a4dba1a 100644 --- a/core/vdbe.rs +++ b/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 { + 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")); diff --git a/testing/all.test b/testing/all.test index 52d2fa222..4cb3d9460 100755 --- a/testing/all.test +++ b/testing/all.test @@ -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 diff --git a/testing/scalar-functions.test b/testing/scalar-functions.test new file mode 100644 index 000000000..0f9b17e0b --- /dev/null +++ b/testing/scalar-functions.test @@ -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); +} {}