diff --git a/COMPAT.md b/COMPAT.md index 5b03e76e3..1e0def3aa 100644 --- a/COMPAT.md +++ b/COMPAT.md @@ -107,7 +107,7 @@ This document describes the SQLite compatibility status of Limbo: | substr(X,Y,Z) | No | | | substr(X,Y) | No | | | substring(X,Y,Z) | Yes | | -| substring(X,Y) | No | | +| substring(X,Y) | Yes | | | total_changes() | No | | | trim(X) | Yes | | | trim(X,Y) | Yes | | diff --git a/core/translate/expr.rs b/core/translate/expr.rs index 1fb7e1de1..dc9396396 100644 --- a/core/translate/expr.rs +++ b/core/translate/expr.rs @@ -304,9 +304,9 @@ pub fn translate_expr( } ScalarFunc::Substring => { let args = if let Some(args) = args { - if args.len() != 3 { + if !(args.len() == 2 || args.len() == 3) { crate::bail_parse_error!( - "{} function with not exactly 3 arguments", + "{} function with wrong number of arguments", srf.to_string() ) } @@ -324,7 +324,9 @@ pub fn translate_expr( translate_expr(program, select, &args[0], str_reg, cursor_hint)?; translate_expr(program, select, &args[1], start_reg, cursor_hint)?; - translate_expr(program, select, &args[2], length_reg, cursor_hint)?; + if args.len() == 3 { + translate_expr(program, select, &args[2], length_reg, cursor_hint)?; + } program.emit_insn(Insn::Function { start_reg: str_reg, diff --git a/core/vdbe/mod.rs b/core/vdbe/mod.rs index 0031b4169..c1580ff11 100644 --- a/core/vdbe/mod.rs +++ b/core/vdbe/mod.rs @@ -1650,20 +1650,31 @@ fn exec_minmax<'a>( } fn exec_substring(str_value: &OwnedValue, start_value: &OwnedValue, length_value: &OwnedValue) -> OwnedValue { - match (str_value, start_value, length_value) { - (OwnedValue::Text(str), OwnedValue::Integer(start), OwnedValue::Integer(length)) => { - let start = *start as usize; - if (start as usize) > str.len() { - OwnedValue::Text(Rc::new("".to_string())) - } else { - let length = *length as usize; - let start_idx = start - 1; - let end = start_idx + length; - let substring = &str[start_idx..end.min(str.len())]; - OwnedValue::Text(Rc::new(substring.to_string())) - } + if let (OwnedValue::Text(str), OwnedValue::Integer(start), OwnedValue::Integer(length)) = (str_value, start_value, length_value) { + let start = *start as usize; + if start > str.len() { + return OwnedValue::Text(Rc::new("".to_string())); } - _ => OwnedValue::Null, + + let start_idx = start - 1; + let str_len = str.len(); + let end = if *length != -1 { start_idx + *length as usize } else { str_len }; + let substring = &str[start_idx..end.min(str_len)]; + + OwnedValue::Text(Rc::new(substring.to_string())) + } else if let (OwnedValue::Text(str), OwnedValue::Integer(start)) = (str_value, start_value) { + let start = *start as usize; + if start > str.len() { + return OwnedValue::Text(Rc::new("".to_string())); + } + + let start_idx = start - 1; + let str_len = str.len(); + let substring = &str[start_idx..str_len]; + + OwnedValue::Text(Rc::new(substring.to_string())) + } else { + OwnedValue::Null } } @@ -2190,5 +2201,17 @@ mod tests { let length_value = OwnedValue::Integer(3); let expected_val = OwnedValue::Text(Rc::new(String::from(""))); assert_eq!(exec_substring(&str_value, &start_value, &length_value), expected_val); + + let str_value = OwnedValue::Text(Rc::new("limbo".to_string())); + let start_value = OwnedValue::Integer(3); + let length_value = OwnedValue::Null; + let expected_val = OwnedValue::Text(Rc::new(String::from("mbo"))); + assert_eq!(exec_substring(&str_value, &start_value, &length_value), expected_val); + + let str_value = OwnedValue::Text(Rc::new("limbo".to_string())); + let start_value = OwnedValue::Integer(10); + let length_value = OwnedValue::Null; + let expected_val = OwnedValue::Text(Rc::new(String::from(""))); + } } diff --git a/testing/scalar-functions.test b/testing/scalar-functions.test index 5f3264564..b29685118 100755 --- a/testing/scalar-functions.test +++ b/testing/scalar-functions.test @@ -239,18 +239,26 @@ do_execsql_test max-null { select max(null,null) } {} -do_execsql_test substr { +do_execsql_test substr-3-args { SELECT substr('limbo', 1, 3); } {lim} -do_execsql_test substr-exceed-length { +do_execsql_test substr-3-args-exceed-length { SELECT substr('limbo', 1, 10); } {limbo} -do_execsql_test substr-start-exceed-length { +do_execsql_test substr-3-args-start-exceed-length { SELECT substr('limbo', 10, 3); } {} +do_execsql_test substr-2-args { + SELECT substr('limbo', 3); +} {mbo} + +do_execsql_test substr-2-args-exceed-length { + SELECT substr('limbo', 10); +} {} + do_execsql_test date-current-date { SELECT length(date('now')) = 10; } {1}