Coverage Report

Created: 2026-09-24 16:06

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