diff --git a/COMPAT.md b/COMPAT.md index 47265c521..7ece6b1c4 100644 --- a/COMPAT.md +++ b/COMPAT.md @@ -66,7 +66,7 @@ This document describes the SQLite compatibility status of Limbo: | char(X1,X2,...,XN) | Yes | | | coalesce(X,Y,...) | Yes | | | concat(X,...) | Yes | | -| concat_ws(SEP,X,...) | No | | +| concat_ws(SEP,X,...) | Yes | | | format(FORMAT,...) | No | | | glob(X,Y) | No | | | hex(X) | No | | diff --git a/core/function.rs b/core/function.rs index a368867a6..6a5a0036c 100644 --- a/core/function.rs +++ b/core/function.rs @@ -43,6 +43,7 @@ pub enum ScalarFunc { Char, Coalesce, Concat, + ConcatWs, IfNull, Like, Abs, @@ -72,6 +73,7 @@ impl ToString for ScalarFunc { ScalarFunc::Char => "char".to_string(), ScalarFunc::Coalesce => "coalesce".to_string(), ScalarFunc::Concat => "concat".to_string(), + ScalarFunc::ConcatWs => "concat_ws".to_string(), ScalarFunc::IfNull => "ifnull".to_string(), ScalarFunc::Like => "like(2)".to_string(), ScalarFunc::Abs => "abs".to_string(), @@ -137,6 +139,7 @@ impl Func { "char" => Ok(Func::Scalar(ScalarFunc::Char)), "coalesce" => Ok(Func::Scalar(ScalarFunc::Coalesce)), "concat" => Ok(Func::Scalar(ScalarFunc::Concat)), + "concat_ws" => Ok(Func::Scalar(ScalarFunc::ConcatWs)), "ifnull" => Ok(Func::Scalar(ScalarFunc::IfNull)), "like" => Ok(Func::Scalar(ScalarFunc::Like)), "abs" => Ok(Func::Scalar(ScalarFunc::Abs)), diff --git a/core/translate/expr.rs b/core/translate/expr.rs index 96c49f505..47e613e03 100644 --- a/core/translate/expr.rs +++ b/core/translate/expr.rs @@ -864,6 +864,45 @@ pub fn translate_expr( }); Ok(target_register) } + ScalarFunc::ConcatWs => { + let args = match args { + Some(args) if args.len() >= 2 => args, + Some(_) => crate::bail_parse_error!( + "{} function requires at least 2 arguments", + srf.to_string() + ), + None => crate::bail_parse_error!( + "{} function requires arguments", + srf.to_string() + ), + }; + + let temp_register = program.alloc_register(); + for arg in args.iter() { + let reg = program.alloc_register(); + translate_expr( + program, + referenced_tables, + arg, + reg, + cursor_hint, + cached_results, + )?; + } + program.emit_insn(Insn::Function { + constant_mask: 0, + start_reg: temp_register + 1, + dest: temp_register, + func: func_ctx, + }); + + program.emit_insn(Insn::Copy { + src_reg: temp_register, + dst_reg: target_register, + amount: 1, + }); + Ok(target_register) + } ScalarFunc::IfNull => { let args = match args { Some(args) if args.len() == 2 => args, diff --git a/core/vdbe/mod.rs b/core/vdbe/mod.rs index e5f24693f..df7f78016 100644 --- a/core/vdbe/mod.rs +++ b/core/vdbe/mod.rs @@ -1434,6 +1434,11 @@ impl Program { ); state.registers[*dest] = result; } + ScalarFunc::ConcatWs => { + let start_reg = *start_reg; + let result = exec_concat_ws(&state.registers[start_reg..]); + state.registers[*dest] = result; + } ScalarFunc::IfNull => {} ScalarFunc::Like => { let pattern = &state.registers[*start_reg]; @@ -1828,6 +1833,34 @@ fn exec_concat(registers: &[OwnedValue]) -> OwnedValue { OwnedValue::Text(Rc::new(result)) } +fn exec_concat_ws(registers: &[OwnedValue]) -> OwnedValue { + if registers.is_empty() { + return OwnedValue::Null; + } + + let separator = match ®isters[0] { + OwnedValue::Text(text) => text.clone(), + OwnedValue::Integer(i) => Rc::new(i.to_string()), + OwnedValue::Float(f) => Rc::new(f.to_string()), + _ => return OwnedValue::Null, + }; + + let mut result = String::new(); + for (i, reg) in registers.iter().enumerate().skip(1) { + if i > 1 { + result.push_str(&separator); + } + match reg { + OwnedValue::Text(text) => result.push_str(text), + OwnedValue::Integer(i) => result.push_str(&i.to_string()), + OwnedValue::Float(f) => result.push_str(&f.to_string()), + _ => continue, + } + } + + OwnedValue::Text(Rc::new(result)) +} + fn exec_abs(reg: &OwnedValue) -> Option { match reg { OwnedValue::Integer(x) => { diff --git a/testing/scalar-functions.test b/testing/scalar-functions.test index d1a6699e1..8ede9f78b 100755 --- a/testing/scalar-functions.test +++ b/testing/scalar-functions.test @@ -19,6 +19,22 @@ do_execsql_test concat-4 { select concat('l', null, 'i'); } {li} +do_execsql_test concat_ws-1 { + select concat_ws(',', 1, 2); +} {1,2} + +do_execsql_test concat_ws-2 { + select concat_ws(',', 1); +} {1} + +do_execsql_test concat_ws-3 { + select concat_ws(0, 1, 2); +} {102} + +do_execsql_test concat_ws-4 { + select concat_ws(null, 1, 2); +} {} + do_execsql_test char { select char(108, 105) } {li} @@ -369,4 +385,4 @@ do_execsql_test quote-null { do_execsql_test quote-integer { SELECT quote(123) -} {123} \ No newline at end of file +} {123}