be/src/exprs/function/cast/cast_to_variant.h
Line | Count | Source |
1 | | // Licensed to the Apache Software Foundation (ASF) under one |
2 | | // or more contributor license agreements. See the NOTICE file |
3 | | // distributed with this work for additional information |
4 | | // regarding copyright ownership. The ASF licenses this file |
5 | | // to you under the Apache License, Version 2.0 (the |
6 | | // "License"); you may not use this file except in compliance |
7 | | // with the License. You may obtain a copy of the License at |
8 | | // |
9 | | // http://www.apache.org/licenses/LICENSE-2.0 |
10 | | // |
11 | | // Unless required by applicable law or agreed to in writing, |
12 | | // software distributed under the License is distributed on an |
13 | | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
14 | | // KIND, either express or implied. See the License for the |
15 | | // specific language governing permissions and limitations |
16 | | // under the License. |
17 | | |
18 | | #pragma once |
19 | | |
20 | | #include <algorithm> |
21 | | |
22 | | #include "core/column/column_nullable.h" |
23 | | #include "core/data_type/data_type_variant.h" |
24 | | #include "exprs/function/cast/cast_base.h" |
25 | | #include "exprs/function/cast/cast_to_string.h" |
26 | | |
27 | | namespace doris::CastWrapper { |
28 | | |
29 | | // shared implementation for casting from variant to arbitrary non-nullable target type |
30 | | inline Status cast_from_variant_impl(FunctionContext* context, Block& block, |
31 | | const ColumnNumbers& arguments, uint32_t result, |
32 | | size_t input_rows_count, const NullMap::value_type* null_map, |
33 | 18 | const DataTypePtr& data_type_to) { |
34 | 18 | auto& col_with_type_and_name = block.get_by_position(arguments[0]); |
35 | 18 | auto& col_from = col_with_type_and_name.column; |
36 | 18 | const IColumn* variant_column = col_from.get(); |
37 | 18 | const auto* nullable = check_and_get_column<ColumnNullable>(*variant_column); |
38 | 18 | if (nullable != nullptr) { |
39 | 0 | variant_column = &nullable->get_nested_column(); |
40 | 0 | } |
41 | 18 | const auto* variant = assert_cast<const ColumnVariant*>(variant_column); |
42 | 18 | ColumnPtr col_to = data_type_to->create_column(); |
43 | | |
44 | 18 | ColumnPtr finalized_input_column; |
45 | 18 | if (!variant->is_finalized()) { |
46 | | // Local exchange can share the same input block across multiple downstream tasks. |
47 | | // Finalize a private copy so variant casts never mutate shared input columns. |
48 | 2 | auto finalized_variant = variant->clone_finalized(); |
49 | 2 | variant = assert_cast<const ColumnVariant*>(finalized_variant.get()); |
50 | 2 | if (nullable != nullptr) { |
51 | 0 | finalized_input_column = ColumnNullable::create(std::move(finalized_variant), |
52 | 0 | nullable->get_null_map_column_ptr()); |
53 | 2 | } else { |
54 | 2 | finalized_input_column = std::move(finalized_variant); |
55 | 2 | } |
56 | 2 | } |
57 | 18 | auto execute_on_finalized_input = [&](auto&& executor) -> Status { |
58 | 4 | if (!finalized_input_column) { |
59 | 3 | return executor(block); |
60 | 3 | } |
61 | 1 | Block finalized_block = block; |
62 | 1 | finalized_block.replace_by_position(arguments[0], finalized_input_column); |
63 | 1 | RETURN_IF_ERROR(executor(finalized_block)); |
64 | 1 | block.replace_by_position(result, finalized_block.get_by_position(result).column); |
65 | 1 | return Status::OK(); |
66 | 1 | }; _ZZN5doris11CastWrapper22cast_from_variant_implEPNS_15FunctionContextERNS_5BlockERKSt6vectorIjSaIjEEjmPKhRKSt10shared_ptrIKNS_9IDataTypeEEENKUlOT_E_clIZNS0_22cast_from_variant_implES2_S4_S9_jmSB_SH_EUlS4_E_EENS_6StatusESJ_ Line | Count | Source | 57 | 4 | auto execute_on_finalized_input = [&](auto&& executor) -> Status { | 58 | 4 | if (!finalized_input_column) { | 59 | 3 | return executor(block); | 60 | 3 | } | 61 | 1 | Block finalized_block = block; | 62 | 1 | finalized_block.replace_by_position(arguments[0], finalized_input_column); | 63 | 1 | RETURN_IF_ERROR(executor(finalized_block)); | 64 | 1 | block.replace_by_position(result, finalized_block.get_by_position(result).column); | 65 | 1 | return Status::OK(); | 66 | 1 | }; |
Unexecuted instantiation: _ZZN5doris11CastWrapper22cast_from_variant_implEPNS_15FunctionContextERNS_5BlockERKSt6vectorIjSaIjEEjmPKhRKSt10shared_ptrIKNS_9IDataTypeEEENKUlOT_E_clIZNS0_22cast_from_variant_implES2_S4_S9_jmSB_SH_EUlS4_E0_EENS_6StatusESJ_ |
67 | | |
68 | | // It's important to convert as many elements as possible in this context. For instance, |
69 | | // if the root of this variant column is a number column, converting it to a number column |
70 | | // is acceptable. However, if the destination type is a string and root is none scalar root, then |
71 | | // we should convert the entire tree to a string. |
72 | | // A scalar variant is only worth converting through its root when the root actually carries a |
73 | | // value. A column whose rows are all empty JSON objects is still "scalar" - it simply has no |
74 | | // path, so its root never got a type - and routing it through the root conversion leaves |
75 | | // nothing to convert and turns every row into NULL, instead of the `{}` that serializing the |
76 | | // whole tree produces for the very same value in a column that does hold paths. See #67367. |
77 | 18 | const bool root_carries_value = !variant->is_null_root() && |
78 | 18 | variant->get_root_type()->get_primitive_type() != INVALID_TYPE; |
79 | 18 | const bool to_string_or_jsonb = is_string_type(data_type_to->get_primitive_type()) || |
80 | 18 | data_type_to->get_primitive_type() == TYPE_JSONB; |
81 | 18 | bool is_root_valuable = |
82 | 18 | root_carries_value && (variant->is_scalar_variant() || !to_string_or_jsonb); |
83 | 18 | if (is_root_valuable && to_string_or_jsonb) { |
84 | | // Once the root is typed, a row that holds an empty JSON object shows up as a NULL root |
85 | | // inside a variant row that is not itself NULL. Converting such a root to STRING/JSONB |
86 | | // yields NULL, while serializing the tree renders it as `{}` - which is what `SELECT |
87 | | // <variant>` returns, and what the same value returns from a column that also holds |
88 | | // paths. When no row's root holds a value there is nothing for the root conversion to |
89 | | // convert, so serialize the tree instead. Rows whose root does hold a value keep the root |
90 | | // conversion, which is what unwraps a JSON string into its text. See #67367. |
91 | | // Only the rows this call actually converts get a say. prepare_remove_nullable hands the |
92 | | // outer null map over as a separate argument and leaves the masked rows' payloads in |
93 | | // place, so a masked row whose root does carry a value would otherwise keep every visible |
94 | | // row on the root conversion. |
95 | 1 | const auto* nullable_root = check_and_get_column<ColumnNullable>(*variant->get_root()); |
96 | 1 | if (nullable_root != nullptr) { |
97 | 0 | const auto& root_null_map = nullable_root->get_null_map_data(); |
98 | 0 | const NullMap::value_type* outer_null_map = null_map; |
99 | 0 | if (outer_null_map == nullptr && nullable != nullptr) { |
100 | 0 | outer_null_map = nullable->get_null_map_data().data(); |
101 | 0 | } |
102 | 0 | const size_t rows_to_scan = std::min(input_rows_count, root_null_map.size()); |
103 | 0 | is_root_valuable = false; |
104 | 0 | for (size_t row = 0; row < rows_to_scan; ++row) { |
105 | 0 | if (outer_null_map != nullptr && outer_null_map[row] != 0) { |
106 | 0 | continue; |
107 | 0 | } |
108 | 0 | if (root_null_map[row] == 0) { |
109 | 0 | is_root_valuable = true; |
110 | 0 | break; |
111 | 0 | } |
112 | 0 | } |
113 | 0 | } |
114 | 1 | } |
115 | | |
116 | 18 | if (is_root_valuable) { |
117 | 11 | ColumnPtr nested = variant->get_root(); |
118 | 11 | auto nested_from_type = variant->get_root_type(); |
119 | | // DCHECK(nested_from_type->is_nullable()); |
120 | 11 | DCHECK(!data_type_to->is_nullable()); |
121 | 11 | auto new_context = context == nullptr ? nullptr : context->clone(); |
122 | 11 | if (new_context != nullptr) { |
123 | 11 | new_context->set_jsonb_string_as_string(true); |
124 | | // Disable strict mode for the inner JSONBātarget conversion. |
125 | | // The variant root column may contain null/empty JSONB entries for rows |
126 | | // where the subcolumn doesn't exist (e.g., mixed-schema variant data). |
127 | | // In strict mode (INSERT context), these null entries cause the ENTIRE |
128 | | // cast to fail and return all NULLs. Since this is an internal type |
129 | | // conversion within variant, not user-provided INSERT data validation, |
130 | | // strict mode should not apply here. |
131 | 11 | new_context->set_enable_strict_mode(false); |
132 | 11 | } |
133 | | // dst type nullable has been removed, so we should remove the inner nullable of root column |
134 | 11 | auto wrapper = |
135 | 11 | prepare_impl(new_context.get(), remove_nullable(nested_from_type), data_type_to); |
136 | 11 | Block tmp_block {{remove_nullable(nested), remove_nullable(nested_from_type), ""}}; |
137 | 11 | tmp_block.insert({nullptr, data_type_to, ""}); |
138 | | /// Perform the requested conversion. |
139 | 11 | Status st = wrapper(new_context.get(), tmp_block, {0}, 1, input_rows_count, nullptr); |
140 | 11 | if (!st.ok()) { |
141 | | // Fill with default values, which is null |
142 | 0 | col_to->assert_mutable()->insert_many_defaults(input_rows_count); |
143 | 0 | col_to = make_nullable(col_to, true); |
144 | 11 | } else { |
145 | 11 | col_to = tmp_block.get_by_position(1).column; |
146 | 11 | col_to = wrap_in_nullable(col_to, |
147 | 11 | Block({{nested, nested_from_type, ""}, |
148 | 11 | {col_from, col_with_type_and_name.type, ""}, |
149 | 11 | {col_to, data_type_to, ""}}), |
150 | 11 | {0, 1}, input_rows_count); |
151 | 11 | } |
152 | 11 | } else { |
153 | | // A variant that only holds default values still has a JSON rendering - every such row is |
154 | | // the empty object - so a STRING/JSONB target has to serialize the tree rather than fall |
155 | | // into the all-NULL branch below, which stays the right answer for scalar targets. |
156 | 7 | if (is_string_type(data_type_to->get_primitive_type())) { |
157 | | // serialize to string |
158 | 4 | return execute_on_finalized_input([&](Block& finalized_block) { |
159 | 4 | return CastToStringFunction::execute_impl(context, finalized_block, arguments, |
160 | 4 | result, input_rows_count); |
161 | 4 | }); |
162 | 4 | } else if (data_type_to->get_primitive_type() == TYPE_JSONB) { |
163 | | // serialize to json by parsing |
164 | 0 | return execute_on_finalized_input([&](Block& finalized_block) { |
165 | 0 | return cast_from_generic_to_jsonb(context, finalized_block, arguments, result, |
166 | 0 | input_rows_count); |
167 | 0 | }); |
168 | 3 | } else if (variant->only_have_default_values()) { |
169 | 0 | col_to->assert_mutable()->insert_many_defaults(input_rows_count); |
170 | 0 | col_to = make_nullable(col_to, true); |
171 | 3 | } else if (!data_type_to->is_nullable() && |
172 | 3 | !is_string_type(data_type_to->get_primitive_type())) { |
173 | | // other types |
174 | 3 | col_to->assert_mutable()->insert_many_defaults(input_rows_count); |
175 | 3 | col_to = make_nullable(col_to, true); |
176 | 3 | } else { |
177 | 0 | assert_cast<ColumnNullable&>(*col_to->assert_mutable()) |
178 | 0 | .insert_many_defaults(input_rows_count); |
179 | 0 | } |
180 | 7 | } |
181 | | |
182 | 14 | if (null_map == nullptr) { |
183 | 9 | if (const auto* nullable_result = check_and_get_column<ColumnNullable>(*col_to); |
184 | 9 | nullable_result != nullptr && !nullable_result->has_null()) { |
185 | 3 | col_to = nullable_result->get_nested_column_ptr(); |
186 | 3 | } |
187 | 9 | } |
188 | | |
189 | 14 | if (col_to->size() != input_rows_count) { |
190 | 0 | return Status::InternalError("Unmatched row count {}, expected {}", col_to->size(), |
191 | 0 | input_rows_count); |
192 | 0 | } |
193 | | |
194 | 14 | block.replace_by_position(result, std::move(col_to)); |
195 | 14 | return Status::OK(); |
196 | 14 | } |
197 | | |
198 | | struct CastFromVariant { |
199 | | static Status execute(FunctionContext* context, Block& block, const ColumnNumbers& arguments, |
200 | | uint32_t result, size_t input_rows_count, |
201 | 0 | const NullMap::value_type* null_map = nullptr) { |
202 | 0 | auto& data_type_to = block.get_by_position(result).type; |
203 | 0 | return cast_from_variant_impl(context, block, arguments, result, input_rows_count, null_map, |
204 | 0 | data_type_to); |
205 | 0 | } |
206 | | }; |
207 | | |
208 | | struct CastToVariant { |
209 | | static Status execute(FunctionContext* context, Block& block, const ColumnNumbers& arguments, |
210 | | uint32_t result, size_t input_rows_count, |
211 | 3 | const NullMap::value_type* null_map = nullptr) { |
212 | | // auto& data_type_to = block.get_by_position(result).type; |
213 | 3 | const auto& col_with_type_and_name = block.get_by_position(arguments[0]); |
214 | 3 | const auto& from_type = col_with_type_and_name.type; |
215 | 3 | const auto& col_from = col_with_type_and_name.column; |
216 | | // set variant root column/type to from column/type |
217 | 3 | const auto& data_type_to = block.get_by_position(result).type; |
218 | 3 | const auto* variant_type = |
219 | 3 | typeid_cast<const DataTypeVariant*>(remove_nullable(data_type_to).get()); |
220 | 3 | auto variant = ColumnVariant::create( |
221 | 3 | variant_type ? variant_type->variant_max_subcolumns_count() : 0, |
222 | 3 | variant_type ? variant_type->enable_doc_mode() : false); |
223 | 3 | variant->create_root(from_type, IColumn::mutate(col_from)); |
224 | 3 | block.replace_by_position(result, std::move(variant)); |
225 | 3 | return Status::OK(); |
226 | 3 | } |
227 | | }; |
228 | | |
229 | | // create corresponding variant value to wrap from_type |
230 | | WrapperType create_cast_to_variant_wrapper(const DataTypePtr& from_type, |
231 | 3 | const DataTypeVariant& to_type) { |
232 | 3 | if (from_type->get_primitive_type() == TYPE_VARIANT) { |
233 | | // variant_max_subcolumns_count is not equal |
234 | 0 | return create_unsupport_wrapper(from_type->get_name(), to_type.get_name()); |
235 | 0 | } |
236 | 3 | return &CastToVariant::execute; |
237 | 3 | } |
238 | | |
239 | | // create corresponding type convert from variant |
240 | | WrapperType create_cast_from_variant_wrapper(const DataTypeVariant& from_type, |
241 | 18 | const DataTypePtr& to_type) { |
242 | 18 | if (to_type->get_primitive_type() == TYPE_VARIANT) { |
243 | | // variant_max_subcolumns_count is not equal |
244 | 0 | return create_unsupport_wrapper(from_type.get_name(), to_type->get_name()); |
245 | 0 | } |
246 | | // Capture explicit target type to make the cast independent from Block[result].type. |
247 | 18 | DataTypePtr captured_to_type = to_type; |
248 | 18 | return [captured_to_type](FunctionContext* context, Block& block, |
249 | 18 | const ColumnNumbers& arguments, uint32_t result, |
250 | 18 | size_t input_rows_count, |
251 | 18 | const NullMap::value_type* null_map) -> Status { |
252 | 18 | return cast_from_variant_impl(context, block, arguments, result, input_rows_count, null_map, |
253 | 18 | captured_to_type); |
254 | 18 | }; |
255 | 18 | } |
256 | | |
257 | | } // namespace doris::CastWrapper |