Coverage Report

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