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 | | #include <glog/logging.h> |
19 | | #include <stddef.h> |
20 | | |
21 | | #include <memory> |
22 | | #include <ostream> |
23 | | #include <span> |
24 | | #include <string> |
25 | | #include <string_view> |
26 | | #include <utility> |
27 | | #include <vector> |
28 | | |
29 | | #include "common/status.h" |
30 | | #include "core/assert_cast.h" |
31 | | #include "core/block/block.h" |
32 | | #include "core/column/column.h" |
33 | | #include "core/column/column_nullable.h" |
34 | | #include "core/column/column_string.h" |
35 | | #include "core/column/column_variant.h" |
36 | | #include "core/column/subcolumn_tree.h" |
37 | | #include "core/column/variant_column_utils.h" |
38 | | #include "core/column/variant_v2/column_variant_v2.h" |
39 | | #include "core/data_type/data_type.h" |
40 | | #include "core/data_type/data_type_nothing.h" |
41 | | #include "core/data_type/data_type_nullable.h" |
42 | | #include "core/data_type/data_type_number.h" |
43 | | #include "core/data_type/data_type_string.h" |
44 | | #include "core/data_type/data_type_variant.h" |
45 | | #include "core/string_ref.h" |
46 | | #include "exprs/function/function.h" |
47 | | #include "exprs/function/function_helpers.h" |
48 | | #include "exprs/function/function_variant_element_v2.h" |
49 | | #include "exprs/function/simple_function_factory.h" |
50 | | #include "exprs/json_functions.h" |
51 | | #include "simdjson.h" |
52 | | #include "util/defer_op.h" |
53 | | #include "util/json/path_in_data.h" |
54 | | |
55 | | namespace doris { |
56 | | |
57 | | class FunctionVariantElement : public IFunction { |
58 | | public: |
59 | | static constexpr auto name = "element_at"; |
60 | 4 | static FunctionPtr create() { return std::make_shared<FunctionVariantElement>(); } |
61 | | |
62 | | // Get function name. |
63 | 2 | String get_name() const override { return name; } |
64 | | |
65 | 0 | bool use_default_implementation_for_nulls() const override { return false; } |
66 | | |
67 | 0 | size_t get_number_of_arguments() const override { return 2; } |
68 | | |
69 | 0 | ColumnNumbers get_arguments_that_are_always_constant() const override { return {1}; } |
70 | | |
71 | 1 | DataTypes get_variadic_argument_types_impl() const override { |
72 | 1 | return {std::make_shared<DataTypeVariant>(), std::make_shared<DataTypeString>()}; |
73 | 1 | } |
74 | | |
75 | 0 | DataTypePtr get_return_type_impl(const DataTypes& arguments) const override { |
76 | 0 | DCHECK_EQ(arguments[0]->get_primitive_type(), TYPE_VARIANT) |
77 | 0 | << "First argument for function: " << name |
78 | 0 | << " should be DataTypeVariant but it has type " << arguments[0]->get_name() << "."; |
79 | 0 | const PrimitiveType index_type = remove_nullable(arguments[1])->get_primitive_type(); |
80 | 0 | DCHECK(is_string_type(index_type) || is_int_or_bool(index_type)) |
81 | 0 | << "Second argument for function: " << name |
82 | 0 | << " should be String or Integer but it has type " << arguments[1]->get_name() |
83 | 0 | << "."; |
84 | 0 | auto arg_variant = remove_nullable(arguments[0]); |
85 | 0 | return make_nullable(std::move(arg_variant)); |
86 | 0 | } |
87 | | |
88 | | // wrap variant column with nullable |
89 | | // 1. if variant is null root(empty or nothing as root), then nullable map is all null |
90 | | // 2. if variant is scalar variant, then use the root's nullable map |
91 | | // 3. if variant is hierarchical variant, then create a nullable map with all none null |
92 | 2 | ColumnPtr wrap_variant_nullable(ColumnPtr col) const { |
93 | 2 | const auto& var = assert_cast<const ColumnVariant&>(*col); |
94 | 2 | if (var.is_null_root()) { |
95 | 0 | return make_nullable(col, true); |
96 | 0 | } |
97 | 2 | if (var.is_scalar_variant() && is_column_nullable(*var.get_root())) { |
98 | 2 | const auto* nullable = assert_cast<const ColumnNullable*>(var.get_root().get()); |
99 | 2 | return ColumnNullable::create(col, nullable->get_null_map_column_ptr()); |
100 | 2 | } |
101 | 0 | return make_nullable(col); |
102 | 2 | } |
103 | | |
104 | | // Keep legacy/V2 physical-column dispatch in one entry point so nullable handling stays shared. |
105 | | // NOLINTNEXTLINE(readability-function-size) |
106 | | Status execute_impl(FunctionContext* context, Block& block, const ColumnNumbers& arguments, |
107 | 2 | uint32_t result, size_t input_rows_count) const override { |
108 | 2 | const ColumnPtr materialized = |
109 | 2 | block.get_by_position(arguments[0]).column->convert_to_full_column_if_const(); |
110 | 2 | const IColumn* physical = materialized.get(); |
111 | 2 | std::span<const uint8_t> outer_nulls; |
112 | 2 | if (const auto* nullable = check_and_get_column<ColumnNullable>(physical)) { |
113 | 0 | outer_nulls = nullable->get_null_map_data(); |
114 | 0 | physical = &nullable->get_nested_column(); |
115 | 0 | } |
116 | 2 | if (const auto* variant_v2 = check_and_get_column<ColumnVariantV2>(physical)) { |
117 | 0 | if (block.empty()) { |
118 | 0 | block.replace_by_position(result, ColumnNullable::create(ColumnVariantV2::create(), |
119 | 0 | ColumnUInt8::create())); |
120 | 0 | return Status::OK(); |
121 | 0 | } |
122 | | |
123 | 0 | auto replace_with_all_null_result = [&]() { |
124 | 0 | auto null_values = ColumnVariantV2::create(); |
125 | 0 | null_values->insert_many_defaults(variant_v2->size()); |
126 | 0 | block.replace_by_position( |
127 | 0 | result, ColumnNullable::create(std::move(null_values), |
128 | 0 | ColumnUInt8::create(variant_v2->size(), 1))); |
129 | 0 | }; |
130 | 0 | const auto& index_argument = block.get_by_position(arguments[1]); |
131 | 0 | const ColumnPtr materialized_index = |
132 | 0 | index_argument.column->convert_to_full_column_if_const(); |
133 | 0 | const IColumn* index_column = materialized_index.get(); |
134 | 0 | if (index_column->is_null_at(0)) { |
135 | 0 | replace_with_all_null_result(); |
136 | 0 | return Status::OK(); |
137 | 0 | } |
138 | 0 | if (const auto* nullable = check_and_get_column<ColumnNullable>(*index_column)) { |
139 | 0 | index_column = &nullable->get_nested_column(); |
140 | 0 | } |
141 | |
|
142 | 0 | std::optional<VariantElementV2PathSegment> segment; |
143 | 0 | const PrimitiveType index_type = |
144 | 0 | remove_nullable(index_argument.type)->get_primitive_type(); |
145 | 0 | if (is_string_type(index_type)) { |
146 | 0 | segment = VariantElementV2PathSegment::object_key(index_column->get_data_at(0)); |
147 | 0 | } else if (is_int_or_bool(index_type)) { |
148 | 0 | const int64_t sql_index = index_column->get_int(0); |
149 | 0 | if (sql_index == 0) { |
150 | 0 | replace_with_all_null_result(); |
151 | 0 | return Status::OK(); |
152 | 0 | } |
153 | 0 | segment = VariantElementV2PathSegment::array_index(sql_index > 0 ? sql_index - 1 |
154 | 0 | : sql_index); |
155 | 0 | } else { |
156 | 0 | return Status::RuntimeError("unsupported index type {} for function {}", |
157 | 0 | index_argument.type->get_name(), get_name()); |
158 | 0 | } |
159 | 0 | std::unique_ptr<ResolvedVariantElementV2Path> path; |
160 | 0 | RETURN_IF_ERROR(resolve_variant_element_v2_path(std::span(&*segment, 1), &path)); |
161 | 0 | ColumnPtr result_column; |
162 | 0 | RETURN_IF_ERROR( |
163 | 0 | extract_variant_element_v2(*variant_v2, *path, outer_nulls, &result_column)); |
164 | 0 | block.replace_by_position(result, std::move(result_column)); |
165 | 0 | return Status::OK(); |
166 | 0 | } |
167 | | |
168 | 2 | const auto* variant_col = |
169 | 2 | check_and_get_column<ColumnVariant>(remove_nullable(materialized).get()); |
170 | 2 | if (!variant_col) { |
171 | 0 | return Status::RuntimeError( |
172 | 0 | fmt::format("unsupported types for function {}({}, {})", get_name(), |
173 | 0 | block.get_by_position(arguments[0]).type->get_name(), |
174 | 0 | block.get_by_position(arguments[1]).type->get_name())); |
175 | 0 | } |
176 | 2 | if (block.empty()) { |
177 | 0 | block.replace_by_position(result, block.get_by_position(result).type->create_column()); |
178 | 0 | return Status::OK(); |
179 | 0 | } |
180 | | |
181 | 2 | auto index_column = block.get_by_position(arguments[1]).column; |
182 | 2 | ColumnPtr result_column; |
183 | 2 | RETURN_IF_ERROR(get_element_column(*variant_col, index_column, &result_column)); |
184 | 2 | if (block.get_by_position(result).type->is_nullable()) { |
185 | 2 | result_column = wrap_variant_nullable(result_column); |
186 | 2 | } |
187 | 2 | block.replace_by_position(result, result_column); |
188 | 2 | return Status::OK(); |
189 | 2 | } |
190 | | |
191 | | private: |
192 | | // Return sub-path by specified prefix. |
193 | | // For example, for prefix a.b: |
194 | | // a.b.c.d -> c.d, a.b.c -> c |
195 | | static std::optional<std::string_view> get_sub_path(const std::string_view& path, |
196 | 3 | const std::string_view& prefix) { |
197 | 3 | if (path.size() <= prefix.size() || path[prefix.size()] != '.') { |
198 | 1 | return std::nullopt; |
199 | 1 | } |
200 | 2 | return path.substr(prefix.size() + 1); |
201 | 3 | } |
202 | | |
203 | | // Extract and populate sparse column data with given path prefix |
204 | | // Copies data from source sparse column, extracting only the sub-paths that match the prefix |
205 | | static void _extract_sparse_column_from_source(ColumnVariant* src_ptr, const PathInData& path, |
206 | 2 | ColumnVariant::MutablePtr& target_ptr) { |
207 | 2 | ColumnVariant::Subcolumn root {0, true, true}; |
208 | | // no root, no sparse column |
209 | 2 | const auto& sparse_data_map = assert_cast<const ColumnMap&>(*src_ptr->get_sparse_column()); |
210 | 2 | const auto& src_sparse_data_offsets = sparse_data_map.get_offsets(); |
211 | 2 | const auto& src_sparse_data_paths = |
212 | 2 | assert_cast<const ColumnString&>(sparse_data_map.get_keys()); |
213 | 2 | const auto& src_sparse_data_values = |
214 | 2 | assert_cast<const ColumnString&>(sparse_data_map.get_values()); |
215 | 2 | auto& sparse_data_offsets = |
216 | 2 | assert_cast<ColumnMap&>(target_ptr->get_sparse_column_mutable()).get_offsets(); |
217 | 2 | auto [sparse_data_paths, sparse_data_values] = |
218 | 2 | target_ptr->get_sparse_data_paths_and_values(); |
219 | 2 | StringRef prefix_ref(path.get_path()); |
220 | 2 | std::string_view path_prefix(prefix_ref.data, prefix_ref.size); |
221 | 4 | for (size_t i = 0; i != src_sparse_data_offsets.size(); ++i) { |
222 | 2 | size_t start = src_sparse_data_offsets[ssize_t(i) - 1]; |
223 | 2 | size_t end = src_sparse_data_offsets[ssize_t(i)]; |
224 | 2 | size_t lower_bound_index = find_variant_sparse_path_lower_bound( |
225 | 2 | prefix_ref, src_sparse_data_paths, start, end); |
226 | 6 | for (; lower_bound_index != end; ++lower_bound_index) { |
227 | 4 | auto path_ref = src_sparse_data_paths.get_data_at(lower_bound_index); |
228 | 4 | std::string_view nested_path(path_ref.data, path_ref.size); |
229 | 4 | if (!nested_path.starts_with(path_prefix)) { |
230 | 0 | break; |
231 | 0 | } |
232 | | // Don't include path that is equal to the prefix. |
233 | 4 | if (nested_path.size() != path_prefix.size()) { |
234 | 3 | auto sub_path_optional = get_sub_path(nested_path, path_prefix); |
235 | 3 | if (!sub_path_optional.has_value()) { |
236 | 1 | continue; |
237 | 1 | } |
238 | 2 | std::string_view sub_path = *sub_path_optional; |
239 | 2 | sparse_data_paths->insert_data(sub_path.data(), sub_path.size()); |
240 | 2 | sparse_data_values->insert_from(src_sparse_data_values, lower_bound_index); |
241 | 2 | } else { |
242 | | // insert into root column, example: access v['b'] and b is in sparse column |
243 | | // data example: |
244 | | // {"b" : 123} |
245 | | // {"b" : {"c" : 456}} |
246 | | // b maybe in sparse column, and b.c is in subolumn, put `b` into root column to distinguish |
247 | | // from "" which is empty path and root |
248 | 1 | root.deserialize_from_binary_column(&src_sparse_data_values, lower_bound_index); |
249 | 1 | } |
250 | 4 | } |
251 | 2 | if (root.size() == sparse_data_offsets.size()) { |
252 | 1 | root.insert_default(); |
253 | 1 | } |
254 | 2 | sparse_data_offsets.push_back(sparse_data_paths->size()); |
255 | 2 | } |
256 | 2 | target_ptr->get_subcolumns().create_root(root); |
257 | 2 | target_ptr->get_doc_value_column_mutable().resize(src_ptr->size()); |
258 | 2 | target_ptr->set_num_rows(src_ptr->size()); |
259 | 2 | } |
260 | | |
261 | | // Extract and populate sparse column data from doc_value column with given path prefix |
262 | | // Copies data from source doc_value column, extracting only the sub-paths that match the prefix |
263 | | static void _extract_doc_value_column_from_source(ColumnVariant* src_ptr, |
264 | | const PathInData& path, |
265 | 1 | ColumnVariant::MutablePtr& target_ptr) { |
266 | 1 | ColumnVariant::Subcolumn root {0, true, true}; |
267 | 1 | const auto& doc_value_data_map = |
268 | 1 | assert_cast<const ColumnMap&>(*src_ptr->get_doc_value_column()); |
269 | 1 | const auto& src_doc_value_data_offsets = doc_value_data_map.get_offsets(); |
270 | 1 | const auto& src_doc_value_data_paths = |
271 | 1 | assert_cast<const ColumnString&>(doc_value_data_map.get_keys()); |
272 | 1 | const auto& src_doc_value_data_values = |
273 | 1 | assert_cast<const ColumnString&>(doc_value_data_map.get_values()); |
274 | 1 | const bool write_to_doc_value = target_ptr->enable_doc_mode(); |
275 | | // Ordinary Variant extraction keeps the selected prefix in sparse data, matching the |
276 | | // source branch behavior. Only doc-mode columns keep extracted data in doc_value. |
277 | 1 | auto& extracted_offsets = |
278 | 1 | assert_cast<ColumnMap&>(write_to_doc_value |
279 | 1 | ? target_ptr->get_doc_value_column_mutable() |
280 | 1 | : target_ptr->get_sparse_column_mutable()) |
281 | 1 | .get_offsets(); |
282 | 1 | auto [extracted_paths, extracted_values] = |
283 | 1 | write_to_doc_value ? target_ptr->get_doc_value_data_paths_and_values() |
284 | 1 | : target_ptr->get_sparse_data_paths_and_values(); |
285 | 1 | StringRef prefix_ref(path.get_path()); |
286 | 1 | std::string_view path_prefix(prefix_ref.data, prefix_ref.size); |
287 | 2 | for (size_t i = 0; i != src_doc_value_data_offsets.size(); ++i) { |
288 | 1 | size_t start = src_doc_value_data_offsets[ssize_t(i) - 1]; |
289 | 1 | size_t end = src_doc_value_data_offsets[ssize_t(i)]; |
290 | 1 | size_t lower_bound_index = find_variant_sparse_path_lower_bound( |
291 | 1 | prefix_ref, src_doc_value_data_paths, start, end); |
292 | 2 | for (; lower_bound_index != end; ++lower_bound_index) { |
293 | 1 | auto path_ref = src_doc_value_data_paths.get_data_at(lower_bound_index); |
294 | 1 | std::string_view nested_path(path_ref.data, path_ref.size); |
295 | 1 | if (!nested_path.starts_with(path_prefix)) { |
296 | 0 | break; |
297 | 0 | } |
298 | 1 | if (nested_path.size() != path_prefix.size()) { |
299 | 0 | auto sub_path_optional = get_sub_path(nested_path, path_prefix); |
300 | 0 | if (!sub_path_optional.has_value()) { |
301 | 0 | continue; |
302 | 0 | } |
303 | 0 | std::string_view sub_path = *sub_path_optional; |
304 | 0 | extracted_paths->insert_data(sub_path.data(), sub_path.size()); |
305 | 0 | extracted_values->insert_from(src_doc_value_data_values, lower_bound_index); |
306 | 1 | } else { |
307 | 1 | root.deserialize_from_binary_column(&src_doc_value_data_values, |
308 | 1 | lower_bound_index); |
309 | 1 | } |
310 | 1 | } |
311 | 1 | if (root.size() == extracted_offsets.size()) { |
312 | 0 | root.insert_default(); |
313 | 0 | } |
314 | 1 | extracted_offsets.push_back(extracted_paths->size()); |
315 | 1 | } |
316 | 1 | target_ptr->get_subcolumns().create_root(root); |
317 | 1 | if (write_to_doc_value) { |
318 | 1 | target_ptr->get_sparse_column_mutable().resize(src_ptr->size()); |
319 | 1 | } else { |
320 | 0 | target_ptr->get_doc_value_column_mutable().resize(src_ptr->size()); |
321 | 0 | } |
322 | 1 | target_ptr->set_num_rows(src_ptr->size()); |
323 | 1 | } |
324 | | |
325 | | static Status get_element_column(const ColumnVariant& src, const ColumnPtr& index_column, |
326 | 6 | ColumnPtr* result) { |
327 | 6 | std::string field_name = index_column->get_data_at(0).to_string(); |
328 | 6 | if (src.empty()) { |
329 | 0 | *result = ColumnVariant::create(src.max_subcolumns_count(), src.enable_doc_mode()); |
330 | | // src subcolumns empty but src row count may not be 0 |
331 | 0 | (*result)->assert_mutable()->insert_many_defaults(src.size()); |
332 | | // ColumnVariant should be finalized before parsing, finalize maybe modify original column structure |
333 | 0 | (*result)->assert_mutable()->finalize(); |
334 | 0 | return Status::OK(); |
335 | 0 | } |
336 | 6 | if (src.is_scalar_variant() && is_string_type(src.get_root_type()->get_primitive_type())) { |
337 | | // use parser to extract from root |
338 | 3 | auto type = std::make_shared<DataTypeString>(); |
339 | 3 | MutableColumnPtr result_column = type->create_column(); |
340 | 3 | const ColumnString& docs = |
341 | 3 | *assert_cast<const ColumnString*>(remove_nullable(src.get_root()).get()); |
342 | 3 | simdjson::ondemand::parser parser; |
343 | 3 | std::vector<JsonPath> parsed_paths; |
344 | 3 | if (field_name.empty() || field_name[0] != '$') { |
345 | 3 | field_name = "$." + field_name; |
346 | 3 | } |
347 | 3 | JsonFunctions::parse_json_paths(field_name, &parsed_paths); |
348 | 3 | ColumnString* col_str = static_cast<ColumnString*>(result_column.get()); |
349 | 6 | for (size_t i = 0; i < docs.size(); ++i) { |
350 | 3 | if (!extract_from_document(parser, docs.get_data_at(i), parsed_paths, col_str)) { |
351 | 0 | VLOG_DEBUG << "failed to parse " << docs.get_data_at(i) << ", field " |
352 | 0 | << field_name; |
353 | 0 | result_column->insert_default(); |
354 | 0 | } |
355 | 3 | } |
356 | 3 | *result = ColumnVariant::create(src.max_subcolumns_count(), src.enable_doc_mode(), type, |
357 | 3 | std::move(result_column)); |
358 | 3 | (*result)->assert_mutable()->finalize(); |
359 | 3 | return Status::OK(); |
360 | 3 | } else { |
361 | 3 | auto mutable_src = src.clone_finalized(); |
362 | 3 | auto* mutable_ptr = assert_cast<ColumnVariant*>(mutable_src.get()); |
363 | 3 | PathInData path(field_name); |
364 | 3 | ColumnVariant::Subcolumns subcolumns = mutable_ptr->get_subcolumns(); |
365 | 3 | const auto* node = subcolumns.find_exact(path); |
366 | 3 | MutableColumnPtr result_col = |
367 | 3 | ColumnVariant::create(src.max_subcolumns_count(), src.enable_doc_mode()); |
368 | 3 | ColumnVariant::Subcolumns new_subcolumns; |
369 | | |
370 | 3 | if (node != nullptr) { |
371 | 0 | std::vector<decltype(node)> nodes; |
372 | 0 | PathsInData paths; |
373 | 0 | ColumnVariant::Subcolumns::get_leaves_of_node(node, nodes, paths); |
374 | 0 | for (const auto* n : nodes) { |
375 | 0 | PathInData new_path = n->path.copy_pop_front(); |
376 | 0 | VLOG_DEBUG << "add node " << new_path.get_path() |
377 | 0 | << ", data size: " << n->data.size() |
378 | 0 | << ", finalized size: " << n->data.get_finalized_column().size() |
379 | 0 | << ", common type: " << n->data.get_least_common_type()->get_name(); |
380 | | // if new_path is empty, indicate it's the root column, but adding a root will return false when calling add |
381 | 0 | if (!new_subcolumns.add(new_path, n->data)) { |
382 | 0 | VLOG_DEBUG << "failed to add node " << new_path.get_path(); |
383 | 0 | } |
384 | 0 | } |
385 | | |
386 | | // handle the root node |
387 | 0 | if (new_subcolumns.empty() && !nodes.empty()) { |
388 | 0 | CHECK_EQ(nodes.size(), 1); |
389 | 0 | new_subcolumns.create_root(ColumnVariant::Subcolumn { |
390 | 0 | IColumn::mutate(nodes[0]->data.get_finalized_column_ptr()), |
391 | 0 | nodes[0]->data.get_least_common_type(), true, true}); |
392 | 0 | auto container = |
393 | 0 | ColumnVariant::create(src.max_subcolumns_count(), src.enable_doc_mode(), |
394 | 0 | std::move(new_subcolumns)); |
395 | 0 | result_col->insert_range_from(*container, 0, container->size()); |
396 | 0 | } else { |
397 | 0 | auto container = |
398 | 0 | ColumnVariant::create(src.max_subcolumns_count(), src.enable_doc_mode(), |
399 | 0 | std::move(new_subcolumns)); |
400 | 0 | container->clear_sparse_column(); |
401 | 0 | _extract_sparse_column_from_source(mutable_ptr, path, container); |
402 | 0 | result_col->insert_range_from(*container, 0, container->size()); |
403 | 0 | } |
404 | 3 | } else { |
405 | 3 | auto container = |
406 | 3 | ColumnVariant::create(src.max_subcolumns_count(), src.enable_doc_mode(), |
407 | 3 | std::move(new_subcolumns)); |
408 | 3 | const auto& sparse_offsets = mutable_ptr->serialized_sparse_column_offsets(); |
409 | 3 | if (sparse_offsets.back() == sparse_offsets[-1]) { |
410 | 1 | _extract_doc_value_column_from_source(mutable_ptr, path, container); |
411 | 2 | } else { |
412 | 2 | _extract_sparse_column_from_source(mutable_ptr, path, container); |
413 | 2 | } |
414 | 3 | result_col->insert_range_from(*container, 0, container->size()); |
415 | 3 | } |
416 | | // ColumnVariant should be finalized before parsing, finalize maybe modify original column structure |
417 | 3 | result_col->finalize(); |
418 | 3 | VLOG_DEBUG << "dump new object " |
419 | 0 | << static_cast<const ColumnVariant*>(result_col.get())->debug_string() |
420 | 0 | << ", path " << path.get_path(); |
421 | 3 | *result = std::move(result_col); |
422 | 3 | return Status::OK(); |
423 | 3 | } |
424 | 6 | } |
425 | | |
426 | | static Status extract_from_document(simdjson::ondemand::parser& parser, const StringRef& doc, |
427 | 3 | const std::vector<JsonPath>& paths, ColumnString* column) { |
428 | 3 | try { |
429 | 3 | simdjson::padded_string json_str {doc.data, doc.size}; |
430 | 3 | simdjson::ondemand::document document = parser.iterate(json_str); |
431 | 3 | simdjson::ondemand::object object = document.get_object(); |
432 | 3 | simdjson::ondemand::value value; |
433 | 3 | RETURN_IF_ERROR(JsonFunctions::extract_from_object(object, paths, &value)); |
434 | 3 | _write_data_to_column(value, column); |
435 | 3 | } catch (simdjson::simdjson_error& e) { |
436 | 0 | VLOG_DEBUG << "simdjson parse exception: " << e.what(); |
437 | 0 | return Status::DataQualityError("simdjson parse exception {}", e.what()); |
438 | 0 | } |
439 | 3 | return Status::OK(); |
440 | 3 | } |
441 | | |
442 | 3 | static void _write_data_to_column(simdjson::ondemand::value& value, ColumnString* column) { |
443 | 3 | switch (value.type()) { |
444 | 0 | case simdjson::ondemand::json_type::null: { |
445 | 0 | column->insert_default(); |
446 | 0 | break; |
447 | 0 | } |
448 | 0 | case simdjson::ondemand::json_type::boolean: { |
449 | 0 | if (value.get_bool()) { |
450 | 0 | column->insert_data("1", 1); |
451 | 0 | } else { |
452 | 0 | column->insert_data("0", 1); |
453 | 0 | } |
454 | 0 | break; |
455 | 0 | } |
456 | 2 | case simdjson::ondemand::json_type::string: { |
457 | | // Extract the raw (unescaped) string value rather than its JSON |
458 | | // representation. simdjson::to_json_string would keep the surrounding |
459 | | // double quotes (e.g. "2026-05-20"), which leaks into the result and |
460 | | // makes scalar-string variants inconsistent with structured ones. |
461 | 2 | std::string_view value_str = value.get_string().value(); |
462 | 2 | column->insert_data(value_str.data(), value_str.length()); |
463 | 2 | break; |
464 | 0 | } |
465 | 1 | default: { |
466 | 1 | auto value_str = simdjson::to_json_string(value).value(); |
467 | 1 | column->insert_data(value_str.data(), value_str.length()); |
468 | 1 | } |
469 | 3 | } |
470 | 3 | } |
471 | | }; |
472 | | |
473 | | class FunctionVariantElementByInteger final : public FunctionVariantElement { |
474 | | public: |
475 | | static constexpr auto name = FunctionVariantElement::name; |
476 | 2 | static FunctionPtr create() { return std::make_shared<FunctionVariantElementByInteger>(); } |
477 | | |
478 | 1 | DataTypes get_variadic_argument_types_impl() const override { |
479 | 1 | return {std::make_shared<DataTypeVariant>(), std::make_shared<DataTypeInt64>()}; |
480 | 1 | } |
481 | | }; |
482 | | |
483 | 1 | void register_function_variant_element(SimpleFunctionFactory& factory) { |
484 | 1 | factory.register_function<FunctionVariantElement>(); |
485 | 1 | factory.register_function<FunctionVariantElementByInteger>(); |
486 | 1 | } |
487 | | |
488 | | } // namespace doris |