diff --git a/src/query/ast/src/ast/expr.rs b/src/query/ast/src/ast/expr.rs index fceb821f5aa02..80e3faafb04a1 100644 --- a/src/query/ast/src/ast/expr.rs +++ b/src/query/ast/src/ast/expr.rs @@ -179,6 +179,12 @@ pub enum Expr { }, /// The `Array` expr Array { span: Span, exprs: Vec }, + ArraySort { + span: Span, + expr: Box, + asc: bool, + null_first: bool, + }, /// The `Interval 1 DAY` expr Interval { span: Span, @@ -383,6 +389,7 @@ impl Expr { | Expr::Subquery { span, .. } | Expr::MapAccess { span, .. } | Expr::Array { span, .. } + | Expr::ArraySort { span, .. } | Expr::Interval { span, .. } | Expr::DateAdd { span, .. } | Expr::DateSub { span, .. } @@ -866,6 +873,26 @@ impl Display for Expr { write_comma_separated_list(f, exprs)?; write!(f, "]")?; } + Expr::ArraySort { + expr, + asc, + null_first, + .. + } => { + write!(f, "ARRAY_SORT(")?; + write!(f, "{expr})")?; + if *asc { + write!(f, " , 'ASC'")?; + } else { + write!(f, " , 'DESC'")?; + } + if *null_first { + write!(f, " , 'NULLS FIRST'")?; + } else { + write!(f, " , 'NULLS LAST'")?; + } + write!(f, ")")?; + } Expr::Interval { expr, unit, .. } => { write!(f, "INTERVAL {expr} {unit}")?; } diff --git a/src/query/ast/src/ast/format/syntax/expr.rs b/src/query/ast/src/ast/format/syntax/expr.rs index a3dfc5260d2ba..f4d9a28df57d0 100644 --- a/src/query/ast/src/ast/format/syntax/expr.rs +++ b/src/query/ast/src/ast/format/syntax/expr.rs @@ -318,6 +318,45 @@ pub(crate) fn pretty_expr(expr: Expr) -> RcDoc<'static> { Expr::Array { exprs, .. } => RcDoc::text("[") .append(inline_comma(exprs.into_iter().map(pretty_expr))) .append(RcDoc::text("]")), + Expr::ArraySort { + expr, + asc, + null_first, + .. + } => { + let res = pretty_expr(*expr); + if asc { + res.clone() + .append(RcDoc::text(",")) + .append(RcDoc::space()) + .append(RcDoc::text("'")) + .append(RcDoc::text("ASC")) + .append(RcDoc::text("'")); + } else { + res.clone() + .append(RcDoc::text(",")) + .append(RcDoc::space()) + .append(RcDoc::text("'")) + .append(RcDoc::text("DESC")) + .append(RcDoc::text("'")); + } + if null_first { + res.clone() + .append(RcDoc::text(",")) + .append(RcDoc::space()) + .append(RcDoc::text("'")) + .append(RcDoc::text("NULL FIRST")) + .append(RcDoc::text("'")); + } else { + res.clone() + .append(RcDoc::text(",")) + .append(RcDoc::space()) + .append(RcDoc::text("'")) + .append(RcDoc::text("NULL LAST")) + .append(RcDoc::text("'")); + } + res.clone().append(RcDoc::text(")")) + } Expr::Interval { expr, unit, .. } => RcDoc::text("INTERVAL") .append(RcDoc::space()) .append(pretty_expr(*expr)) diff --git a/src/query/ast/src/parser/expr.rs b/src/query/ast/src/parser/expr.rs index c5394a43e537f..edc2157e7901d 100644 --- a/src/query/ast/src/parser/expr.rs +++ b/src/query/ast/src/parser/expr.rs @@ -278,6 +278,14 @@ pub enum ExprElement { Array { exprs: Vec, }, + /// ARRAY_SORT([1,2,3], ASC|DESC, NULLS FIRST|LAST) + ArraySort { + expr: Box, + // Optional `ASC` or `DESC` + asc: Option, + // Optional `NULLS FIRST` or `NULLS LAST` + nulls_first: Option, + }, Interval { expr: Expr, unit: IntervalKind, @@ -461,6 +469,41 @@ impl<'a, I: Iterator>> PrattParser for ExprP span: transform_span(elem.span.0), exprs, }, + ExprElement::ArraySort { + expr, + asc, + nulls_first, + } => { + let asc = if let Some(asc) = asc { + if asc.to_lowercase() == "asc" { + true + } else if asc.to_lowercase() == "desc" { + false + } else { + return Err("Sorting order must be either ASC or DESC"); + } + } else { + true + }; + let null_first = if let Some(nulls_first) = nulls_first { + let null_first = nulls_first.trim().to_lowercase(); + if null_first == "nulls first" { + true + } else if null_first == "nulls last" { + false + } else { + return Err("Null sorting order must be either NULLS FIRST or NULLS LAST"); + } + } else { + true + }; + Expr::ArraySort { + span: transform_span(elem.span.0), + expr, + asc, + null_first, + } + } ExprElement::Interval { expr, unit } => Expr::Interval { span: transform_span(elem.span.0), expr: Box::new(expr), @@ -830,6 +873,22 @@ pub fn expr_element(i: Input) -> IResult> { ExprElement::Array { exprs } }, ); + // ARRAY_SORT([...], ASC | DESC, NULLS FIRST | LAST) + let array_sort = map( + rule! { + ( ARRAY_SORT ) + ~ "(" + ~ #subexpr(0) + ~ ( "," ~ #literal_string )? + ~ ( "," ~ #literal_string )? + ~ ")" + }, + |(_, _, expr, opt_asc, opt_null_first, _)| ExprElement::ArraySort { + expr: Box::new(expr), + asc: opt_asc.map(|(_, asc)| asc), + nulls_first: opt_null_first.map(|(_, first_last)| first_last), + }, + ); let date_add = map( rule! { DATE_ADD ~ "(" ~ #interval_kind ~ "," ~ #subexpr(0) ~ "," ~ #subexpr(0) ~ ")" @@ -890,6 +949,7 @@ pub fn expr_element(i: Input) -> IResult> { | #extract : "`EXTRACT((YEAR | QUARTER | MONTH | DAY | HOUR | MINUTE | SECOND) FROM ...)`" | #position : "`POSITION(... IN ...)`" | #substring : "`SUBSTRING(... [FROM ...] [FOR ...])`" + | #array_sort : "`ARRAY_SORT([...], 'ASC' | 'DESC', 'NULLS FIRST' | 'NULLS LAST')`" | #trim : "`TRIM(...)`" | #trim_from : "`TRIM([(BOTH | LEADEING | TRAILING) ... FROM ...)`" ), diff --git a/src/query/ast/src/parser/token.rs b/src/query/ast/src/parser/token.rs index d2cb633b110c2..2cb03bddb4f48 100644 --- a/src/query/ast/src/parser/token.rs +++ b/src/query/ast/src/parser/token.rs @@ -751,6 +751,8 @@ pub enum TokenKind { TRANSIENT, #[token("TRIM", ignore(ascii_case))] TRIM, + #[token("ARRAY_SORT", ignore(ascii_case))] + ARRAY_SORT, #[token("TRUE", ignore(ascii_case))] TRUE, #[token("TRUNCATE", ignore(ascii_case))] @@ -981,6 +983,7 @@ impl TokenKind { | TokenKind::TRAILING // | TokenKind::TREAT | TokenKind::TRIM + | TokenKind::ARRAY_SORT | TokenKind::TRUE | TokenKind::TRY_CAST // | TokenKind::UNIQUE diff --git a/src/query/ast/src/visitors/visitor.rs b/src/query/ast/src/visitors/visitor.rs index c2e2272b8ba49..3d52c5d867d92 100644 --- a/src/query/ast/src/visitors/visitor.rs +++ b/src/query/ast/src/visitors/visitor.rs @@ -257,6 +257,10 @@ pub trait Visitor<'ast>: Sized { } } + fn visit_array_sort(&mut self, _span: Span, expr: &'ast Expr, _asc: bool, _null_first: bool) { + walk_expr(self, expr); + } + fn visit_interval(&mut self, _span: Span, expr: &'ast Expr, _unit: &'ast IntervalKind) { walk_expr(self, expr); } diff --git a/src/query/ast/src/visitors/visitor_mut.rs b/src/query/ast/src/visitors/visitor_mut.rs index 59148ae8ea0b9..a25c681ce5d71 100644 --- a/src/query/ast/src/visitors/visitor_mut.rs +++ b/src/query/ast/src/visitors/visitor_mut.rs @@ -261,6 +261,10 @@ pub trait VisitorMut: Sized { } } + fn visit_array_sort(&mut self, _span: Span, expr: &mut Expr, _asc: bool, _null_first: bool) { + walk_expr_mut(self, expr); + } + fn visit_interval(&mut self, _span: Span, expr: &mut Expr, _unit: &mut IntervalKind) { walk_expr_mut(self, expr); } diff --git a/src/query/ast/src/visitors/walk.rs b/src/query/ast/src/visitors/walk.rs index fcc82500534df..65d762e4bb74d 100644 --- a/src/query/ast/src/visitors/walk.rs +++ b/src/query/ast/src/visitors/walk.rs @@ -117,6 +117,12 @@ pub fn walk_expr<'a, V: Visitor<'a>>(visitor: &mut V, expr: &'a Expr) { accessor, } => visitor.visit_map_access(*span, expr, accessor), Expr::Array { span, exprs } => visitor.visit_array(*span, exprs), + Expr::ArraySort { + span, + expr, + asc, + null_first, + } => visitor.visit_array_sort(*span, expr, *asc, *null_first), Expr::Interval { span, expr, unit } => visitor.visit_interval(*span, expr, unit), Expr::DateAdd { span, diff --git a/src/query/ast/src/visitors/walk_mut.rs b/src/query/ast/src/visitors/walk_mut.rs index 1047d84bab5af..ff32595345e52 100644 --- a/src/query/ast/src/visitors/walk_mut.rs +++ b/src/query/ast/src/visitors/walk_mut.rs @@ -117,6 +117,12 @@ pub fn walk_expr_mut(visitor: &mut V, expr: &mut Expr) { accessor, } => visitor.visit_map_access(*span, expr, accessor), Expr::Array { span, exprs } => visitor.visit_array(*span, exprs), + Expr::ArraySort { + span, + expr, + asc, + null_first, + } => visitor.visit_array_sort(*span, expr, *asc, *null_first), Expr::Interval { span, expr, unit } => visitor.visit_interval(*span, expr, unit), Expr::DateAdd { span, diff --git a/src/query/ast/tests/it/parser.rs b/src/query/ast/tests/it/parser.rs index f18e8e0e2d5de..d0f07e5f52cc4 100644 --- a/src/query/ast/tests/it/parser.rs +++ b/src/query/ast/tests/it/parser.rs @@ -540,6 +540,9 @@ fn test_expr() { r#"position('a' in str)"#, r#"substring(a from b for c)"#, r#"substring(a, b, c)"#, + r#"array_sort([2])"#, + r#"array_sort([2,0.1], 'ASC')"#, + r#"array_sort([3,2], 'DESC', 'NULLS FIRST')"#, r#"col1::UInt8"#, r#"(arr[0]:a).b"#, r#"arr[4]["k"]"#, diff --git a/src/query/ast/tests/it/testdata/expr-error.txt b/src/query/ast/tests/it/testdata/expr-error.txt index 96096e5ea0974..47e75f6808d35 100644 --- a/src/query/ast/tests/it/testdata/expr-error.txt +++ b/src/query/ast/tests/it/testdata/expr-error.txt @@ -55,7 +55,7 @@ error: --> SQL:1:10 | 1 | CAST(col1) - | ---- ^ expected `AS`, `,`, `(`, `.`, `IS`, `NOT`, or 55 more ... + | ---- ^ expected `AS`, `,`, `(`, `.`, `IS`, `NOT`, or 56 more ... | | | while parsing `CAST(... AS ...)` | while parsing expression diff --git a/src/query/ast/tests/it/testdata/expr.txt b/src/query/ast/tests/it/testdata/expr.txt index df7953c78fddd..f62369076704c 100644 --- a/src/query/ast/tests/it/testdata/expr.txt +++ b/src/query/ast/tests/it/testdata/expr.txt @@ -1646,6 +1646,109 @@ Substring { } +---------- Input ---------- +array_sort([2]) +---------- Output --------- +ARRAY_SORT([2]) , 'ASC' , 'NULLS FIRST') +---------- AST ------------ +ArraySort { + span: Some( + 0..15, + ), + expr: Array { + span: Some( + 11..14, + ), + exprs: [ + Literal { + span: Some( + 12..13, + ), + lit: Integer( + 2, + ), + }, + ], + }, + asc: true, + null_first: true, +} + + +---------- Input ---------- +array_sort([2,0.1], 'ASC') +---------- Output --------- +ARRAY_SORT([2, 0.1]) , 'ASC' , 'NULLS FIRST') +---------- AST ------------ +ArraySort { + span: Some( + 0..26, + ), + expr: Array { + span: Some( + 11..18, + ), + exprs: [ + Literal { + span: Some( + 12..13, + ), + lit: Integer( + 2, + ), + }, + Literal { + span: Some( + 14..17, + ), + lit: Float( + 0.1, + ), + }, + ], + }, + asc: true, + null_first: true, +} + + +---------- Input ---------- +array_sort([3,2], 'DESC', 'NULLS FIRST') +---------- Output --------- +ARRAY_SORT([3, 2]) , 'DESC' , 'NULLS FIRST') +---------- AST ------------ +ArraySort { + span: Some( + 0..40, + ), + expr: Array { + span: Some( + 11..16, + ), + exprs: [ + Literal { + span: Some( + 12..13, + ), + lit: Integer( + 3, + ), + }, + Literal { + span: Some( + 14..15, + ), + lit: Integer( + 2, + ), + }, + ], + }, + asc: false, + null_first: true, +} + + ---------- Input ---------- col1::UInt8 ---------- Output --------- diff --git a/src/query/functions/src/scalars/array.rs b/src/query/functions/src/scalars/array.rs index fb8b4cc4fb1d2..3a00ab5a5d6d9 100644 --- a/src/query/functions/src/scalars/array.rs +++ b/src/query/functions/src/scalars/array.rs @@ -44,8 +44,10 @@ use common_expression::vectorize_with_builder_1_arg; use common_expression::vectorize_with_builder_2_arg; use common_expression::vectorize_with_builder_3_arg; use common_expression::with_number_mapped_type; +use common_expression::BlockEntry; use common_expression::Column; use common_expression::ColumnBuilder; +use common_expression::DataBlock; use common_expression::Domain; use common_expression::EvalContext; use common_expression::Function; @@ -55,6 +57,7 @@ use common_expression::FunctionRegistry; use common_expression::FunctionSignature; use common_expression::Scalar; use common_expression::ScalarRef; +use common_expression::SortColumnDescription; use common_expression::Value; use common_expression::ValueRef; use common_hashtable::HashtableKeyable; @@ -76,6 +79,13 @@ const ARRAY_AGGREGATE_FUNCTIONS: &[(&str, &str); 6] = &[ ("array_any", "any"), ]; +const ARRAY_SORT_FUNCTIONS: &[(&str, (bool, bool)); 4] = &[ + ("array_sort_asc_null_first", (true, true)), + ("array_sort_desc_null_first", (false, true)), + ("array_sort_asc_null_last", (true, false)), + ("array_sort_desc_null_last", (false, false)), +]; + pub fn register(registry: &mut FunctionRegistry) { registry.register_aliases("contains", &["array_contains"]); registry.register_aliases("get", &["array_get"]); @@ -744,4 +754,38 @@ fn register_array_aggr(registry: &mut FunctionRegistry) { })) }); } + + for (fn_name, sort_desc) in ARRAY_SORT_FUNCTIONS { + registry.register_passthrough_nullable_1_arg::( + fn_name, + FunctionProperty::default(), + |_| FunctionDomain::Full, + vectorize_1_arg::(|arr, _| arr), + ); + + registry.register_passthrough_nullable_1_arg::>, ArrayType>, _, _>( + fn_name, + FunctionProperty::default(), + |_| FunctionDomain::Full, + vectorize_1_arg::>, ArrayType>>(|arr, _| { + let len = arr.len(); + if arr.len() > 1 { + let sort_desc = vec![SortColumnDescription { + offset: 0, + asc: sort_desc.0, + nulls_first: sort_desc.1, + }]; + let columns = vec![BlockEntry{ + data_type: arr.data_type(), + value: Value::Column(arr) + }]; + let sort_block = DataBlock::sort(&DataBlock::new(columns, len), &sort_desc, None).unwrap(); + sort_block.columns()[0].value.clone().into_column().unwrap() + } else { + arr + } + }, + ), + ); + } } diff --git a/src/query/functions/tests/it/scalars/array.rs b/src/query/functions/tests/it/scalars/array.rs index 139da3cf13808..f54c6f8a8b47a 100644 --- a/src/query/functions/tests/it/scalars/array.rs +++ b/src/query/functions/tests/it/scalars/array.rs @@ -44,6 +44,7 @@ fn test_array() { test_array_max(file); test_array_min(file); test_array_any(file); + test_array_sort(file); } fn test_create(file: &mut impl Write) { @@ -480,3 +481,39 @@ fn test_array_any(file: &mut impl Write) { ), ]); } + +fn test_array_sort(file: &mut impl Write) { + run_ast(file, "array_sort([])", &[]); + run_ast(file, "array_sort(NULL)", &[]); + run_ast(file, "array_sort([8, 20, 1, 2, 3, 4, 5, 6, 7], 'ASC')", &[]); + run_ast(file, "array_sort([], 'ASC')", &[]); + run_ast(file, "array_sort([], 'DESC')", &[]); + run_ast(file, "array_sort([8, 20, 1, 2, 3, 4, 5, 6, 7], 'DESC')", &[ + ]); + run_ast(file, "array_sort([9.32, 0, 1.2, 3.4, 5.6, 7.8])", &[]); + run_ast(file, "array_sort(['x', 0, 1.2, 3.4, 5.6, 7.8])", &[]); + run_ast( + file, + "array_sort([1.2, NULL, 3.4, 5.6, '2.2', NULL], 'DESC', 'NULLS FIRST')", + &[], + ); + run_ast(file, "array_sort([], 'DESC', 'NULLS FIRST')", &[]); + run_ast( + file, + "array_sort([1.2, NULL, 3.4, 5.6, '2.2', NULL], 'DESC', 'NULLS LAST')", + &[], + ); + run_ast(file, "array_sort([], 'DESC', 'NULLS LAST')", &[]); + run_ast( + file, + "array_sort([1.2, NULL, 3.4, 5.6, '2.2', NULL], 'ASC', 'NULLS FIRST')", + &[], + ); + run_ast(file, "array_sort([], 'ASC', 'NULLS FIRST')", &[]); + run_ast( + file, + "array_sort(['z', 'a', NULL, 'v', 'd', NULL], 'ASC', 'NULLS LAST')", + &[], + ); + run_ast(file, "array_sort([], 'ASC', 'NULLS LAST')", &[]); +} diff --git a/src/query/functions/tests/it/scalars/parser.rs b/src/query/functions/tests/it/scalars/parser.rs index 782d3e7b1f957..c7081e7e11ca4 100644 --- a/src/query/functions/tests/it/scalars/parser.rs +++ b/src/query/functions/tests/it/scalars/parser.rs @@ -279,6 +279,25 @@ pub fn transform_expr(ast: AExpr, columns: &[(&str, DataType)]) -> RawExpr { .map(|expr| transform_expr(expr, columns)) .collect(), }, + AExpr::ArraySort { + span, + expr, + asc, + null_first, + } => { + let name = match (asc, null_first) { + (true, true) => "array_sort_asc_null_first".to_string(), + (true, false) => "array_sort_asc_null_last".to_string(), + (false, true) => "array_sort_desc_null_first".to_string(), + (false, false) => "array_sort_desc_null_last".to_string(), + }; + RawExpr::FunctionCall { + span, + name, + params: vec![], + args: vec![transform_expr(*expr, columns)], + } + } AExpr::Tuple { span, exprs } => RawExpr::FunctionCall { span, name: "tuple".to_string(), diff --git a/src/query/functions/tests/it/scalars/testdata/array.txt b/src/query/functions/tests/it/scalars/testdata/array.txt index 34d32ce36e0d0..bb12302f4bed1 100644 --- a/src/query/functions/tests/it/scalars/testdata/array.txt +++ b/src/query/functions/tests/it/scalars/testdata/array.txt @@ -1675,3 +1675,147 @@ evaluation (internal): +--------+-------------------------------------------------------------------------+ +ast : array_sort([]) +raw expr : array_sort_asc_null_first(array()) +checked expr : array_sort_asc_null_first(array<>()) +optimized expr : [] :: Array(Nothing) +output type : Array(Nothing) +output domain : [] +output : [] + + +ast : array_sort(NULL) +raw expr : array_sort_asc_null_first(NULL) +checked expr : array_sort_asc_null_first(CAST(NULL AS Array(Nothing) NULL)) +optimized expr : NULL +output type : Array(Nothing) NULL +output domain : {NULL} +output : NULL + + +ast : array_sort([8, 20, 1, 2, 3, 4, 5, 6, 7], 'ASC') +raw expr : array_sort_asc_null_first(array(8_u8, 20_u8, 1_u8, 2_u8, 3_u8, 4_u8, 5_u8, 6_u8, 7_u8)) +checked expr : array_sort_asc_null_first(array(8_u8, 20_u8, 1_u8, 2_u8, 3_u8, 4_u8, 5_u8, 6_u8, 7_u8)) +optimized expr : [1, 2, 3, 4, 5, 6, 7, 8, 20] +output type : Array(UInt8) +output domain : [{1..=20}] +output : [1, 2, 3, 4, 5, 6, 7, 8, 20] + + +ast : array_sort([], 'ASC') +raw expr : array_sort_asc_null_first(array()) +checked expr : array_sort_asc_null_first(array<>()) +optimized expr : [] :: Array(Nothing) +output type : Array(Nothing) +output domain : [] +output : [] + + +ast : array_sort([], 'DESC') +raw expr : array_sort_desc_null_first(array()) +checked expr : array_sort_desc_null_first(array<>()) +optimized expr : [] :: Array(Nothing) +output type : Array(Nothing) +output domain : [] +output : [] + + +ast : array_sort([8, 20, 1, 2, 3, 4, 5, 6, 7], 'DESC') +raw expr : array_sort_desc_null_first(array(8_u8, 20_u8, 1_u8, 2_u8, 3_u8, 4_u8, 5_u8, 6_u8, 7_u8)) +checked expr : array_sort_desc_null_first(array(8_u8, 20_u8, 1_u8, 2_u8, 3_u8, 4_u8, 5_u8, 6_u8, 7_u8)) +optimized expr : [20, 8, 7, 6, 5, 4, 3, 2, 1] +output type : Array(UInt8) +output domain : [{1..=20}] +output : [20, 8, 7, 6, 5, 4, 3, 2, 1] + + +ast : array_sort([9.32, 0, 1.2, 3.4, 5.6, 7.8]) +raw expr : array_sort_asc_null_first(array(9.32_f64, 0_u8, 1.2_f64, 3.4_f64, 5.6_f64, 7.8_f64)) +checked expr : array_sort_asc_null_first(array(9.32_f64, to_float64(0_u8), 1.2_f64, 3.4_f64, 5.6_f64, 7.8_f64)) +optimized expr : [0, 1.2, 3.4, 5.6, 7.8, 9.32] +output type : Array(Float64) +output domain : [{0..=9.32}] +output : [0, 1.2, 3.4, 5.6, 7.8, 9.32] + + +ast : array_sort(['x', 0, 1.2, 3.4, 5.6, 7.8]) +raw expr : array_sort_asc_null_first(array("x", 0_u8, 1.2_f64, 3.4_f64, 5.6_f64, 7.8_f64)) +checked expr : array_sort_asc_null_first(array(to_variant("x"), to_variant(0_u8), to_variant(1.2_f64), to_variant(3.4_f64), to_variant(5.6_f64), to_variant(7.8_f64))) +optimized expr : [0, 1.2, 3.4, 5.6, 7.8, "x"] +output type : Array(Variant) +output domain : [Undefined] +output : [0, 1.2, 3.4, 5.6, 7.8, "x"] + + +ast : array_sort([1.2, NULL, 3.4, 5.6, '2.2', NULL], 'DESC', 'NULLS FIRST') +raw expr : array_sort_desc_null_first(array(1.2_f64, NULL, 3.4_f64, 5.6_f64, "2.2", NULL)) +checked expr : array_sort_desc_null_first(array(CAST(1.2_f64 AS Variant NULL), CAST(NULL AS Variant NULL), CAST(3.4_f64 AS Variant NULL), CAST(5.6_f64 AS Variant NULL), CAST("2.2" AS Variant NULL), CAST(NULL AS Variant NULL))) +optimized expr : [NULL, NULL, "2.2", 5.6, 3.4, 1.2] +output type : Array(Variant NULL) +output domain : [Undefined ∪ {NULL}] +output : [NULL, NULL, "2.2", 5.6, 3.4, 1.2] + + +ast : array_sort([], 'DESC', 'NULLS FIRST') +raw expr : array_sort_desc_null_first(array()) +checked expr : array_sort_desc_null_first(array<>()) +optimized expr : [] :: Array(Nothing) +output type : Array(Nothing) +output domain : [] +output : [] + + +ast : array_sort([1.2, NULL, 3.4, 5.6, '2.2', NULL], 'DESC', 'NULLS LAST') +raw expr : array_sort_desc_null_last(array(1.2_f64, NULL, 3.4_f64, 5.6_f64, "2.2", NULL)) +checked expr : array_sort_desc_null_last(array(CAST(1.2_f64 AS Variant NULL), CAST(NULL AS Variant NULL), CAST(3.4_f64 AS Variant NULL), CAST(5.6_f64 AS Variant NULL), CAST("2.2" AS Variant NULL), CAST(NULL AS Variant NULL))) +optimized expr : ["2.2", 5.6, 3.4, 1.2, NULL, NULL] +output type : Array(Variant NULL) +output domain : [Undefined ∪ {NULL}] +output : ["2.2", 5.6, 3.4, 1.2, NULL, NULL] + + +ast : array_sort([], 'DESC', 'NULLS LAST') +raw expr : array_sort_desc_null_last(array()) +checked expr : array_sort_desc_null_last(array<>()) +optimized expr : [] :: Array(Nothing) +output type : Array(Nothing) +output domain : [] +output : [] + + +ast : array_sort([1.2, NULL, 3.4, 5.6, '2.2', NULL], 'ASC', 'NULLS FIRST') +raw expr : array_sort_asc_null_first(array(1.2_f64, NULL, 3.4_f64, 5.6_f64, "2.2", NULL)) +checked expr : array_sort_asc_null_first(array(CAST(1.2_f64 AS Variant NULL), CAST(NULL AS Variant NULL), CAST(3.4_f64 AS Variant NULL), CAST(5.6_f64 AS Variant NULL), CAST("2.2" AS Variant NULL), CAST(NULL AS Variant NULL))) +optimized expr : [NULL, NULL, 1.2, 3.4, 5.6, "2.2"] +output type : Array(Variant NULL) +output domain : [Undefined ∪ {NULL}] +output : [NULL, NULL, 1.2, 3.4, 5.6, "2.2"] + + +ast : array_sort([], 'ASC', 'NULLS FIRST') +raw expr : array_sort_asc_null_first(array()) +checked expr : array_sort_asc_null_first(array<>()) +optimized expr : [] :: Array(Nothing) +output type : Array(Nothing) +output domain : [] +output : [] + + +ast : array_sort(['z', 'a', NULL, 'v', 'd', NULL], 'ASC', 'NULLS LAST') +raw expr : array_sort_asc_null_last(array("z", "a", NULL, "v", "d", NULL)) +checked expr : array_sort_asc_null_last(array(CAST("z" AS String NULL), CAST("a" AS String NULL), CAST(NULL AS String NULL), CAST("v" AS String NULL), CAST("d" AS String NULL), CAST(NULL AS String NULL))) +optimized expr : ["a", "d", "v", "z", NULL, NULL] +output type : Array(String NULL) +output domain : [{""..="z"} ∪ {NULL}] +output : ["a", "d", "v", "z", NULL, NULL] + + +ast : array_sort([], 'ASC', 'NULLS LAST') +raw expr : array_sort_asc_null_last(array()) +checked expr : array_sort_asc_null_last(array<>()) +optimized expr : [] :: Array(Nothing) +output type : Array(Nothing) +output domain : [] +output : [] + + diff --git a/src/query/functions/tests/it/scalars/testdata/function_list.txt b/src/query/functions/tests/it/scalars/testdata/function_list.txt index 24ef0926997ca..e51e0271a3c85 100644 --- a/src/query/functions/tests/it/scalars/testdata/function_list.txt +++ b/src/query/functions/tests/it/scalars/testdata/function_list.txt @@ -60,6 +60,22 @@ array_remove_last(Array(Nothing)) :: Array(Nothing) array_remove_last(Array(Nothing) NULL) :: Array(Nothing) NULL array_remove_last(Array(T0)) :: Array(T0) array_remove_last(Array(T0) NULL) :: Array(T0) NULL +array_sort_asc_null_first(Array(Nothing)) :: Array(Nothing) +array_sort_asc_null_first(Array(Nothing) NULL) :: Array(Nothing) NULL +array_sort_asc_null_first(Array(T0)) :: Array(T0) +array_sort_asc_null_first(Array(T0) NULL) :: Array(T0) NULL +array_sort_asc_null_last(Array(Nothing)) :: Array(Nothing) +array_sort_asc_null_last(Array(Nothing) NULL) :: Array(Nothing) NULL +array_sort_asc_null_last(Array(T0)) :: Array(T0) +array_sort_asc_null_last(Array(T0) NULL) :: Array(T0) NULL +array_sort_desc_null_first(Array(Nothing)) :: Array(Nothing) +array_sort_desc_null_first(Array(Nothing) NULL) :: Array(Nothing) NULL +array_sort_desc_null_first(Array(T0)) :: Array(T0) +array_sort_desc_null_first(Array(T0) NULL) :: Array(T0) NULL +array_sort_desc_null_last(Array(Nothing)) :: Array(Nothing) +array_sort_desc_null_last(Array(Nothing) NULL) :: Array(Nothing) NULL +array_sort_desc_null_last(Array(T0)) :: Array(T0) +array_sort_desc_null_last(Array(T0) NULL) :: Array(T0) NULL array_unique(Array(Nothing)) :: UInt64 array_unique(Array(Nothing) NULL) :: UInt64 NULL array_unique(Array(T0)) :: UInt64 diff --git a/src/query/sql/src/planner/semantic/type_check.rs b/src/query/sql/src/planner/semantic/type_check.rs index ccbc7020f6332..1bf76ef9e12e4 100644 --- a/src/query/sql/src/planner/semantic/type_check.rs +++ b/src/query/sql/src/planner/semantic/type_check.rs @@ -904,6 +904,17 @@ impl<'a> TypeChecker<'a> { Expr::Array { span, exprs, .. } => self.resolve_array(*span, exprs).await?, + Expr::ArraySort { + span, + expr, + asc, + null_first, + .. + } => { + self.resolve_array_sort(*span, expr, asc, null_first) + .await? + } + Expr::Position { substr_expr, str_expr, @@ -1739,6 +1750,25 @@ impl<'a> TypeChecker<'a> { .await } + #[async_recursion::async_recursion] + async fn resolve_array_sort( + &mut self, + span: Span, + expr: &Expr, + asc: &bool, + null_first: &bool, + ) -> Result> { + let box (arg, _type) = self.resolve(expr, None).await?; + let func_name = match (*asc, *null_first) { + (true, true) => "array_sort_asc_null_first", + (true, false) => "array_sort_asc_null_last", + (false, true) => "array_sort_desc_null_first", + (false, false) => "array_sort_desc_null_last", + }; + self.resolve_scalar_function_call(span, func_name, vec![], vec![arg], None) + .await + } + #[async_recursion::async_recursion] async fn resolve_tuple( &mut self, @@ -2242,6 +2272,19 @@ impl<'a> TypeChecker<'a> { .map(|expr| self.clone_expr_with_replacement(expr, replacement_fn)) .collect::>>()?, }), + Expr::ArraySort { + span, + expr, + asc, + null_first, + } => Ok(Expr::ArraySort { + span: *span, + expr: Box::new( + self.clone_expr_with_replacement(expr.as_ref(), replacement_fn)?, + ), + asc: *asc, + null_first: *null_first, + }), Expr::Interval { span, expr, unit } => Ok(Expr::Interval { span: *span, expr: Box::new( diff --git a/tests/sqllogictests/suites/base/05_ddl/05_0026_ddl_unset_settings b/tests/sqllogictests/suites/base/05_ddl/05_0026_ddl_unset_settings index 75d90eefc4494..3c0c64de66be8 100644 --- a/tests/sqllogictests/suites/base/05_ddl/05_0026_ddl_unset_settings +++ b/tests/sqllogictests/suites/base/05_ddl/05_0026_ddl_unset_settings @@ -3,8 +3,8 @@ query TTTT SELECT name, value, default, level from system.settings where name in ('sql_dialect', 'timezone', 'wait_for_async_insert_timeout') ---- sql_dialect PostgreSQL PostgreSQL SESSION -timezone UTC UTC SESSION -wait_for_async_insert_timeout 100 100 SESSION +timezone UTC UTC SESSION +wait_for_async_insert_timeout 100 100 SESSION skipif clickhouse statement ok diff --git a/tests/sqllogictests/suites/query/02_function/02_0061_function_array b/tests/sqllogictests/suites/query/02_function/02_0061_function_array index 0eec6dc1e1f1d..01ff44bb7ddcb 100644 --- a/tests/sqllogictests/suites/query/02_function/02_0061_function_array +++ b/tests/sqllogictests/suites/query/02_function/02_0061_function_array @@ -126,5 +126,30 @@ select array_any(col1), array_any(col2), array_any(col3) from t ---- 1 x 2022-02-02 +query TTTTT +select array_sort(col1),array_sort(col2),array_sort(col3),array_sort(col4),array_sort(col5) from t +---- +[1,2,3,3] ['x','x','y','z'] ['2022-02-02'] ['2023-01-01 02:00:01.000000'] [[],[NULL],[1,2]] + +statement ok +drop table t; + +statement ok +create table t(col1 Array(Int Null), col2 Array(String), col3 Array(Date), col4 Array(Timestamp), col5 Array(Array(Int null))) + +statement ok +insert into t values([1,2,3,3],['x','x','y','z'], ['2022-02-02'], ['2023-01-01 02:00:01'], [[1,2],[],[2,1,3,null]]) + +query TTTTT +select array_sort(col1, 'asc', 'NULLS FIRST'),array_sort(col2, 'desc'),array_sort(col3, 'desc', 'nulls last'),array_sort(col4),array_sort(col5, 'DESC', 'NULLS FIRST') from t +---- +[1,2,3,3] ['z','y','x','x'] ['2022-02-02'] ['2023-01-01 02:00:01.000000'] [[2,1,3,NULL],[1,2],[]] + +statement error 1005 +select array_sort(col1, 'asc', 'nulls fir') from t; + +statement error 1005 +select array_sort(col1, 'asca', 'nulls firt') from t; + statement ok DROP DATABASE array_func_test