diff --git a/src/query/ast/src/ast/common.rs b/src/query/ast/src/ast/common.rs index e9ce8ebc5d87..54986980b3ab 100644 --- a/src/query/ast/src/ast/common.rs +++ b/src/query/ast/src/ast/common.rs @@ -174,27 +174,6 @@ impl Display for TableRef { } } -#[derive(Debug, Clone, PartialEq, Eq, Drive, DriveMut)] -pub struct DictionaryRef { - pub catalog: Option, - pub database: Option, - pub dictionary_name: Identifier, -} - -impl Display for DictionaryRef { - fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { - assert!(self.catalog.is_none() || (self.catalog.is_some() && self.database.is_some())); - if let Some(catalog) = &self.catalog { - write!(f, "{}.", catalog)?; - } - if let Some(database) = &self.database { - write!(f, "{}.", database)?; - } - write!(f, "{}", self.dictionary_name)?; - Ok(()) - } -} - #[derive(Debug, Clone, PartialEq, Eq, Drive, DriveMut)] pub struct ColumnRef { pub database: Option, diff --git a/src/query/ast/src/ast/visitors/walk.rs b/src/query/ast/src/ast/visitors/walk.rs index 699c8e28e20a..b3ce0e395ed1 100644 --- a/src/query/ast/src/ast/visitors/walk.rs +++ b/src/query/ast/src/ast/visitors/walk.rs @@ -167,16 +167,6 @@ pub fn walk_table_ref<'a, V: Visitor<'a>>(visitor: &mut V, table: &'a TableRef) visitor.visit_identifier(&table.table); } -pub fn walk_dictionary_ref<'a, V: Visitor<'a>>(visitor: &mut V, dictionary: &'a DictionaryRef) { - if let Some(catalog) = &dictionary.catalog { - visitor.visit_identifier(catalog); - } - if let Some(database) = &dictionary.database { - visitor.visit_identifier(database); - } - visitor.visit_identifier(&dictionary.dictionary_name); -} - pub fn walk_query<'a, V: Visitor<'a>>(visitor: &mut V, query: &'a Query) { let Query { with, diff --git a/src/query/sql/src/planner/binder/bind_context.rs b/src/query/sql/src/planner/binder/bind_context.rs index 83f4c9faf7b8..020bd3bb9660 100644 --- a/src/query/sql/src/planner/binder/bind_context.rs +++ b/src/query/sql/src/planner/binder/bind_context.rs @@ -45,6 +45,7 @@ use crate::binder::ColumnBindingBuilder; use crate::normalize_identifier; use crate::optimizer::SExpr; use crate::plans::ScalarExpr; +use crate::plans::ScalarItem; use crate::ColumnSet; use crate::IndexType; use crate::MetadataRef; @@ -135,8 +136,7 @@ pub struct BindContext { pub view_info: Option<(String, String)>, /// Set-returning functions in current context. - /// The key is the `Expr::to_string` of the function. - pub srfs: DashMap, + pub srfs: Vec, pub inverted_index_map: Box>, @@ -177,7 +177,7 @@ impl BindContext { cte_map_ref: Box::default(), in_grouping: false, view_info: None, - srfs: DashMap::new(), + srfs: Vec::new(), inverted_index_map: Box::default(), expr_context: ExprContext::default(), planning_agg_index: false, @@ -197,7 +197,7 @@ impl BindContext { cte_map_ref: parent.cte_map_ref.clone(), in_grouping: false, view_info: None, - srfs: DashMap::new(), + srfs: Vec::new(), inverted_index_map: Box::default(), expr_context: ExprContext::default(), planning_agg_index: false, diff --git a/src/query/sql/src/planner/binder/bind_query/bind_select.rs b/src/query/sql/src/planner/binder/bind_query/bind_select.rs index a671a6d6fb5f..c4e94f7842d5 100644 --- a/src/query/sql/src/planner/binder/bind_query/bind_select.rs +++ b/src/query/sql/src/planner/binder/bind_query/bind_select.rs @@ -38,7 +38,6 @@ use derive_visitor::Drive; use derive_visitor::Visitor; use log::warn; -use crate::binder::project_set::SrfCollector; use crate::optimizer::SExpr; use crate::planner::binder::BindContext; use crate::planner::binder::Binder; @@ -102,20 +101,6 @@ impl Binder { let new_stmt = rewriter.rewrite(stmt)?; let stmt = new_stmt.as_ref().unwrap_or(stmt); - // Collect set returning functions - let set_returning_functions = { - let mut collector = SrfCollector::new(); - stmt.select_list.iter().for_each(|item| { - if let SelectTarget::AliasedExpr { expr, .. } = item { - collector.visit(expr); - } - }); - collector.into_srfs() - }; - - // Bind set returning functions - s_expr = self.bind_project_set(&mut from_context, &set_returning_functions, s_expr)?; - // Try put window definitions into bind context. // This operation should be before `normalize_select_list` because window functions can be used in select list. self.analyze_window_definition(&mut from_context, &stmt.window_list)?; @@ -123,6 +108,9 @@ impl Binder { // Generate a analyzed select list with from context let mut select_list = self.normalize_select_list(&mut from_context, &stmt.select_list)?; + // analyze set returning functions + self.analyze_project_set_select(&mut from_context, &mut select_list)?; + // This will potentially add some alias group items to `from_context` if find some. if let Some(group_by) = stmt.group_by.as_ref() { self.analyze_group_items(&mut from_context, &select_list, group_by)?; @@ -140,6 +128,12 @@ impl Binder { .map(|item| (item.alias.clone(), item.scalar.clone())) .collect::>(); + let have_srfs = !from_context.srfs.is_empty(); + if have_srfs { + // Bind set returning functions first. + s_expr = self.bind_project_set(&mut from_context, s_expr)?; + } + // To support using aliased column in `WHERE` clause, // we should bind where after `select_list` is rewritten. let where_scalar = if let Some(expr) = &stmt.selection { @@ -179,7 +173,7 @@ impl Binder { )?; // After all analysis is done. - if set_returning_functions.is_empty() { + if !have_srfs { // Ignore SRFs. self.analyze_lazy_materialization( &from_context, diff --git a/src/query/sql/src/planner/binder/bind_table_reference/bind_table_function.rs b/src/query/sql/src/planner/binder/bind_table_reference/bind_table_function.rs index 27d13ab2b077..98b17a22025e 100644 --- a/src/query/sql/src/planner/binder/bind_table_reference/bind_table_function.rs +++ b/src/query/sql/src/planner/binder/bind_table_reference/bind_table_function.rs @@ -348,10 +348,19 @@ impl Binder { lambda: None, }, }; - let srfs = vec![srf.clone()]; - let srf_expr = self.bind_project_set(&mut bind_context, &srfs, child)?; - - if let Some((_, srf_result)) = bind_context.srfs.remove(&srf.to_string()) { + let select_list = vec![SelectTarget::AliasedExpr { + expr: Box::new(srf.clone()), + alias: None, + }]; + let mut select_list = + self.normalize_select_list(&mut bind_context, &select_list)?; + // analyze set returning functions + self.analyze_project_set_select(&mut bind_context, &mut select_list)?; + // bind set returning functions + let srf_expr = self.bind_project_set(&mut bind_context, child)?; + + if let Some(item) = select_list.items.pop() { + let srf_result = item.scalar; let column_binding = if let ScalarExpr::BoundColumnRef(column_ref) = &srf_result { column_ref.column.clone() @@ -408,7 +417,7 @@ impl Binder { "The function '{}' is not supported for lateral joins. Lateral joins currently support only Set Returning Functions (SRFs).", func_name )) - .set_span(*span)) + .set_span(*span)) } } _ => unreachable!(), diff --git a/src/query/sql/src/planner/binder/project_set.rs b/src/query/sql/src/planner/binder/project_set.rs index 0b813b05599d..f52d653d2f79 100644 --- a/src/query/sql/src/planner/binder/project_set.rs +++ b/src/query/sql/src/planner/binder/project_set.rs @@ -12,209 +12,121 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::mem; use std::sync::Arc; -use databend_common_ast::ast::Expr; -use databend_common_ast::ast::FunctionCall; -use databend_common_ast::ast::Window; use databend_common_exception::Result; -use databend_common_expression::types::NumberScalar; use databend_common_expression::FunctionKind; -use databend_common_expression::Scalar; use databend_common_functions::BUILTIN_FUNCTIONS; -use derive_visitor::Drive; -use derive_visitor::Visitor; +use crate::binder::select::SelectList; use crate::binder::ColumnBindingBuilder; -use crate::binder::ExprContext; -use crate::normalize_identifier; use crate::optimizer::SExpr; +use crate::plans::walk_expr_mut; use crate::plans::BoundColumnRef; -use crate::plans::FunctionCall as ScalarExprFunctionCall; +use crate::plans::FunctionCall; use crate::plans::ProjectSet; -use crate::plans::SrfItem; +use crate::plans::ScalarItem; +use crate::plans::VisitorMut; use crate::BindContext; use crate::Binder; -use crate::ScalarBinder; +use crate::MetadataRef; use crate::ScalarExpr; use crate::Visibility; -#[derive(Visitor)] -#[visitor(FunctionCall(enter), Window)] -pub struct SrfCollector { - srfs: Vec, - in_window: bool, +pub(super) struct SetReturningRewriter<'a> { + bind_context: &'a mut BindContext, + metadata: MetadataRef, } -impl SrfCollector { - fn enter_function_call(&mut self, func: &FunctionCall) { - // TODO(andylokandy/leisky): SRF in window function is not supported yet. - // This is a workaround to skip SRF in window function. - if self.in_window { - return; +impl<'a> SetReturningRewriter<'a> { + fn new(bind_context: &'a mut BindContext, metadata: MetadataRef) -> Self { + Self { + bind_context, + metadata, } + } - let FunctionCall { - distinct, - name, - args, - params, - window, - lambda, - } = func; - - if BUILTIN_FUNCTIONS - .get_property(&name.name) - .map(|property| property.kind == FunctionKind::SRF) - .unwrap_or(false) - { - // Collect the srf - self.srfs.push(Expr::FunctionCall { - span: name.span, - func: FunctionCall { - distinct: *distinct, - name: name.clone(), - args: args.to_vec(), - params: params.to_vec(), - window: window.clone(), - lambda: lambda.clone(), - }, - }); + /// Replace the set returning function with a BoundColumnRef. + fn replace_set_returning_function(&mut self, func: &FunctionCall) -> Result { + let srf_func = ScalarExpr::FunctionCall(func.clone()); + let data_type = srf_func.data_type()?; + + let column_index = self.metadata.write().add_derived_column( + func.func_name.clone(), + data_type.clone(), + Some(srf_func.clone()), + ); + let column = ColumnBindingBuilder::new( + func.func_name.clone(), + column_index, + Box::new(data_type), + Visibility::InVisible, + ) + .build(); + + // Add the srf to bind context, build ProjectSet plan later. + self.bind_context.srfs.push(ScalarItem { + index: column_index, + scalar: srf_func, + }); + + Ok(BoundColumnRef { + span: func.span, + column, } + .into()) } +} - fn enter_window(&mut self, _window: &Window) { - self.in_window = true; - } +impl<'a> VisitorMut<'a> for SetReturningRewriter<'a> { + fn visit(&mut self, expr: &'a mut ScalarExpr) -> Result<()> { + if let ScalarExpr::FunctionCall(func) = expr { + if BUILTIN_FUNCTIONS + .get_property(&func.func_name) + .map(|property| property.kind == FunctionKind::SRF) + .unwrap_or(false) + { + *expr = self.replace_set_returning_function(func)?; + return Ok(()); + } + } - fn exit_window(&mut self, _window: &Window) { - self.in_window = false; + walk_expr_mut(self, expr) } } -impl SrfCollector { - pub fn new() -> Self { - SrfCollector { - srfs: vec![], - in_window: false, +impl Binder { + /// Analyze project sets in select clause, this will rewrite project set functions. + /// See [`SetReturningRewriter`] for more details. + pub(crate) fn analyze_project_set_select( + &mut self, + bind_context: &mut BindContext, + select_list: &mut SelectList, + ) -> Result<()> { + for item in select_list.items.iter_mut() { + let mut rewriter = SetReturningRewriter::new(bind_context, self.metadata.clone()); + rewriter.visit(&mut item.scalar)?; } - } - - pub fn visit(&mut self, expr: &Expr) { - expr.drive(self); - } - pub fn into_srfs(self) -> Vec { - self.srfs + Ok(()) } -} -impl Binder { pub fn bind_project_set( &mut self, bind_context: &mut BindContext, - srfs: &[Expr], - s_expr: SExpr, + child: SExpr, ) -> Result { - if srfs.is_empty() { - return Ok(s_expr); + if bind_context.srfs.is_empty() { + return Ok(child); } - let mut items = Vec::with_capacity(srfs.len()); - for srf in srfs { - let (name, srf_scalar) = match srf { - Expr::FunctionCall { - func: FunctionCall { name, args, .. }, - .. - } => { - let name = normalize_identifier(name, &self.name_resolution_ctx).to_string(); - - let original_context = bind_context.expr_context.clone(); - bind_context.set_expr_context(ExprContext::InSetReturningFunction); - - let mut arguments = Vec::with_capacity(args.len()); - for arg in args.iter() { - let mut scalar_binder = ScalarBinder::new( - bind_context, - self.ctx.clone(), - &self.name_resolution_ctx, - self.metadata.clone(), - &[], - self.m_cte_bound_ctx.clone(), - self.ctes_map.clone(), - ); - let (scalar, _) = scalar_binder.bind(arg)?; - arguments.push(scalar); - } - - // Restore the original context - bind_context.set_expr_context(original_context); - - let scalar = ScalarExpr::FunctionCall(ScalarExprFunctionCall { - span: srf.span(), - func_name: name.clone(), - params: vec![], - arguments, - }); - - (name, scalar) - } - - // Should have been checked by SrfCollector - _ => unreachable!(), - }; - - let srf_expr = srf_scalar.as_expr()?; - let return_types = srf_expr.data_type().as_tuple().unwrap(); - - // Add result column to metadata - let column_index = self.metadata.write().add_derived_column( - name.clone(), - srf_expr.data_type().clone(), - Some(srf_scalar.clone()), - ); - let column = ColumnBindingBuilder::new( - name.clone(), - column_index, - Box::new(srf_expr.data_type().clone()), - Visibility::InVisible, - ) - .build(); - - let item = SrfItem { - scalar: srf_scalar, - index: column_index, - }; - items.push(item); - - // If tuple has more than one field, return the tuple column, - // otherwise, extract the tuple field to top level column. - let result_column = if return_types.len() > 1 { - ScalarExpr::BoundColumnRef(BoundColumnRef { - span: srf.span(), - column, - }) - } else { - ScalarExpr::FunctionCall(ScalarExprFunctionCall { - span: srf.span(), - func_name: "get".to_string(), - params: vec![Scalar::Number(NumberScalar::Int64(1))], - arguments: vec![ScalarExpr::BoundColumnRef(BoundColumnRef { - span: srf.span(), - column, - })], - }) - }; - - // Add the srf to bind context, so we can replace the srfs later. - bind_context.srfs.insert(srf.to_string(), result_column); - } + // Build a ProjectSet Plan. + let srfs = mem::take(&mut bind_context.srfs); + let project_set = ProjectSet { srfs }; - let project_set = ProjectSet { srfs: items }; + let new_expr = SExpr::create_unary(Arc::new(project_set.into()), Arc::new(child)); - Ok(SExpr::create_unary( - Arc::new(project_set.into()), - Arc::new(s_expr), - )) + Ok(new_expr) } } diff --git a/src/query/sql/src/planner/binder/table.rs b/src/query/sql/src/planner/binder/table.rs index 47df576a18e4..e0adf096848c 100644 --- a/src/query/sql/src/planner/binder/table.rs +++ b/src/query/sql/src/planner/binder/table.rs @@ -208,7 +208,7 @@ impl Binder { cte_map_ref: Box::default(), in_grouping: false, view_info: None, - srfs: Default::default(), + srfs: vec![], inverted_index_map: Box::default(), expr_context: ExprContext::default(), planning_agg_index: false, diff --git a/src/query/sql/src/planner/optimizer/decorrelate/flatten_plan.rs b/src/query/sql/src/planner/optimizer/decorrelate/flatten_plan.rs index 7cd29ec2e487..74bd88267650 100644 --- a/src/query/sql/src/planner/optimizer/decorrelate/flatten_plan.rs +++ b/src/query/sql/src/planner/optimizer/decorrelate/flatten_plan.rs @@ -43,7 +43,6 @@ use crate::plans::ScalarExpr; use crate::plans::ScalarItem; use crate::plans::Scan; use crate::plans::Sort; -use crate::plans::SrfItem; use crate::plans::UnionAll; use crate::plans::Window; use crate::BaseTableColumn; @@ -324,7 +323,7 @@ impl SubqueryRewriter { )?; let mut srfs = Vec::with_capacity(project_set.srfs.len()); for item in project_set.srfs.iter() { - let new_item = SrfItem { + let new_item = ScalarItem { scalar: self.flatten_scalar(&item.scalar, correlated_columns)?, index: item.index, }; diff --git a/src/query/sql/src/planner/plans/project_set.rs b/src/query/sql/src/planner/plans/project_set.rs index ea6f8d3f7f5a..b94dea408bf1 100644 --- a/src/query/sql/src/planner/plans/project_set.rs +++ b/src/query/sql/src/planner/plans/project_set.rs @@ -20,23 +20,14 @@ use crate::optimizer::RelationalProperty; use crate::optimizer::StatInfo; use crate::plans::Operator; use crate::plans::RelOp; -use crate::IndexType; -use crate::ScalarExpr; - -/// An item of set-returning function. -/// Contains definition of srf and its output columns. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct SrfItem { - pub scalar: ScalarExpr, - pub index: IndexType, -} +use crate::plans::ScalarItem; /// `ProjectSet` is a plan that evaluate a series of /// set-returning functions, zip the result together, /// and return the joined result with input relation. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct ProjectSet { - pub srfs: Vec, + pub srfs: Vec, } impl Operator for ProjectSet { diff --git a/src/query/sql/src/planner/semantic/type_check.rs b/src/query/sql/src/planner/semantic/type_check.rs index 1fad08b1e494..5d94ae7db84a 100644 --- a/src/query/sql/src/planner/semantic/type_check.rs +++ b/src/query/sql/src/planner/semantic/type_check.rs @@ -230,18 +230,6 @@ impl<'a> TypeChecker<'a> { #[recursive::recursive] pub fn resolve(&mut self, expr: &Expr) -> Result> { - if let Some(scalar) = self.bind_context.srfs.get(&expr.to_string()) { - if !matches!(self.bind_context.expr_context, ExprContext::SelectClause) { - return Err(ErrorCode::SemanticError( - "set-returning functions are only allowed in SELECT clause", - ) - .set_span(expr.span())); - } - // Found a SRF, return it directly. - // See `Binder::bind_project_set` for more details. - return Ok(Box::new((scalar.clone(), scalar.data_type()?))); - } - let box (scalar, data_type): Box<(ScalarExpr, DataType)> = match expr { Expr::ColumnRef { span, @@ -751,6 +739,7 @@ impl<'a> TypeChecker<'a> { .chain(GENERAL_WINDOW_FUNCTIONS.iter().cloned().map(str::to_string)) .chain(GENERAL_LAMBDA_FUNCTIONS.iter().cloned().map(str::to_string)) .chain(GENERAL_SEARCH_FUNCTIONS.iter().cloned().map(str::to_string)) + .chain(ASYNC_FUNCTIONS.iter().cloned().map(str::to_string)) .chain( Self::all_sugar_functions() .iter() @@ -801,40 +790,6 @@ impl<'a> TypeChecker<'a> { let args: Vec<&Expr> = args.iter().collect(); - // Check assumptions if it is a set returning function - if BUILTIN_FUNCTIONS - .get_property(func_name) - .map(|property| property.kind == FunctionKind::SRF) - .unwrap_or(false) - { - if matches!( - self.bind_context.expr_context, - ExprContext::InSetReturningFunction - ) { - return Err(ErrorCode::SemanticError( - "set-returning functions cannot be nested".to_string(), - ) - .set_span(*span)); - } - - if self.in_window_function { - return Err(ErrorCode::SemanticError( - "set-returning functions cannot be used in window spec", - ) - .set_span(*span)); - } - - if !matches!(self.bind_context.expr_context, ExprContext::SelectClause) { - return Err(ErrorCode::SemanticError( - "set-returning functions can only be used in SELECT".to_string(), - ) - .set_span(*span)); - } - - // Should have been handled with `BindContext::srfs` - return Err(ErrorCode::Internal("Logical error, there is a bug!")); - } - if GENERAL_WINDOW_FUNCTIONS.contains(&func_name) { // general window function if window.is_none() { @@ -926,6 +881,13 @@ impl<'a> TypeChecker<'a> { let data_type = async_func.return_type.as_ref().clone(); Box::new((async_func.into(), data_type)) + } else if BUILTIN_FUNCTIONS + .get_property(func_name) + .map(|property| property.kind == FunctionKind::SRF) + .unwrap_or(false) + { + // Set returning function + self.resolve_set_returning_function(*span, func_name, &args)? } else { // Scalar function let mut new_params: Vec = Vec::with_capacity(params.len()); @@ -2519,8 +2481,80 @@ impl<'a> TypeChecker<'a> { Ok(Box::new((scalar_expr, data_type))) } - /// Resolve function call. + /// Resolve set returning function. + pub fn resolve_set_returning_function( + &mut self, + span: Span, + func_name: &str, + args: &[&Expr], + ) -> Result> { + if matches!( + self.bind_context.expr_context, + ExprContext::InSetReturningFunction + ) { + return Err(ErrorCode::SemanticError( + "set-returning functions cannot be nested".to_string(), + ) + .set_span(span)); + } + if self.in_window_function { + return Err(ErrorCode::SemanticError( + "set-returning functions cannot be used in window spec", + ) + .set_span(span)); + } + if !matches!(self.bind_context.expr_context, ExprContext::SelectClause) { + return Err(ErrorCode::SemanticError( + "set-returning functions can only be used in SELECT".to_string(), + ) + .set_span(span)); + } + + let original_context = self.bind_context.expr_context.clone(); + self.bind_context + .set_expr_context(ExprContext::InSetReturningFunction); + + let mut arguments = Vec::with_capacity(args.len()); + for arg in args.iter() { + let box (scalar, _) = self.resolve(arg)?; + arguments.push(scalar); + } + + // Restore the original context + self.bind_context.set_expr_context(original_context); + + let srf_scalar = ScalarExpr::FunctionCall(FunctionCall { + span, + func_name: func_name.to_string(), + params: vec![], + arguments, + }); + let srf_expr = srf_scalar.as_expr()?; + let srf_tuple_types = srf_expr.data_type().as_tuple().ok_or_else(|| { + ErrorCode::Internal(format!( + "The return type of srf should be tuple, but got {}", + srf_expr.data_type() + )) + })?; + // If tuple has more than one field, return the tuple column, + // otherwise, extract the tuple field to top level column. + let (return_scalar, return_type) = if srf_tuple_types.len() > 1 { + (srf_scalar, srf_expr.data_type().clone()) + } else { + let child_scalar = ScalarExpr::FunctionCall(FunctionCall { + span, + func_name: "get".to_string(), + params: vec![Scalar::Number(NumberScalar::Int64(1))], + arguments: vec![srf_scalar], + }); + (child_scalar, srf_tuple_types[0].clone()) + }; + + Ok(Box::new((return_scalar, return_type))) + } + + /// Resolve function call. pub fn resolve_function( &mut self, span: Span, diff --git a/tests/sqllogictests/suites/query/functions/02_0065_function_json.test b/tests/sqllogictests/suites/query/functions/02_0065_function_json.test index 5593645ee593..5f219285b155 100644 --- a/tests/sqllogictests/suites/query/functions/02_0065_function_json.test +++ b/tests/sqllogictests/suites/query/functions/02_0065_function_json.test @@ -356,6 +356,12 @@ SELECT json_each(parse_json('{"a": 1, "b": [1,2,3], "c": true, "d": {"k1": 1, "k ('c','true') ('d','{"k1":1,"k2":2}') +query TT +SELECT parse_json('{"k1": "v1", "k2": "v2"}') config, json_each(config) +---- +{"k1":"v1","k2":"v2"} ('k1','"v1"') +{"k1":"v1","k2":"v2"} ('k2','"v2"') + query TT SELECT * FROM json_each(parse_json('{"a": 1, "b": [1,2,3], "c": true, "d": {"k1": 1, "k2": 2}}')) ---- @@ -387,6 +393,13 @@ SELECT json_array_elements(parse_json('[1, 2, 3]')) 2 3 +query TT +SELECT parse_json('{"k": [1, 2, 3]}') config, json_array_elements(config:k) +---- +{"k":[1,2,3]} 1 +{"k":[1,2,3]} 2 +{"k":[1,2,3]} 3 + query T SELECT json_array_elements(parse_json('{"a": 1, "b": [1,2,3], "c": true, "d": {"k1": 1, "k2": 2}}')) ----