From 73c8fc23ba8627e867a0aaad2ac957023c1b56b0 Mon Sep 17 00:00:00 2001 From: Brayan Jules Date: Fri, 19 Jul 2024 00:38:12 -0400 Subject: [PATCH] implementation of scalar functions upper and lower --- COMPAT.md | 4 +-- core/expr.rs | 44 ++++++++++++++++++++++++++++ core/function.rs | 6 ++++ core/vdbe.rs | 55 ++++++++++++++++++++++++++++++++++- testing/scalar-functions.test | 32 ++++++++++++++++++++ 5 files changed, 138 insertions(+), 3 deletions(-) diff --git a/COMPAT.md b/COMPAT.md index 12d479d88..c32e71e20 100644 --- a/COMPAT.md +++ b/COMPAT.md @@ -81,7 +81,7 @@ This document describes the SQLite compatibility status of Limbo: | likely(X) | No | | | load_extension(X) | No | | | load_extension(X,Y) | No | | -| lower(X) | No | | +| lower(X) | Yes | | | ltrim(X) | No | | | ltrim(X,Y) | No | | | max(X,Y,...) | No | | @@ -116,7 +116,7 @@ This document describes the SQLite compatibility status of Limbo: | unhex(X,Y) | No | | | unicode(X) | No | | | unlikely(X) | No | | -| upper(X) | No | | +| upper(X) | Yes | | | zeroblob(N) | No | | ### Aggregate functions diff --git a/core/expr.rs b/core/expr.rs index b4bf181fe..dfc79d634 100644 --- a/core/expr.rs +++ b/core/expr.rs @@ -309,6 +309,50 @@ pub fn translate_expr( func: SingleRowFunc::Abs, }); + Ok(target_register) + } + SingleRowFunc::Upper => { + let args = if let Some(args) = args { + if args.len() != 1 { + anyhow::bail!( + "Parse error: upper function with not exactly 1 argument" + ); + } + args + } else { + anyhow::bail!("Parse error: upper 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::Upper, + }); + + Ok(target_register) + } + SingleRowFunc::Lower => { + let args = if let Some(args) = args { + if args.len() != 1 { + anyhow::bail!( + "Parse error: lower function with not exactly 1 argument" + ); + } + args + } else { + anyhow::bail!("Parse error: lower 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::Lower, + }); + Ok(target_register) } } diff --git a/core/function.rs b/core/function.rs index b8a49c247..5bd12eb02 100644 --- a/core/function.rs +++ b/core/function.rs @@ -32,6 +32,8 @@ pub enum SingleRowFunc { Coalesce, Like, Abs, + Upper, + Lower, } impl ToString for SingleRowFunc { @@ -40,6 +42,8 @@ impl ToString for SingleRowFunc { SingleRowFunc::Coalesce => "coalesce".to_string(), SingleRowFunc::Like => "like(2)".to_string(), SingleRowFunc::Abs => "abs".to_string(), + SingleRowFunc::Upper => "upper".to_string(), + SingleRowFunc::Lower => "lower".to_string(), } } } @@ -65,6 +69,8 @@ impl FromStr for Func { "coalesce" => Ok(Func::SingleRow(SingleRowFunc::Coalesce)), "like" => Ok(Func::SingleRow(SingleRowFunc::Like)), "abs" => Ok(Func::SingleRow(SingleRowFunc::Abs)), + "upper" => Ok(Func::SingleRow(SingleRowFunc::Upper)), + "lower" => Ok(Func::SingleRow(SingleRowFunc::Lower)), _ => Err(()), } } diff --git a/core/vdbe.rs b/core/vdbe.rs index 1f0c2f993..f3857fbb4 100644 --- a/core/vdbe.rs +++ b/core/vdbe.rs @@ -1271,6 +1271,24 @@ impl Program { } state.pc += 1; } + SingleRowFunc::Upper => { + let reg_value = state.registers[*start_reg].borrow_mut(); + if let Some(value) = exec_upper(reg_value) { + state.registers[*dest] = value; + } else { + state.registers[*dest] = OwnedValue::Null; + } + state.pc += 1; + } + SingleRowFunc::Lower => { + let reg_value = state.registers[*start_reg].borrow_mut(); + if let Some(value) = exec_lower(reg_value) { + state.registers[*dest] = value; + } else { + state.registers[*dest] = OwnedValue::Null; + } + state.pc += 1; + } }, } } @@ -1809,6 +1827,19 @@ fn get_indent_count(indent_count: usize, curr_insn: &Insn, prev_insn: Option<&In } } +fn exec_lower(reg: &OwnedValue) -> Option { + match reg { + OwnedValue::Text(t) => Some(OwnedValue::Text(Rc::new(t.to_lowercase()))), + t => Some(t.to_owned()), + } +} + +fn exec_upper(reg: &OwnedValue) -> Option { + match reg { + OwnedValue::Text(t) => Some(OwnedValue::Text(Rc::new(t.to_uppercase()))), + t => Some(t.to_owned()), + } +} fn exec_abs(reg: &OwnedValue) -> Option { match reg { OwnedValue::Integer(x) => { @@ -1852,9 +1883,31 @@ fn exec_if(reg: &OwnedValue, null_reg: &OwnedValue, not: bool) -> bool { #[cfg(test)] mod tests { - use super::{exec_abs, exec_if, exec_like, OwnedValue}; + use super::{exec_abs, exec_if, exec_like, exec_lower, exec_upper, OwnedValue}; use std::rc::Rc; + #[test] + fn test_upper_case() { + let input_str = OwnedValue::Text(Rc::new(String::from("Limbo"))); + let expected_str = OwnedValue::Text(Rc::new(String::from("LIMBO"))); + assert_eq!(exec_upper(&input_str).unwrap(), expected_str); + + let input_int = OwnedValue::Integer(10); + assert_eq!(exec_upper(&input_int).unwrap(), input_int); + assert_eq!(exec_upper(&OwnedValue::Null).unwrap(), OwnedValue::Null) + } + + #[test] + fn test_lower_case() { + let input_str = OwnedValue::Text(Rc::new(String::from("Limbo"))); + let expected_str = OwnedValue::Text(Rc::new(String::from("limbo"))); + assert_eq!(exec_lower(&input_str).unwrap(), expected_str); + + let input_int = OwnedValue::Integer(10); + assert_eq!(exec_lower(&input_int).unwrap(), input_int); + assert_eq!(exec_lower(&OwnedValue::Null).unwrap(), OwnedValue::Null) + } + #[test] fn test_abs() { let int_positive_reg = OwnedValue::Integer(10); diff --git a/testing/scalar-functions.test b/testing/scalar-functions.test index 0f9b17e0b..b93f82cd6 100644 --- a/testing/scalar-functions.test +++ b/testing/scalar-functions.test @@ -18,3 +18,35 @@ do_execsql_test abs-char { do_execsql_test abs-null { select abs(null); } {} + +do_execsql_test upper { + select upper('Limbo') +} {LIMBO} + +do_execsql_test upper-number { + select upper(1) +} {1} + +do_execsql_test upper-char { + select upper('a') +} {A} + +do_execsql_test upper-null { + select upper(null) +} {} + +do_execsql_test lower { + select lower('Limbo') +} {limbo} + +do_execsql_test lower-number { + select lower(1) +} {1} + +do_execsql_test lower-char { + select lower('A') +} {a} + +do_execsql_test lower-null { + select lower(null) +} {}