Coverage Report

Created: 2026-10-13 00:13

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
19
        : _file_writer(file_writer), _cur_pos(0), _written_len(0) {
52
19
    set_mode(arrow::io::FileMode::WRITE);
53
19
}
Unexecuted instantiation: _ZN5doris19ParquetOutputStreamC2EPNS_2io10FileWriterE
_ZN5doris19ParquetOutputStreamC1EPNS_2io10FileWriterE
Line
Count
Source
51
19
        : _file_writer(file_writer), _cur_pos(0), _written_len(0) {
52
19
    set_mode(arrow::io::FileMode::WRITE);
53
19
}
54
55
19
ParquetOutputStream::~ParquetOutputStream() {
56
19
    arrow::Status st = Close();
57
19
    if (!st.ok()) {
58
0
        LOG(WARNING) << "close parquet file error: " << st.ToString();
59
0
    }
60
19
}
61
62
1.20k
arrow::Status ParquetOutputStream::Write(const void* data, int64_t nbytes) {
63
1.20k
    if (_is_closed) {
64
0
        return arrow::Status::OK();
65
0
    }
66
1.20k
    size_t written_len = nbytes;
67
1.20k
    Status st = _file_writer->append({static_cast<const uint8_t*>(data), written_len});
68
1.20k
    if (!st.ok()) {
69
0
        return arrow::Status::IOError(st.to_string());
70
0
    }
71
1.20k
    _cur_pos += written_len;
72
1.20k
    _written_len += written_len;
73
1.20k
    return arrow::Status::OK();
74
1.20k
}
75
76
1.94k
arrow::Result<int64_t> ParquetOutputStream::Tell() const {
77
1.94k
    return _cur_pos;
78
1.94k
}
79
80
38
arrow::Status ParquetOutputStream::Close() {
81
38
    if (!_is_closed) {
82
19
        Defer defer {[this] { _is_closed = true; }};
83
19
        Status st = _file_writer->close();
84
19
        if (!st.ok()) {
85
0
            LOG(WARNING) << "close parquet output stream failed: " << st;
86
0
            return arrow::Status::IOError(st.to_string());
87
0
        }
88
19
    }
89
38
    return arrow::Status::OK();
90
38
}
91
92
5
int64_t ParquetOutputStream::get_written_len() const {
93
5
    return _written_len;
94
5
}
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
5
        TFileCompressType::type compression_type) {
102
5
    switch (compression_type) {
103
5
    case TFileCompressType::PLAIN:
104
5
        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
5
    }
117
5
}
118
119
void ParquetBuildHelper::build_compression_type(
120
        ::parquet::WriterProperties::Builder& builder,
121
19
        const TParquetCompressionType::type& compression_type) {
122
19
    switch (compression_type) {
123
0
    case TParquetCompressionType::SNAPPY: {
124
0
        builder.compression(arrow::Compression::SNAPPY);
125
0
        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
19
    case TParquetCompressionType::UNCOMPRESSED: {
164
19
        builder.compression(arrow::Compression::UNCOMPRESSED);
165
19
        break;
166
0
    }
167
0
    default:
168
0
        builder.compression(arrow::Compression::SNAPPY);
169
19
    }
170
19
}
171
172
void ParquetBuildHelper::build_version(::parquet::WriterProperties::Builder& builder,
173
19
                                       const TParquetVersion::type& parquet_version) {
174
19
    switch (parquet_version) {
175
18
    case TParquetVersion::PARQUET_1_0: {
176
18
        builder.version(::parquet::ParquetVersion::PARQUET_1_0);
177
18
        break;
178
0
    }
179
1
    case TParquetVersion::PARQUET_2_LATEST: {
180
1
        builder.version(::parquet::ParquetVersion::PARQUET_2_LATEST);
181
1
        break;
182
0
    }
183
0
    default:
184
0
        builder.version(::parquet::ParquetVersion::PARQUET_1_0);
185
19
    }
186
19
}
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
19
        : VFileFormatTransformer(state, output_vexpr_ctxs, output_object_data),
193
19
          _column_names(std::move(column_names)),
194
19
          _parquet_options(parquet_options) {
195
19
    _outstream = std::shared_ptr<ParquetOutputStream>(new ParquetOutputStream(file_writer));
196
19
}
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
19
Status VParquetWriter::_parse_properties() {
209
19
    try {
210
19
        arrow::MemoryPool* pool = ExecEnv::GetInstance()->arrow_memory_pool();
211
212
        //build parquet writer properties
213
19
        ::parquet::WriterProperties::Builder builder;
214
19
        ParquetBuildHelper::build_compression_type(builder, _parquet_options.compression_type);
215
19
        ParquetBuildHelper::build_version(builder, _parquet_options.parquet_version);
216
19
        if (_parquet_options.parquet_disable_dictionary) {
217
0
            builder.disable_dictionary();
218
19
        } else {
219
19
            builder.enable_dictionary();
220
19
        }
221
19
        builder.created_by(
222
19
                fmt::format("{}({})", doris::get_short_version(), ::parquet::DEFAULT_CREATED_BY));
223
19
        builder.max_row_group_length(std::numeric_limits<int64_t>::max());
224
19
        builder.memory_pool(pool);
225
19
        _parquet_writer_properties = builder.build();
226
227
        //build arrow  writer properties
228
19
        ::parquet::ArrowWriterProperties::Builder arrow_builder;
229
19
        if (_parquet_options.enable_int96_timestamps) {
230
6
            arrow_builder.enable_force_write_int96_timestamps();
231
6
        }
232
19
        arrow_builder.store_schema();
233
19
        _arrow_properties = arrow_builder.build();
234
19
    } catch (const ::parquet::ParquetException& e) {
235
0
        return Status::InternalError("parquet writer parse properties error: {}", e.what());
236
0
    }
237
19
    return Status::OK();
238
19
}
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
19
Status VParquetWriter::write(const Block& block) {
248
19
    if (block.rows() == 0) {
249
0
        return Status::OK();
250
0
    }
251
252
    // serialize
253
19
    std::shared_ptr<arrow::RecordBatch> result;
254
19
    RETURN_IF_ERROR(_arrow_block_convertor->convert_to_arrow(
255
19
            block, ExecEnv::GetInstance()->arrow_memory_pool(), &result));
256
19
    if (_write_size == 0) {
257
19
        RETURN_DORIS_STATUS_IF_ERROR(_writer->NewBufferedRowGroup());
258
19
    }
259
19
    RETURN_DORIS_STATUS_IF_ERROR(_writer->WriteRecordBatch(*result));
260
19
    _write_size += block.bytes();
261
19
    if (_write_size >= doris::config::min_row_group_size) {
262
0
        _write_size = 0;
263
0
    }
264
19
    return Status::OK();
265
19
}
266
267
19
arrow::Status VParquetWriter::_open_file_writer() {
268
19
    ARROW_ASSIGN_OR_RAISE(_writer, ::parquet::arrow::FileWriter::Open(
269
19
                                           *_arrow_block_convertor->arrow_schema(),
270
19
                                           ExecEnv::GetInstance()->arrow_memory_pool(), _outstream,
271
19
                                           _parquet_writer_properties, _arrow_properties));
272
19
    return arrow::Status::OK();
273
19
}
274
275
19
Status VParquetWriter::open() {
276
19
    _timezone = _state->timezone();
277
19
    _timezone_obj = _state->timezone_obj();
278
19
    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
19
    RETURN_IF_ERROR(_parse_properties());
286
19
    DataTypes types;
287
19
    types.reserve(_output_vexpr_ctxs.size());
288
378
    for (const auto& context : _output_vexpr_ctxs) {
289
378
        types.emplace_back(context->root()->data_type());
290
378
    }
291
19
    std::vector<std::string> names = _column_names;
292
19
    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
19
    _arrow_block_convertor =
300
19
            _create_arrow_block_convertor(std::move(types), std::move(names), _timezone,
301
19
                                          _timezone_obj, _parquet_options.enable_int96_timestamps);
302
19
    RETURN_IF_ERROR(_arrow_block_convertor->init());
303
19
    try {
304
19
        RETURN_DORIS_STATUS_IF_ERROR(_open_file_writer());
305
19
    } 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
19
    if (_writer == nullptr) {
310
0
        return Status::InternalError("Failed to create file writer");
311
0
    }
312
19
    return Status::OK();
313
19
}
314
315
5
int64_t VParquetWriter::written_len() {
316
5
    return _outstream->get_written_len();
317
5
}
318
319
19
Status VParquetWriter::close() {
320
19
    try {
321
19
        if (_writer != nullptr) {
322
19
            RETURN_DORIS_STATUS_IF_ERROR(_writer->Close());
323
19
        }
324
19
        RETURN_DORIS_STATUS_IF_ERROR(_outstream->Close());
325
326
19
    } 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
19
    return Status::OK();
332
19
}
333
334
} // namespace doris