diff --git a/Cargo.lock b/Cargo.lock index 34800dd0b7b89..3578dc0c12f76 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3736,6 +3736,7 @@ dependencies = [ "databend-common-column", "databend-common-exception", "databend-common-expression", + "databend-common-formats", "databend-common-hashtable", "databend-common-io", "databend-common-vector", diff --git a/src/query/formats/src/column_from_json.rs b/src/query/formats/src/column_from_json.rs new file mode 100644 index 0000000000000..036ab2022cbe2 --- /dev/null +++ b/src/query/formats/src/column_from_json.rs @@ -0,0 +1,92 @@ +// Copyright 2021 Datafuse Labs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use chrono_tz::UTC; +use databend_common_exception::ErrorCode; +use databend_common_exception::Result; +use databend_common_expression::types::DataType; +use databend_common_expression::Column; +use databend_common_expression::ColumnBuilder; +use databend_common_io::GeometryDataType; +use jiff::tz::TimeZone; +use serde_json::Value; + +use crate::field_decoder::FieldJsonAstDecoder; +use crate::FileFormatOptionsExt; + +fn default_json_options() -> FileFormatOptionsExt { + FileFormatOptionsExt { + ident_case_sensitive: false, + headers: 0, + json_compact: false, + json_strings: false, + disable_variant_check: false, + timezone: UTC, + jiff_timezone: TimeZone::UTC, + is_select: false, + is_clickhouse: false, + is_rounding_mode: true, + geometry_format: GeometryDataType::default(), + enable_dst_hour_fix: false, + } +} + +pub fn column_from_json_value(data_type: &DataType, json: Value) -> Result { + let rows = match json { + Value::Array(values) => values, + other => { + return Err(ErrorCode::BadArguments(format!( + "from_json! expects a json array to describe column values, got {other:?}" + ))) + } + }; + + let options = default_json_options(); + let decoder = FieldJsonAstDecoder::create(&options); + let mut builder = ColumnBuilder::with_capacity(data_type, rows.len()); + for value in rows { + decoder.read_field(&mut builder, &value)?; + } + Ok(builder.build()) +} + +#[macro_export] +macro_rules! column_from_json { + ($data_type:expr, $($json:tt)+) => {{ + $crate::column_from_json_value(&$data_type, ::serde_json::json!($($json)+)) + .expect("from_json! expects a valid json literal for the provided type") + }}; +} + +#[cfg(test)] +mod tests { + use databend_common_expression::types::*; + use databend_common_expression::FromData; + + #[test] + fn test_from_json_macro_strings() { + let column = column_from_json!(DataType::String, ["a", "b", "c"]); + assert_eq!(column, StringType::from_data(vec!["a", "b", "c"])); + } + + #[test] + fn test_from_json_nullable_booleans() { + let data_type = DataType::Nullable(Box::new(DataType::Boolean)); + let column = column_from_json!(data_type, [true, null, false]); + assert_eq!( + column, + BooleanType::from_data_with_validity(vec![true, false, false], vec![true, false, true]) + ); + } +} diff --git a/src/query/formats/src/field_decoder/json_ast.rs b/src/query/formats/src/field_decoder/json_ast.rs index df7007081ab87..823503374b28f 100644 --- a/src/query/formats/src/field_decoder/json_ast.rs +++ b/src/query/formats/src/field_decoder/json_ast.rs @@ -113,9 +113,20 @@ impl FieldJsonAstDecoder { ColumnBuilder::Geography(c) => self.read_geography(c, value), ColumnBuilder::Interval(c) => self.read_interval(c, value), ColumnBuilder::Vector(c) => self.read_vector(c, value), - ColumnBuilder::EmptyArray { .. } | ColumnBuilder::EmptyMap { .. } => { - Err(ErrorCode::Unimplemented("empty array/map literal")) - } + ColumnBuilder::EmptyArray { len } => match value.as_array() { + Some(array) if array.is_empty() => { + *len += 1; + Ok(()) + } + _ => Err(ErrorCode::BadBytes("Incorrect empty array value")), + }, + ColumnBuilder::EmptyMap { len } => match value.as_object() { + Some(array) if array.is_empty() => { + *len += 1; + Ok(()) + } + _ => Err(ErrorCode::BadBytes("Incorrect empty map value")), + }, ColumnBuilder::Opaque(_) => Err(ErrorCode::Unimplemented( "Opaque type not supported in json_ast", )), diff --git a/src/query/formats/src/lib.rs b/src/query/formats/src/lib.rs index 3dfc23bad0b1f..2bc36066b9479 100644 --- a/src/query/formats/src/lib.rs +++ b/src/query/formats/src/lib.rs @@ -12,14 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -#![allow(clippy::uninlined_format_args)] #![feature(box_patterns)] #![feature(cursor_split)] -extern crate core; - mod binary; mod clickhouse; +pub mod column_from_json; mod common_settings; mod delimiter; mod field_decoder; @@ -28,6 +26,7 @@ mod file_format_type; pub mod output_format; pub use clickhouse::ClickhouseFormatType; +pub use column_from_json::column_from_json_value; pub use delimiter::RecordDelimiter; pub use field_decoder::*; pub use file_format_type::parse_timezone; diff --git a/src/query/functions/Cargo.toml b/src/query/functions/Cargo.toml index ab1dc468da1d0..ec4a352365be1 100644 --- a/src/query/functions/Cargo.toml +++ b/src/query/functions/Cargo.toml @@ -70,6 +70,7 @@ unicase = { workspace = true } [dev-dependencies] comfy-table = { workspace = true } databend-common-ast = { workspace = true } +databend-common-formats = { workspace = true } divan = { workspace = true } goldenfile = { workspace = true } diff --git a/src/query/functions/src/aggregates/aggregator_common.rs b/src/query/functions/src/aggregates/aggregator_common.rs index 3d06a697e69ad..cd1aa47d5d921 100644 --- a/src/query/functions/src/aggregates/aggregator_common.rs +++ b/src/query/functions/src/aggregates/aggregator_common.rs @@ -173,7 +173,7 @@ pub fn eval_aggr( rows: usize, sort_descs: Vec, ) -> Result<(Column, DataType)> { - eval_aggr_for_test(name, params, entries, rows, false, sort_descs) + eval_aggr_inner(name, params, entries, rows, false, sort_descs) } pub fn eval_aggr_for_test( @@ -183,6 +183,18 @@ pub fn eval_aggr_for_test( rows: usize, with_serialize: bool, sort_descs: Vec, +) -> Result<(Column, DataType)> { + eval_aggr_inner(name, params, entries, rows, with_serialize, sort_descs) +} + +#[inline] +fn eval_aggr_inner( + name: &str, + params: Vec, + entries: &[BlockEntry], + rows: usize, + with_serialize: bool, + sort_descs: Vec, ) -> Result<(Column, DataType)> { let factory = AggregateFunctionFactory::instance(); let arguments = entries.iter().map(BlockEntry::data_type).collect(); diff --git a/src/query/functions/src/scalars/array.rs b/src/query/functions/src/scalars/array.rs index d40b3218d7d9f..76e0ba64112fe 100644 --- a/src/query/functions/src/scalars/array.rs +++ b/src/query/functions/src/scalars/array.rs @@ -16,12 +16,18 @@ use std::hash::Hash; use std::ops::Range; use std::sync::Arc; +use bumpalo::Bump; +use databend_common_base::runtime::drop_guard; use databend_common_exception::ErrorCode; use databend_common_exception::Result; +use databend_common_expression::aggregate::get_states_layout; +use databend_common_expression::aggregate::AggrState; +use databend_common_expression::aggregate::AggregateFunctionRef; +use databend_common_expression::aggregate::StateAddr; +use databend_common_expression::aggregate::StatesLayout; use databend_common_expression::types::array::ArrayColumnBuilder; use databend_common_expression::types::boolean::BooleanDomain; use databend_common_expression::types::nullable::NullableDomain; -use databend_common_expression::types::number::NumberScalar; use databend_common_expression::types::number::SimpleDomain; use databend_common_expression::types::number::UInt64Type; use databend_common_expression::types::AccessType; @@ -78,7 +84,6 @@ use jsonb::RawJsonb; use siphasher::sip128::Hasher128; use siphasher::sip128::SipHasher24; -use crate::aggregates::eval_aggr; use crate::AggregateFunctionFactory; const ARRAY_AGGREGATE_FUNCTIONS: &[(&str, &str); 14] = &[ @@ -1154,136 +1159,246 @@ pub fn register(registry: &mut FunctionRegistry) { ); } -fn register_array_aggr(registry: &mut FunctionRegistry) { - fn scalar_to_array_column(scalar: ScalarRef) -> Result { - match scalar { - ScalarRef::Array(col) => Ok(col.clone()), - ScalarRef::Variant(val) => { - let array_val = RawJsonb::new(val); - match array_val.array_values() { - Ok(vals_opt) => { - let vals = vals_opt.unwrap_or(vec![array_val.to_owned()]); - let variant_col = BinaryColumn::from_iter(vals.iter().map(|v| v.as_raw())); - Ok(Column::Variant(variant_col)) - } - Err(err) => Err(ErrorCode::Internal(err.to_string())), - } +struct ArrayAggEvaluator<'a> { + func: &'a AggregateFunctionRef, + state_layout: &'a StatesLayout, + addr: StateAddr, + need_manual_drop_state: bool, + _arena: Bump, +} + +impl<'a> ArrayAggEvaluator<'a> { + fn new(func: &'a AggregateFunctionRef, state_layout: &'a StatesLayout) -> Self { + let arena = Bump::new(); + let addr = arena.alloc_layout(state_layout.layout).into(); + func.init_state(AggrState::new(addr, &state_layout.states_loc[0])); + Self { + state_layout, + addr, + need_manual_drop_state: func.need_manual_drop_state(), + func, + _arena: arena, + } + } + + fn state(&self) -> AggrState { + AggrState::new(self.addr, &self.state_layout.states_loc[0]) + } + + fn eval(&mut self, entry: BlockEntry, builder: &mut ColumnBuilder) -> Result<()> { + let state = self.state(); + if self.need_manual_drop_state { + unsafe { + self.func.drop_state(state); } - _ => unreachable!(), } + self.func.init_state(state); + let rows = entry.len(); + let entries = &[entry]; + self.func.accumulate(state, entries.into(), None, rows)?; + self.func.merge_result(state, false, builder)?; + Ok(()) } +} - fn eval_array_aggr( - name: &str, - args: &[Value], - ctx: &mut EvalContext, - ) -> Value { - match &args[0] { - Value::Scalar(scalar) => match &scalar { - Scalar::EmptyArray | Scalar::Null => { - if name == "count" { - Value::Scalar(Scalar::Number(NumberScalar::UInt64(0))) - } else { - Value::Scalar(Scalar::Null) - } - } - Scalar::Array(_) | Scalar::Variant(_) => { - match scalar_to_array_column(scalar.as_ref()) { - Ok(col) => { - let len = col.len(); - match eval_aggr(name, vec![], &[col.clone().into()], len, vec![]) { - Ok((res_col, _)) => { - let val = unsafe { res_col.index_unchecked(0) }; - Value::Scalar(val.to_owned()) - } - Err(err) => { - ctx.set_error(0, err.to_string()); - Value::Scalar(Scalar::Null) - } - } - } +impl Drop for ArrayAggEvaluator<'_> { + fn drop(&mut self) { + if !self.need_manual_drop_state { + return; + } + drop_guard(move || unsafe { + self.func.drop_state(self.state()); + }) + } +} + +struct ArrayAggDesc { + func: AggregateFunctionRef, + state_layout: Arc, + return_type: DataType, +} + +impl ArrayAggDesc { + fn new(name: &str, array_type: &DataType) -> Result { + let factory = AggregateFunctionFactory::instance(); + let func = factory.get(name, vec![], vec![array_type.clone()], vec![])?; + let return_type = func.return_type()?; + let funcs = [func.clone()]; + let state_layout = Arc::new(get_states_layout(&funcs)?); + Ok(Self { + func, + state_layout, + return_type, + }) + } + + fn create_evaluator(&self) -> ArrayAggEvaluator { + ArrayAggEvaluator::new(&self.func, &self.state_layout) + } +} + +struct ArrayAggFunctionImpl { + desc: Option, + return_type: DataType, +} + +impl ArrayAggFunctionImpl { + fn new(name: &'static str, arg_type: &DataType) -> Option { + let (desc, return_type) = match arg_type { + DataType::Nullable(box DataType::EmptyArray) | DataType::EmptyArray => ( + None, + if name == "count" { + UInt64Type::data_type() + } else { + DataType::Null + }, + ), + DataType::Nullable(box DataType::Array(box array_type)) + | DataType::Array(box array_type) + | DataType::Nullable(box array_type @ DataType::Variant) + | array_type @ DataType::Variant => { + let desc = ArrayAggDesc::new(name, array_type).ok()?; + let return_type = desc.return_type.clone(); + (Some(desc), return_type) + } + _ => return None, + }; + Some(Self { + desc, + return_type: if arg_type.is_nullable() { + return_type.wrap_nullable() + } else { + return_type + }, + }) + } + + fn eval(&self, args: &[Value], ctx: &mut EvalContext) -> Value { + let Some(desc) = &self.desc else { + return match args { + [_] => Value::Scalar(Scalar::default_value(&self.return_type)), + _ => unreachable!(), + }; + }; + + match args { + [Value::Scalar(Scalar::Null | Scalar::EmptyArray)] => { + Value::Scalar(Scalar::default_value(&self.return_type)) + } + [Value::Scalar(scalar @ Scalar::Array(_) | scalar @ Scalar::Variant(_))] => { + scalar_to_array_column(scalar.as_ref()) + .and_then(|col| { + let mut evaluator = desc.create_evaluator(); + let mut builder = ColumnBuilder::with_capacity(&desc.return_type, 1); + evaluator.eval(col.into(), &mut builder)?; + Ok(Value::Scalar(builder.build_scalar())) + }) + .unwrap_or_else(|err| { + ctx.set_error(0, err.to_string()); + Value::Scalar(Scalar::default_value(&self.return_type)) + }) + } + [Value::Scalar(_)] => unreachable!(), + [Value::Column(Column::Nullable(box column))] + if desc.return_type != self.return_type => + { + let mut builder = ColumnBuilder::with_capacity(&self.return_type, column.len()); + let mut evaluator = desc.create_evaluator(); + let ColumnBuilder::Nullable(box nullable) = &mut builder else { + unreachable!() + }; + for (row_index, scalar) in column.iter().enumerate() { + let Some(scalar) = scalar else { + nullable.push_null(); + continue; + }; + + let col = match scalar_to_array_column(scalar) { + Ok(col) => col, Err(err) => { - ctx.set_error(0, err.to_string()); - Value::Scalar(Scalar::Null) + ctx.set_error(row_index, err.to_string()); + nullable.push_null(); + continue; + } + }; + + nullable.validity.push(true); + if let Err(err) = evaluator.eval(col.into(), &mut nullable.builder) { + ctx.set_error(row_index, err.to_string()); + if nullable.builder.len() == row_index { + nullable.builder.push_default(); } } } - _ => unreachable!(), - }, - Value::Column(column) => { - let return_type = eval_aggr_return_type(name, &[column.data_type()]).unwrap(); - let mut builder = ColumnBuilder::with_capacity(&return_type, column.len()); - for scalar in column.iter() { + Value::Column(builder.build()) + } + [Value::Column(column)] => { + let mut builder = ColumnBuilder::with_capacity(&self.return_type, column.len()); + let mut evaluator = desc.create_evaluator(); + for (row_index, scalar) in column.iter().enumerate() { if scalar == ScalarRef::Null { builder.push_default(); continue; } - match scalar_to_array_column(scalar) { - Ok(col) => { - let len = col.len(); - match eval_aggr(name, vec![], &[col.clone().into()], len, vec![]) { - Ok((col, _)) => { - let val = unsafe { col.index_unchecked(0) }; - builder.push(val) - } - Err(err) => { - ctx.set_error(builder.len(), err.to_string()); - builder.push_default(); - } - } - } + + let col = match scalar_to_array_column(scalar) { + Ok(col) => col, Err(err) => { - ctx.set_error(builder.len(), err.to_string()); + ctx.set_error(row_index, err.to_string()); + builder.push_default(); + continue; + } + }; + + if let Err(err) = evaluator.eval(col.into(), &mut builder) { + ctx.set_error(row_index, err.to_string()); + if builder.len() == row_index { builder.push_default(); } } } Value::Column(builder.build()) } + _ => unreachable!(), } } +} - fn eval_aggr_return_type(name: &str, args_type: &[DataType]) -> Option { - if args_type.len() != 1 { - return None; - } - let arg_type = args_type[0].remove_nullable(); - if arg_type == DataType::EmptyArray { - if name == "count" { - return Some(DataType::Number(NumberDataType::UInt64)); - } - return Some(DataType::Null); - } - - let array_type = match arg_type { - DataType::Array(box array_type) => array_type.clone(), - DataType::Variant => DataType::Variant, - _ => { - return None; +fn scalar_to_array_column(scalar: ScalarRef) -> Result { + match scalar { + ScalarRef::Array(col) => Ok(col.clone()), + ScalarRef::Variant(val) => { + let array_val = RawJsonb::new(val); + match array_val.array_values() { + Ok(vals_opt) => { + let vals = vals_opt.unwrap_or(vec![array_val.to_owned()]); + let variant_col = BinaryColumn::from_iter(vals.iter().map(|v| v.as_raw())); + Ok(Column::Variant(variant_col)) + } + Err(err) => Err(ErrorCode::Internal(err.to_string())), } - }; - let factory = AggregateFunctionFactory::instance(); - let func = factory.get(name, vec![], vec![array_type], vec![]).ok()?; - let return_type = func.return_type().ok()?; - if args_type[0].is_nullable() { - Some(return_type.wrap_nullable()) - } else { - Some(return_type) } + _ => unreachable!(), } +} +fn register_array_aggr(registry: &mut FunctionRegistry) { for (fn_name, name) in ARRAY_AGGREGATE_FUNCTIONS { - let factory = FunctionFactory::Closure(Box::new(|_, args_type: &[DataType]| { - let return_type = eval_aggr_return_type(name, args_type)?; + let factory = FunctionFactory::Closure(Box::new(move |_, args_type: &[DataType]| { + let [arg] = args_type else { + return None; + }; + let impl_info = ArrayAggFunctionImpl::new(name, arg)?; + let return_type = impl_info.return_type.clone(); Some(Arc::new(Function { signature: FunctionSignature { name: fn_name.to_string(), - args_type: vec![args_type[0].clone()], + args_type: vec![arg.clone()], return_type, }, eval: FunctionEval::Scalar { calc_domain: Box::new(move |_, _| FunctionDomain::MayThrow), - eval: Box::new(|args, ctx| eval_array_aggr(name, args, ctx)), + eval: Box::new(move |args, ctx| impl_info.eval(args, ctx)), }, })) })); diff --git a/src/query/functions/tests/it/scalars/array.rs b/src/query/functions/tests/it/scalars/array.rs index 138fa988958f6..859f215a47f89 100644 --- a/src/query/functions/tests/it/scalars/array.rs +++ b/src/query/functions/tests/it/scalars/array.rs @@ -16,6 +16,7 @@ use std::io::Write; use databend_common_expression::types::*; use databend_common_expression::FromData; +use databend_common_formats::column_from_json; use goldenfile::Mint; use super::run_ast; @@ -422,6 +423,11 @@ fn test_array_count(file: &mut impl Write) { run_ast(file, "array_count([1.2, NULL, 3.4, 5.6, NULL])", &[]); run_ast(file, "array_count(['a', 'b', 'c', 'd', 'e'])", &[]); run_ast(file, "array_count(['a', 'b', NULL, 'c', 'd', NULL])", &[]); + run_ast( + file, + "array_count(CAST(NULL AS Nullable(Array(Int64))))", + &[], + ); run_ast(file, "array_count([a, b, c, d])", &[ ("a", Int16Type::from_data(vec![1i16, 5, 8, 3])), @@ -430,29 +436,35 @@ fn test_array_count(file: &mut impl Write) { ("d", Int16Type::from_data(vec![4i16, 8, 1, 9])), ]); + { + let data_type = DataType::Array(Box::new(Int16Type::data_type())).wrap_nullable(); + let column = column_from_json!(data_type, [null, [1, 5, 8, 3], [1, 5], null]); + run_ast(file, "array_count(a)", &[("a", column)]); + } + + let u64_type = UInt64Type::data_type().wrap_nullable(); run_ast(file, "array_count([a, b, c, d])", &[ - ( - "a", - UInt64Type::from_data_with_validity(vec![1u64, 2, 0, 4], vec![true, true, false, true]), - ), - ( - "b", - UInt64Type::from_data_with_validity(vec![2u64, 0, 5, 6], vec![true, false, true, true]), - ), - ( - "c", - UInt64Type::from_data_with_validity(vec![3u64, 7, 8, 9], vec![true, true, true, true]), - ), - ( - "d", - UInt64Type::from_data_with_validity(vec![4u64, 6, 5, 0], vec![true, true, true, false]), - ), + ("a", column_from_json!(u64_type, [1, 2, null, 4])), + ("b", column_from_json!(u64_type, [2, null, 5, 6])), + ("c", column_from_json!(u64_type, [3, 7, 8, 9])), + ("d", column_from_json!(u64_type, [4, 6, 5, null])), ]); // Test with variant type run_ast(file, "array_count(parse_json('[1, 2, 3, 4, 5]'))", &[]); run_ast(file, "array_count(parse_json('[1, 2, null, 4, 5]'))", &[]); run_ast(file, "array_count(parse_json('[1.2, 3.4, 5.6, 7.8]'))", &[]); + + { + let column = column_from_json!(DataType::EmptyArray, [[], [], []]); + run_ast(file, "array_count(a)", &[("a", column)]); + } + + { + let data_type = DataType::EmptyArray.wrap_nullable(); + let column = column_from_json!(data_type, [null, [], null]); + run_ast(file, "array_count(a)", &[("a", column)]); + } } fn test_array_max(file: &mut impl Write) { @@ -463,6 +475,15 @@ fn test_array_max(file: &mut impl Write) { run_ast(file, "array_max([1.2, NULL, 3.4, 5.6, NULL])", &[]); run_ast(file, "array_max(['a', 'b', 'c', 'd', 'e'])", &[]); run_ast(file, "array_max(['a', 'b', NULL, 'c', 'd', NULL])", &[]); + run_ast(file, "array_max(CAST(NULL AS Nullable(Array(Int64))))", &[]); + + run_ast(file, "array_max(a)", &[( + "a", + column_from_json!( + DataType::Array(Box::new(Int16Type::data_type())).wrap_nullable(), + [null, [1, 5, 8, 3], [1, 5], null] + ), + )]); run_ast(file, "array_max([a, b, c, d])", &[ ("a", Int16Type::from_data(vec![1i16, 5, 8, 3])), @@ -498,6 +519,16 @@ fn test_array_max(file: &mut impl Write) { "array_max(parse_json('[\"a\", \"b\", \"c\", \"d\"]'))", &[], ); + + run_ast(file, "array_max(a)", &[( + "a", + column_from_json!(DataType::EmptyArray, [[], []]), + )]); + + run_ast(file, "array_max(a)", &[( + "a", + column_from_json!(DataType::EmptyArray.wrap_nullable(), [null, [], []]), + )]); } fn test_array_min(file: &mut impl Write) { diff --git a/src/query/functions/tests/it/scalars/testdata/array.txt b/src/query/functions/tests/it/scalars/testdata/array.txt index 6c14c43248998..f938fad52ea93 100644 --- a/src/query/functions/tests/it/scalars/testdata/array.txt +++ b/src/query/functions/tests/it/scalars/testdata/array.txt @@ -1491,6 +1491,15 @@ output domain : {4..=4} output : 4 +ast : array_count(CAST(NULL AS Nullable(Array(Int64)))) +raw expr : array_count(CAST(NULL AS Array(Int64) NULL)) +checked expr : array_count(CAST(NULL AS Array(Int64) NULL)) +optimized expr : NULL +output type : UInt64 NULL +output domain : {NULL} +output : NULL + + ast : array_count([a, b, c, d]) raw expr : array_count(array(a::Int16, b::Int16, c::Int16, d::Int16)) checked expr : array_count(array(a, b, c, d)) @@ -1517,6 +1526,29 @@ evaluation (internal): +--------+-----------------------------+ +ast : array_count(a) +raw expr : array_count(a::Array(Int16) NULL) +checked expr : array_count(a) +evaluation: ++--------+--------------------+-------------+ +| | a | Output | ++--------+--------------------+-------------+ +| Type | Array(Int16) NULL | UInt64 NULL | +| Domain | [{1..=8}] ∪ {NULL} | Unknown | +| Row 0 | NULL | NULL | +| Row 1 | [1, 5, 8, 3] | 4 | +| Row 2 | [1, 5] | 2 | +| Row 3 | NULL | NULL | ++--------+--------------------+-------------+ +evaluation (internal): ++--------+----------------------------------------------------------------------------------------------------------------------------------------+ +| Column | Data | ++--------+----------------------------------------------------------------------------------------------------------------------------------------+ +| a | Column(NullableColumn { column: ArrayColumn { values: Int16([1, 5, 8, 3, 1, 5]), offsets: [0, 0, 4, 6, 6] }, validity: [0b____0110] }) | +| Output | NullableColumn { column: UInt64([0, 4, 2, 0]), validity: [0b____0110] } | ++--------+----------------------------------------------------------------------------------------------------------------------------------------+ + + ast : array_count([a, b, c, d]) raw expr : array_count(array(a::UInt64 NULL, b::UInt64 NULL, c::UInt64 NULL, d::UInt64 NULL)) checked expr : array_count(array(a, b, c, d)) @@ -1570,6 +1602,22 @@ output domain : {4..=4} output : 4 +ast : array_count(a) +raw expr : array_count(a::Array(Nothing)) +checked expr : array_count(a) +output type : UInt64 +output domain : Unknown +output : 0 + + +ast : array_count(a) +raw expr : array_count(a::Array(Nothing) NULL) +checked expr : array_count(a) +output type : UInt64 NULL +output domain : Unknown +output : NULL + + ast : array_max([]) raw expr : array_max(array()) checked expr : array_max(array<>()) @@ -1633,6 +1681,38 @@ output domain : {"d"..="d"} output : 'd' +ast : array_max(CAST(NULL AS Nullable(Array(Int64)))) +raw expr : array_max(CAST(NULL AS Array(Int64) NULL)) +checked expr : array_max(CAST(NULL AS Array(Int64) NULL)) +optimized expr : NULL +output type : Int64 NULL +output domain : {NULL} +output : NULL + + +ast : array_max(a) +raw expr : array_max(a::Array(Int16) NULL) +checked expr : array_max(a) +evaluation: ++--------+--------------------+------------+ +| | a | Output | ++--------+--------------------+------------+ +| Type | Array(Int16) NULL | Int16 NULL | +| Domain | [{1..=8}] ∪ {NULL} | Unknown | +| Row 0 | NULL | NULL | +| Row 1 | [1, 5, 8, 3] | 8 | +| Row 2 | [1, 5] | 5 | +| Row 3 | NULL | NULL | ++--------+--------------------+------------+ +evaluation (internal): ++--------+----------------------------------------------------------------------------------------------------------------------------------------+ +| Column | Data | ++--------+----------------------------------------------------------------------------------------------------------------------------------------+ +| a | Column(NullableColumn { column: ArrayColumn { values: Int16([1, 5, 8, 3, 1, 5]), offsets: [0, 0, 4, 6, 6] }, validity: [0b____0110] }) | +| Output | NullableColumn { column: Int16([0, 8, 5, 0]), validity: [0b____0110] } | ++--------+----------------------------------------------------------------------------------------------------------------------------------------+ + + ast : array_max([a, b, c, d]) raw expr : array_max(array(a::Int16, b::Int16, c::Int16, d::Int16)) checked expr : array_max(array(a, b, c, d)) @@ -1712,6 +1792,22 @@ output domain : Undefined output : '"d"' +ast : array_max(a) +raw expr : array_max(a::Array(Nothing)) +checked expr : array_max(a) +output type : NULL +output domain : Unknown +output : NULL + + +ast : array_max(a) +raw expr : array_max(a::Array(Nothing) NULL) +checked expr : array_max(a) +output type : NULL +output domain : Unknown +output : NULL + + ast : array_min([]) raw expr : array_min(array()) checked expr : array_min(array<>())