Coverage Report

Created: 2026-09-29 18:23

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/uuid.h>
23
#include <arrow/io/memory.h>
24
#include <arrow/ipc/writer.h>
25
#include <arrow/record_batch.h>
26
#include <arrow/result.h>
27
#include <arrow/status.h>
28
#include <arrow/type.h>
29
#include <arrow/type_fwd.h>
30
#include <arrow/util/key_value_metadata.h>
31
#include <glog/logging.h>
32
#include <stdint.h>
33
34
#include <algorithm>
35
#include <cstdlib>
36
#include <memory>
37
#include <utility>
38
#include <vector>
39
40
#include "core/block/block.h"
41
#include "core/data_type/data_type_agg_state.h"
42
#include "core/data_type/data_type_array.h"
43
#include "core/data_type/data_type_map.h"
44
#include "core/data_type/data_type_struct.h"
45
#include "core/data_type/define_primitive_type.h"
46
#include "exprs/vexpr.h"
47
#include "exprs/vexpr_context.h"
48
#include "format/arrow/arrow_block_convertor.h"
49
#include "runtime/descriptors.h"
50
51
namespace doris {
52
53
Status convert_to_arrow_type(const DataTypePtr& origin_type,
54
                             std::shared_ptr<arrow::DataType>* result, const std::string& timezone,
55
1.44k
                             bool datetime_naive) {
56
1.44k
    auto type = get_serialized_type(origin_type);
57
1.44k
    switch (type->get_primitive_type()) {
58
0
    case TYPE_NULL:
59
0
        *result = arrow::null();
60
0
        break;
61
24
    case TYPE_TINYINT:
62
24
        *result = arrow::int8();
63
24
        break;
64
24
    case TYPE_SMALLINT:
65
24
        *result = arrow::int16();
66
24
        break;
67
69
    case TYPE_INT:
68
69
        *result = arrow::int32();
69
69
        break;
70
26
    case TYPE_BIGINT:
71
26
        *result = arrow::int64();
72
26
        break;
73
26
    case TYPE_FLOAT:
74
26
        *result = arrow::float32();
75
26
        break;
76
50
    case TYPE_DOUBLE:
77
50
        *result = arrow::float64();
78
50
        break;
79
2
    case TYPE_TIMEV2:
80
2
        *result = arrow::float64();
81
2
        break;
82
50
    case TYPE_IPV4:
83
        // ipv4 is uint32, but parquet not uint32, it's will be convert to int64
84
        // so use int32 directly
85
50
        *result = arrow::int32();
86
50
        break;
87
40
    case TYPE_IPV6:
88
40
        *result = arrow::utf8();
89
40
        break;
90
39
    case TYPE_UUID:
91
39
        *result = arrow::extension::uuid();
92
39
        break;
93
50
    case TYPE_LARGEINT:
94
56
    case TYPE_VARCHAR:
95
62
    case TYPE_CHAR:
96
96
    case TYPE_DATE:
97
122
    case TYPE_DATETIME:
98
250
    case TYPE_STRING:
99
250
    case TYPE_JSONB:
100
250
        *result = arrow::utf8();
101
250
        break;
102
30
    case TYPE_DATEV2:
103
30
        *result = std::make_shared<arrow::Date32Type>();
104
30
        break;
105
11
    case TYPE_TIMESTAMP_NS:
106
        // TIMESTAMP_NS is stored as signed epoch nanoseconds, but its SQL type has no timezone.
107
11
        *result = std::make_shared<arrow::TimestampType>(arrow::TimeUnit::NANO);
108
11
        break;
109
12
    case TYPE_TIMESTAMPTZ:
110
109
    case TYPE_DATETIMEV2: {
111
109
        arrow::TimeUnit::type time_unit;
112
109
        if (type->get_scale() > 3) {
113
72
            time_unit = arrow::TimeUnit::MICRO;
114
72
        } else if (type->get_scale() > 0) {
115
9
            time_unit = arrow::TimeUnit::MILLI;
116
28
        } else {
117
28
            time_unit = arrow::TimeUnit::SECOND;
118
28
        }
119
        // Doris DATETIMEV2 represents a wall-clock value without a timezone. Arrow Flight
120
        // exposes it as a timezone-naive timestamp so clients do not interpret it as an instant.
121
        // This option only changes the DATETIMEV2 output schema. TIMESTAMPTZ remains timezone-aware,
122
        // and Arrow-to-Doris conversions are unaffected.
123
109
        if (type->get_primitive_type() == TYPE_DATETIMEV2 && datetime_naive) {
124
6
            *result = std::make_shared<arrow::TimestampType>(time_unit);
125
103
        } else {
126
            // Arrow clients resolve timezone metadata as an IANA name; use the canonical UTC
127
            // name instead of the ISO-8601 "Z" alias without changing the encoded instant.
128
103
            *result = std::make_shared<arrow::TimestampType>(time_unit,
129
103
                                                             timezone == "Z" ? "UTC" : timezone);
130
103
        }
131
109
        break;
132
12
    }
133
14
    case TYPE_DECIMALV2:
134
62
    case TYPE_DECIMAL32:
135
98
    case TYPE_DECIMAL64:
136
122
    case TYPE_DECIMAL128I:
137
122
        *result = std::make_shared<arrow::Decimal128Type>(type->get_precision(), type->get_scale());
138
122
        break;
139
32
    case TYPE_DECIMAL256:
140
32
        *result = std::make_shared<arrow::Decimal256Type>(type->get_precision(), type->get_scale());
141
32
        break;
142
26
    case TYPE_BOOLEAN:
143
26
        *result = arrow::boolean();
144
26
        break;
145
234
    case TYPE_ARRAY: {
146
234
        const auto* type_arr = assert_cast<const DataTypeArray*>(remove_nullable(type).get());
147
234
        std::shared_ptr<arrow::DataType> item_type;
148
234
        RETURN_IF_ERROR(convert_to_arrow_type(type_arr->get_nested_type(), &item_type, timezone,
149
234
                                              datetime_naive));
150
234
        *result = std::make_shared<arrow::ListType>(item_type);
151
234
        break;
152
234
    }
153
140
    case TYPE_MAP: {
154
140
        const auto* type_map = assert_cast<const DataTypeMap*>(remove_nullable(type).get());
155
140
        std::shared_ptr<arrow::DataType> key_type;
156
140
        std::shared_ptr<arrow::DataType> val_type;
157
140
        RETURN_IF_ERROR(convert_to_arrow_type(type_map->get_key_type(), &key_type, timezone,
158
140
                                              datetime_naive));
159
140
        RETURN_IF_ERROR(convert_to_arrow_type(type_map->get_value_type(), &val_type, timezone,
160
140
                                              datetime_naive));
161
140
        *result = std::make_shared<arrow::MapType>(key_type, val_type);
162
140
        break;
163
140
    }
164
118
    case TYPE_STRUCT: {
165
118
        const auto* type_struct = assert_cast<const DataTypeStruct*>(remove_nullable(type).get());
166
118
        std::vector<std::shared_ptr<arrow::Field>> fields;
167
388
        for (size_t i = 0; i < type_struct->get_elements().size(); i++) {
168
270
            std::shared_ptr<arrow::DataType> field_type;
169
270
            RETURN_IF_ERROR(convert_to_arrow_type(type_struct->get_element(i), &field_type,
170
270
                                                  timezone, datetime_naive));
171
270
            fields.push_back(
172
270
                    std::make_shared<arrow::Field>(type_struct->get_element_name(i), field_type,
173
270
                                                   type_struct->get_element(i)->is_nullable()));
174
270
        }
175
118
        *result = std::make_shared<arrow::StructType>(fields);
176
118
        break;
177
118
    }
178
0
    case TYPE_VARIANT: {
179
0
        *result = arrow::utf8();
180
0
        break;
181
118
    }
182
2
    case TYPE_QUANTILE_STATE:
183
6
    case TYPE_BITMAP:
184
10
    case TYPE_HLL: {
185
10
        *result = arrow::binary();
186
10
        break;
187
6
    }
188
12
    case TYPE_VARBINARY: {
189
12
        *result = arrow::binary();
190
12
        break;
191
6
    }
192
0
    default:
193
0
        return Status::InvalidArgument("Unknown primitive type({}) convert to Arrow type",
194
0
                                       type->get_name());
195
1.44k
    }
196
1.44k
    return Status::OK();
197
1.44k
}
198
199
// Helper function to create an Arrow Field with type metadata if applicable, such as IP types
200
std::shared_ptr<arrow::Field> create_arrow_field_with_metadata(
201
        const std::string& field_name, const std::shared_ptr<arrow::DataType>& arrow_type,
202
291
        bool is_nullable, PrimitiveType primitive_type) {
203
291
    if (primitive_type == PrimitiveType::TYPE_IPV4) {
204
4
        auto metadata = arrow::KeyValueMetadata::Make({"doris_type"}, {"IPV4"});
205
4
        return std::make_shared<arrow::Field>(field_name, arrow_type, is_nullable, metadata);
206
287
    } else if (primitive_type == PrimitiveType::TYPE_IPV6) {
207
4
        auto metadata = arrow::KeyValueMetadata::Make({"doris_type"}, {"IPV6"});
208
4
        return std::make_shared<arrow::Field>(field_name, arrow_type, is_nullable, metadata);
209
283
    } else if (primitive_type == PrimitiveType::TYPE_UUID) {
210
3
        auto metadata = arrow::KeyValueMetadata::Make({"doris_type"}, {"UUID"});
211
3
        return std::make_shared<arrow::Field>(field_name, arrow_type, is_nullable, metadata);
212
280
    } else if (primitive_type == PrimitiveType::TYPE_LARGEINT) {
213
4
        auto metadata = arrow::KeyValueMetadata::Make({"doris_type"}, {"LARGEINT"});
214
4
        return std::make_shared<arrow::Field>(field_name, arrow_type, is_nullable, metadata);
215
276
    } else {
216
276
        return std::make_shared<arrow::Field>(field_name, arrow_type, is_nullable);
217
276
    }
218
291
}
219
220
Status get_arrow_schema_from_block(const Block& block, std::shared_ptr<arrow::Schema>* result,
221
51
                                   const std::string& timezone, bool datetime_naive) {
222
51
    std::vector<std::shared_ptr<arrow::Field>> fields;
223
291
    for (const auto& type_and_name : block) {
224
291
        std::shared_ptr<arrow::DataType> arrow_type;
225
291
        RETURN_IF_ERROR(
226
291
                convert_to_arrow_type(type_and_name.type, &arrow_type, timezone, datetime_naive));
227
291
        auto field = create_arrow_field_with_metadata(type_and_name.name, arrow_type,
228
291
                                                      type_and_name.type->is_nullable(),
229
291
                                                      type_and_name.type->get_primitive_type());
230
291
        fields.push_back(field);
231
291
    }
232
51
    *result = arrow::schema(std::move(fields));
233
51
    return Status::OK();
234
51
}
235
236
Status get_arrow_schema_from_expr_ctxs(const VExprContextSPtrs& output_vexpr_ctxs,
237
                                       std::shared_ptr<arrow::Schema>* result,
238
0
                                       const std::string& timezone, bool datetime_naive) {
239
0
    std::vector<std::shared_ptr<arrow::Field>> fields;
240
0
    for (int i = 0; i < output_vexpr_ctxs.size(); i++) {
241
0
        std::shared_ptr<arrow::DataType> arrow_type;
242
0
        auto root_expr = output_vexpr_ctxs.at(i)->root();
243
0
        RETURN_IF_ERROR(convert_to_arrow_type(root_expr->data_type(), &arrow_type, timezone,
244
0
                                              datetime_naive));
245
0
        auto field_name = root_expr->is_slot_ref() && !root_expr->expr_label().empty()
246
0
                                  ? root_expr->expr_label()
247
0
                                  : fmt::format("{}_{}", root_expr->data_type()->get_name(), i);
248
0
        auto field =
249
0
                create_arrow_field_with_metadata(field_name, arrow_type, root_expr->is_nullable(),
250
0
                                                 root_expr->data_type()->get_primitive_type());
251
0
        fields.push_back(field);
252
0
    }
253
0
    *result = arrow::schema(std::move(fields));
254
0
    return Status::OK();
255
0
}
256
257
1
Status serialize_record_batch(const arrow::RecordBatch& record_batch, std::string* result) {
258
    // create sink memory buffer outputstream with the computed capacity
259
1
    int64_t capacity;
260
1
    arrow::Status a_st = arrow::ipc::GetRecordBatchSize(record_batch, &capacity);
261
1
    if (!a_st.ok()) {
262
0
        return Status::InternalError("GetRecordBatchSize failure, reason: {}", a_st.ToString());
263
0
    }
264
1
    auto sink_res = arrow::io::BufferOutputStream::Create(capacity, arrow::default_memory_pool());
265
1
    if (!sink_res.ok()) {
266
0
        return Status::InternalError("create BufferOutputStream failure, reason: {}",
267
0
                                     sink_res.status().ToString());
268
0
    }
269
1
    std::shared_ptr<arrow::io::BufferOutputStream> sink = sink_res.ValueOrDie();
270
    // create RecordBatch Writer
271
1
    auto res = arrow::ipc::MakeStreamWriter(sink.get(), record_batch.schema());
272
1
    if (!res.ok()) {
273
0
        return Status::InternalError("open RecordBatchStreamWriter failure, reason: {}",
274
0
                                     res.status().ToString());
275
0
    }
276
    // write RecordBatch to memory buffer outputstream
277
1
    std::shared_ptr<arrow::ipc::RecordBatchWriter> record_batch_writer = res.ValueOrDie();
278
1
    a_st = record_batch_writer->WriteRecordBatch(record_batch);
279
1
    if (!a_st.ok()) {
280
0
        return Status::InternalError("write record batch failure, reason: {}", a_st.ToString());
281
0
    }
282
1
    a_st = record_batch_writer->Close();
283
1
    if (!a_st.ok()) {
284
0
        return Status::InternalError("Close failed, reason: {}", a_st.ToString());
285
0
    }
286
1
    auto finish_res = sink->Finish();
287
1
    if (!finish_res.ok()) {
288
0
        return Status::InternalError("allocate result buffer failure, reason: {}",
289
0
                                     finish_res.status().ToString());
290
0
    }
291
1
    *result = finish_res.ValueOrDie()->ToString();
292
    // close the sink
293
1
    a_st = sink->Close();
294
1
    if (!a_st.ok()) {
295
0
        return Status::InternalError("Close failed, reason: {}", a_st.ToString());
296
0
    }
297
1
    return Status::OK();
298
1
}
299
300
1
Status serialize_arrow_schema(std::shared_ptr<arrow::Schema>* schema, std::string* result) {
301
1
    std::vector<std::shared_ptr<arrow::Array>> columns;
302
1
    columns.reserve((*schema)->num_fields());
303
6
    for (const auto& field : (*schema)->fields()) {
304
6
        auto empty = arrow::MakeArrayOfNull(field->type(), 0);
305
6
        if (!empty.ok()) {
306
0
            return Status::InternalError("serialize_arrow_schema failed, reason: {}",
307
0
                                         empty.status().ToString());
308
0
        }
309
6
        columns.push_back(*empty);
310
6
    }
311
1
    auto batch = arrow::RecordBatch::Make(*schema, 0, columns);
312
1
    return serialize_record_batch(*batch, result);
313
1
}
314
315
} // namespace doris