Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions be/src/vec/data_types/data_type.h
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,7 @@ struct WhichDataType {
bool is_uuid() const { return idx == TypeIndex::UUID; }
bool is_array() const { return idx == TypeIndex::Array; }
bool is_tuple() const { return idx == TypeIndex::Tuple; }
bool is_struct() const { return idx == TypeIndex::Struct; }
bool is_map() const { return idx == TypeIndex::Map; }
bool is_set() const { return idx == TypeIndex::Set; }
bool is_interval() const { return idx == TypeIndex::Interval; }
Expand Down Expand Up @@ -431,5 +432,10 @@ inline bool is_compilable_type(const DataTypePtr& data_type) {
return data_type->is_value_represented_by_number() && !is_decimal(data_type);
}

inline bool is_complex_type(const DataTypePtr& data_type) {
WhichDataType which(data_type);
return which.is_array() || which.is_map() || which.is_struct();
}

} // namespace vectorized
} // namespace doris
3 changes: 3 additions & 0 deletions be/src/vec/data_types/data_type_struct.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,9 @@ Status DataTypeStruct::from_string(ReadBuffer& rb, IColumn* column) const {

// here need handle the empty struct '{}'
if (rb.count() == 2) {
for (size_t i = 0; i < struct_column->tuple_size(); ++i) {
struct_column->get_column(i).insert_default();
}
return Status::OK();
}

Expand Down
81 changes: 76 additions & 5 deletions be/src/vec/functions/function_cast.h
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,10 @@ struct ConvertImplGenericFromString {
// Note: here we should handle the null element
if (val.size == 0) {
col_to->insert_default();
// empty string('') is an invalid format for complex type, set null_map to 1
if (is_complex_type(data_type_to)) {
(*vec_null_map_to)[i] = 1;
}
continue;
}
ReadBuffer read_buffer((char*)(val.data), val.size);
Expand Down Expand Up @@ -1263,6 +1267,7 @@ class FunctionCast final : public IFunctionBase {
public:
using WrapperType =
std::function<Status(FunctionContext*, Block&, const ColumnNumbers&, size_t, size_t)>;
using ElementWrappers = std::vector<WrapperType>;
using MonotonicityForRange =
std::function<Monotonicity(const IDataType&, const Field&, const Field&)>;

Expand Down Expand Up @@ -1552,15 +1557,80 @@ class FunctionCast final : public IFunctionBase {
}
}

ElementWrappers get_element_wrappers(FunctionContext* context,
const DataTypes& from_element_types,
const DataTypes& to_element_types) const {
DCHECK(from_element_types.size() == to_element_types.size());
ElementWrappers element_wrappers;
element_wrappers.reserve(from_element_types.size());
for (size_t i = 0; i < from_element_types.size(); ++i) {
const DataTypePtr& from_element_type = from_element_types[i];
const DataTypePtr& to_element_type = to_element_types[i];
element_wrappers.push_back(
prepare_unpack_dictionaries(context, from_element_type, to_element_type));
}
return element_wrappers;
}

// check struct value type and get to_type value
// TODO: need handle another type to cast struct
WrapperType create_struct_wrapper(const DataTypePtr& from_type,
WrapperType create_struct_wrapper(FunctionContext* context, const DataTypePtr& from_type,
const DataTypeStruct& to_type) const {
switch (from_type->get_type_id()) {
case TypeIndex::String:
default:
// support CAST AS Struct from string
if (from_type->get_type_id() == TypeIndex::String) {
return &ConvertImplGenericFromString<ColumnString>::execute;
}

// only support CAST AS Struct from struct or string types
auto from = check_and_get_data_type<DataTypeStruct>(from_type.get());
if (!from) {
return create_unsupport_wrapper(
fmt::format("CAST AS Struct can only be performed between struct types or from "
"String. Left type: {}, right type: {}",
from_type->get_name(), to_type.get_name()));
}

const auto& from_element_types = from->get_elements();
const auto& to_element_types = to_type.get_elements();
// only support CAST AS Struct from struct type with same number of elements
if (from_element_types.size() != to_element_types.size()) {
return create_unsupport_wrapper(
fmt::format("CAST AS Struct can only be performed between struct types with "
"the same number of elements. Left type: {}, right type: {}",
from_type->get_name(), to_type.get_name()));
}

auto element_wrappers = get_element_wrappers(context, from_element_types, to_element_types);
return [element_wrappers, from_element_types, to_element_types](
FunctionContext* context, Block& block, const ColumnNumbers& arguments,
const size_t result, size_t /*input_rows_count*/) -> Status {
auto& from_column = block.get_by_position(arguments.front()).column;
auto from_col_struct = check_and_get_column<ColumnStruct>(from_column.get());
if (!from_col_struct) {
return Status::RuntimeError("Illegal column {} for function CAST AS Struct",
from_column->get_name());
}

size_t elements_num = to_element_types.size();
Columns converted_columns(elements_num);
for (size_t i = 0; i < elements_num; ++i) {
ColumnWithTypeAndName from_element_column {from_col_struct->get_column_ptr(i),
from_element_types[i], ""};
ColumnNumbers element_arguments {block.columns()};
block.insert(from_element_column);

size_t element_result = block.columns();
block.insert({to_element_types[i], ""});

RETURN_IF_ERROR(element_wrappers[i](context, block, element_arguments,
element_result,
from_col_struct->get_column(i).size()));
converted_columns[i] = block.get_by_position(element_result).column;
}

block.get_by_position(result).column = ColumnStruct::create(converted_columns);
return Status::OK();
};
}

WrapperType prepare_unpack_dictionaries(FunctionContext* context, const DataTypePtr& from_type,
Expand Down Expand Up @@ -1737,7 +1807,8 @@ class FunctionCast final : public IFunctionBase {
return create_array_wrapper(context, from_type,
static_cast<const DataTypeArray&>(*to_type));
case TypeIndex::Struct:
return create_struct_wrapper(from_type, static_cast<const DataTypeStruct&>(*to_type));
return create_struct_wrapper(context, from_type,
static_cast<const DataTypeStruct&>(*to_type));
case TypeIndex::Map:
return create_map_wrapper(from_type, static_cast<const DataTypeMap&>(*to_type));
default:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,6 @@
-- !sql7 --
['a', 'b', 'c']

-- !sql8 --
\N

Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
-- This file is automatically generated. You should know what you did if you want to edit this
-- !sql1 --
\N

-- !sql2 --
\N

-- !sql3 --
\N

-- !sql4 --
\N

-- !sql5 --
\N

-- !sql6 --
{NULL}

-- !sql7 --
{1, '2'}

-- !sql8 --
{NULL, NULL}

-- !sql9 --
{'a', 'b'}

-- !sql10 --
{1, '2'}

-- !sql11 --
{1, '2'}

-- !sql12 --
{1, '2'}

-- !sql13 --
{1, 2022-10-10}

-- !sql14 --
{1, 2022-10-10 00:00:00}

Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ suite("test_cast_array_functions_by_literal") {
qt_sql5 "select cast('[]' as array<int>)"
qt_sql6 """select cast('["a", "b", "c"]' as array<int>)"""
qt_sql7 """select cast('["a", "b", "c"]' as array<string>)"""
// empty string is invalid array, return NULL
qt_sql8 """select cast('' as array<string>)"""

test {
sql "select cast(NULL as array<int>)"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// 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("test_cast_struct") {
// cast NULL to struct type
qt_sql1 "select cast(NULL as struct<f1:string,f2:int>)"

// string with invalid struct literal format cast to struct type
qt_sql2 "select cast('' as struct<f1:char>)"
qt_sql3 "select cast(cast('' as char) as struct<f1:char>)"
qt_sql4 "select cast(cast('x' as char) as struct<f1:char>)"
qt_sql5 "select cast(cast('x' as string) as struct<f1:string>)"

// valid string format cast to struct type
qt_sql6 "select cast('{}' as struct<f1:int>)"
qt_sql7 "select cast('{1,2}' as struct<f1:int,f2:string>)"
qt_sql8 """select cast('{"a", "b"}' as struct<f1:int,f2:int>)"""
qt_sql9 """select cast('{"a", "b"}' as struct<f1:string,f2:string>)"""

// struct literal cast to struct
qt_sql10 """select cast({1,2} as struct<f1:int,f2:string>)"""
qt_sql11 """select cast({'1','2'} as struct<f1:int,f2:string>)"""
qt_sql12 """select cast({"1","2"} as struct<f1:int,f2:string>)"""
qt_sql13 """select cast({1,'2022-10-10'} as struct<f1:int,f2:date>)"""

// struct type cast to struct
qt_sql14 "select cast(cast({1,'2022-10-10'} as struct<f1:int,f2:date>) as struct<f1:double,f2:datetime>)"

// basic types except string can not cast to struct
test {
sql "select cast(cast(1 as int) as struct<f1:int>)"
exception "errCode = 2,"
}
test {
sql "select cast(cast(999.999 as double) as struct<f1:double>)"
exception "errCode = 2,"
}

// struct literal can not cast to basic types
test {
sql "select cast({1,2} as string)"
exception "errCode = 2,"
}

// struct literal cast to struct MUST with same field number
test {
sql "select cast({1,2} as struct<f1:int,f2:string,f3:string>)"
exception "errCode = 2,"
}
test {
sql "select cast({1,2} as struct<f1:int>)"
exception "errCode = 2,"
}

// struct type cast to struct MUST with same field number
test {
sql "select cast(cast({1,'2022-10-10'} as struct<f1:int,f2:date>) as struct<f1:double,f2:datetime,f3:string>)"
exception "errCode = 2,"
}
test {
sql "select cast(cast({1,'2022-10-10'} as struct<f1:int,f2:date>) as struct<f1:double>)"
exception "errCode = 2,"
}
}