This is an automated email from the ASF dual-hosted git repository.
HappenLee pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/master by this push:
new 556d74801e1 [feature](function) Support Spark/Hive-compatible stack
table function (#66734)
556d74801e1 is described below
commit 556d74801e122e1872c9ceb4a08f781c4f926488
Author: HappenLee <[email protected]>
AuthorDate: Tue Aug 18 18:50:03 2026 +0800
[feature](function) Support Spark/Hive-compatible stack table function
(#66734)
Issue Number: close #66687
Related PR:
[apache/doris-website#4059](https://github.com/apache/doris-website/pull/4059)
Problem Summary: Add Spark/Hive-compatible stack(num_rows, ...) support
with row-major output, NULL padding, column type validation, and
recursive constant folding.
### Release note
Support Spark/Hive-compatible stack table-generating function semantics.
### Check List (For Author)
- Test: FE StackTest 6/6; BE TableFunctionTest.vstack; targeted
regression generation and rerun; FE build/checkstyle; BE
format/clang-tidy
- Behavior changed: Yes (adds stack table function compatibility)
- Does this need documentation: Yes
([apache/doris-website#4059](https://github.com/apache/doris-website/pull/4059))
---
be/src/exprs/function/function_fake.cpp | 10 ++
.../table_function/table_function_factory.cpp | 2 +
be/src/exprs/table_function/vstack.cpp | 135 +++++++++++++++
be/src/exprs/table_function/vstack.h | 54 ++++++
be/test/exprs/function/function_test_util.cpp | 3 +-
be/test/exprs/function/table_function_test.cpp | 53 ++++++
.../catalog/BuiltinTableGeneratingFunctions.java | 4 +-
.../nereids/rules/analysis/BindExpression.java | 22 ++-
.../expression/rules/FoldConstantRuleOnFE.java | 40 +++++
.../functions/executable/ArrayArithmetic.java | 14 ++
.../expressions/functions/generator/Stack.java | 163 ++++++++++++++++++
.../visitor/TableGeneratingFunctionVisitor.java | 5 +
.../expressions/functions/generator/StackTest.java | 191 +++++++++++++++++++++
.../sql_functions/table_function/stack.out | 52 ++++++
.../sql_functions/table_function/stack.groovy | 147 ++++++++++++++++
15 files changed, 891 insertions(+), 4 deletions(-)
diff --git a/be/src/exprs/function/function_fake.cpp
b/be/src/exprs/function/function_fake.cpp
index 0966ed9af61..5e048d15fee 100644
--- a/be/src/exprs/function/function_fake.cpp
+++ b/be/src/exprs/function/function_fake.cpp
@@ -181,6 +181,15 @@ struct FunctionEsquery {
static std::string get_error_msg() { return "esquery only supported on es
table"; }
};
+class FunctionStack : public FunctionFake<UDTFImpl> {
+public:
+ static FunctionPtr create() { return std::make_shared<FunctionStack>(); }
+
+ bool skip_return_type_check() const override { return true; }
+
+ ColumnNumbers get_arguments_that_are_always_constant() const override {
return {0}; }
+};
+
template <typename FunctionImpl>
void register_function(SimpleFunctionFactory& factory, const std::string&
name) {
factory.register_function<FunctionFake<FunctionImpl>>(name);
@@ -254,6 +263,7 @@ void
register_table_function_with_impl(SimpleFunctionFactory& factory, const std
void register_function_fake(SimpleFunctionFactory& factory) {
register_function<FunctionEsquery>(factory, "esquery");
+ factory.register_function<FunctionStack>("stack");
register_table_function_expand_outer<FunctionExplodeV2>(factory,
"explode");
register_table_alternative_function_expand_outer<FunctionExplode>(factory,
"explode");
diff --git a/be/src/exprs/table_function/table_function_factory.cpp
b/be/src/exprs/table_function/table_function_factory.cpp
index f4c705c183c..57bd58d2b25 100644
--- a/be/src/exprs/table_function/table_function_factory.cpp
+++ b/be/src/exprs/table_function/table_function_factory.cpp
@@ -35,6 +35,7 @@
#include "exprs/table_function/vexplode_numbers.h"
#include "exprs/table_function/vexplode_v2.h"
#include "exprs/table_function/vjson_each.h"
+#include "exprs/table_function/vstack.h"
namespace doris {
@@ -53,6 +54,7 @@ const std::unordered_map<std::string,
std::function<std::unique_ptr<TableFunctio
{"json_each", TableFunctionCreator<VJsonEachTableFn> {}},
{"json_each_text", TableFunctionCreator<VJsonEachTextTableFn>
{}},
{"posexplode", TableFunctionCreator<VExplodeV2TableFunction>
{}},
+ {"stack", TableFunctionCreator<VStackTableFunction> {}},
{"explode", TableFunctionCreator<VExplodeV2TableFunction> {}},
{"explode_variant_array_old",
TableFunctionCreator<VExplodeTableFunction>()},
{"explode_old", TableFunctionCreator<VExplodeTableFunction>
{}}};
diff --git a/be/src/exprs/table_function/vstack.cpp
b/be/src/exprs/table_function/vstack.cpp
new file mode 100644
index 00000000000..a0eb24417e6
--- /dev/null
+++ b/be/src/exprs/table_function/vstack.cpp
@@ -0,0 +1,135 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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.
+
+#include "exprs/table_function/vstack.h"
+
+#include <algorithm>
+
+#include "core/assert_cast.h"
+#include "core/block/block.h"
+#include "core/column/column_const.h"
+#include "core/column/column_nullable.h"
+#include "core/column/column_struct.h"
+#include "exprs/vexpr.h"
+
+namespace doris {
+
+VStackTableFunction::VStackTableFunction() {
+ _fn_name = "stack";
+}
+
+Status VStackTableFunction::process_init(Block* block, RuntimeState*
/*state*/) {
+ const auto& children = _expr_context->root()->children();
+ DORIS_CHECK_GE(children.size(), 2);
+
+ int column_index = -1;
+ RETURN_IF_ERROR(children[0]->execute(_expr_context.get(), block,
&column_index));
+ const auto& num_rows_column = block->get_by_position(column_index).column;
+ DORIS_CHECK(is_column_const(*num_rows_column));
+ const auto num_rows = assert_cast<const
ColumnConst&>(*num_rows_column).get_int(0);
+ DORIS_CHECK_GT(num_rows, 0);
+ _num_rows = static_cast<size_t>(num_rows);
+ _num_fields = (children.size() - 2) / _num_rows + 1;
+
+ _value_columns.clear();
+ _value_columns.reserve(children.size() - 1);
+ for (size_t i = 1; i < children.size(); ++i) {
+ RETURN_IF_ERROR(children[i]->execute(_expr_context.get(), block,
&column_index));
+ const auto& value_column = block->get_by_position(column_index).column;
+ const auto& [column, is_const] = unpack_if_const(value_column);
+ _value_columns.emplace_back(ValueColumn {.column = column, .is_const =
is_const});
+ }
+ return Status::OK();
+}
+
+void VStackTableFunction::process_row(size_t row_idx) {
+ TableFunction::process_row(row_idx);
+ _row_idx = row_idx;
+ _cur_size = static_cast<int64_t>(_num_rows);
+}
+
+void VStackTableFunction::process_close() {
+ _value_columns.clear();
+ _row_idx = 0;
+ _num_rows = 0;
+ _num_fields = 0;
+}
+
+void VStackTableFunction::_insert_value(IColumn& destination, const IColumn&
source,
+ size_t source_row) {
+ auto* nullable_destination =
check_and_get_column<ColumnNullable>(&destination);
+ DORIS_CHECK(nullable_destination != nullptr);
+
+ if (const auto* nullable_source =
check_and_get_column<ColumnNullable>(&source)) {
+
nullable_destination->get_nested_column().insert_from(nullable_source->get_nested_column(),
+ source_row);
+ nullable_destination->get_null_map_data().push_back(
+ nullable_source->get_null_map_data()[source_row]);
+ } else {
+ nullable_destination->get_nested_column().insert_from(source,
source_row);
+ nullable_destination->get_null_map_data().push_back(0);
+ }
+}
+
+void VStackTableFunction::_insert_output_row(MutableColumnPtr& column, size_t
output_row) const {
+ IColumn* output = column.get();
+ if (_num_fields == 1) {
+ const size_t value_index = output_row;
+ if (value_index < _value_columns.size()) {
+ const auto& value_column = _value_columns[value_index];
+ _insert_value(*output, *value_column.column, value_column.is_const
? 0 : _row_idx);
+ } else {
+ output->insert_default();
+ }
+ return;
+ }
+
+ if (_is_nullable) {
+ auto& nullable_output = assert_cast<ColumnNullable&>(*output);
+ nullable_output.get_null_map_data().push_back(0);
+ output = &nullable_output.get_nested_column();
+ }
+
+ auto& struct_output = assert_cast<ColumnStruct&>(*output);
+ for (size_t field_index = 0; field_index < _num_fields; ++field_index) {
+ const size_t value_index = output_row * _num_fields + field_index;
+ auto& field = struct_output.get_column(field_index);
+ if (value_index < _value_columns.size()) {
+ const auto& value_column = _value_columns[value_index];
+ _insert_value(field, *value_column.column, value_column.is_const ?
0 : _row_idx);
+ } else {
+ field.insert_default();
+ }
+ }
+}
+
+void VStackTableFunction::get_same_many_values(MutableColumnPtr& column, int
length) {
+ for (int i = 0; i < length; ++i) {
+ _insert_output_row(column, static_cast<size_t>(_cur_offset));
+ }
+}
+
+int VStackTableFunction::get_value(MutableColumnPtr& column, int max_step) {
+ max_step = std::min(max_step, static_cast<int>(_cur_size - _cur_offset));
+ for (int i = 0; i < max_step; ++i) {
+ _insert_output_row(column, static_cast<size_t>(_cur_offset + i));
+ }
+ forward(max_step);
+ return max_step;
+}
+
+} // namespace doris
diff --git a/be/src/exprs/table_function/vstack.h
b/be/src/exprs/table_function/vstack.h
new file mode 100644
index 00000000000..c71f0438469
--- /dev/null
+++ b/be/src/exprs/table_function/vstack.h
@@ -0,0 +1,54 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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.
+
+#pragma once
+
+#include <cstddef>
+#include <vector>
+
+#include "exprs/table_function/table_function.h"
+
+namespace doris {
+
+class VStackTableFunction : public TableFunction {
+ ENABLE_FACTORY_CREATOR(VStackTableFunction);
+
+public:
+ VStackTableFunction();
+
+ Status process_init(Block* block, RuntimeState* state) override;
+ void process_row(size_t row_idx) override;
+ void process_close() override;
+ void get_same_many_values(MutableColumnPtr& column, int length) override;
+ int get_value(MutableColumnPtr& column, int max_step) override;
+
+private:
+ struct ValueColumn {
+ ColumnPtr column;
+ bool is_const;
+ };
+
+ void _insert_output_row(MutableColumnPtr& column, size_t output_row) const;
+ static void _insert_value(IColumn& destination, const IColumn& source,
size_t source_row);
+
+ std::vector<ValueColumn> _value_columns;
+ size_t _row_idx = 0;
+ size_t _num_rows = 0;
+ size_t _num_fields = 0;
+};
+
+} // namespace doris
diff --git a/be/test/exprs/function/function_test_util.cpp
b/be/test/exprs/function/function_test_util.cpp
index b37e452b69a..b24b8846b30 100644
--- a/be/test/exprs/function/function_test_util.cpp
+++ b/be/test/exprs/function/function_test_util.cpp
@@ -599,7 +599,8 @@ static Block* create_block_from_inputset(const
InputTypeSet& input_types,
?
((DataTypeNullable*)(desc.data_type.get()))->get_nested_type()
: desc.data_type;
- for (int r = 0; r < row_size; r++) {
+ const size_t rows_to_insert = desc.is_const ? 1 : row_size;
+ for (size_t r = 0; r < rows_to_insert; r++) {
if (!insert_cell(column, type_ptr, input_set[r][i *
input_col_size + j])) {
return nullptr;
}
diff --git a/be/test/exprs/function/table_function_test.cpp
b/be/test/exprs/function/table_function_test.cpp
index 326a2d98f98..06ae08af713 100644
--- a/be/test/exprs/function/table_function_test.cpp
+++ b/be/test/exprs/function/table_function_test.cpp
@@ -31,6 +31,7 @@
#include "exprs/table_function/vexplode_numbers.h"
#include "exprs/table_function/vexplode_v2.h"
#include "exprs/table_function/vjson_each.h"
+#include "exprs/table_function/vstack.h"
#include "testutil/any_type.h"
#include "util/jsonb_parser_simd.h"
#include "util/jsonb_utils.h"
@@ -304,6 +305,58 @@ TEST_F(TableFunctionTest, vexplode_v2_two_param) {
}
}
+TEST_F(TableFunctionTest, vstack) {
+ init_expr_context(4);
+ VStackTableFunction stack;
+ stack.set_expr_context(_ctx);
+
+ {
+ InputTypeSet input_types = {ConstedNotnull {PrimitiveType::TYPE_INT},
+ PrimitiveType::TYPE_INT,
PrimitiveType::TYPE_INT,
+ PrimitiveType::TYPE_INT};
+ InputDataSet input_set = {{Int32(2), Int32(1), Int32(2), Int32(3)}};
+
+ InputTypeSet output_types = {PrimitiveType::TYPE_STRUCT,
PrimitiveType::TYPE_INT,
+ PrimitiveType::TYPE_INT};
+ InputDataSet output_set = {{{TestArray {Int32(1), Int32(2)}}},
+ {{TestArray {Int32(3), Null()}}}};
+
+ check_vec_table_function(&stack, input_types, input_set, output_types,
output_set, false);
+ check_vec_table_function(&stack, input_types, input_set, output_types,
output_set, true);
+ }
+
+ {
+ InputTypeSet input_types = {
+ ConstedNotnull {PrimitiveType::TYPE_INT}, ConstedNotnull
{PrimitiveType::TYPE_INT},
+ ConstedNotnull {PrimitiveType::TYPE_INT}, ConstedNotnull
{PrimitiveType::TYPE_INT}};
+ InputDataSet input_set = {{Int32(2), Int32(1), Int32(2), Int32(3)},
+ {Int32(2), Int32(1), Int32(2), Int32(3)}};
+
+ InputTypeSet output_types = {PrimitiveType::TYPE_STRUCT,
PrimitiveType::TYPE_INT,
+ PrimitiveType::TYPE_INT};
+ InputDataSet output_set = {{{TestArray {Int32(1), Int32(2)}}},
+ {{TestArray {Int32(3), Null()}}},
+ {{TestArray {Int32(1), Int32(2)}}},
+ {{TestArray {Int32(3), Null()}}}};
+
+ check_vec_table_function(&stack, input_types, input_set, output_types,
output_set, false);
+ check_vec_table_function(&stack, input_types, input_set, output_types,
output_set, true);
+ }
+
+ {
+ InputTypeSet input_types = {ConstedNotnull {PrimitiveType::TYPE_INT},
+ PrimitiveType::TYPE_INT,
PrimitiveType::TYPE_INT,
+ PrimitiveType::TYPE_INT};
+ InputDataSet input_set = {{Int32(4), Int32(1), Null(), Int32(3)}};
+
+ InputTypeSet output_types = {PrimitiveType::TYPE_INT};
+ InputDataSet output_set = {{Int32(1)}, {Null()}, {Int32(3)}, {Null()}};
+
+ check_vec_table_function(&stack, input_types, input_set, output_types,
output_set, false);
+ check_vec_table_function(&stack, input_types, input_set, output_types,
output_set, true);
+ }
+}
+
TEST_F(TableFunctionTest, vexplode_numbers) {
init_expr_context(1);
VExplodeNumbersTableFunction tfn;
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/catalog/BuiltinTableGeneratingFunctions.java
b/fe/fe-core/src/main/java/org/apache/doris/catalog/BuiltinTableGeneratingFunctions.java
index f764da5718c..165f3c5f966 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/catalog/BuiltinTableGeneratingFunctions.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/catalog/BuiltinTableGeneratingFunctions.java
@@ -44,6 +44,7 @@ import
org.apache.doris.nereids.trees.expressions.functions.generator.JsonEachTe
import
org.apache.doris.nereids.trees.expressions.functions.generator.JsonEachTextOuter;
import
org.apache.doris.nereids.trees.expressions.functions.generator.PosExplode;
import
org.apache.doris.nereids.trees.expressions.functions.generator.PosExplodeOuter;
+import org.apache.doris.nereids.trees.expressions.functions.generator.Stack;
import org.apache.doris.nereids.trees.expressions.functions.generator.Unnest;
import com.google.common.collect.ImmutableList;
@@ -88,6 +89,7 @@ public class BuiltinTableGeneratingFunctions implements
FunctionHelper {
tableGenerating(JsonEachTextOuter.class, "json_each_text_outer"),
tableGenerating(PosExplode.class, "posexplode"),
tableGenerating(PosExplodeOuter.class, "posexplode_outer"),
+ tableGenerating(Stack.class, "stack"),
tableGenerating(Unnest.class, "unnest")
);
@@ -99,7 +101,7 @@ public class BuiltinTableGeneratingFunctions implements
FunctionHelper {
.add("explode_json_array_json_outer").add("explode_split").add("explode_split_outer")
.add("json_each").add("json_each_outer")
.add("json_each_text").add("json_each_text_outer")
- .add("posexplode").add("posexplode_outer").build();
+ .add("posexplode").add("posexplode_outer").add("stack").build();
public Set<String> getReturnManyColumnFunctions() {
return RETURN_MULTI_COLUMNS_FUNCTIONS;
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindExpression.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindExpression.java
index a650d17d934..74019610b15 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindExpression.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindExpression.java
@@ -59,6 +59,7 @@ import
org.apache.doris.nereids.trees.expressions.functions.Function;
import org.apache.doris.nereids.trees.expressions.functions.FunctionBuilder;
import
org.apache.doris.nereids.trees.expressions.functions.agg.AggregateFunction;
import org.apache.doris.nereids.trees.expressions.functions.agg.AnyValue;
+import org.apache.doris.nereids.trees.expressions.functions.generator.Stack;
import
org.apache.doris.nereids.trees.expressions.functions.generator.TableGeneratingFunction;
import org.apache.doris.nereids.trees.expressions.functions.generator.Unnest;
import org.apache.doris.nereids.trees.expressions.functions.scalar.Coalesce;
@@ -439,12 +440,29 @@ public class BindExpression implements
AnalysisRuleFactory {
// 2. the expandColumnsAlias is empty, we should use origin
boundSlot
if (generate.getExpandColumnAlias() != null && i <
generate.getExpandColumnAlias().size()
&&
!CollectionUtils.isEmpty(generate.getExpandColumnAlias().get(i))) {
- if (boundSlot.getDataType() instanceof StructType
- && generate.getExpandColumnAlias().get(i).size() > 1) {
+ int aliasCount = generate.getExpandColumnAlias().get(i).size();
+ boolean shouldExpandStruct = boundSlot.getDataType()
instanceof StructType && aliasCount > 1;
+ if (boundGenerator instanceof Stack) {
+ int outputColumnCount = ((Stack)
boundGenerator).getOutputColumnCount();
+ if (aliasCount != outputColumnCount) {
+ throw new AnalysisException(String.format(
+ "table %s has %d columns available but %d
columns specified",
+ slot.getQualifier().get(0), outputColumnCount,
aliasCount));
+ }
+ shouldExpandStruct = outputColumnCount > 1;
+ }
+ if (shouldExpandStruct) {
+ Preconditions.checkState(boundSlot.getDataType()
instanceof StructType,
+ "multi-column generator output must use a struct
carrier");
// if the alias is not empty, we should bind it with
struct_element as child expr with alias
// element_at(#expand_col#k, #k) as #k
// element_at(#expand_col#v, #v) as #v
List<StructField> fields = ((StructType)
boundSlot.getDataType()).getFields();
+ if (aliasCount != fields.size()) {
+ throw new AnalysisException(String.format(
+ "table %s has %d columns available but %d
columns specified",
+ slot.getQualifier().get(0), fields.size(),
aliasCount));
+ }
for (int idx = 0; idx < fields.size(); ++idx) {
expandAlias.add(new Alias(new ElementAt(
boundSlot, new
StringLiteral(fields.get(idx).getName())),
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/rules/FoldConstantRuleOnFE.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/rules/FoldConstantRuleOnFE.java
index 5338bf7df9b..27d5a509609 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/rules/FoldConstantRuleOnFE.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/rules/FoldConstantRuleOnFE.java
@@ -121,6 +121,8 @@ public class FoldConstantRuleOnFE extends
AbstractExpressionRewriteRule
public static final FoldConstantRuleOnFE VISITOR_INSTANCE = new
FoldConstantRuleOnFE(true);
public static final FoldConstantRuleOnFE PATTERN_MATCH_INSTANCE = new
FoldConstantRuleOnFE(false);
+ private static final FoldConstantRuleOnFE CONTEXT_FREE_VISITOR_INSTANCE
+ = new FoldConstantRuleOnFE(true, false);
// record whether current expression is in an aggregate function with
distinct,
// if is, we will skip to fold constant
@@ -128,15 +130,26 @@ public class FoldConstantRuleOnFE extends
AbstractExpressionRewriteRule
private static final CheckWhetherUnderAggDistinct NOT_UNDER_AGG_DISTINCT =
new CheckWhetherUnderAggDistinct();
private final boolean deepRewrite;
+ private final boolean foldContextDependentExpressions;
public FoldConstantRuleOnFE(boolean deepRewrite) {
+ this(deepRewrite, true);
+ }
+
+ private FoldConstantRuleOnFE(boolean deepRewrite, boolean
foldContextDependentExpressions) {
this.deepRewrite = deepRewrite;
+ this.foldContextDependentExpressions = foldContextDependentExpressions;
}
public static Expression evaluate(Expression expression,
ExpressionRewriteContext expressionRewriteContext) {
return VISITOR_INSTANCE.rewrite(expression, expressionRewriteContext);
}
+ /** Evaluate expressions that do not require a rewrite or connection
context. */
+ public static Expression evaluateWithoutContext(Expression expression) {
+ return CONTEXT_FREE_VISITOR_INSTANCE.rewrite(expression, null);
+ }
+
@Override
public List<ExpressionListenerMatcher<? extends Expression>>
buildListeners() {
return ImmutableList.of(
@@ -229,12 +242,18 @@ public class FoldConstantRuleOnFE extends
AbstractExpressionRewriteRule
@Override
public Expression visitUnboundVariable(UnboundVariable unboundVariable,
ExpressionRewriteContext context) {
+ if (!foldContextDependentExpressions) {
+ return unboundVariable;
+ }
Variable variable =
ExpressionAnalyzer.resolveUnboundVariable(unboundVariable);
return variable.getRealExpression();
}
@Override
public Expression visitEncryptKeyRef(EncryptKeyRef encryptKeyRef,
ExpressionRewriteContext context) {
+ if (!foldContextDependentExpressions) {
+ return encryptKeyRef;
+ }
String dbName = encryptKeyRef.getDbName();
ConnectContext connectContext =
context.cascadesContext.getConnectContext();
if (Strings.isNullOrEmpty(dbName)) {
@@ -357,36 +376,54 @@ public class FoldConstantRuleOnFE extends
AbstractExpressionRewriteRule
@Override
public Expression visitDatabase(Database database,
ExpressionRewriteContext context) {
+ if (!foldContextDependentExpressions) {
+ return database;
+ }
String res = context.cascadesContext.getConnectContext().getDatabase();
return new VarcharLiteral(res);
}
@Override
public Expression visitCurrentUser(CurrentUser currentUser,
ExpressionRewriteContext context) {
+ if (!foldContextDependentExpressions) {
+ return currentUser;
+ }
String res =
context.cascadesContext.getConnectContext().getCurrentUserIdentity().toString();
return new VarcharLiteral(res);
}
@Override
public Expression visitCurrentCatalog(CurrentCatalog currentCatalog,
ExpressionRewriteContext context) {
+ if (!foldContextDependentExpressions) {
+ return currentCatalog;
+ }
String res =
context.cascadesContext.getConnectContext().getDefaultCatalog();
return new VarcharLiteral(res);
}
@Override
public Expression visitUser(User user, ExpressionRewriteContext context) {
+ if (!foldContextDependentExpressions) {
+ return user;
+ }
String res =
context.cascadesContext.getConnectContext().getUserWithLoginRemoteIpString();
return new VarcharLiteral(res);
}
@Override
public Expression visitSessionUser(SessionUser user,
ExpressionRewriteContext context) {
+ if (!foldContextDependentExpressions) {
+ return user;
+ }
String res =
context.cascadesContext.getConnectContext().getUserWithLoginRemoteIpString();
return new VarcharLiteral(res);
}
@Override
public Expression visitLastQueryId(LastQueryId queryId,
ExpressionRewriteContext context) {
+ if (!foldContextDependentExpressions) {
+ return queryId;
+ }
String res = "Not Available";
TUniqueId id =
context.cascadesContext.getConnectContext().getLastQueryId();
if (id != null) {
@@ -397,6 +434,9 @@ public class FoldConstantRuleOnFE extends
AbstractExpressionRewriteRule
@Override
public Expression visitConnectionId(ConnectionId connectionId,
ExpressionRewriteContext context) {
+ if (!foldContextDependentExpressions) {
+ return connectionId;
+ }
return new
BigIntLiteral(context.cascadesContext.getConnectContext().getConnectionId());
}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/executable/ArrayArithmetic.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/executable/ArrayArithmetic.java
index 1f3f9ffaf8d..c1201807f48 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/executable/ArrayArithmetic.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/executable/ArrayArithmetic.java
@@ -21,8 +21,10 @@ import org.apache.doris.nereids.exceptions.AnalysisException;
import org.apache.doris.nereids.trees.expressions.ExecFunction;
import org.apache.doris.nereids.trees.expressions.Expression;
import org.apache.doris.nereids.trees.expressions.literal.ArrayLiteral;
+import org.apache.doris.nereids.trees.expressions.literal.BigIntLiteral;
import org.apache.doris.nereids.trees.expressions.literal.FloatLiteral;
import org.apache.doris.nereids.trees.expressions.literal.Literal;
+import org.apache.doris.nereids.trees.expressions.literal.MapLiteral;
import org.apache.doris.nereids.trees.expressions.literal.NullLiteral;
import org.apache.doris.nereids.types.ArrayType;
import org.apache.doris.nereids.types.FloatType;
@@ -36,6 +38,18 @@ import java.util.List;
*/
public class ArrayArithmetic {
+ /** Return the number of elements in an array. */
+ @ExecFunction(name = "cardinality")
+ public static Expression cardinality(ArrayLiteral array) {
+ return new BigIntLiteral(array.getValue().size());
+ }
+
+ /** Return the number of entries in a map. */
+ @ExecFunction(name = "cardinality")
+ public static Expression cardinality(MapLiteral map) {
+ return new BigIntLiteral(map.getValue().size());
+ }
+
/**
* Compute the cross product between two 3D float arrays.
*/
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/generator/Stack.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/generator/Stack.java
new file mode 100644
index 00000000000..b9075d65571
--- /dev/null
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/generator/Stack.java
@@ -0,0 +1,163 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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.
+
+package org.apache.doris.nereids.trees.expressions.functions.generator;
+
+import org.apache.doris.catalog.FunctionSignature;
+import org.apache.doris.nereids.exceptions.AnalysisException;
+import org.apache.doris.nereids.rules.expression.rules.FoldConstantRuleOnFE;
+import org.apache.doris.nereids.trees.expressions.Expression;
+import org.apache.doris.nereids.trees.expressions.functions.AlwaysNullable;
+import org.apache.doris.nereids.trees.expressions.functions.ComputePrecision;
+import org.apache.doris.nereids.trees.expressions.functions.CustomSignature;
+import org.apache.doris.nereids.trees.expressions.literal.IntegerLikeLiteral;
+import org.apache.doris.nereids.trees.expressions.visitor.ExpressionVisitor;
+import org.apache.doris.nereids.types.DataType;
+import org.apache.doris.nereids.types.IntegerType;
+import org.apache.doris.nereids.types.NullType;
+import org.apache.doris.nereids.types.StructField;
+import org.apache.doris.nereids.types.StructType;
+import org.apache.doris.nereids.util.ExpressionUtils;
+import org.apache.doris.nereids.util.LazyCompute;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Supplier;
+
+/**
+ * stack(n, expr1, ..., exprk) separates the expressions into n rows in
row-major order.
+ * Missing values in the last row are padded with nulls.
+ */
+public class Stack extends TableGeneratingFunction implements CustomSignature,
ComputePrecision, AlwaysNullable {
+
+ private final Supplier<Integer> numRows =
LazyCompute.of(this::computeNumRows);
+
+ /** constructor with two or more arguments. */
+ public Stack(Expression numRows, Expression argument, Expression...
otherArguments) {
+ super("stack", ExpressionUtils.mergeArguments(numRows, argument,
otherArguments));
+ }
+
+ /** constructor for withChildren and reuse signature. */
+ private Stack(GeneratorFunctionParams functionParams) {
+ super(functionParams);
+ }
+
+ @Override
+ public Stack withChildren(List<Expression> children) {
+ Preconditions.checkArgument(children.size() >= 2);
+ return new Stack(getFunctionParams(children));
+ }
+
+ @Override
+ public void checkLegalityBeforeTypeCoercion() {
+ getColumnTypes();
+ }
+
+ @Override
+ public FunctionSignature computePrecision(FunctionSignature signature) {
+ return signature;
+ }
+
+ @Override
+ public FunctionSignature searchSignature(List<FunctionSignature>
signatures) {
+ return super.searchSignature(signatures);
+ }
+
+ @Override
+ public FunctionSignature customSignature() {
+ List<DataType> columnTypes = getColumnTypes();
+ List<DataType> argumentTypes = new ArrayList<>(arity());
+ argumentTypes.add(IntegerType.INSTANCE);
+ for (int i = 1; i < arity(); i++) {
+ argumentTypes.add(columnTypes.get((i - 1) % columnTypes.size()));
+ }
+
+ if (columnTypes.size() == 1) {
+ return FunctionSignature.of(columnTypes.get(0), argumentTypes);
+ }
+ ImmutableList.Builder<StructField> fields = ImmutableList.builder();
+ for (int i = 0; i < columnTypes.size(); i++) {
+ fields.add(new StructField("col" + i, columnTypes.get(i), true,
""));
+ }
+ return FunctionSignature.of(new StructType(fields.build()),
argumentTypes);
+ }
+
+ private int getNumRows() {
+ return numRows.get();
+ }
+
+ private int computeNumRows() {
+ Expression numRowsArgument = getArgument(0);
+ if (!numRowsArgument.isConstant()) {
+ throw new AnalysisException("The first argument of stack must be a
positive constant integer, but got: "
+ + numRowsArgument.toSql());
+ }
+ Expression evaluated =
FoldConstantRuleOnFE.evaluateWithoutContext(numRowsArgument);
+ if (!(evaluated instanceof IntegerLikeLiteral)) {
+ throw new AnalysisException("The first argument of stack must be a
positive constant integer, but got: "
+ + numRowsArgument.toSql());
+ }
+ long numRowsValue = ((IntegerLikeLiteral) evaluated).getLongValue();
+ if (numRowsValue <= 0 || numRowsValue > Integer.MAX_VALUE) {
+ throw new AnalysisException("The first argument of stack must be
in (0, " + Integer.MAX_VALUE
+ + "], but got: " + numRowsValue);
+ }
+ return (int) numRowsValue;
+ }
+
+ /** Return the number of logical output columns derived from the row count
and value arguments. */
+ public int getOutputColumnCount() {
+ int numRows = getNumRows();
+ return (arity() - 2) / numRows + 1;
+ }
+
+ private List<DataType> getColumnTypes() {
+ int numFields = getOutputColumnCount();
+ List<DataType> columnTypes = new ArrayList<>(numFields);
+ for (int columnIndex = 0; columnIndex < numFields; columnIndex++) {
+ DataType referenceType = NullType.INSTANCE;
+ int referenceArgumentIndex = -1;
+ for (int argumentIndex = columnIndex + 1; argumentIndex < arity();
argumentIndex += numFields) {
+ DataType fieldType = getArgument(argumentIndex).getDataType();
+ if (fieldType.isNullType()) {
+ continue;
+ }
+ if (referenceType.isNullType()) {
+ referenceType = fieldType;
+ referenceArgumentIndex = argumentIndex;
+ continue;
+ }
+ if (!referenceType.equals(fieldType)) {
+ throw new AnalysisException("The expressions for stack
output column " + columnIndex
+ + " must have compatible types, but argument " +
referenceArgumentIndex + " is "
+ + referenceType.toSql() + " while argument " +
argumentIndex + " is "
+ + fieldType.toSql());
+ }
+ }
+ columnTypes.add(referenceType);
+ }
+ return columnTypes;
+ }
+
+ @Override
+ public <R, C> R accept(ExpressionVisitor<R, C> visitor, C context) {
+ return visitor.visitStack(this, context);
+ }
+}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/visitor/TableGeneratingFunctionVisitor.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/visitor/TableGeneratingFunctionVisitor.java
index 384b3209ee5..9bcdae6351f 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/visitor/TableGeneratingFunctionVisitor.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/visitor/TableGeneratingFunctionVisitor.java
@@ -44,6 +44,7 @@ import
org.apache.doris.nereids.trees.expressions.functions.generator.JsonEachTe
import
org.apache.doris.nereids.trees.expressions.functions.generator.JsonEachTextOuter;
import
org.apache.doris.nereids.trees.expressions.functions.generator.PosExplode;
import
org.apache.doris.nereids.trees.expressions.functions.generator.PosExplodeOuter;
+import org.apache.doris.nereids.trees.expressions.functions.generator.Stack;
import
org.apache.doris.nereids.trees.expressions.functions.generator.TableGeneratingFunction;
import org.apache.doris.nereids.trees.expressions.functions.generator.Unnest;
import org.apache.doris.nereids.trees.expressions.functions.udf.JavaUdtf;
@@ -171,6 +172,10 @@ public interface TableGeneratingFunctionVisitor<R, C> {
return visitTableGeneratingFunction(posExplodeOuter, context);
}
+ default R visitStack(Stack stack, C context) {
+ return visitTableGeneratingFunction(stack, context);
+ }
+
default R visitUnnest(Unnest unnest, C context) {
return visitTableGeneratingFunction(unnest, context);
}
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/functions/generator/StackTest.java
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/functions/generator/StackTest.java
new file mode 100644
index 00000000000..8669d92692e
--- /dev/null
+++
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/functions/generator/StackTest.java
@@ -0,0 +1,191 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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.
+
+package org.apache.doris.nereids.trees.expressions.functions.generator;
+
+import org.apache.doris.catalog.FunctionSignature;
+import org.apache.doris.nereids.exceptions.AnalysisException;
+import org.apache.doris.nereids.trees.expressions.Cast;
+import org.apache.doris.nereids.trees.expressions.SlotReference;
+import org.apache.doris.nereids.trees.expressions.Subtract;
+import org.apache.doris.nereids.trees.expressions.functions.scalar.Array;
+import org.apache.doris.nereids.trees.expressions.functions.scalar.Cardinality;
+import
org.apache.doris.nereids.trees.expressions.functions.scalar.ConnectionId;
+import
org.apache.doris.nereids.trees.expressions.functions.scalar.CurrentCatalog;
+import org.apache.doris.nereids.trees.expressions.literal.IntegerLiteral;
+import org.apache.doris.nereids.trees.expressions.literal.NullLiteral;
+import org.apache.doris.nereids.trees.expressions.literal.StringLiteral;
+import org.apache.doris.nereids.types.IntegerType;
+import org.apache.doris.nereids.types.NullType;
+import org.apache.doris.nereids.types.StringType;
+import org.apache.doris.nereids.types.StructType;
+import org.apache.doris.nereids.util.MemoTestUtils;
+import org.apache.doris.nereids.util.PlanChecker;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class StackTest {
+
+ @Test
+ public void testNumRowsLazyCompute() {
+ AtomicInteger computeCount = new AtomicInteger();
+ IntegerLiteral numRows = new IntegerLiteral(2) {
+ @Override
+ public boolean isConstant() {
+ computeCount.incrementAndGet();
+ return super.isConstant();
+ }
+ };
+ Stack stack = new Stack(numRows, new IntegerLiteral(1),
+ new IntegerLiteral(2), new IntegerLiteral(3));
+
+ Assertions.assertEquals(0, computeCount.get());
+ Assertions.assertEquals(2, stack.getOutputColumnCount());
+ Assertions.assertEquals(2, stack.getOutputColumnCount());
+ Assertions.assertEquals(1, computeCount.get());
+ }
+
+ @Test
+ public void testMultiColumnSignature() {
+ Stack stack = new Stack(new IntegerLiteral(2), new IntegerLiteral(1),
+ new StringLiteral("a"), new IntegerLiteral(2), new
StringLiteral("b"));
+
+ FunctionSignature signature = stack.getSignatures().get(0);
+ Assertions.assertEquals(5, signature.argumentsTypes.size());
+ Assertions.assertEquals(IntegerType.INSTANCE,
signature.argumentsTypes.get(0));
+ Assertions.assertEquals(IntegerType.INSTANCE,
signature.argumentsTypes.get(1));
+ Assertions.assertEquals(StringType.INSTANCE,
signature.argumentsTypes.get(2));
+ Assertions.assertTrue(signature.returnType.isStructType());
+ StructType returnType = (StructType) signature.returnType;
+ Assertions.assertEquals(2, returnType.getFields().size());
+ Assertions.assertEquals("col0",
returnType.getFields().get(0).getName());
+ Assertions.assertEquals(IntegerType.INSTANCE,
returnType.getFields().get(0).getDataType());
+ Assertions.assertEquals("col1",
returnType.getFields().get(1).getName());
+ Assertions.assertEquals(StringType.INSTANCE,
returnType.getFields().get(1).getDataType());
+ }
+
+ @Test
+ public void testNullUsesOutputColumnType() {
+ Stack stack = new Stack(new IntegerLiteral(2), new IntegerLiteral(1),
+ new StringLiteral("a"), new NullLiteral(), new
StringLiteral("b"));
+
+ FunctionSignature signature = stack.getSignatures().get(0);
+ Assertions.assertEquals(IntegerType.INSTANCE,
signature.argumentsTypes.get(3));
+ StructType returnType = (StructType) signature.returnType;
+ Assertions.assertEquals(IntegerType.INSTANCE,
returnType.getFields().get(0).getDataType());
+ Assertions.assertEquals(StringType.INSTANCE,
returnType.getFields().get(1).getDataType());
+ }
+
+ @Test
+ public void testSingleColumnSignature() {
+ Stack stack = new Stack(new IntegerLiteral(4), new IntegerLiteral(1),
+ new IntegerLiteral(2), new IntegerLiteral(3));
+
+ FunctionSignature signature = stack.getSignatures().get(0);
+ Assertions.assertEquals(IntegerType.INSTANCE, signature.returnType);
+ Assertions.assertEquals(4, signature.argumentsTypes.size());
+ }
+
+ @Test
+ public void testFoldableNumRowsExpression() {
+ Stack stack = new Stack(
+ new Cast(new Subtract(new IntegerLiteral(3), new
IntegerLiteral(1)), IntegerType.INSTANCE),
+ new IntegerLiteral(1), new IntegerLiteral(2), new
IntegerLiteral(3));
+
+ FunctionSignature signature = stack.getSignatures().get(0);
+ Assertions.assertTrue(signature.returnType.isStructType());
+ Assertions.assertEquals(2, ((StructType)
signature.returnType).getFields().size());
+ }
+
+ @Test
+ public void testCardinalityNumRowsExpression() {
+ Stack stack = new Stack(new Cardinality(new Array(new
IntegerLiteral(1), new IntegerLiteral(2))),
+ new IntegerLiteral(1), new IntegerLiteral(2), new
IntegerLiteral(3));
+
+ FunctionSignature signature = stack.getSignatures().get(0);
+ Assertions.assertTrue(signature.returnType.isStructType());
+ Assertions.assertEquals(2, ((StructType)
signature.returnType).getFields().size());
+ }
+
+ @Test
+ public void testAllNullOutputColumn() {
+ Stack stack = new Stack(new IntegerLiteral(2), new NullLiteral(), new
NullLiteral());
+
+ FunctionSignature signature = stack.getSignatures().get(0);
+ Assertions.assertEquals(NullType.INSTANCE, signature.returnType);
+ }
+
+ @Test
+ public void testInvalidArguments() {
+ Assertions.assertThrows(AnalysisException.class,
+ () -> new Stack(new IntegerLiteral(0), new
IntegerLiteral(1)).getSignatures());
+ Assertions.assertThrows(AnalysisException.class,
+ () -> new Stack(SlotReference.of("n", IntegerType.INSTANCE),
+ new IntegerLiteral(1)).getSignatures());
+ Assertions.assertThrows(AnalysisException.class,
+ () -> new Stack(new ConnectionId(), new
IntegerLiteral(1)).getSignatures());
+ Assertions.assertThrows(AnalysisException.class,
+ () -> new Stack(new CurrentCatalog(), new
IntegerLiteral(1)).getSignatures());
+ Assertions.assertThrows(AnalysisException.class,
+ () -> new Stack(new IntegerLiteral(2), new IntegerLiteral(1),
+ new StringLiteral("a")).getSignatures());
+ }
+
+ @Test
+ public void testContextDependentNumRowsExpression() {
+ AnalysisException keyException =
Assertions.assertThrows(AnalysisException.class,
+ () ->
PlanChecker.from(MemoTestUtils.createConnectContext()).analyze(
+ "select c1 from (select 1) t "
+ + "lateral view stack(KEY test_db.test_key, 1)
s as c1"));
+ Assertions.assertTrue(keyException.getMessage().contains(
+ "The first argument of stack must be a positive constant
integer"), keyException.getMessage());
+
+ AnalysisException castKeyException =
Assertions.assertThrows(AnalysisException.class,
+ () ->
PlanChecker.from(MemoTestUtils.createConnectContext()).analyze(
+ "select c1 from (select 1) t "
+ + "lateral view stack(CAST(KEY
test_db.test_key AS INT), 1) s as c1"));
+ Assertions.assertTrue(castKeyException.getMessage().contains(
+ "The first argument of stack must be a positive constant
integer"), castKeyException.getMessage());
+ }
+
+ @Test
+ public void testMultiColumnAliasCount() {
+ AnalysisException exception =
Assertions.assertThrows(AnalysisException.class,
+ () ->
PlanChecker.from(MemoTestUtils.createConnectContext()).analyze(
+ "select c1 from (select 1) t "
+ + "lateral view stack(2, 1, 2, 3, 4, 5) s as
c1"));
+ Assertions.assertTrue(exception.getMessage().contains(
+ "table s has 3 columns available but 1 columns specified"));
+
+ PlanChecker.from(MemoTestUtils.createConnectContext()).analyze(
+ "select c1, c2, c3 from (select 1) t "
+ + "lateral view stack(2, 1, 2, 3, 4, 5) s as c1, c2,
c3");
+ }
+
+ @Test
+ public void testSingleStructOutputColumn() {
+ PlanChecker.from(MemoTestUtils.createConnectContext()).analyze(
+ "select c.a from (select 1) t "
+ + "lateral view stack(2, named_struct('a', 1),
named_struct('a', 2)) s as c");
+ PlanChecker.from(MemoTestUtils.createConnectContext()).analyze(
+ "select c.a, c.b from (select 1) t lateral view stack(2, "
+ + "named_struct('a', 1, 'b', 2), named_struct('a', 3,
'b', 4)) s as c");
+ }
+}
diff --git
a/regression-test/data/query_p0/sql_functions/table_function/stack.out
b/regression-test/data/query_p0/sql_functions/table_function/stack.out
new file mode 100644
index 00000000000..fbb0d284438
--- /dev/null
+++ b/regression-test/data/query_p0/sql_functions/table_function/stack.out
@@ -0,0 +1,52 @@
+-- This file is automatically generated. You should know what you did if you
want to edit this
+-- !two_rows --
+1 2
+3 \N
+
+-- !three_rows --
+1 a
+2 b
+3 c
+
+-- !single_column --
+\N
+1
+2
+3
+
+-- !null_type_coercion --
+\N b
+1 a
+
+-- !all_null_column --
+\N
+\N
+
+-- !constant_expression --
+1 2
+3 \N
+
+-- !multi_row_constants --
+1 1 a
+1 2 b
+2 1 a
+2 2 b
+
+-- !cardinality_num_rows --
+1 2
+3 \N
+
+-- !single_field_struct_value --
+{"a":1}
+{"a":2}
+
+-- !multi_field_struct_value --
+{"a":1, "b":2}
+{"a":3, "b":4}
+
+-- !column_arguments --
+1 10 x
+1 20 y
+2 \N \N
+2 30 m
+
diff --git
a/regression-test/suites/query_p0/sql_functions/table_function/stack.groovy
b/regression-test/suites/query_p0/sql_functions/table_function/stack.groovy
new file mode 100644
index 00000000000..97b54c79ef4
--- /dev/null
+++ b/regression-test/suites/query_p0/sql_functions/table_function/stack.groovy
@@ -0,0 +1,147 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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.
+
+suite("stack") {
+ order_qt_two_rows """
+ select c1, c2
+ from (select 1) t lateral view stack(2, 1, 2, 3) s as c1, c2
+ order by c1, c2
+ """
+
+ order_qt_three_rows """
+ select c1, c2
+ from (select 1) t lateral view stack(3, 1, 'a', 2, 'b', 3, 'c') s as
c1, c2
+ order by c1, c2
+ """
+
+ order_qt_single_column """
+ select c1
+ from (select 1) t lateral view stack(4, 1, 2, 3) s as c1
+ order by c1
+ """
+
+ order_qt_null_type_coercion """
+ select c1, c2
+ from (select 1) t lateral view stack(2, 1, 'a', null, 'b') s as c1, c2
+ order by c1, c2
+ """
+
+ order_qt_all_null_column """
+ select c1
+ from (select 1) t lateral view stack(2, null, null) s as c1
+ order by c1
+ """
+
+ order_qt_constant_expression """
+ select c1, c2
+ from (select 1) t lateral view stack(3 - 1, 1, 2, 3) s as c1, c2
+ order by c1, c2
+ """
+
+ order_qt_multi_row_constants """
+ select id, c1, c2
+ from (select 1 as id union all select 2 as id) t
+ lateral view stack(2, 1, 'a', 2, 'b') s as c1, c2
+ order by id, c1, c2
+ """
+
+ order_qt_cardinality_num_rows """
+ select c1, c2
+ from (select 1) t lateral view stack(cardinality([1, 2]), 1, 2, 3) s
as c1, c2
+ order by c1, c2
+ """
+
+ order_qt_single_field_struct_value """
+ select c
+ from (select 1) t lateral view stack(2,
+ named_struct('a', 1), named_struct('a', 2)) s as c
+ order by c.a
+ """
+
+ order_qt_multi_field_struct_value """
+ select c
+ from (select 1) t lateral view stack(2,
+ named_struct('a', 1, 'b', 2), named_struct('a', 3, 'b', 4)) s as c
+ order by c.a, c.b
+ """
+
+ sql "drop table if exists test_stack"
+ sql """
+ create table test_stack (
+ id int,
+ a int,
+ b int,
+ s1 string,
+ s2 string
+ )
+ duplicate key(id)
+ distributed by hash(id) buckets 1
+ properties("replication_num" = "1")
+ """
+ sql "insert into test_stack values (1, 10, 20, 'x', 'y'), (2, 30, null,
'm', null)"
+
+ order_qt_column_arguments """
+ select id, c1, c2
+ from test_stack lateral view stack(2, a, s1, b, s2) s as c1, c2
+ order by id, c1, c2
+ """
+
+ test {
+ sql "select c1 from (select 1) t lateral view stack(0, 1) s as c1"
+ exception "The first argument of stack must be in"
+ }
+
+ test {
+ sql "select c1 from (select 1) t lateral view stack(1.5, 1) s as c1"
+ exception "The first argument of stack must be a positive constant
integer"
+ }
+
+ test {
+ sql "select c1 from test_stack lateral view stack(id, a) s as c1"
+ exception "The first argument of stack must be a positive constant
integer"
+ }
+
+ test {
+ sql "select c1 from (select 1) t lateral view stack(2, 1, 'a') s as c1"
+ exception "must have compatible types"
+ }
+
+ test {
+ sql "select c1 from (select 1) t lateral view stack(connection_id(),
1) s as c1"
+ exception "The first argument of stack must be a positive constant
integer"
+ }
+
+ test {
+ sql "select c1 from (select 1) t lateral view stack(current_catalog(),
1) s as c1"
+ exception "The first argument of stack must be a positive constant
integer"
+ }
+
+ test {
+ sql "select c1 from (select 1) t lateral view stack(2, 1, 2, 3, 4, 5)
s as c1"
+ exception "has 3 columns available but 1 columns specified"
+ }
+
+ test {
+ sql "select c1 from (select 1) t lateral view stack(2, 1, 2, 3, 4, 5)
s as c1, c2"
+ exception "has 3 columns available but 2 columns specified"
+ }
+
+ test {
+ sql "select c1 from (select 1) t lateral view stack(2, 1, 2, 3, 4, 5)
s as c1, c2, c3, c4"
+ exception "has 3 columns available but 4 columns specified"
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]