Skip to content

Commit

Permalink
Add LIST type support in CONCAT function (gluesql#1021)
Browse files Browse the repository at this point in the history
Add LIST Datatype to Concat Function
This Concat Function will concatenate two arrays
  • Loading branch information
seonghun-dev committed Nov 26, 2022
1 parent 90efdf9 commit 075a3c4
Show file tree
Hide file tree
Showing 6 changed files with 68 additions and 45 deletions.
3 changes: 3 additions & 0 deletions core/src/data/value/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ pub enum ValueError {
#[error("unimplemented cast")]
UnimplementedCast,

#[error("function CONCAT requires at least 1 argument")]
EmptyArgNotAllowedInConcat,

// Cast errors from literal to value
#[error("literal cast failed from text to integer: {0}")]
LiteralCastFromTextToIntegerFailed(String),
Expand Down
41 changes: 25 additions & 16 deletions core/src/data/value/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -240,10 +240,11 @@ impl Value {
}
}

pub fn concat(&self, other: &Value) -> Value {
pub fn concat(self, other: Value) -> Value {
match (self, other) {
(Value::Null, _) | (_, Value::Null) => Value::Null,
_ => Value::Str(String::from(self) + &String::from(other)),
(Value::List(l), Value::List(r)) => Value::List([l, r].concat()),
(l, r) => Value::Str(String::from(l) + &String::from(r)),
}
}

Expand Down Expand Up @@ -1502,20 +1503,28 @@ mod tests {

#[test]
fn concat() {
let a = Str("A".to_owned());

assert_eq!(a.concat(&Str("B".to_owned())), Str("AB".to_owned()));
assert_eq!(a.concat(&Bool(true)), Str("ATRUE".to_owned()));
assert_eq!(a.concat(&I8(1)), Str("A1".to_owned()));
assert_eq!(a.concat(&I16(1)), Str("A1".to_owned()));
assert_eq!(a.concat(&I32(1)), Str("A1".to_owned()));
assert_eq!(a.concat(&I64(1)), Str("A1".to_owned()));
assert_eq!(a.concat(&I128(1)), Str("A1".to_owned()));
assert_eq!(a.concat(&U8(1)), Str("A1".to_owned()));
assert_eq!(a.concat(&U16(1)), Str("A1".to_owned()));
assert_eq!(a.concat(&F64(1.0)), Str("A1".to_owned()));
assert_eq!(I64(2).concat(&I64(1)), Str("21".to_owned()));
assert!(a.concat(&Null).is_null());
assert_eq!(
Str("A".to_owned()).concat(Str("B".to_owned())),
Str("AB".to_owned())
);
assert_eq!(
Str("A".to_owned()).concat(Bool(true)),
Str("ATRUE".to_owned())
);
assert_eq!(Str("A".to_owned()).concat(I8(1)), Str("A1".to_owned()));
assert_eq!(Str("A".to_owned()).concat(I16(1)), Str("A1".to_owned()));
assert_eq!(Str("A".to_owned()).concat(I32(1)), Str("A1".to_owned()));
assert_eq!(Str("A".to_owned()).concat(I64(1)), Str("A1".to_owned()));
assert_eq!(Str("A".to_owned()).concat(I128(1)), Str("A1".to_owned()));
assert_eq!(Str("A".to_owned()).concat(U8(1)), Str("A1".to_owned()));
assert_eq!(Str("A".to_owned()).concat(U16(1)), Str("A1".to_owned()));
assert_eq!(Str("A".to_owned()).concat(F64(1.0)), Str("A1".to_owned()));
assert_eq!(
List(vec![I64(1)]).concat(List(vec![I64(2)])),
List(vec![I64(1), I64(2)])
);
assert_eq!(I64(2).concat(I64(1)), Str("21".to_owned()));
assert!(Str("A".to_owned()).concat(Null).is_null());
}

#[test]
Expand Down
6 changes: 3 additions & 3 deletions core/src/executor/evaluate/evaluated.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,12 +169,12 @@ impl<'a> Evaluated<'a> {
let evaluated = match (self, other) {
(Evaluated::Literal(l), Evaluated::Literal(r)) => Evaluated::Literal(l.concat(r)),
(Evaluated::Literal(l), Evaluated::Value(r)) => {
Evaluated::from((&Value::try_from(l)?).concat(&r))
Evaluated::from((Value::try_from(l)?).concat(r))
}
(Evaluated::Value(l), Evaluated::Literal(r)) => {
Evaluated::from(l.concat(&Value::try_from(r)?))
Evaluated::from(l.concat(Value::try_from(r)?))
}
(Evaluated::Value(l), Evaluated::Value(r)) => Evaluated::from(l.concat(&r)),
(Evaluated::Value(l), Evaluated::Value(r)) => Evaluated::from(l.concat(r)),
};

Ok(evaluated)
Expand Down
16 changes: 9 additions & 7 deletions core/src/executor/evaluate/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use {
super::{EvaluateError, Evaluated},
crate::{
ast::{DataType, DateTimeField, TrimWhereField},
data::Value,
data::{Value, ValueError},
result::Result,
},
std::{
Expand Down Expand Up @@ -64,16 +64,18 @@ pub fn concat(exprs: Vec<Evaluated<'_>>) -> Result<Evaluated> {
}

let control_flow = exprs.into_iter().map(|expr| expr.try_into()).try_fold(
Value::Str(String::new()),
|left, right: Result<Value>| match right {
Ok(value) if value.is_null() => ControlFlow::Break(BreakCase::Null),
Err(err) => ControlFlow::Break(BreakCase::Err(err)),
Ok(value) => ControlFlow::Continue(left.concat(&value)),
None,
|left: Option<Value>, right: Result<Value>| match (left, right) {
(_, Ok(value)) if value.is_null() => ControlFlow::Break(BreakCase::Null),
(_, Err(err)) => ControlFlow::Break(BreakCase::Err(err)),
(Some(left), Ok(value)) => ControlFlow::Continue(Some(left.concat(value))),
(None, Ok(value)) => ControlFlow::Continue(Some(value)),
},
);

match control_flow {
ControlFlow::Continue(value) => Ok(Evaluated::from(value)),
ControlFlow::Continue(Some(value)) => Ok(Evaluated::from(value)),
ControlFlow::Continue(None) => Err(ValueError::EmptyArgNotAllowedInConcat.into()),
ControlFlow::Break(BreakCase::Null) => Ok(Evaluated::from(Value::Null)),
ControlFlow::Break(BreakCase::Err(err)) => Err(err),
}
Expand Down
1 change: 0 additions & 1 deletion core/src/translate/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,6 @@ pub fn translate_function(sql_function: &SqlFunction) -> Result<Expr> {
"VARIANCE" => translate_aggregate_one_arg(Aggregate::Variance, args, name),
"STDEV" => translate_aggregate_one_arg(Aggregate::Stdev, args, name),
"CONCAT" => {
check_len_min(name, args.len(), 1)?;
let exprs = args
.into_iter()
.map(translate_expr)
Expand Down
46 changes: 28 additions & 18 deletions test-suite/src/function/concat.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use {
crate::*,
gluesql_core::{data::ValueError, prelude::Value::*, translate::TranslateError},
gluesql_core::{data::ValueError, prelude::Value::*},
};

test_case!(concat, async move {
Expand Down Expand Up @@ -40,16 +40,6 @@ test_case!(concat, async move {
Ok(select_with_null!(myconcat; Null))
);

test!(
"select concat() as myconcat from Concat;",
Err(TranslateError::FunctionArgsLengthNotMatchingMin {
name: "CONCAT".to_owned(),
expected_minimum: 1,
found: 0
}
.into())
);

test!(
"select concat(DATE '2020-06-11', DATE '2020-16-3') as myconcat from Concat;",
Err(ValueError::FailedToParseDate("2020-16-3".to_owned()).into())
Expand All @@ -66,12 +56,32 @@ test_case!(concat, async move {
);
// test with zero arguments
test!(
"select concat() as myconcat from Concat;",
Err(TranslateError::FunctionArgsLengthNotMatchingMin {
name: "CONCAT".to_owned(),
expected_minimum: 1,
found: 0
}
.into())
r#"select concat() as myconcat from Concat;"#,
Err(ValueError::EmptyArgNotAllowedInConcat.into())
);

run!(
r#"
CREATE TABLE ListTypeConcat (
id INTEGER,
items LIST,
items2 LIST
)"#
);

run!(
r#"
INSERT INTO ListTypeConcat VALUES
(1, '[1, 2, 3]', '["one", "two", "three"]');
"#
);

test!(
r#"select concat(items, items2) as myconcat from ListTypeConcat;"#,
Ok(select!(
myconcat
List;
vec![I64(1), I64(2), I64(3), Str("one".to_owned()), Str("two".to_owned()), Str("three".to_owned())]
))
);
});

0 comments on commit 075a3c4

Please sign in to comment.