Coverage Report

Created: 2026-10-12 19:06

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/format/arrow/arrow_row_batch.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 "format/arrow/arrow_row_batch.h"
19
20
#include <arrow/array/util.h>
21
#include <arrow/buffer.h>
22
#include <arrow/extension/parquet_variant.h>
23
#include <arrow/extension/uuid.h>
24
#include <arrow/io/memory.h>
25
#include <arrow/ipc/writer.h>
26
#include <arrow/record_batch.h>
27
#include <arrow/result.h>
28
#include <arrow/status.h>
29
#include <arrow/type.h>
30
#include <arrow/type_fwd.h>
31
#include <arrow/util/key_value_metadata.h>
32
#include <glog/logging.h>
33
#include <stdint.h>
34
35
#include <algorithm>
36
#include <cstdlib>
37
#include <memory>
38
#include <utility>
39
#include <vector>
40
41
#include "core/block/block.h"
42
#include "core/data_type/data_type_agg_state.h"
43
#include "core/data_type/data_type_array.h"
44
#include "core/data_type/data_type_map.h"
45
#include "core/data_type/data_type_struct.h"
46
#include "core/data_type/data_type_variant_v2.h"
47
#include "core/data_type/define_primitive_type.h"
48
#include "exprs/vexpr.h"
49
#include "exprs/vexpr_context.h"
50
#include "format/arrow/arrow_block_convertor.h"
51
#include "format/arrow/arrow_utils.h"
52
#include "runtime/descriptors.h"
53
54
namespace doris {
55
56
43
Status register_arrow_variant_extension() {
57
    // Remote Flight readers must restore the extension before decoding the result schema.
58
43
    static const auto status = [] {
59
1
        if (arrow::GetExtensionType("arrow.parquet.variant") != nullptr) {
60
0
            return arrow::Status::OK();
61
0
        }
62
1
        return arrow::RegisterExtensionType(
63
1
                std::static_pointer_cast<arrow::ExtensionType>(arrow::extension::variant(
64
1
                        arrow::struct_({arrow::field("metadata", arrow::binary(), false),
65
1
                                        arrow::field("value", arrow::binary(), false)}))));
66
1
    }();
67
43
    return status.ok() ? Status::OK() : Status::InternalError(status.ToString());
68
43
}
69
70
// Keep the exhaustive type mapping together so protocol-specific overrides remain visible.
71
// NOLINTNEXTLINE(readability-function-size)
72
Status DorisArrowSchemaConvertor::convert_to_arrow_type(
73
1.66k
        const DataTypePtr& origin_type, std::shared_ptr<arrow::DataType>* result) const {
74
1.66k
    auto type = get_serialized_type(origin_type);
75
1.66k
    switch (type->get_primitive_type()) {
76
0
    case TYPE_NULL:
77
0
        *result = arrow::null();
78
0
        break;
79
24
    case TYPE_TINYINT:
80
24
        *result = arrow::int8();
81
24
        break;
82
24
    case TYPE_SMALLINT:
83
24
        *result = arrow::int16();
84
24
        break;
85
70
    case TYPE_INT:
86
70
        *result = arrow::int32();
87
70
        break;
88
26
    case TYPE_BIGINT:
89
26
        *result = arrow::int64();
90
26
        break;
91
26
    case TYPE_FLOAT:
92
26
        *result = arrow::float32();
93
26
        break;
94
50
    case TYPE_DOUBLE:
95
50
        *result = arrow::float64();
96
50
        break;
97
2
    case TYPE_TIMEV2:
98
2
        *result = arrow::float64();
99
2
        break;
100
63
    case TYPE_IPV4:
101
        // ipv4 is uint32, but parquet not uint32, it's will be convert to int64
102
        // so use int32 directly
103
63
        *result = arrow::int32();
104
63
        break;
105
53
    case TYPE_IPV6:
106
53
        *result = arrow::utf8();
107
53
        break;
108
52
    case TYPE_UUID:
109
52
        *result = arrow::extension::uuid();
110
52
        break;
111
82
    case TYPE_LARGEINT:
112
88
    case TYPE_VARCHAR:
113
94
    case TYPE_CHAR:
114
128
    case TYPE_DATE:
115
154
    case TYPE_DATETIME:
116
311
    case TYPE_STRING:
117
324
    case TYPE_JSONB:
118
324
        *result = arrow::utf8();
119
324
        break;
120
30
    case TYPE_DATEV2:
121
30
        *result = std::make_shared<arrow::Date32Type>();
122
30
        break;
123
11
    case TYPE_TIMESTAMP_NS:
124
        // TIMESTAMP_NS is stored as signed epoch nanoseconds, but its SQL type has no timezone.
125
11
        *result = std::make_shared<arrow::TimestampType>(arrow::TimeUnit::NANO);
126
11
        break;
127
18
    case TYPE_TIMESTAMPTZ:
128
118
    case TYPE_DATETIMEV2: {
129
118
        arrow::TimeUnit::type time_unit;
130
118
        if (type->get_scale() > 3) {
131
81
            time_unit = arrow::TimeUnit::MICRO;
132
81
        } else if (type->get_scale() > 0) {
133
9
            time_unit = arrow::TimeUnit::MILLI;
134
28
        } else {
135
28
            time_unit = arrow::TimeUnit::SECOND;
136
28
        }
137
118
        *result = std::make_shared<arrow::TimestampType>(
138
118
                time_unit, timestamp_timezone(type->get_primitive_type()));
139
118
        break;
140
18
    }
141
14
    case TYPE_DECIMALV2:
142
62
    case TYPE_DECIMAL32:
143
98
    case TYPE_DECIMAL64:
144
122
    case TYPE_DECIMAL128I:
145
122
        *result = std::make_shared<arrow::Decimal128Type>(type->get_precision(), type->get_scale());
146
122
        break;
147
32
    case TYPE_DECIMAL256:
148
32
        *result = std::make_shared<arrow::Decimal256Type>(type->get_precision(), type->get_scale());
149
32
        break;
150
26
    case TYPE_BOOLEAN:
151
26
        *result = arrow::boolean();
152
26
        break;
153
264
    case TYPE_ARRAY: {
154
264
        const auto* type_arr = assert_cast<const DataTypeArray*>(remove_nullable(type).get());
155
264
        std::shared_ptr<arrow::DataType> item_type;
156
264
        RETURN_IF_ERROR(convert_to_arrow_type(type_arr->get_nested_type(), &item_type));
157
        // Arrow stores metadata on fields, so implicit child fields lose the Doris logical type.
158
264
        *result = std::make_shared<arrow::ListType>(make_child_field(
159
264
                "item", item_type, true, type_arr->get_nested_type()->get_primitive_type()));
160
264
        break;
161
264
    }
162
165
    case TYPE_MAP: {
163
165
        const auto* type_map = assert_cast<const DataTypeMap*>(remove_nullable(type).get());
164
165
        std::shared_ptr<arrow::DataType> key_type;
165
165
        std::shared_ptr<arrow::DataType> val_type;
166
165
        RETURN_IF_ERROR(convert_to_arrow_type(type_map->get_key_type(), &key_type));
167
165
        RETURN_IF_ERROR(convert_to_arrow_type(type_map->get_value_type(), &val_type));
168
165
        auto key_field = make_child_field("key", key_type, false,
169
165
                                          type_map->get_key_type()->get_primitive_type());
170
165
        auto value_field = make_child_field("value", val_type, true,
171
165
                                            type_map->get_value_type()->get_primitive_type());
172
165
        *result = std::make_shared<arrow::MapType>(key_field, value_field);
173
165
        break;
174
165
    }
175
155
    case TYPE_STRUCT: {
176
155
        const auto* type_struct = assert_cast<const DataTypeStruct*>(remove_nullable(type).get());
177
155
        std::vector<std::shared_ptr<arrow::Field>> fields;
178
482
        for (size_t i = 0; i < type_struct->get_elements().size(); i++) {
179
327
            std::shared_ptr<arrow::DataType> field_type;
180
327
            RETURN_IF_ERROR(convert_to_arrow_type(type_struct->get_element(i), &field_type));
181
327
            fields.push_back(make_child_field(type_struct->get_element_name(i), field_type,
182
327
                                              type_struct->get_element(i)->is_nullable(),
183
327
                                              type_struct->get_element(i)->get_primitive_type()));
184
327
        }
185
155
        *result = std::make_shared<arrow::StructType>(fields);
186
155
        break;
187
155
    }
188
8
    case TYPE_VARIANT:
189
8
        *result = arrow::utf8();
190
8
        break;
191
2
    case TYPE_QUANTILE_STATE:
192
6
    case TYPE_BITMAP:
193
10
    case TYPE_HLL: {
194
10
        *result = arrow::binary();
195
10
        break;
196
6
    }
197
13
    case TYPE_VARBINARY: {
198
13
        *result = arrow::binary();
199
13
        break;
200
6
    }
201
0
    default:
202
0
        return Status::InvalidArgument("Unknown primitive type({}) convert to Arrow type",
203
0
                                       type->get_name());
204
1.66k
    }
205
1.66k
    return Status::OK();
206
1.66k
}
207
208
// Logical types sharing Arrow storage need the same marker at the root and every child field.
209
std::shared_ptr<arrow::Field> create_arrow_field_with_metadata(
210
        const std::string& field_name, const std::shared_ptr<arrow::DataType>& arrow_type,
211
1.26k
        bool is_nullable, PrimitiveType primitive_type) {
212
1.26k
    const char* type_name;
213
1.26k
    switch (primitive_type) {
214
41
    case TYPE_UUID:
215
41
        type_name = "UUID";
216
41
        break;
217
55
    case TYPE_IPV4:
218
55
        type_name = "IPV4";
219
55
        break;
220
45
    case TYPE_IPV6:
221
45
        type_name = "IPV6";
222
45
        break;
223
69
    case TYPE_LARGEINT:
224
69
        type_name = "LARGEINT";
225
69
        break;
226
8
    case TYPE_JSONB:
227
8
        type_name = "JSON";
228
8
        break;
229
30
    case TYPE_VARIANT:
230
30
        type_name = "VARIANT";
231
30
        break;
232
1.02k
    default:
233
1.02k
        return std::make_shared<arrow::Field>(field_name, arrow_type, is_nullable);
234
1.26k
    }
235
248
    auto metadata = arrow::KeyValueMetadata::Make({"doris_type"}, {type_name});
236
248
    return std::make_shared<arrow::Field>(field_name, arrow_type, is_nullable, metadata);
237
1.26k
}
238
239
Status DorisArrowSchemaConvertor::get_arrow_schema_from_block(
240
67
        const Block& block, std::shared_ptr<arrow::Schema>* result) const {
241
67
    std::vector<std::shared_ptr<arrow::Field>> fields;
242
331
    for (const auto& type_and_name : block) {
243
331
        std::shared_ptr<arrow::DataType> arrow_type;
244
331
        RETURN_IF_ERROR(convert_to_arrow_type(type_and_name.type, &arrow_type));
245
331
        auto field = make_field(type_and_name.name, arrow_type, type_and_name.type->is_nullable(),
246
331
                                type_and_name.type->get_primitive_type());
247
331
        fields.push_back(field);
248
331
    }
249
67
    *result = arrow::schema(std::move(fields));
250
67
    return Status::OK();
251
67
}
252
253
17
Status DorisArrowSchemaConvertor::get_arrow_schema(std::shared_ptr<arrow::Schema>* result) const {
254
17
    return get_arrow_schema_from_block(_header, result);
255
17
}
256
257
109
std::string DorisArrowSchemaConvertor::timestamp_timezone(PrimitiveType) const {
258
    // Arrow clients expect a timezone name rather than the ISO-8601 UTC alias.
259
109
    return _timezone == "Z" ? "UTC" : _timezone;
260
109
}
261
262
Status ArrowFlightSchemaConvertor::convert_to_arrow_type(
263
307
        const DataTypePtr& type, std::shared_ptr<arrow::DataType>* result) const {
264
    // Flight always uses native Variant, including recursively converted children.
265
307
    if (type->get_primitive_type() == TYPE_VARIANT) {
266
        // Reject by type before reading rows, including empty results and nested legacy leaves.
267
41
        if (dynamic_cast<const DataTypeVariantV2*>(remove_nullable(type).get()) == nullptr) {
268
0
            return Status::NotSupported(
269
0
                    "Native Arrow Flight output only supports Variant V2, not legacy Variant; "
270
0
                    "cast the result to STRING for text output");
271
0
        }
272
41
        RETURN_IF_ERROR(register_arrow_variant_extension());
273
41
        *result = arrow::extension::variant(
274
41
                arrow::struct_({arrow::field("metadata", arrow::binary(), false),
275
41
                                arrow::field("value", arrow::binary(), false)}));
276
41
        return Status::OK();
277
41
    }
278
266
    return DorisArrowSchemaConvertor::convert_to_arrow_type(type, result);
279
307
}
280
281
17
std::string ArrowFlightSchemaConvertor::timestamp_timezone(PrimitiveType type) const {
282
    // DATETIMEV2 is wall-clock time; TIMESTAMPTZ must still describe an instant.
283
17
    return type == TYPE_DATETIMEV2 ? "" : DorisArrowSchemaConvertor::timestamp_timezone(type);
284
17
}
285
286
std::shared_ptr<arrow::Field> DorisArrowSchemaConvertor::make_field(
287
        const std::string& name, const std::shared_ptr<arrow::DataType>& type, bool nullable,
288
379
        PrimitiveType primitive) const {
289
379
    return create_arrow_field_with_metadata(name, type, nullable, primitive);
290
379
}
291
292
std::shared_ptr<arrow::Field> DorisArrowSchemaConvertor::make_child_field(
293
        const std::string& name, const std::shared_ptr<arrow::DataType>& type, bool nullable,
294
890
        PrimitiveType primitive) const {
295
890
    return create_arrow_field_with_metadata(name, type, nullable, primitive);
296
890
}
297
298
std::shared_ptr<arrow::Field> LegacyArrowFlightSchemaConvertor::make_field(
299
        const std::string& name, const std::shared_ptr<arrow::DataType>& type, bool nullable,
300
27
        PrimitiveType primitive) const {
301
27
    if (primitive == TYPE_JSONB || primitive == TYPE_VARIANT) {
302
2
        return arrow::field(name, type, nullable);
303
2
    }
304
25
    return DorisArrowSchemaConvertor::make_field(name, type, nullable, primitive);
305
27
}
306
307
std::shared_ptr<arrow::Field> LegacyArrowFlightSchemaConvertor::make_child_field(
308
        const std::string& name, const std::shared_ptr<arrow::DataType>& type, bool nullable,
309
31
        PrimitiveType) const {
310
31
    return arrow::field(name, type, nullable);
311
31
}
312
313
Status DorisArrowSchemaConvertor::get_arrow_schema_from_expr_ctxs(
314
14
        const VExprContextSPtrs& output_vexpr_ctxs, std::shared_ptr<arrow::Schema>* result) const {
315
14
    std::vector<std::shared_ptr<arrow::Field>> fields;
316
64
    for (int i = 0; i < output_vexpr_ctxs.size(); i++) {
317
50
        std::shared_ptr<arrow::DataType> arrow_type;
318
50
        auto root_expr = output_vexpr_ctxs.at(i)->root();
319
50
        RETURN_IF_ERROR(convert_to_arrow_type(root_expr->data_type(), &arrow_type));
320
50
        auto field_name = root_expr->is_slot_ref() && !root_expr->expr_label().empty()
321
50
                                  ? root_expr->expr_label()
322
50
                                  : fmt::format("{}_{}", root_expr->data_type()->get_name(), i);
323
50
        auto field = make_field(field_name, arrow_type, root_expr->is_nullable(),
324
50
                                root_expr->data_type()->get_primitive_type());
325
50
        fields.push_back(std::move(field));
326
50
    }
327
14
    *result = arrow::schema(std::move(fields));
328
14
    return Status::OK();
329
14
}
330
331
4
Status serialize_record_batch(const arrow::RecordBatch& record_batch, std::string* result) {
332
    // create sink memory buffer outputstream with the computed capacity
333
4
    int64_t capacity;
334
4
    arrow::Status a_st = arrow::ipc::GetRecordBatchSize(record_batch, &capacity);
335
4
    if (!a_st.ok()) {
336
0
        return Status::InternalError("GetRecordBatchSize failure, reason: {}", a_st.ToString());
337
0
    }
338
4
    auto sink_res = arrow::io::BufferOutputStream::Create(capacity, arrow::default_memory_pool());
339
4
    if (!sink_res.ok()) {
340
0
        return Status::InternalError("create BufferOutputStream failure, reason: {}",
341
0
                                     sink_res.status().ToString());
342
0
    }
343
4
    std::shared_ptr<arrow::io::BufferOutputStream> sink = sink_res.ValueOrDie();
344
    // create RecordBatch Writer
345
4
    auto res = arrow::ipc::MakeStreamWriter(sink.get(), record_batch.schema());
346
4
    if (!res.ok()) {
347
0
        return Status::InternalError("open RecordBatchStreamWriter failure, reason: {}",
348
0
                                     res.status().ToString());
349
0
    }
350
    // write RecordBatch to memory buffer outputstream
351
4
    std::shared_ptr<arrow::ipc::RecordBatchWriter> record_batch_writer = res.ValueOrDie();
352
4
    a_st = record_batch_writer->WriteRecordBatch(record_batch);
353
4
    if (!a_st.ok()) {
354
0
        return Status::InternalError("write record batch failure, reason: {}", a_st.ToString());
355
0
    }
356
4
    a_st = record_batch_writer->Close();
357
4
    if (!a_st.ok()) {
358
0
        return Status::InternalError("Close failed, reason: {}", a_st.ToString());
359
0
    }
360
4
    auto finish_res = sink->Finish();
361
4
    if (!finish_res.ok()) {
362
0
        return Status::InternalError("allocate result buffer failure, reason: {}",
363
0
                                     finish_res.status().ToString());
364
0
    }
365
4
    *result = finish_res.ValueOrDie()->ToString();
366
    // close the sink
367
4
    a_st = sink->Close();
368
4
    if (!a_st.ok()) {
369
0
        return Status::InternalError("Close failed, reason: {}", a_st.ToString());
370
0
    }
371
4
    return Status::OK();
372
4
}
373
374
14
Status serialize_arrow_schema(std::shared_ptr<arrow::Schema>* schema, std::string* result) {
375
    // Schema RPC readers only consume the IPC schema. Building an empty batch would require
376
    // nested extension builders, which Arrow does not provide for ARRAY/MAP/STRUCT<VARIANT>.
377
14
    std::shared_ptr<arrow::io::BufferOutputStream> sink;
378
14
    RETURN_DORIS_STATUS_IF_RESULT_ERROR(sink, arrow::io::BufferOutputStream::Create());
379
14
    std::shared_ptr<arrow::ipc::RecordBatchWriter> writer;
380
14
    RETURN_DORIS_STATUS_IF_RESULT_ERROR(writer, arrow::ipc::MakeStreamWriter(sink.get(), *schema));
381
14
    RETURN_DORIS_STATUS_IF_ERROR(writer->Close());
382
14
    std::shared_ptr<arrow::Buffer> buffer;
383
14
    RETURN_DORIS_STATUS_IF_RESULT_ERROR(buffer, sink->Finish());
384
14
    *result = buffer->ToString();
385
14
    return Status::OK();
386
14
}
387
388
} // namespace doris