From 918b99b7a3478d3f34e206b503c6607efa6aecd0 Mon Sep 17 00:00:00 2001 From: macroexpansion Date: Sun, 28 Jul 2024 21:17:53 +0700 Subject: [PATCH] feat: add RTRIM(X) and RTRIM(X,Y) scalar function --- COMPAT.md | 4 +-- core/function.rs | 3 +++ core/translate/expr.rs | 5 +++- core/vdbe/mod.rs | 48 ++++++++++++++++++++++++++++++++++- testing/scalar-functions.test | 36 ++++++++++++++++++++++++++ 5 files changed, 92 insertions(+), 4 deletions(-) diff --git a/COMPAT.md b/COMPAT.md index b41901788..a4d4b68f8 100644 --- a/COMPAT.md +++ b/COMPAT.md @@ -95,8 +95,8 @@ This document describes the SQLite compatibility status of Limbo: | replace(X,Y,Z) | No | | | round(X) | Yes | | | round(X,Y) | Yes | | -| rtrim(X) | No | | -| rtrim(X,Y) | No | | +| rtrim(X) | Yes | | +| rtrim(X,Y) | Yes | | | sign(X) | No | | | soundex(X) | No | | | sqlite_compileoption_get(N) | No | | diff --git a/core/function.rs b/core/function.rs index 29477f9ed..75b53f940 100644 --- a/core/function.rs +++ b/core/function.rs @@ -35,6 +35,7 @@ pub enum ScalarFunc { Random, Trim, LTrim, + RTrim, Round, Length, Min, @@ -54,6 +55,7 @@ impl ToString for ScalarFunc { ScalarFunc::Random => "random".to_string(), ScalarFunc::Trim => "trim".to_string(), ScalarFunc::LTrim => "ltrim".to_string(), + ScalarFunc::RTrim => "rtrim".to_string(), ScalarFunc::Round => "round".to_string(), ScalarFunc::Length => "length".to_string(), ScalarFunc::Min => "min".to_string(), @@ -91,6 +93,7 @@ impl Func { "random" => Ok(Func::Scalar(ScalarFunc::Random)), "trim" => Ok(Func::Scalar(ScalarFunc::Trim)), "ltrim" => Ok(Func::Scalar(ScalarFunc::LTrim)), + "rtrim" => Ok(Func::Scalar(ScalarFunc::RTrim)), "round" => Ok(Func::Scalar(ScalarFunc::Round)), "length" => Ok(Func::Scalar(ScalarFunc::Length)), "date" => Ok(Func::Scalar(ScalarFunc::Date)), diff --git a/core/translate/expr.rs b/core/translate/expr.rs index d9ac92a21..163357978 100644 --- a/core/translate/expr.rs +++ b/core/translate/expr.rs @@ -275,7 +275,10 @@ pub fn translate_expr( }); Ok(target_register) } - ScalarFunc::Trim | ScalarFunc::LTrim | ScalarFunc::Round => { + ScalarFunc::Trim + | ScalarFunc::LTrim + | ScalarFunc::RTrim + | ScalarFunc::Round => { let args = if let Some(args) = args { if args.len() > 2 { crate::bail_parse_error!( diff --git a/core/vdbe/mod.rs b/core/vdbe/mod.rs index defc63e14..587536bed 100644 --- a/core/vdbe/mod.rs +++ b/core/vdbe/mod.rs @@ -1125,6 +1125,16 @@ impl Program { state.registers[*dest] = result; state.pc += 1; } + ScalarFunc::RTrim => { + let start_reg = *start_reg; + let reg_value = state.registers[start_reg].clone(); + let pattern_value = state.registers.get(start_reg + 1).cloned(); + + let result = exec_rtrim(®_value, pattern_value); + + state.registers[*dest] = result; + state.pc += 1; + } ScalarFunc::Round => { let start_reg = *start_reg; let reg_value = state.registers[start_reg].clone(); @@ -1379,6 +1389,25 @@ fn exec_ltrim(reg: &OwnedValue, pattern: Option) -> OwnedValue { } } +// Implements RTRIM pattern matching. +fn exec_rtrim(reg: &OwnedValue, pattern: Option) -> OwnedValue { + match (reg, pattern) { + (reg, Some(pattern)) => match reg { + OwnedValue::Text(_) | OwnedValue::Integer(_) | OwnedValue::Float(_) => { + let pattern_chars: Vec = pattern.to_string().chars().collect(); + OwnedValue::Text(Rc::new( + reg.to_string() + .trim_end_matches(&pattern_chars[..]) + .to_string(), + )) + } + _ => reg.to_owned(), + }, + (OwnedValue::Text(t), None) => OwnedValue::Text(Rc::new(t.trim_end().to_string())), + (reg, _) => reg.to_owned(), + } +} + // exec_if returns whether you should jump fn exec_if(reg: &OwnedValue, null_reg: &OwnedValue, not: bool) -> bool { match reg { @@ -1397,7 +1426,7 @@ fn exec_if(reg: &OwnedValue, null_reg: &OwnedValue, not: bool) -> bool { mod tests { use super::{ exec_abs, exec_if, exec_length, exec_like, exec_lower, exec_ltrim, exec_minmax, - exec_random, exec_round, exec_trim, exec_unicode, exec_upper, OwnedValue, + exec_random, exec_round, exec_rtrim, exec_trim, exec_unicode, exec_upper, OwnedValue, }; use std::rc::Rc; @@ -1518,6 +1547,23 @@ mod tests { assert_eq!(exec_ltrim(&input_str, Some(pattern_str)), expected_str); } + #[test] + fn test_rtrim() { + let input_str = OwnedValue::Text(Rc::new(String::from(" Bob and Alice "))); + let expected_str = OwnedValue::Text(Rc::new(String::from(" Bob and Alice"))); + assert_eq!(exec_rtrim(&input_str, None), expected_str); + + let input_str = OwnedValue::Text(Rc::new(String::from(" Bob and Alice "))); + let pattern_str = OwnedValue::Text(Rc::new(String::from("Bob and"))); + let expected_str = OwnedValue::Text(Rc::new(String::from(" Bob and Alice"))); + assert_eq!(exec_rtrim(&input_str, Some(pattern_str)), expected_str); + + let input_str = OwnedValue::Text(Rc::new(String::from(" Bob and Alice "))); + let pattern_str = OwnedValue::Text(Rc::new(String::from("and Alice"))); + let expected_str = OwnedValue::Text(Rc::new(String::from(" Bob"))); + assert_eq!(exec_rtrim(&input_str, Some(pattern_str)), expected_str); + } + #[test] fn test_upper_case() { let input_str = OwnedValue::Text(Rc::new(String::from("Limbo"))); diff --git a/testing/scalar-functions.test b/testing/scalar-functions.test index cf894abfc..eb10ba8b1 100644 --- a/testing/scalar-functions.test +++ b/testing/scalar-functions.test @@ -123,6 +123,42 @@ do_execsql_test ltrim-no-match-pattern { SELECT ltrim('Limbo', 'xyz'); } {Limbo} +do_execsql_test rtrim { + SELECT rtrim(' Limbo '); +} {" Limbo"} + +do_execsql_test rtrim-number { + SELECT rtrim(1); +} {1} + +do_execsql_test rtrim-null { + SELECT rtrim(null); +} {} + +do_execsql_test rtrim-trailing-whitespace { + SELECT rtrim('Trailing '); +} {Trailing} + +do_execsql_test rtrim-no-trailing-whitespace { + SELECT rtrim('Limbo'); +} {Limbo} + +do_execsql_test rtrim-pattern { + SELECT rtrim('Limbo', 'Limbo'); +} {} + +do_execsql_test rtrim-pattern-number { + SELECT rtrim(1, '1'); +} {} + +do_execsql_test rtrim-pattern-null { + SELECT rtrim(null, 'null'); +} {} + +do_execsql_test rtrim-no-match-pattern { + SELECT rtrim('Limbo', 'xyz'); +} {Limbo} + do_execsql_test round-float-no-precision { SELECT round(123.456); } {123.0}