Coverage Report

Created: 2026-08-15 17:11

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/exprs/function/function_variant_element.cpp
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
144
    static FunctionPtr create() { return std::make_shared<FunctionVariantElement>(); }
61
62
    // Get function name.
63
2
    String get_name() const override { return name; }
64
65
268
    bool use_default_implementation_for_nulls() const override { return false; }
66
67
133
    size_t get_number_of_arguments() const override { return 2; }
68
69
135
    ColumnNumbers get_arguments_that_are_always_constant() const override { return {1}; }
70
71
8
    DataTypes get_variadic_argument_types_impl() const override {
72
8
        return {std::make_shared<DataTypeVariant>(), std::make_shared<DataTypeString>()};
73
8
    }
74
75
133
    DataTypePtr get_return_type_impl(const DataTypes& arguments) const override {
76
133
        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
133
        const PrimitiveType index_type = remove_nullable(arguments[1])->get_primitive_type();
80
133
        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
133
        auto arg_variant = remove_nullable(arguments[0]);
85
133
        return make_nullable(std::move(arg_variant));
86
133
    }
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
137
    ColumnPtr wrap_variant_nullable(ColumnPtr col) const {
93
137
        const auto& var = assert_cast<const ColumnVariant&>(*col);
94
137
        if (var.is_null_root()) {
95
22
            return make_nullable(col, true);
96
22
        }
97
115
        if (var.is_scalar_variant() && is_column_nullable(*var.get_root())) {
98
32
            const auto* nullable = assert_cast<const ColumnNullable*>(var.get_root().get());
99
32
            return ColumnNullable::create(col, nullable->get_null_map_column_ptr());
100
32
        }
101
83
        return make_nullable(col);
102
115
    }
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
137
                        uint32_t result, size_t input_rows_count) const override {
108
137
        const ColumnPtr materialized =
109
137
                block.get_by_position(arguments[0]).column->convert_to_full_column_if_const();
110
137
        const IColumn* physical = materialized.get();
111
137
        std::span<const uint8_t> outer_nulls;
112
137
        if (const auto* nullable = check_and_get_column<ColumnNullable>(physical)) {
113
134
            outer_nulls = nullable->get_null_map_data();
114
134
            physical = &nullable->get_nested_column();
115
134
        }
116
137
        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
137
        const auto* variant_col =
169
137
                check_and_get_column<ColumnVariant>(remove_nullable(materialized).get());
170
137
        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
137
        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
137
        auto index_column = block.get_by_position(arguments[1]).column;
182
137
        ColumnPtr result_column;
183
137
        RETURN_IF_ERROR(get_element_column(*variant_col, index_column, &result_column));
184
137
        if (block.get_by_position(result).type->is_nullable()) {
185
137
            result_column = wrap_variant_nullable(result_column);
186
137
        }
187
137
        block.replace_by_position(result, result_column);
188
137
        return Status::OK();
189
137
    }
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
4
                                                        const std::string_view& prefix) {
197
4
        if (path.size() <= prefix.size() || path[prefix.size()] != '.') {
198
1
            return std::nullopt;
199
1
        }
200
3
        return path.substr(prefix.size() + 1);
201
4
    }
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
4
                                                   ColumnVariant::MutablePtr& target_ptr) {
207
4
        ColumnVariant::Subcolumn root {0, true, true};
208
        // no root, no sparse column
209
4
        const auto& sparse_data_map = assert_cast<const ColumnMap&>(*src_ptr->get_sparse_column());
210
4
        const auto& src_sparse_data_offsets = sparse_data_map.get_offsets();
211
4
        const auto& src_sparse_data_paths =
212
4
                assert_cast<const ColumnString&>(sparse_data_map.get_keys());
213
4
        const auto& src_sparse_data_values =
214
4
                assert_cast<const ColumnString&>(sparse_data_map.get_values());
215
4
        auto& sparse_data_offsets =
216
4
                assert_cast<ColumnMap&>(target_ptr->get_sparse_column_mutable()).get_offsets();
217
4
        auto [sparse_data_paths, sparse_data_values] =
218
4
                target_ptr->get_sparse_data_paths_and_values();
219
4
        StringRef prefix_ref(path.get_path());
220
4
        std::string_view path_prefix(prefix_ref.data, prefix_ref.size);
221
8
        for (size_t i = 0; i != src_sparse_data_offsets.size(); ++i) {
222
4
            size_t start = src_sparse_data_offsets[ssize_t(i) - 1];
223
4
            size_t end = src_sparse_data_offsets[ssize_t(i)];
224
4
            size_t lower_bound_index = find_variant_sparse_path_lower_bound(
225
4
                    prefix_ref, src_sparse_data_paths, start, end);
226
8
            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
4
            if (root.size() == sparse_data_offsets.size()) {
252
3
                root.insert_default();
253
3
            }
254
4
            sparse_data_offsets.push_back(sparse_data_paths->size());
255
4
        }
256
4
        target_ptr->get_subcolumns().create_root(root);
257
4
        target_ptr->get_doc_value_column_mutable().resize(src_ptr->size());
258
4
        target_ptr->set_num_rows(src_ptr->size());
259
4
    }
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
50
                                                      ColumnVariant::MutablePtr& target_ptr) {
266
50
        ColumnVariant::Subcolumn root {0, true, true};
267
50
        const auto& doc_value_data_map =
268
50
                assert_cast<const ColumnMap&>(*src_ptr->get_doc_value_column());
269
50
        const auto& src_doc_value_data_offsets = doc_value_data_map.get_offsets();
270
50
        const auto& src_doc_value_data_paths =
271
50
                assert_cast<const ColumnString&>(doc_value_data_map.get_keys());
272
50
        const auto& src_doc_value_data_values =
273
50
                assert_cast<const ColumnString&>(doc_value_data_map.get_values());
274
50
        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
50
        auto& extracted_offsets =
278
50
                assert_cast<ColumnMap&>(write_to_doc_value
279
50
                                                ? target_ptr->get_doc_value_column_mutable()
280
50
                                                : target_ptr->get_sparse_column_mutable())
281
50
                        .get_offsets();
282
50
        auto [extracted_paths, extracted_values] =
283
50
                write_to_doc_value ? target_ptr->get_doc_value_data_paths_and_values()
284
50
                                   : target_ptr->get_sparse_data_paths_and_values();
285
50
        StringRef prefix_ref(path.get_path());
286
50
        std::string_view path_prefix(prefix_ref.data, prefix_ref.size);
287
100
        for (size_t i = 0; i != src_doc_value_data_offsets.size(); ++i) {
288
50
            size_t start = src_doc_value_data_offsets[ssize_t(i) - 1];
289
50
            size_t end = src_doc_value_data_offsets[ssize_t(i)];
290
50
            size_t lower_bound_index = find_variant_sparse_path_lower_bound(
291
50
                    prefix_ref, src_doc_value_data_paths, start, end);
292
81
            for (; lower_bound_index != end; ++lower_bound_index) {
293
62
                auto path_ref = src_doc_value_data_paths.get_data_at(lower_bound_index);
294
62
                std::string_view nested_path(path_ref.data, path_ref.size);
295
62
                if (!nested_path.starts_with(path_prefix)) {
296
31
                    break;
297
31
                }
298
31
                if (nested_path.size() != path_prefix.size()) {
299
1
                    auto sub_path_optional = get_sub_path(nested_path, path_prefix);
300
1
                    if (!sub_path_optional.has_value()) {
301
0
                        continue;
302
0
                    }
303
1
                    std::string_view sub_path = *sub_path_optional;
304
1
                    extracted_paths->insert_data(sub_path.data(), sub_path.size());
305
1
                    extracted_values->insert_from(src_doc_value_data_values, lower_bound_index);
306
30
                } else {
307
30
                    root.deserialize_from_binary_column(&src_doc_value_data_values,
308
30
                                                        lower_bound_index);
309
30
                }
310
31
            }
311
50
            if (root.size() == extracted_offsets.size()) {
312
20
                root.insert_default();
313
20
            }
314
50
            extracted_offsets.push_back(extracted_paths->size());
315
50
        }
316
50
        target_ptr->get_subcolumns().create_root(root);
317
50
        if (write_to_doc_value) {
318
50
            target_ptr->get_sparse_column_mutable().resize(src_ptr->size());
319
50
        } else {
320
0
            target_ptr->get_doc_value_column_mutable().resize(src_ptr->size());
321
0
        }
322
50
        target_ptr->set_num_rows(src_ptr->size());
323
50
    }
324
325
    static Status get_element_column(const ColumnVariant& src, const ColumnPtr& index_column,
326
141
                                     ColumnPtr* result) {
327
141
        std::string field_name = index_column->get_data_at(0).to_string();
328
141
        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
141
        if (src.is_scalar_variant() && is_string_type(src.get_root_type()->get_primitive_type())) {
337
            // use parser to extract from root
338
86
            auto type = std::make_shared<DataTypeString>();
339
86
            MutableColumnPtr result_column = type->create_column();
340
86
            const ColumnString& docs =
341
86
                    *assert_cast<const ColumnString*>(remove_nullable(src.get_root()).get());
342
86
            simdjson::ondemand::parser parser;
343
86
            std::vector<JsonPath> parsed_paths;
344
86
            if (field_name.empty() || field_name[0] != '$') {
345
86
                field_name = "$." + field_name;
346
86
            }
347
86
            JsonFunctions::parse_json_paths(field_name, &parsed_paths);
348
86
            ColumnString* col_str = static_cast<ColumnString*>(result_column.get());
349
172
            for (size_t i = 0; i < docs.size(); ++i) {
350
86
                if (!extract_from_document(parser, docs.get_data_at(i), parsed_paths, col_str)) {
351
13
                    VLOG_DEBUG << "failed to parse " << docs.get_data_at(i) << ", field "
352
0
                               << field_name;
353
13
                    result_column->insert_default();
354
13
                }
355
86
            }
356
86
            *result = ColumnVariant::create(src.max_subcolumns_count(), src.enable_doc_mode(), type,
357
86
                                            std::move(result_column));
358
86
            (*result)->assert_mutable()->finalize();
359
86
            return Status::OK();
360
86
        } else {
361
55
            auto mutable_src = src.clone_finalized();
362
55
            auto* mutable_ptr = assert_cast<ColumnVariant*>(mutable_src.get());
363
55
            PathInData path(field_name);
364
55
            ColumnVariant::Subcolumns subcolumns = mutable_ptr->get_subcolumns();
365
55
            const auto* node = subcolumns.find_exact(path);
366
55
            MutableColumnPtr result_col =
367
55
                    ColumnVariant::create(src.max_subcolumns_count(), src.enable_doc_mode());
368
55
            ColumnVariant::Subcolumns new_subcolumns;
369
370
55
            if (node != nullptr) {
371
3
                std::vector<decltype(node)> nodes;
372
3
                PathsInData paths;
373
3
                ColumnVariant::Subcolumns::get_leaves_of_node(node, nodes, paths);
374
6
                for (const auto* n : nodes) {
375
6
                    PathInData new_path = n->path.copy_pop_front();
376
6
                    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
6
                    if (!new_subcolumns.add(new_path, n->data)) {
382
1
                        VLOG_DEBUG << "failed to add node " << new_path.get_path();
383
1
                    }
384
6
                }
385
386
                // handle the root node
387
3
                if (new_subcolumns.empty() && !nodes.empty()) {
388
1
                    CHECK_EQ(nodes.size(), 1);
389
1
                    new_subcolumns.create_root(ColumnVariant::Subcolumn {
390
1
                            IColumn::mutate(nodes[0]->data.get_finalized_column_ptr()),
391
1
                            nodes[0]->data.get_least_common_type(), true, true});
392
1
                    auto container =
393
1
                            ColumnVariant::create(src.max_subcolumns_count(), src.enable_doc_mode(),
394
1
                                                  std::move(new_subcolumns));
395
1
                    result_col->insert_range_from(*container, 0, container->size());
396
2
                } else {
397
2
                    auto container =
398
2
                            ColumnVariant::create(src.max_subcolumns_count(), src.enable_doc_mode(),
399
2
                                                  std::move(new_subcolumns));
400
2
                    container->clear_sparse_column();
401
2
                    _extract_sparse_column_from_source(mutable_ptr, path, container);
402
2
                    result_col->insert_range_from(*container, 0, container->size());
403
2
                }
404
52
            } else {
405
52
                auto container =
406
52
                        ColumnVariant::create(src.max_subcolumns_count(), src.enable_doc_mode(),
407
52
                                              std::move(new_subcolumns));
408
52
                const auto& sparse_offsets = mutable_ptr->serialized_sparse_column_offsets();
409
52
                if (sparse_offsets.back() == sparse_offsets[-1]) {
410
50
                    _extract_doc_value_column_from_source(mutable_ptr, path, container);
411
50
                } else {
412
2
                    _extract_sparse_column_from_source(mutable_ptr, path, container);
413
2
                }
414
52
                result_col->insert_range_from(*container, 0, container->size());
415
52
            }
416
            // ColumnVariant should be finalized before parsing, finalize maybe modify original column structure
417
55
            result_col->finalize();
418
55
            VLOG_DEBUG << "dump new object "
419
0
                       << static_cast<const ColumnVariant*>(result_col.get())->debug_string()
420
0
                       << ", path " << path.get_path();
421
55
            *result = std::move(result_col);
422
55
            return Status::OK();
423
55
        }
424
141
    }
425
426
    static Status extract_from_document(simdjson::ondemand::parser& parser, const StringRef& doc,
427
86
                                        const std::vector<JsonPath>& paths, ColumnString* column) {
428
86
        try {
429
86
            simdjson::padded_string json_str {doc.data, doc.size};
430
86
            simdjson::ondemand::document document = parser.iterate(json_str);
431
86
            simdjson::ondemand::object object = document.get_object();
432
86
            simdjson::ondemand::value value;
433
86
            RETURN_IF_ERROR(JsonFunctions::extract_from_object(object, paths, &value));
434
77
            _write_data_to_column(value, column);
435
77
        } catch (simdjson::simdjson_error& e) {
436
4
            VLOG_DEBUG << "simdjson parse exception: " << e.what();
437
4
            return Status::DataQualityError("simdjson parse exception {}", e.what());
438
4
        }
439
73
        return Status::OK();
440
86
    }
441
442
73
    static void _write_data_to_column(simdjson::ondemand::value& value, ColumnString* column) {
443
73
        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
9
        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
9
            std::string_view value_str = value.get_string().value();
462
9
            column->insert_data(value_str.data(), value_str.length());
463
9
            break;
464
0
        }
465
64
        default: {
466
64
            auto value_str = simdjson::to_json_string(value).value();
467
64
            column->insert_data(value_str.data(), value_str.length());
468
64
        }
469
73
        }
470
73
    }
471
};
472
473
class FunctionVariantElementByInteger final : public FunctionVariantElement {
474
public:
475
    static constexpr auto name = FunctionVariantElement::name;
476
9
    static FunctionPtr create() { return std::make_shared<FunctionVariantElementByInteger>(); }
477
478
8
    DataTypes get_variadic_argument_types_impl() const override {
479
8
        return {std::make_shared<DataTypeVariant>(), std::make_shared<DataTypeInt64>()};
480
8
    }
481
};
482
483
8
void register_function_variant_element(SimpleFunctionFactory& factory) {
484
8
    factory.register_function<FunctionVariantElement>();
485
8
    factory.register_function<FunctionVariantElementByInteger>();
486
8
}
487
488
} // namespace doris