Coverage Report

Created: 2026-10-27 15:35

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/format/transformer/vparquet_writer.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/transformer/vparquet_writer.h"
19
20
#include <arrow/io/type_fwd.h>
21
#include <arrow/table.h>
22
#include <glog/logging.h>
23
#include <parquet/api/reader.h>
24
#include <parquet/column_writer.h>
25
#include <parquet/platform.h>
26
#include <parquet/schema.h>
27
#include <parquet/type_fwd.h>
28
#include <parquet/types.h>
29
30
#include <ctime>
31
#include <exception>
32
#include <ostream>
33
#include <string>
34
35
#include "common/config.h"
36
#include "common/status.h"
37
#include "exprs/vexpr.h"
38
#include "exprs/vexpr_context.h"
39
#include "format/arrow/arrow_row_batch.h"
40
#include "format/arrow/arrow_utils.h"
41
#include "format/parquet/parquet_arrow_block_convertor.h"
42
#include "io/fs/file_writer.h"
43
#include "runtime/exec_env.h"
44
#include "runtime/runtime_state.h"
45
#include "util/debug_util.h"
46
#include "util/timezone_utils.h"
47
48
namespace doris {
49
50
ParquetOutputStream::ParquetOutputStream(doris::io::FileWriter* file_writer)
51
49
        : _file_writer(file_writer), _cur_pos(0), _written_len(0) {
52
49
    set_mode(arrow::io::FileMode::WRITE);
53
49
}
Unexecuted instantiation: _ZN5doris19ParquetOutputStreamC2EPNS_2io10FileWriterE
_ZN5doris19ParquetOutputStreamC1EPNS_2io10FileWriterE
Line
Count
Source
51
49
        : _file_writer(file_writer), _cur_pos(0), _written_len(0) {
52
49
    set_mode(arrow::io::FileMode::WRITE);
53
49
}
54
55
49
ParquetOutputStream::~ParquetOutputStream() {
56
49
    arrow::Status st = Close();
57
49
    if (!st.ok()) {
58
2
        LOG(WARNING) << "close parquet file error: " << st.ToString();
59
2
    }
60
49
}
61
62
1.37k
arrow::Status ParquetOutputStream::Write(const void* data, int64_t nbytes) {
63
1.37k
    if (_is_closed) {
64
0
        return arrow::Status::OK();
65
0
    }
66
1.37k
    size_t written_len = nbytes;
67
1.37k
    Status st = _file_writer->append({static_cast<const uint8_t*>(data), written_len});
68
1.37k
    if (!st.ok()) {
69
0
        return arrow::Status::IOError(st.to_string());
70
0
    }
71
1.37k
    _cur_pos += written_len;
72
1.37k
    _written_len += written_len;
73
1.37k
    return arrow::Status::OK();
74
1.37k
}
75
76
2.18k
arrow::Result<int64_t> ParquetOutputStream::Tell() const {
77
2.18k
    return _cur_pos;
78
2.18k
}
79
80
91
arrow::Status ParquetOutputStream::Close() {
81
91
    if (!_is_closed) {
82
49
        Defer defer {[this] { _is_closed = true; }};
83
49
        Status st = _file_writer->close();
84
49
        if (!st.ok()) {
85
2
            LOG(WARNING) << "close parquet output stream failed: " << st;
86
2
            return arrow::Status::IOError(st.to_string());
87
2
        }
88
49
    }
89
89
    return arrow::Status::OK();
90
91
}
91
92
7
int64_t ParquetOutputStream::get_written_len() const {
93
7
    return _written_len;
94
7
}
95
96
0
void ParquetOutputStream::set_written_len(int64_t written_len) {
97
0
    _written_len = written_len;
98
0
}
99
100
Result<TParquetCompressionType::type> ParquetBuildHelper::to_parquet_compression_type(
101
6
        TFileCompressType::type compression_type) {
102
6
    switch (compression_type) {
103
6
    case TFileCompressType::PLAIN:
104
6
        return TParquetCompressionType::UNCOMPRESSED;
105
0
    case TFileCompressType::SNAPPYBLOCK:
106
0
        return TParquetCompressionType::SNAPPY;
107
0
    case TFileCompressType::ZSTD:
108
0
        return TParquetCompressionType::ZSTD;
109
0
    case TFileCompressType::GZ:
110
0
        return TParquetCompressionType::GZIP;
111
0
    case TFileCompressType::LZ4BLOCK:
112
0
        return TParquetCompressionType::LZ4_HADOOP;
113
0
    default:
114
0
        return ResultError(Status::InternalError("Unsupported compress type {} with parquet",
115
0
                                                 to_string(compression_type)));
116
6
    }
117
6
}
118
119
void ParquetBuildHelper::build_compression_type(
120
        ::parquet::WriterProperties::Builder& builder,
121
46
        const TParquetCompressionType::type& compression_type) {
122
46
    switch (compression_type) {
123
5
    case TParquetCompressionType::SNAPPY: {
124
5
        builder.compression(arrow::Compression::SNAPPY);
125
5
        break;
126
0
    }
127
0
    case TParquetCompressionType::GZIP: {
128
0
        builder.compression(arrow::Compression::GZIP);
129
0
        break;
130
0
    }
131
0
    case TParquetCompressionType::BROTLI: {
132
0
        builder.compression(arrow::Compression::BROTLI);
133
0
        break;
134
0
    }
135
0
    case TParquetCompressionType::ZSTD: {
136
0
        builder.compression(arrow::Compression::ZSTD);
137
0
        break;
138
0
    }
139
0
    case TParquetCompressionType::LZ4: {
140
0
        builder.compression(arrow::Compression::LZ4);
141
0
        break;
142
0
    }
143
0
    case TParquetCompressionType::LZ4_HADOOP: {
144
0
        constexpr int64_t HADOOP_LZ4_DEFAULT_BUFFER_SIZE = 256 * 1024;
145
        // Hadoop-framed LZ4 -> Parquet thrift codec "LZ4" (deprecated). This matches what
146
        // Spark/Iceberg writes for `write.parquet.compression-codec=lz4`. Arrow 17 emits one
147
        // Hadoop LZ4 block per Parquet page/dictionary page, while Hadoop JVM readers default to
148
        // a 256 KiB LZ4 codec buffer, so keep page targets below that buffer size.
149
0
        builder.compression(arrow::Compression::LZ4_HADOOP);
150
0
        builder.data_pagesize(HADOOP_LZ4_DEFAULT_BUFFER_SIZE / 2);
151
0
        builder.dictionary_pagesize_limit(HADOOP_LZ4_DEFAULT_BUFFER_SIZE / 2);
152
0
        break;
153
0
    }
154
    // arrow do not support lzo and bz2 compression type.
155
    // case TParquetCompressionType::LZO: {
156
    //     builder.compression(arrow::Compression::LZO);
157
    //     break;
158
    // }
159
    // case TParquetCompressionType::BZ2: {
160
    //     builder.compression(arrow::Compression::BZ2);
161
    //     break;
162
    // }
163
41
    case TParquetCompressionType::UNCOMPRESSED: {
164
41
        builder.compression(arrow::Compression::UNCOMPRESSED);
165
41
        break;
166
0
    }
167
0
    default:
168
0
        builder.compression(arrow::Compression::SNAPPY);
169
46
    }
170
46
}
171
172
void ParquetBuildHelper::build_version(::parquet::WriterProperties::Builder& builder,
173
46
                                       const TParquetVersion::type& parquet_version) {
174
46
    switch (parquet_version) {
175
25
    case TParquetVersion::PARQUET_1_0: {
176
25
        builder.version(::parquet::ParquetVersion::PARQUET_1_0);
177
25
        break;
178
0
    }
179
21
    case TParquetVersion::PARQUET_2_LATEST: {
180
21
        builder.version(::parquet::ParquetVersion::PARQUET_2_LATEST);
181
21
        break;
182
0
    }
183
0
    default:
184
0
        builder.version(::parquet::ParquetVersion::PARQUET_1_0);
185
46
    }
186
46
}
187
188
VParquetWriter::VParquetWriter(RuntimeState* state, doris::io::FileWriter* file_writer,
189
                               const VExprContextSPtrs& output_vexpr_ctxs,
190
                               std::vector<std::string> column_names, bool output_object_data,
191
                               const ParquetFileOptions& parquet_options)
192
46
        : VFileFormatTransformer(state, output_vexpr_ctxs, output_object_data),
193
46
          _column_names(std::move(column_names)),
194
46
          _parquet_options(parquet_options) {
195
46
    _outstream = std::shared_ptr<ParquetOutputStream>(new ParquetOutputStream(file_writer));
196
46
}
197
198
VParquetWriter::VParquetWriter(RuntimeState* state, doris::io::FileWriter* file_writer,
199
                               const VExprContextSPtrs& output_vexpr_ctxs,
200
                               std::vector<TParquetSchema> parquet_schemas, bool output_object_data,
201
                               const ParquetFileOptions& parquet_options)
202
0
        : VFileFormatTransformer(state, output_vexpr_ctxs, output_object_data),
203
0
          _parquet_schemas(std::move(parquet_schemas)),
204
0
          _parquet_options(parquet_options) {
205
0
    _outstream = std::shared_ptr<ParquetOutputStream>(new ParquetOutputStream(file_writer));
206
0
}
207
208
46
Status VParquetWriter::_parse_properties() {
209
46
    try {
210
46
        arrow::MemoryPool* pool = ExecEnv::GetInstance()->arrow_memory_pool();
211
212
        //build parquet writer properties
213
46
        ::parquet::WriterProperties::Builder builder;
214
46
        ParquetBuildHelper::build_compression_type(builder, _parquet_options.compression_type);
215
46
        ParquetBuildHelper::build_version(builder, _parquet_options.parquet_version);
216
46
        if (_parquet_options.parquet_disable_dictionary) {
217
1
            builder.disable_dictionary();
218
45
        } else {
219
45
            builder.enable_dictionary();
220
45
        }
221
46
        builder.created_by(
222
46
                fmt::format("{}({})", doris::get_short_version(), ::parquet::DEFAULT_CREATED_BY));
223
46
        builder.max_row_group_length(std::numeric_limits<int64_t>::max());
224
46
        builder.memory_pool(pool);
225
46
        _parquet_writer_properties = builder.build();
226
227
        //build arrow  writer properties
228
46
        ::parquet::ArrowWriterProperties::Builder arrow_builder;
229
46
        if (_parquet_options.enable_int96_timestamps) {
230
6
            arrow_builder.enable_force_write_int96_timestamps();
231
6
        }
232
46
        arrow_builder.store_schema();
233
46
        _arrow_properties = arrow_builder.build();
234
46
    } catch (const ::parquet::ParquetException& e) {
235
0
        return Status::InternalError("parquet writer parse properties error: {}", e.what());
236
0
    }
237
46
    return Status::OK();
238
46
}
239
240
std::unique_ptr<ArrowBlockConvertor> VParquetWriter::_create_arrow_block_convertor(
241
        DataTypes types, std::vector<std::string> names, const std::string& timezone_name,
242
2
        const cctz::time_zone& timezone, bool enable_int96_timestamps) const {
243
2
    return std::make_unique<ParquetArrowBlockConvertor>(
244
2
            std::move(types), std::move(names), timezone_name, timezone, enable_int96_timestamps);
245
2
}
246
247
45
Status VParquetWriter::write(const Block& block) {
248
45
    if (block.rows() == 0) {
249
0
        return Status::OK();
250
0
    }
251
252
    // serialize
253
45
    std::shared_ptr<arrow::RecordBatch> result;
254
45
    RETURN_IF_ERROR(_arrow_block_convertor->convert_to_arrow(
255
45
            block, ExecEnv::GetInstance()->arrow_memory_pool(), &result));
256
45
    if (_write_size == 0) {
257
45
        RETURN_DORIS_STATUS_IF_ERROR(_writer->NewBufferedRowGroup());
258
45
    }
259
45
    RETURN_DORIS_STATUS_IF_ERROR(_writer->WriteRecordBatch(*result));
260
45
    _write_size += block.bytes();
261
45
    if (_write_size >= doris::config::min_row_group_size) {
262
7
        _write_size = 0;
263
7
    }
264
45
    return Status::OK();
265
45
}
266
267
27
arrow::Status VParquetWriter::_open_file_writer() {
268
27
    ARROW_ASSIGN_OR_RAISE(_writer, ::parquet::arrow::FileWriter::Open(
269
27
                                           *_arrow_block_convertor->arrow_schema(),
270
27
                                           ExecEnv::GetInstance()->arrow_memory_pool(), _outstream,
271
27
                                           _parquet_writer_properties, _arrow_properties));
272
27
    return arrow::Status::OK();
273
27
}
274
275
46
Status VParquetWriter::open() {
276
46
    _timezone = _state->timezone();
277
46
    _timezone_obj = _state->timezone_obj();
278
46
    if (_parquet_options.enable_int96_timestamps && _parquet_options.int96_timezone.has_value()) {
279
4
        _timezone = *_parquet_options.int96_timezone;
280
        // Cache the override on this writer, never mutate the shared query RuntimeState.
281
4
        if (!TimezoneUtils::find_cctz_time_zone(_timezone, _timezone_obj)) {
282
0
            return Status::InvalidArgument("Invalid Parquet INT96 writer timezone: {}", _timezone);
283
0
        }
284
4
    }
285
46
    RETURN_IF_ERROR(_parse_properties());
286
46
    DataTypes types;
287
46
    types.reserve(_output_vexpr_ctxs.size());
288
406
    for (const auto& context : _output_vexpr_ctxs) {
289
406
        types.emplace_back(context->root()->data_type());
290
406
    }
291
46
    std::vector<std::string> names = _column_names;
292
46
    if (!_parquet_schemas.empty()) {
293
0
        names.clear();
294
0
        names.reserve(_parquet_schemas.size());
295
0
        for (const auto& schema : _parquet_schemas) {
296
0
            names.emplace_back(schema.schema_column_name);
297
0
        }
298
0
    }
299
46
    _arrow_block_convertor =
300
46
            _create_arrow_block_convertor(std::move(types), std::move(names), _timezone,
301
46
                                          _timezone_obj, _parquet_options.enable_int96_timestamps);
302
46
    RETURN_IF_ERROR(_arrow_block_convertor->init());
303
46
    try {
304
46
        RETURN_DORIS_STATUS_IF_ERROR(_open_file_writer());
305
46
    } catch (const ::parquet::ParquetStatusException& e) {
306
0
        LOG(WARNING) << "parquet file writer open error: " << e.what();
307
0
        return Status::InternalError("parquet file writer open error: {}", e.what());
308
0
    }
309
42
    if (_writer == nullptr) {
310
0
        return Status::InternalError("Failed to create file writer");
311
0
    }
312
42
    return Status::OK();
313
42
}
314
315
7
int64_t VParquetWriter::written_len() {
316
7
    return _outstream->get_written_len();
317
7
}
318
319
42
Status VParquetWriter::close() {
320
42
    try {
321
42
        if (_writer != nullptr) {
322
42
            RETURN_DORIS_STATUS_IF_ERROR(_writer->Close());
323
42
        }
324
42
        RETURN_DORIS_STATUS_IF_ERROR(_outstream->Close());
325
326
42
    } catch (const std::exception& e) {
327
0
        LOG(WARNING) << "Parquet writer close error: " << e.what();
328
0
        return Status::IOError(e.what());
329
0
    }
330
331
42
    return Status::OK();
332
42
}
333
334
} // namespace doris