diff --git a/COMPAT.md b/COMPAT.md index 7d192760d..18c073f5d 100644 --- a/COMPAT.md +++ b/COMPAT.md @@ -82,8 +82,8 @@ This document describes the SQLite compatibility status of Limbo: | load_extension(X) | No | | | load_extension(X,Y) | No | | | lower(X) | Yes | | -| ltrim(X) | No | | -| ltrim(X,Y) | No | | +| ltrim(X) | Yes | | +| ltrim(X,Y) | Yes | | | max(X,Y,...) | No | | | min(X,Y,...) | No | | | nullif(X,Y) | No | | diff --git a/core/function.rs b/core/function.rs index e686c6721..29477f9ed 100644 --- a/core/function.rs +++ b/core/function.rs @@ -34,6 +34,7 @@ pub enum ScalarFunc { Lower, Random, Trim, + LTrim, Round, Length, Min, @@ -52,6 +53,7 @@ impl ToString for ScalarFunc { ScalarFunc::Lower => "lower".to_string(), ScalarFunc::Random => "random".to_string(), ScalarFunc::Trim => "trim".to_string(), + ScalarFunc::LTrim => "ltrim".to_string(), ScalarFunc::Round => "round".to_string(), ScalarFunc::Length => "length".to_string(), ScalarFunc::Min => "min".to_string(), @@ -88,6 +90,7 @@ impl Func { "lower" => Ok(Func::Scalar(ScalarFunc::Lower)), "random" => Ok(Func::Scalar(ScalarFunc::Random)), "trim" => Ok(Func::Scalar(ScalarFunc::Trim)), + "ltrim" => Ok(Func::Scalar(ScalarFunc::LTrim)), "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 370db02f1..391391b42 100644 --- a/core/translate/expr.rs +++ b/core/translate/expr.rs @@ -275,7 +275,7 @@ pub fn translate_expr( }); Ok(target_register) } - ScalarFunc::Trim | ScalarFunc::Round => { + ScalarFunc::Trim | ScalarFunc::LTrim | 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 10e764f77..defc63e14 100644 --- a/core/vdbe/mod.rs +++ b/core/vdbe/mod.rs @@ -1115,6 +1115,16 @@ impl Program { state.registers[*dest] = result; state.pc += 1; } + ScalarFunc::LTrim => { + 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_ltrim(®_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(); @@ -1350,6 +1360,25 @@ fn exec_trim(reg: &OwnedValue, pattern: Option) -> OwnedValue { } } +// Implements LTRIM pattern matching. +fn exec_ltrim(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_start_matches(&pattern_chars[..]) + .to_string(), + )) + } + _ => reg.to_owned(), + }, + (OwnedValue::Text(t), None) => OwnedValue::Text(Rc::new(t.trim_start().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 { @@ -1367,8 +1396,8 @@ fn exec_if(reg: &OwnedValue, null_reg: &OwnedValue, not: bool) -> bool { #[cfg(test)] mod tests { use super::{ - exec_abs, exec_if, exec_length, exec_like, exec_lower, exec_minmax, exec_random, - exec_round, exec_trim, exec_unicode, exec_upper, OwnedValue, + 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, }; use std::rc::Rc; @@ -1477,6 +1506,18 @@ mod tests { assert_eq!(exec_trim(&input_str, Some(pattern_str)), expected_str); } + #[test] + fn test_ltrim() { + 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_ltrim(&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("Alice "))); + assert_eq!(exec_ltrim(&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 248a69987..cf894abfc 100644 --- a/testing/scalar-functions.test +++ b/testing/scalar-functions.test @@ -87,6 +87,42 @@ do_execsql_test trim-no-match-pattern { SELECT trim('Limbo', 'xyz'); } {Limbo} +do_execsql_test ltrim { + SELECT ltrim(' Limbo '); +} {"Limbo "} + +do_execsql_test ltrim-number { + SELECT ltrim(1); +} {1} + +do_execsql_test ltrim-null { + SELECT ltrim(null); +} {} + +do_execsql_test ltrim-leading-whitespace { + SELECT ltrim(' Leading'); +} {Leading} + +do_execsql_test ltrim-no-leading-whitespace { + SELECT ltrim('Limbo'); +} {Limbo} + +do_execsql_test ltrim-pattern { + SELECT ltrim('Limbo', 'Limbo'); +} {} + +do_execsql_test ltrim-pattern-number { + SELECT ltrim(1, '1'); +} {} + +do_execsql_test ltrim-pattern-null { + SELECT ltrim(null, 'null'); +} {} + +do_execsql_test ltrim-no-match-pattern { + SELECT ltrim('Limbo', 'xyz'); +} {Limbo} + do_execsql_test round-float-no-precision { SELECT round(123.456); } {123.0}