This is an automated email from the ASF dual-hosted git repository.
yiguolei 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 50123ad8a83 [opt](function) Enhance the handling of a single
struct-type argument in the EXPLODE function (#57050)
50123ad8a83 is described below
commit 50123ad8a838e5ddbde8f77f674d3d172cbb73ec
Author: Jerry Hu <[email protected]>
AuthorDate: Fri Oct 17 12:06:32 2025 +0800
[opt](function) Enhance the handling of a single struct-type argument in
the EXPLODE function (#57050)
### What problem does this PR solve?
Since the EXPLODE function supports multiple arguments, its return value
is defined as a STRUCT type. Even when it has only a single argument, it
still returns a STRUCT.
Consider the following example:
```sql
SELECT *
FROM (SELECT 1) t1
LATERAL VIEW explode(array(struct('a', 1), struct('b', 2))) t2 AS c1, c2;
```
```text
+------+------------------------+
| 1 | c1 |
+------+------------------------+
| 1 | {"col1":"a", "col2":1} |
| 1 | {"col1":"b", "col2":2} |
+------+------------------------+
```
In this SQL statement, the return value of the EXPLODE function is a
nested STRUCT (that is, the child column inside the struct is struct
too).
As a result, the final output extracts the child column from the outer
struct, but those child columns themselves are not correctly assigned to
c1 and c2.
With this PR, the result will be like this:
```text
+------+------+------+
| 1 | c1 | c2 |
+------+------+------+
| 1 | a | 1 |
| 1 | b | 2 |
+------+------+------+
```
Related PR: #xxx
Problem Summary:
### Release note
None
### Check List (For Author)
- Test <!-- At least one of them must be included. -->
- [ ] Regression test
- [ ] Unit Test
- [ ] Manual test (add detailed scripts or steps below)
- [ ] No need to test or manual test. Explain why:
- [ ] This is a refactor/code format and no logic has been changed.
- [ ] Previous test can cover this change.
- [ ] No code files have been changed.
- [ ] Other reason <!-- Add your reason? -->
- Behavior changed:
- [ ] No.
- [ ] Yes. <!-- Explain the behavior change -->
- Does this need documentation?
- [ ] No.
- [ ] Yes. <!-- Add document PR link here. eg:
https://github.com/apache/doris-website/pull/1214 -->
### Check List (For Reviewer who merge this PR)
- [ ] Confirm the release note
- [ ] Confirm test cases
- [ ] Confirm document
- [ ] Add branch pick label <!-- Add branch pick label that this PR
should merge into -->
---
be/src/vec/exprs/table_function/vexplode_v2.cpp | 71 ++++++++++++++--------
be/src/vec/functions/function_fake.cpp | 6 +-
be/test/vec/function/table_function_test.cpp | 46 ++++++--------
.../nereids/rules/analysis/BindExpression.java | 22 ++++---
.../expressions/functions/generator/Explode.java | 7 ++-
.../functions/generator/ExplodeOuter.java | 7 ++-
.../functions/generator/ExplodeVariantArray.java | 7 ++-
.../sql_functions/table_function/explode.out | 16 +++++
.../hive/test_parquet_nested_types.groovy | 8 +--
.../sql_functions/table_function/explode.groovy | 22 +++++++
10 files changed, 142 insertions(+), 70 deletions(-)
diff --git a/be/src/vec/exprs/table_function/vexplode_v2.cpp
b/be/src/vec/exprs/table_function/vexplode_v2.cpp
index 47a4f41147b..dbeef4f438b 100644
--- a/be/src/vec/exprs/table_function/vexplode_v2.cpp
+++ b/be/src/vec/exprs/table_function/vexplode_v2.cpp
@@ -29,6 +29,7 @@
#include "vec/columns/column_array.h"
#include "vec/columns/column_nothing.h"
#include "vec/columns/column_variant.h"
+#include "vec/common/assert_cast.h"
#include "vec/core/block.h"
#include "vec/core/column_with_type_and_name.h"
#include "vec/data_types/data_type.h"
@@ -134,18 +135,28 @@ void
VExplodeV2TableFunction::get_same_many_values(MutableColumnPtr& column, int
return;
}
ColumnStruct* struct_column = nullptr;
- if (_is_nullable) {
- auto* nullable_column = assert_cast<ColumnNullable*>(column.get());
- struct_column =
assert_cast<ColumnStruct*>(nullable_column->get_nested_column_ptr().get());
- auto* nullmap_column =
-
assert_cast<ColumnUInt8*>(nullable_column->get_null_map_column_ptr().get());
- nullmap_column->insert_many_defaults(length);
+ std::vector<IColumn*> columns;
+
+ const bool multi_sub_columns = _multi_detail.size() > 1 ||
_generate_row_index;
+
+ if (multi_sub_columns) {
+ if (_is_nullable) {
+ auto* nullable_column = assert_cast<ColumnNullable*>(column.get());
+ struct_column =
+
assert_cast<ColumnStruct*>(nullable_column->get_nested_column_ptr().get());
+ auto* nullmap_column =
+
assert_cast<ColumnUInt8*>(nullable_column->get_null_map_column_ptr().get());
+ nullmap_column->insert_many_defaults(length);
+
+ } else {
+ struct_column = assert_cast<ColumnStruct*>(column.get());
+ }
+
+ for (size_t i = 0; i != _multi_detail.size(); ++i) {
+ columns.emplace_back(&struct_column->get_column(i +
(_generate_row_index ? 1 : 0)));
+ }
} else {
- struct_column = assert_cast<ColumnStruct*>(column.get());
- }
- if (!struct_column) {
- throw Exception(ErrorCode::INTERNAL_ERROR,
- "Only multiple columns can be returned within a
struct.");
+ columns.push_back(column.get());
}
if (_generate_row_index) {
@@ -157,7 +168,7 @@ void
VExplodeV2TableFunction::get_same_many_values(MutableColumnPtr& column, int
auto& detail = _multi_detail[i];
size_t pos = _array_offsets[i] + _cur_offset;
size_t element_size = _multi_detail[i].array_col->size_at(_row_idx);
- auto& struct_field = struct_column->get_column(i +
(_generate_row_index ? 1 : 0));
+ auto& struct_field = *columns.at(i);
if ((detail.array_nullmap_data &&
detail.array_nullmap_data[_row_idx])) {
struct_field.insert_many_defaults(length);
} else {
@@ -179,25 +190,33 @@ void
VExplodeV2TableFunction::get_same_many_values(MutableColumnPtr& column, int
int VExplodeV2TableFunction::get_value(MutableColumnPtr& column, int max_step)
{
max_step = std::min(max_step, (int)(_cur_size - _cur_offset));
+ const bool multi_sub_columns = _multi_detail.size() > 1 ||
_generate_row_index;
+
+ ColumnStruct* struct_column = nullptr;
+ std::vector<IColumn*> columns;
+
if (current_empty()) {
column->insert_default();
max_step = 1;
} else {
- ColumnStruct* struct_column = nullptr;
- if (_is_nullable) {
- auto* nullable_column = assert_cast<ColumnNullable*>(column.get());
- struct_column =
-
assert_cast<ColumnStruct*>(nullable_column->get_nested_column_ptr().get());
- auto* nullmap_column =
-
assert_cast<ColumnUInt8*>(nullable_column->get_null_map_column_ptr().get());
- nullmap_column->insert_many_defaults(max_step);
+ if (multi_sub_columns) {
+ if (_is_nullable) {
+ auto* nullable_column =
assert_cast<ColumnNullable*>(column.get());
+ struct_column =
+
assert_cast<ColumnStruct*>(nullable_column->get_nested_column_ptr().get());
+ auto* nullmap_column =
+
assert_cast<ColumnUInt8*>(nullable_column->get_null_map_column_ptr().get());
+ nullmap_column->insert_many_defaults(max_step);
+
+ } else {
+ struct_column = assert_cast<ColumnStruct*>(column.get());
+ }
+ for (size_t i = 0; i != _multi_detail.size(); ++i) {
+ columns.emplace_back(&struct_column->get_column(i +
(_generate_row_index ? 1 : 0)));
+ }
} else {
- struct_column = assert_cast<ColumnStruct*>(column.get());
- }
- if (!struct_column) {
- throw Exception(ErrorCode::INTERNAL_ERROR,
- "Only multiple columns can be returned within a
struct.");
+ columns.emplace_back(column.get());
}
if (_generate_row_index) {
@@ -210,7 +229,7 @@ int VExplodeV2TableFunction::get_value(MutableColumnPtr&
column, int max_step) {
auto& detail = _multi_detail[i];
size_t pos = _array_offsets[i] + _cur_offset;
size_t element_size =
_multi_detail[i].array_col->size_at(_row_idx);
- auto& struct_field = struct_column->get_column(i +
(_generate_row_index ? 1 : 0));
+ auto& struct_field = *columns.at(i);
if (detail.array_nullmap_data &&
detail.array_nullmap_data[_row_idx]) {
struct_field.insert_many_defaults(max_step);
} else {
diff --git a/be/src/vec/functions/function_fake.cpp
b/be/src/vec/functions/function_fake.cpp
index db20245683f..350f6b4e95f 100644
--- a/be/src/vec/functions/function_fake.cpp
+++ b/be/src/vec/functions/function_fake.cpp
@@ -90,7 +90,11 @@ struct FunctionExplodeV2 {
}
}
- return
make_nullable(std::make_shared<vectorized::DataTypeStruct>(fieldTypes));
+ if (fieldTypes.size() > 1) {
+ return
make_nullable(std::make_shared<vectorized::DataTypeStruct>(fieldTypes));
+ } else {
+ return make_nullable(fieldTypes[0]);
+ }
}
static DataTypes get_variadic_argument_types() { return {}; }
static std::string get_error_msg() { return "Fake function do not support
execute"; }
diff --git a/be/test/vec/function/table_function_test.cpp
b/be/test/vec/function/table_function_test.cpp
index fb25913e304..7ef2e9f56af 100644
--- a/be/test/vec/function/table_function_test.cpp
+++ b/be/test/vec/function/table_function_test.cpp
@@ -132,11 +132,10 @@ TEST_F(TableFunctionTest, vexplode_outer_v2) {
TestArray vec = {Int32(1), Null(), Int32(2), Int32(3)};
InputDataSet input_set = {{AnyType {vec}}, {Null()}, {AnyType
{TestArray {}}}};
- InputTypeSet output_types = {PrimitiveType::TYPE_STRUCT,
PrimitiveType::TYPE_INT};
+ InputTypeSet output_types = {PrimitiveType::TYPE_INT};
- InputDataSet output_set = {{{TestArray {Int32(1)}}}, {{TestArray
{Null()}}},
- {{TestArray {Int32(2)}}}, {{TestArray
{Int32(3)}}},
- {{TestArray {Null()}}}, {{TestArray
{Null()}}}};
+ InputDataSet output_set = {{Int32(1)}, {Null()}, {Int32(2)},
+ {Int32(3)}, {Null()}, {Null()}};
check_vec_table_function(&explode_outer, input_types, input_set,
output_types, output_set);
}
@@ -147,29 +146,26 @@ TEST_F(TableFunctionTest, vexplode_outer_v2) {
TestArray vec = {std::string("abc"), std::string(""),
std::string("def")};
InputDataSet input_set = {{Null()}, {AnyType {TestArray {}}}, {AnyType
{vec}}};
- InputTypeSet output_types = {PrimitiveType::TYPE_STRUCT,
PrimitiveType::TYPE_VARCHAR};
+ InputTypeSet output_types = {PrimitiveType::TYPE_VARCHAR};
- InputDataSet output_set = {{{TestArray {Null()}}},
- {{TestArray {Null()}}},
- {{TestArray {std::string("abc")}}},
- {{TestArray {std::string("")}}},
- {{TestArray {std::string("def")}}}};
+ InputDataSet output_set = {
+ {Null()}, {Null()}, {std::string("abc")}, {std::string("")},
{std::string("def")}};
check_vec_table_function(&explode_outer, input_types, input_set,
output_types, output_set);
}
- // explode_outer(Array<Decimal>)
+ // // explode_outer(Array<Decimal>)
{
InputTypeSet input_types = {PrimitiveType::TYPE_ARRAY,
PrimitiveType::TYPE_DECIMALV2};
TestArray vec = {ut_type::DECIMALV2(17014116.67),
ut_type::DECIMALV2(-17014116.67)};
InputDataSet input_set = {{Null()}, {AnyType {TestArray {}}}, {AnyType
{vec}}};
- InputTypeSet output_types = {PrimitiveType::TYPE_STRUCT,
PrimitiveType::TYPE_DECIMALV2};
+ InputTypeSet output_types = {PrimitiveType::TYPE_DECIMALV2};
- InputDataSet output_set = {{{TestArray {Null()}}},
- {{TestArray {Null()}}},
- {{TestArray
{ut_type::DECIMALV2(17014116.67)}}},
- {{TestArray
{ut_type::DECIMALV2(-17014116.67)}}}};
+ InputDataSet output_set = {{Null()},
+ {Null()},
+ {ut_type::DECIMALV2(17014116.67)},
+ {ut_type::DECIMALV2(-17014116.67)}};
check_vec_table_function(&explode_outer, input_types, input_set,
output_types, output_set);
}
@@ -230,11 +226,8 @@ TEST_F(TableFunctionTest, vexplode_v2) {
TestArray vec = {Int32(1), Null(), Int32(2), Int32(3)};
InputDataSet input_set = {{AnyType {vec}}, {Null()}, {AnyType
{TestArray {}}}};
- InputTypeSet output_types = {PrimitiveType::TYPE_STRUCT,
PrimitiveType::TYPE_INT};
- InputDataSet output_set = {{{TestArray {Int32(1)}}},
- {{TestArray {Null()}}},
- {{TestArray {Int32(2)}}},
- {{TestArray {Int32(3)}}}};
+ InputTypeSet output_types = {PrimitiveType::TYPE_INT};
+ InputDataSet output_set = {{Int32(1)}, {Null()}, {Int32(2)},
{Int32(3)}};
check_vec_table_function(&explode, input_types, input_set,
output_types, output_set);
}
@@ -245,11 +238,9 @@ TEST_F(TableFunctionTest, vexplode_v2) {
TestArray vec = {std::string("abc"), std::string(""),
std::string("def")};
InputDataSet input_set = {{Null()}, {AnyType {TestArray {}}}, {AnyType
{vec}}};
- InputTypeSet output_types = {PrimitiveType::TYPE_STRUCT,
PrimitiveType::TYPE_VARCHAR};
+ InputTypeSet output_types = {PrimitiveType::TYPE_VARCHAR};
- InputDataSet output_set = {{{TestArray {std::string("abc")}}},
- {{TestArray {std::string("")}}},
- {{TestArray {std::string("def")}}}};
+ InputDataSet output_set = {{std::string("abc")}, {std::string("")},
{std::string("def")}};
check_vec_table_function(&explode, input_types, input_set,
output_types, output_set);
}
@@ -259,10 +250,9 @@ TEST_F(TableFunctionTest, vexplode_v2) {
TestArray vec = {Null(), std::string("2022-01-02")};
InputDataSet input_set = {{Null()}, {AnyType {TestArray {}}}, {AnyType
{vec}}};
- InputTypeSet output_types = {PrimitiveType::TYPE_STRUCT,
PrimitiveType::TYPE_DATE};
+ InputTypeSet output_types = {PrimitiveType::TYPE_DATE};
- InputDataSet output_set = {{{TestArray {Null()}}},
- {{TestArray {std::string("2022-01-02")}}}};
+ InputDataSet output_set = {{Null()}, {std::string("2022-01-02")}};
check_vec_table_function(&explode, input_types, input_set,
output_types, output_set);
}
}
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 20d8e981af1..b41968450ec 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
@@ -294,14 +294,20 @@ 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 the alias is not empty, we should bind it with
struct_element as child expr with alias
- // struct_element(#expand_col#k, #k) as #k
- // struct_element(#expand_col#v, #v) as #v
- List<StructField> fields = ((StructType)
boundSlot.getDataType()).getFields();
- for (int idx = 0; idx < fields.size(); ++idx) {
- expandAlias.add(new Alias(new StructElement(
- boundSlot, new
StringLiteral(fields.get(idx).getName())),
- generate.getExpandColumnAlias().get(i).get(idx),
+ if (boundSlot.getDataType() instanceof StructType
+ && generate.getExpandColumnAlias().get(i).size() > 1) {
+ // if the alias is not empty, we should bind it with
struct_element as child expr with alias
+ // struct_element(#expand_col#k, #k) as #k
+ // struct_element(#expand_col#v, #v) as #v
+ List<StructField> fields = ((StructType)
boundSlot.getDataType()).getFields();
+ for (int idx = 0; idx < fields.size(); ++idx) {
+ expandAlias.add(new Alias(new StructElement(
+ boundSlot, new
StringLiteral(fields.get(idx).getName())),
+
generate.getExpandColumnAlias().get(i).get(idx),
+ slot.getQualifier()));
+ }
+ } else {
+ expandAlias.add(new Alias(boundSlot,
generate.getExpandColumnAlias().get(i).get(0),
slot.getQualifier()));
}
}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/generator/Explode.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/generator/Explode.java
index 472e6d7461b..c8e23b2a0b7 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/generator/Explode.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/generator/Explode.java
@@ -87,7 +87,12 @@ public class Explode extends TableGeneratingFunction
implements CustomSignature,
SearchSignature.throwCanNotFoundFunctionException(this.getName(),
getArguments());
}
}
- return FunctionSignature.of(new StructType(structFields.build()),
arguments);
+
+ StructType structType = new StructType(structFields.build());
+ if (arguments.size() == 1) {
+ return
FunctionSignature.of(structType.getFields().get(0).getDataType(), arguments);
+ }
+ return FunctionSignature.of(structType, arguments);
}
@Override
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/generator/ExplodeOuter.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/generator/ExplodeOuter.java
index 15b2772e128..4a26ea14fb6 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/generator/ExplodeOuter.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/generator/ExplodeOuter.java
@@ -87,7 +87,12 @@ public class ExplodeOuter extends TableGeneratingFunction
implements CustomSigna
SearchSignature.throwCanNotFoundFunctionException(this.getName(),
getArguments());
}
}
- return FunctionSignature.of(new StructType(structFields.build()),
arguments);
+
+ StructType structType = new StructType(structFields.build());
+ if (arguments.size() == 1) {
+ return
FunctionSignature.of(structType.getFields().get(0).getDataType(), arguments);
+ }
+ return FunctionSignature.of(structType, arguments);
}
@Override
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/generator/ExplodeVariantArray.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/generator/ExplodeVariantArray.java
index 5ba897c3c33..8b0d29683af 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/generator/ExplodeVariantArray.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/generator/ExplodeVariantArray.java
@@ -82,7 +82,12 @@ public class ExplodeVariantArray extends
TableGeneratingFunction implements
SearchSignature.throwCanNotFoundFunctionException(this.getName(),
getArguments());
}
}
- return FunctionSignature.of(new StructType(structFields.build()),
arguments);
+
+ StructType structType = new StructType(structFields.build());
+ if (arguments.size() == 1) {
+ return
FunctionSignature.of(structType.getFields().get(0).getDataType(), arguments);
+ }
+ return FunctionSignature.of(structType, arguments);
}
@Override
diff --git
a/regression-test/data/query_p0/sql_functions/table_function/explode.out
b/regression-test/data/query_p0/sql_functions/table_function/explode.out
index 1a6280bb84f..377a757a429 100644
--- a/regression-test/data/query_p0/sql_functions/table_function/explode.out
+++ b/regression-test/data/query_p0/sql_functions/table_function/explode.out
@@ -747,3 +747,19 @@ j \N 2 cd \N
j \N 4 \N 2
j \N 5 \N 3
+-- !select25 --
+1 {"col1":1, "col2":"a"}
+1 {"col1":2, "col2":"b"}
+
+-- !select26 --
+1 1 a
+1 2 b
+
+-- !select27 --
+1 {"col1":{"col1":1, "col2":"a"}, "col2":{"col1":3, "col2":"c"}}
+1 {"col1":{"col1":2, "col2":"b"}, "col2":{"col1":4, "col2":"d"}}
+
+-- !select28 --
+1 {"col1":1, "col2":"a"} {"col1":3, "col2":"c"}
+1 {"col1":2, "col2":"b"} {"col1":4, "col2":"d"}
+
diff --git
a/regression-test/suites/external_table_p0/hive/test_parquet_nested_types.groovy
b/regression-test/suites/external_table_p0/hive/test_parquet_nested_types.groovy
index 034559aaa78..d94f909142e 100644
---
a/regression-test/suites/external_table_p0/hive/test_parquet_nested_types.groovy
+++
b/regression-test/suites/external_table_p0/hive/test_parquet_nested_types.groovy
@@ -132,11 +132,11 @@ suite("test_parquet_nested_types",
"p0,external,hive,external_docker,external_do
order_qt_nested_cross_page2_parquet_q5 """
SELECT
id,
- STRUCT_ELEMENT(item, 'x'),
- STRUCT_ELEMENT(item, 'y')
+ item_x as x_value,
+ item_y as y_value
FROM nested_cross_page2_parquet
- LATERAL VIEW EXPLODE(array_struct_col) tmp AS item
- WHERE id = 1 AND STRUCT_ELEMENT(item, 'x') > 100
+ LATERAL VIEW EXPLODE(array_struct_col) tmp AS item_x, item_y
+ WHERE id = 1 AND item_x > 100
"""
order_qt_nested_cross_page2_parquet_q6 """
diff --git
a/regression-test/suites/query_p0/sql_functions/table_function/explode.groovy
b/regression-test/suites/query_p0/sql_functions/table_function/explode.groovy
index fb1a9e29e09..51b8dcf4ec6 100644
---
a/regression-test/suites/query_p0/sql_functions/table_function/explode.groovy
+++
b/regression-test/suites/query_p0/sql_functions/table_function/explode.groovy
@@ -213,4 +213,26 @@ suite("explode") {
lateral view explode([], [1, 2, null, 4, 5], ["ab", "cd", "ef"],
[null, null, 1, 2, 3, 4, 5]) t2 as c0, c1, c2, c3
order by 1,2,3,4,5;
"""
+
+ qt_select25 """
+ select * from (select 1) t1 lateral view explode(array(struct(1, "a"),
struct(2, "b"))) t2 as c1 order by 1,2;
+ """
+ qt_select26 """
+ select * from (select 1) t1 lateral view explode(array(struct(1, "a"),
struct(2, "b"))) t2 as c1, c2 order by 1,2,3;
+ """
+
+ qt_select27 """
+ select * from (select 1) t1
+ lateral view explode(
+ array(struct(1, "a"), struct(2, "b")),
+ array(struct(3, "c"), struct(4, "d"))
+ ) t2 as c1 order by 1,2;
+ """
+ qt_select28 """
+ select * from (select 1) t1
+ lateral view explode(
+ array(struct(1, "a"), struct(2, "b")),
+ array(struct(3, "c"), struct(4, "d"))
+ ) t2 as c1, c2 order by 1,2;
+ """
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]