Coverage Report

Created: 2026-10-23 20:08

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/storage/segment/column_writer.h
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
#pragma once
19
20
#include <gen_cpp/AgentService_types.h>
21
#include <gen_cpp/olap_file.pb.h>
22
#include <gen_cpp/segment_v2.pb.h>
23
#include <stddef.h>
24
#include <stdint.h>
25
26
#include <algorithm>
27
#include <functional>
28
#include <memory> // for unique_ptr
29
#include <ostream>
30
#include <span>
31
#include <string>
32
#include <unordered_map>
33
#include <utility>
34
#include <vector>
35
36
#include "common/status.h" // for Status
37
#include "core/column/column.h"
38
#include "core/column/column_vector.h"
39
#include "storage/index/ann/ann_index_writer.h"
40
#include "storage/index/bloom_filter/bloom_filter.h"
41
#include "storage/index/inverted/clucene/inverted_index_writer.h"
42
#include "storage/segment/common.h"
43
#include "storage/segment/options.h"
44
#include "storage/segment/variant/nested_group_provider.h"
45
#include "storage/segment/variant/variant_statistics.h"
46
#include "storage/storage_layout.h"       // for StorageBuffers
47
#include "storage/tablet/tablet_schema.h" // for TabletColumnPtr
48
#include "util/slice.h"                   // for OwnedSlice
49
50
namespace doris {
51
52
class BlockCompressionCodec;
53
class TabletColumn;
54
class TabletIndex;
55
struct RowsetWriterContext;
56
57
namespace io {
58
class FileWriter;
59
}
60
61
namespace segment_v2 {
62
63
enum class VariantWriterInputFormat : uint8_t {
64
    UNSET,
65
    V2,
66
};
67
68
struct ColumnWriterOptions {
69
    // input and output parameter:
70
    // - input: column_id/unique_id/type/length/encoding/compression/is_nullable members
71
    // - output: encoding/indexes/dict_page members
72
    ColumnMetaPB* meta = nullptr;
73
    size_t data_page_size = STORAGE_PAGE_SIZE_DEFAULT_VALUE;
74
    size_t dict_page_size = STORAGE_DICT_PAGE_SIZE_DEFAULT_VALUE;
75
    // store compressed page only when space saving is above the threshold.
76
    // space saving = 1 - compressed_size / uncompressed_size
77
    double compression_min_space_saving = 0.1;
78
    bool need_zone_map = false;
79
    bool need_bloom_filter = false;
80
    bool is_ngram_bf_index = false;
81
    bool need_inverted_index = false;
82
    bool need_ann_index = false;
83
    uint8_t gram_size;
84
    uint16_t gram_bf_size;
85
    BloomFilterOptions bf_options;
86
    std::vector<const TabletIndex*> inverted_indexes;
87
    IndexFileWriter* index_file_writer = nullptr;
88
    // The owning segment serves a direct load (stream/broker load,
89
    // DataWriteType::TYPE_DIRECT) rather than compaction / schema change. Set
90
    // once by the segment writer and propagated to variant subcolumn writers;
91
    // forwarded to every created IndexColumnWriter via set_direct_load() so
92
    // SNII can select its direct-load PRX zstd level without plumbing
93
    // DataWriteType itself down here.
94
    bool is_direct_load = false;
95
96
    SegmentFooterPB* footer = nullptr;
97
    io::FileWriter* file_writer = nullptr;
98
    CompressionTypePB compression_type = UNKNOWN_COMPRESSION;
99
    RowsetWriterContext* rowset_ctx = nullptr;
100
    // For collect segment statistics for compaction
101
    std::vector<RowsetReaderSharedPtr> input_rs_readers;
102
    const TabletIndex* ann_index = nullptr;
103
104
    // Storage format of the owning tablet (V2 or V3). Set once by the segment writer
105
    // (from TabletMeta::storage_format()) and propagated down to aux child writers
106
    // (null / array-length / map-length), struct subcolumn writers and variant subcolumn
107
    // writers. All encoding-default decisions consult this via resolve_default_encoding().
108
    // Also forwarded to BinaryDictPageBuilder via PageBuilderOptions::binary_plain_encoding.
109
    TabletStorageFormatPB storage_format = TabletStorageFormatPB::TABLET_STORAGE_FORMAT_V2;
110
111
0
    std::string to_string() const {
112
0
        std::stringstream ss;
113
0
        ss << std::boolalpha << "meta=" << meta->DebugString()
114
0
           << ", data_page_size=" << data_page_size << ", dict_page_size=" << dict_page_size
115
0
           << ", compression_min_space_saving = " << compression_min_space_saving
116
0
           << ", need_zone_map=" << need_zone_map << ", need_bloom_filter" << need_bloom_filter;
117
0
        return ss.str();
118
0
    }
119
};
120
121
class EncodingInfo;
122
class NullBitmapBuilder;
123
class OrdinalIndexWriter;
124
class PageBuilder;
125
class BloomFilterIndexWriter;
126
class ZoneMapIndexWriter;
127
class VariantColumnWriterImpl;
128
class VariantShredder;
129
class VariantPathBuilder;
130
class ColumnWriter;
131
132
// Base interface for scalar, ARRAY, MAP, STRUCT, AGG_STATE and VARIANT columns.
133
class ColumnWriter {
134
public:
135
    static Status create(const ColumnWriterOptions& opts, const TabletColumn* column,
136
                         io::FileWriter* file_writer, std::unique_ptr<ColumnWriter>* writer);
137
    static Status create_struct_writer(const ColumnWriterOptions& opts, const TabletColumn* column,
138
                                       io::FileWriter* file_writer,
139
                                       std::unique_ptr<ColumnWriter>* writer);
140
    static Status create_array_writer(const ColumnWriterOptions& opts, const TabletColumn* column,
141
                                      io::FileWriter* file_writer,
142
                                      std::unique_ptr<ColumnWriter>* writer);
143
    static Status create_map_writer(const ColumnWriterOptions& opts, const TabletColumn* column,
144
                                    io::FileWriter* file_writer,
145
                                    std::unique_ptr<ColumnWriter>* writer);
146
147
    static Status create_variant_writer(const ColumnWriterOptions& opts, const TabletColumn* column,
148
                                        io::FileWriter* file_writer,
149
                                        std::unique_ptr<ColumnWriter>* writer);
150
151
    static Status create_agg_state_writer(const ColumnWriterOptions& opts,
152
                                          const TabletColumn* column, io::FileWriter* file_writer,
153
                                          std::unique_ptr<ColumnWriter>* writer);
154
155
    explicit ColumnWriter(TabletColumnPtr column, bool is_nullable, ColumnMetaPB* meta);
156
157
23.7k
    virtual ~ColumnWriter() = default;
158
159
    virtual Status init() = 0;
160
161
    // Write rows [row_pos, row_pos + num_rows) of `column`.
162
    virtual Status append(const IColumn& column, size_t row_pos, size_t num_rows) = 0;
163
164
    // Write num_rows NULL rows without any data. The variant writers use this to
165
    // pad a sub-column over rows where its path is absent.
166
    virtual Status append_nulls(size_t num_rows) = 0;
167
168
    virtual Status finish_current_page() = 0;
169
170
    virtual uint64_t estimate_buffer_size() = 0;
171
172
    // finish append data
173
    virtual Status finish() = 0;
174
175
    // write all data into file
176
    virtual Status write_data() = 0;
177
178
    virtual Status write_ordinal_index() = 0;
179
180
    virtual Status write_zone_map() = 0;
181
182
    virtual Status write_inverted_index() = 0;
183
184
17.6k
    virtual Status write_ann_index() { return Status::OK(); }
185
186
    virtual Status write_bloom_filter_index() = 0;
187
188
    virtual ordinal_t get_next_rowid() const = 0;
189
190
    virtual uint64_t get_raw_data_bytes() const = 0;
191
    virtual uint64_t get_total_uncompressed_data_pages_bytes() const = 0;
192
    virtual uint64_t get_total_compressed_data_pages_bytes() const = 0;
193
    // Points to one of the three data bytes getters above.
194
    using DataBytesGetter = uint64_t (ColumnWriter::*)() const;
195
196
956k
    bool is_nullable() const { return _is_nullable; }
197
198
859k
    const TabletColumn* get_column() const { return _column.get(); }
199
200
    // Per-row footprint of a storage cell of this writer's column, used to step across the
201
    // prepared cells in the append run loops.
202
830k
    size_t cell_size() const { return field_type_size(_column->type()); }
203
204
35.3k
    ColumnMetaPB* get_column_meta() const { return _column_meta; }
205
206
private:
207
    TabletColumnPtr _column;
208
    bool _is_nullable;
209
    ColumnMetaPB* _column_meta;
210
};
211
212
class FlushPageCallback {
213
public:
214
952
    virtual ~FlushPageCallback() = default;
215
0
    virtual void put_extra_info_in_page(DataPageFooterPB* footer) {}
216
};
217
218
// Writes scalar values and the offset/null streams of the composite writers. append cuts a
219
// batch into its NULL runs, written to the null bitmap (append_nulls), and its value runs,
220
// each converted to its storage cells (append_values) and written to the data pages and
221
// every index (append_data).
222
class ScalarColumnWriter : public ColumnWriter {
223
public:
224
    ScalarColumnWriter(const ColumnWriterOptions& opts, TabletColumnPtr column,
225
                       io::FileWriter* file_writer);
226
227
    ~ScalarColumnWriter() override;
228
229
    Status init() override;
230
231
    Status append(const IColumn& column, size_t row_pos, size_t num_rows) override;
232
233
    Status append_nulls(size_t num_rows) override;
234
235
    Status finish_current_page() override;
236
237
    uint64_t estimate_buffer_size() override;
238
239
    // finish append data
240
    Status finish() override;
241
242
    Status write_data() override;
243
    Status write_ordinal_index() override;
244
    Status write_zone_map() override;
245
    Status write_inverted_index() override;
246
    Status write_bloom_filter_index() override;
247
3.63k
    ordinal_t get_next_rowid() const override { return _next_rowid; }
248
249
21.2k
    uint64_t get_raw_data_bytes() const override { return _raw_data_bytes; }
250
251
21.2k
    uint64_t get_total_uncompressed_data_pages_bytes() const override {
252
21.2k
        return _total_uncompressed_data_pages_size;
253
21.2k
    }
254
255
21.5k
    uint64_t get_total_compressed_data_pages_bytes() const override {
256
21.5k
        return _total_compressed_data_pages_size;
257
21.5k
    }
258
259
952
    void register_flush_page_callback(FlushPageCallback* flush_page_callback) {
260
952
        _new_page_callback = flush_page_callback;
261
952
    }
262
263
protected:
264
    // used for append not null data. When page is full, will append data not reach num_rows.
265
831k
    Status append_data_in_current_page(const uint8_t* ptr, size_t* num_written) {
266
831k
        RETURN_IF_CATCH_EXCEPTION(
267
831k
                { return _internal_append_data_in_current_page(ptr, num_written); });
268
831k
    }
269
    friend class OffsetColumnWriter;
270
    friend class NullMapColumnWriter;
271
272
private:
273
    Status _internal_append_data_in_current_page(const uint8_t* ptr, size_t* num_written);
274
275
    // Converts rows [row_pos, row_pos + num_rows) of `values`, a run without NULL rows, to
276
    // num_rows cells of cell_size() bytes back to back through the StorageLayout of
277
    // _storage_type, borrowed from the column or held in _buffers until the next run, and
278
    // writes them.
279
    Status append_values(const IColumn& values, size_t row_pos, size_t num_rows);
280
281
    // Writes cells already in this column's storage layout, none of them NULL, page by page.
282
    // NullMapColumnWriter feeds a null map's flags, which are its TINYINT cells, directly.
283
    Status append_data(const uint8_t* cells, size_t num_rows);
284
285
    void _reserve_null_bitmap(const uint8_t* null_map, size_t num_rows);
286
287
    // The StorageLayout the values are stored through: the column's own type, or what an
288
    // AGG_STATE column's aggregate function serialises to.
289
    FieldType _storage_type;
290
    // Where append_values puts what it cannot borrow from the column.
291
    StorageBuffers _buffers;
292
293
    std::unique_ptr<PageBuilder> _page_builder;
294
295
    std::unique_ptr<NullBitmapBuilder> _null_bitmap_builder;
296
297
    ColumnWriterOptions _opts;
298
299
    const EncodingInfo* _encoding_info = nullptr;
300
301
    ordinal_t _next_rowid = 0;
302
303
    // All Pages will be organized into a linked list
304
    struct Page {
305
        // the data vector may contain:
306
        //     1. one OwnedSlice if the page body is compressed
307
        //     2. one OwnedSlice if the page body is not compressed and doesn't have nullmap
308
        //     3. two OwnedSlice if the page body is not compressed and has nullmap
309
        // use vector for easier management for lifetime of OwnedSlice
310
        std::vector<OwnedSlice> data;
311
        PageFooterPB footer;
312
    };
313
314
28.4k
    void _push_back_page(std::unique_ptr<Page> page) {
315
48.4k
        for (auto& data_slice : page->data) {
316
48.4k
            _data_size += data_slice.slice().size;
317
48.4k
        }
318
        // estimate (page footer + footer size + checksum) took 20 bytes
319
28.4k
        _data_size += 20;
320
        // add page to pages' tail
321
28.4k
        _pages.emplace_back(std::move(page));
322
28.4k
    }
323
324
    Status _write_data_page(Page* page);
325
326
private:
327
    io::FileWriter* _file_writer = nullptr;
328
    // total size of data page list
329
    uint64_t _data_size;
330
331
    uint64_t _raw_data_bytes {0};
332
    uint64_t _total_uncompressed_data_pages_size {0};
333
    uint64_t _total_compressed_data_pages_size {0};
334
335
    // cached generated pages,
336
    std::vector<std::unique_ptr<Page>> _pages;
337
    ordinal_t _first_rowid = 0;
338
339
    BlockCompressionCodec* _compress_codec;
340
341
    std::unique_ptr<OrdinalIndexWriter> _ordinal_index_builder;
342
    std::unique_ptr<ZoneMapIndexWriter> _zone_map_index_builder;
343
    std::vector<std::unique_ptr<IndexColumnWriter>> _inverted_index_builders;
344
    std::unique_ptr<BloomFilterIndexWriter> _bloom_filter_index_builder;
345
346
    // call before flush data page.
347
    FlushPageCallback* _new_page_callback = nullptr;
348
};
349
350
// offsetColumnWriter is used column which has offset column, like array, map.
351
//  column type is only uint64 and should response for whole column value [start, end], end will set
352
//  in footer.next_array_item_ordinal which in finish_cur_page() callback put_extra_info_in_page()
353
class OffsetColumnWriter final : public ScalarColumnWriter, FlushPageCallback {
354
public:
355
    OffsetColumnWriter(const ColumnWriterOptions& opts, TabletColumnPtr column,
356
                       io::FileWriter* file_writer);
357
358
    ~OffsetColumnWriter() override;
359
360
    Status init() override;
361
362
    // `column` holds the rebased offsets, one entry more than the rows.
363
    Status append(const IColumn& column, size_t row_pos, size_t num_rows) override;
364
365
private:
366
    void put_extra_info_in_page(DataPageFooterPB* footer) override;
367
368
    uint64_t _next_offset;
369
};
370
371
// The null map of a nullable STRUCT, ARRAY or MAP, stored as a NOT NULL TINYINT column. append
372
// takes the composite column's null map column, whose flag bytes are the stored cells.
373
class NullMapColumnWriter final : public ScalarColumnWriter {
374
public:
375
    using ScalarColumnWriter::ScalarColumnWriter;
376
377
    Status append(const IColumn& null_map, size_t row_pos, size_t num_rows) override;
378
};
379
380
// STRUCT values, including nested structs, with one child writer per field.
381
class StructColumnWriter final : public ColumnWriter {
382
public:
383
    explicit StructColumnWriter(const ColumnWriterOptions& opts, TabletColumnPtr column,
384
                                NullMapColumnWriter* null_writer,
385
                                std::vector<std::unique_ptr<ColumnWriter>>& sub_column_writers);
386
39
    ~StructColumnWriter() override = default;
387
388
    Status init() override;
389
390
    // Recurse into each sub-column writer; write our own null bits.
391
    Status append(const IColumn& column, size_t row_pos, size_t num_rows) override;
392
393
    uint64_t estimate_buffer_size() override;
394
395
    Status finish() override;
396
    Status write_data() override;
397
    Status write_ordinal_index() override;
398
0
    Status append_nulls(size_t num_rows) override {
399
0
        return Status::NotSupported("struct writer can not append_nulls");
400
0
    }
401
402
    Status finish_current_page() override;
403
404
38
    Status write_zone_map() override {
405
38
        if (_opts.need_zone_map) {
406
0
            return Status::NotSupported("struct not support zone map");
407
0
        }
408
38
        return Status::OK();
409
38
    }
410
411
    Status write_inverted_index() override;
412
38
    Status write_bloom_filter_index() override {
413
38
        if (_opts.need_bloom_filter) {
414
0
            return Status::NotSupported("struct not support bloom filter index");
415
0
        }
416
38
        return Status::OK();
417
38
    }
418
419
41
    ordinal_t get_next_rowid() const override { return _sub_column_writers[0]->get_next_rowid(); }
420
421
38
    uint64_t get_raw_data_bytes() const override {
422
38
        return _get_total_data_pages_bytes(&ColumnWriter::get_raw_data_bytes);
423
38
    }
424
425
38
    uint64_t get_total_uncompressed_data_pages_bytes() const override {
426
38
        return _get_total_data_pages_bytes(&ColumnWriter::get_total_uncompressed_data_pages_bytes);
427
38
    }
428
429
38
    uint64_t get_total_compressed_data_pages_bytes() const override {
430
38
        return _get_total_data_pages_bytes(&ColumnWriter::get_total_compressed_data_pages_bytes);
431
38
    }
432
433
private:
434
    template <typename Func>
435
114
    uint64_t _get_total_data_pages_bytes(Func func) const {
436
114
        uint64_t size = is_nullable() ? std::invoke(func, _null_writer.get()) : 0;
437
324
        for (const auto& writer : _sub_column_writers) {
438
324
            size += std::invoke(func, writer.get());
439
324
        }
440
114
        return size;
441
114
    }
442
443
private:
444
    size_t _num_sub_column_writers;
445
    std::unique_ptr<NullMapColumnWriter> _null_writer;
446
    std::vector<std::unique_ptr<ColumnWriter>> _sub_column_writers;
447
    ColumnWriterOptions _opts;
448
};
449
450
// ARRAY values and array-backed AGG_STATE values, with offset, null-map and item writers.
451
class ArrayColumnWriter final : public ColumnWriter {
452
public:
453
    explicit ArrayColumnWriter(const ColumnWriterOptions& opts, TabletColumnPtr column,
454
                               OffsetColumnWriter* offset_writer, NullMapColumnWriter* null_writer,
455
                               std::unique_ptr<ColumnWriter> item_writer);
456
223
    ~ArrayColumnWriter() override = default;
457
458
    Status init() override;
459
460
    // Rebase the offsets to disk layout, recurse into _item_writer, feed the
461
    // array index writers.
462
    Status append(const IColumn& column, size_t row_pos, size_t num_rows) override;
463
464
    uint64_t estimate_buffer_size() override;
465
466
    Status finish() override;
467
    Status write_data() override;
468
    Status write_ordinal_index() override;
469
    Status append_nulls(size_t num_rows) override;
470
471
    Status finish_current_page() override;
472
473
98
    Status write_zone_map() override {
474
98
        if (_opts.need_zone_map) {
475
0
            return Status::NotSupported("array not support zone map");
476
0
        }
477
98
        return Status::OK();
478
98
    }
479
480
    Status write_inverted_index() override;
481
    Status write_ann_index() override;
482
98
    Status write_bloom_filter_index() override {
483
98
        if (_opts.need_bloom_filter) {
484
0
            return Status::NotSupported("array not support bloom filter index");
485
0
        }
486
98
        return Status::OK();
487
98
    }
488
227
    ordinal_t get_next_rowid() const override { return _offset_writer->get_next_rowid(); }
489
490
197
    uint64_t get_raw_data_bytes() const override {
491
197
        return _get_total_data_pages_bytes(&ColumnWriter::get_raw_data_bytes);
492
197
    }
493
494
197
    uint64_t get_total_uncompressed_data_pages_bytes() const override {
495
197
        return _get_total_data_pages_bytes(&ColumnWriter::get_total_uncompressed_data_pages_bytes);
496
197
    }
497
498
205
    uint64_t get_total_compressed_data_pages_bytes() const override {
499
205
        return _get_total_data_pages_bytes(&ColumnWriter::get_total_compressed_data_pages_bytes);
500
205
    }
501
502
private:
503
    template <typename Func>
504
599
    uint64_t _get_total_data_pages_bytes(Func func) const {
505
599
        uint64_t size = std::invoke(func, _offset_writer.get());
506
599
        if (is_nullable()) {
507
386
            size += std::invoke(func, _null_writer.get());
508
386
        }
509
599
        size += std::invoke(func, _item_writer.get());
510
599
        return size;
511
599
    }
512
513
private:
514
215
    bool has_empty_items() const { return _item_writer->get_next_rowid() == 0; }
515
516
private:
517
    std::unique_ptr<OffsetColumnWriter> _offset_writer;
518
    std::unique_ptr<NullMapColumnWriter> _null_writer;
519
    std::unique_ptr<ColumnWriter> _item_writer;
520
    std::unique_ptr<IndexColumnWriter> _inverted_index_writer;
521
    std::unique_ptr<AnnIndexColumnWriter> _ann_index_writer;
522
    ColumnWriterOptions _opts;
523
    // The current batch's offsets as the offset writer and the index writers
524
    // read them.
525
    ColumnOffset64::MutablePtr _offsets_buffer = ColumnOffset64::create();
526
    // Storage cells for array indexes, independent of the element writer's page buffers.
527
    StorageBuffers _index_buffers;
528
};
529
530
// MAP values and map-backed AGG_STATE values, with offset, null-map, key and value writers.
531
class MapColumnWriter final : public ColumnWriter {
532
public:
533
    explicit MapColumnWriter(const ColumnWriterOptions& opts, TabletColumnPtr column,
534
                             NullMapColumnWriter* null_writer, OffsetColumnWriter* offsets_writer,
535
                             std::vector<std::unique_ptr<ColumnWriter>>& _kv_writers);
536
537
727
    ~MapColumnWriter() override = default;
538
539
    Status init() override;
540
541
    // Rebase the offsets to disk layout, recurse into the key and value writers.
542
    Status append(const IColumn& column, size_t row_pos, size_t num_rows) override;
543
544
    uint64_t estimate_buffer_size() override;
545
546
    Status finish() override;
547
    Status write_data() override;
548
    Status write_ordinal_index() override;
549
    Status write_inverted_index() override;
550
0
    Status append_nulls(size_t num_rows) override {
551
0
        return Status::NotSupported("map writer can not append_nulls");
552
0
    }
553
554
    Status finish_current_page() override;
555
556
114
    Status write_zone_map() override {
557
114
        if (_opts.need_zone_map) {
558
0
            return Status::NotSupported("map not support zone map");
559
0
        }
560
114
        return Status::OK();
561
114
    }
562
563
114
    Status write_bloom_filter_index() override {
564
114
        if (_opts.need_bloom_filter) {
565
0
            return Status::NotSupported("map not support bloom filter index");
566
0
        }
567
114
        return Status::OK();
568
114
    }
569
570
    // according key writer to get next rowid
571
724
    ordinal_t get_next_rowid() const override { return _offsets_writer->get_next_rowid(); }
572
573
601
    uint64_t get_raw_data_bytes() const override {
574
601
        return _get_total_data_pages_bytes(&ColumnWriter::get_raw_data_bytes);
575
601
    }
576
577
601
    uint64_t get_total_uncompressed_data_pages_bytes() const override {
578
601
        return _get_total_data_pages_bytes(&ColumnWriter::get_total_uncompressed_data_pages_bytes);
579
601
    }
580
581
685
    uint64_t get_total_compressed_data_pages_bytes() const override {
582
685
        return _get_total_data_pages_bytes(&ColumnWriter::get_total_compressed_data_pages_bytes);
583
685
    }
584
585
private:
586
    template <typename Func>
587
1.88k
    uint64_t _get_total_data_pages_bytes(Func func) const {
588
1.88k
        uint64_t size = std::invoke(func, _offsets_writer.get());
589
1.88k
        if (is_nullable()) {
590
63
            size += std::invoke(func, _null_writer.get());
591
63
        }
592
3.77k
        for (const auto& writer : _kv_writers) {
593
3.77k
            size += std::invoke(func, writer.get());
594
3.77k
        }
595
1.88k
        return size;
596
1.88k
    }
597
598
private:
599
    std::vector<std::unique_ptr<ColumnWriter>> _kv_writers;
600
    // we need null writer to make sure a row is null or not
601
    std::unique_ptr<NullMapColumnWriter> _null_writer;
602
    std::unique_ptr<OffsetColumnWriter> _offsets_writer;
603
    std::unique_ptr<IndexColumnWriter> _index_builder;
604
    ColumnWriterOptions _opts;
605
    ColumnOffset64::MutablePtr _map_offsets_buffer = ColumnOffset64::create();
606
};
607
608
// Extracted VARIANT subcolumns.
609
class VariantSubcolumnWriter : public ColumnWriter {
610
public:
611
    explicit VariantSubcolumnWriter(const ColumnWriterOptions& opts, TabletColumnPtr column);
612
613
    ~VariantSubcolumnWriter() override;
614
615
    Status init() override;
616
617
    uint64_t estimate_buffer_size() override;
618
619
    Status finish() override;
620
    Status write_data() override;
621
    Status write_ordinal_index() override;
622
623
    Status write_zone_map() override;
624
625
    Status write_inverted_index() override;
626
    Status write_bloom_filter_index() override;
627
2
    ordinal_t get_next_rowid() const override { return _next_rowid; }
628
629
10
    uint64_t get_raw_data_bytes() const override { return _writer->get_raw_data_bytes(); }
630
631
10
    uint64_t get_total_uncompressed_data_pages_bytes() const override {
632
10
        return _writer->get_total_uncompressed_data_pages_bytes();
633
10
    }
634
635
39
    uint64_t get_total_compressed_data_pages_bytes() const override {
636
39
        return _writer->get_total_compressed_data_pages_bytes();
637
39
    }
638
639
0
    Status append_nulls(size_t num_rows) override {
640
0
        return Status::NotSupported("variant writer can not append_nulls");
641
0
    }
642
643
    // Splits off the null map and hands the nested column down to the
644
    // variant writers.
645
    Status append(const IColumn& column, size_t row_pos, size_t num_rows) override;
646
647
0
    Status finish_current_page() override {
648
0
        return Status::NotSupported("variant writer has no data, can not finish_current_page");
649
0
    }
650
651
0
    size_t get_non_null_size() const { return none_null_size; }
652
653
#ifdef BE_TEST
654
    // Force small chunk budgets so tests can exercise multi-chunk flushes cheaply.
655
    static void set_chunk_bytes_for_test(size_t bytes);
656
    static void set_chunk_values_for_test(size_t values);
657
    size_t num_chunks_for_test() const;
658
    size_t max_chunk_bytes_for_test() const;
659
#endif
660
661
    Status finalize();
662
663
private:
664
    Status _append(const IColumn& column, size_t row_pos, size_t num_rows, const uint8_t* null_map);
665
    Status _append_v2(const IColumn& column, size_t row_pos, size_t num_rows,
666
                      std::span<const uint8_t> outer_nulls);
667
    Status _ensure_input_format(const IColumn& column);
668
    Status _initialize_v2_builder();
669
    Status _promote_chunks(DataTypePtr* inferred_type);
670
    Status _seal_chunk();
671
    // Bounded value chunks; each chunk is finalized and flushed independently so the writer never
672
    // holds a segment-sized ColumnString.
673
    struct ValueChunk {
674
        std::unique_ptr<VariantPathBuilder> builder;
675
        size_t row_begin = 0;
676
        size_t rows = 0;
677
    };
678
    static constexpr size_t kChunkBytes = 256ULL << 20;
679
    static constexpr size_t kChunkValues = 4ULL << 20;
680
    bool is_finalized() const;
681
    bool _is_finalized = false;
682
    ordinal_t _next_rowid = 0;
683
    size_t none_null_size = 0;
684
    VariantWriterInputFormat _input_format = VariantWriterInputFormat::UNSET;
685
    std::unique_ptr<VariantPathBuilder> _v2_builder;
686
    std::vector<ValueChunk> _chunks;
687
    size_t _chunk_row_begin = 0;
688
    size_t _chunk_rows = 0;
689
    // Type carried into the next chunk. Inferred promotions also check and update sealed chunks
690
    // so fallback follows the same sequence as one uninterrupted builder.
691
    DataTypePtr _running_type;
692
    size_t _num_rows = 0;
693
    ColumnWriterOptions _opts;
694
    std::unique_ptr<ColumnWriter> _writer;
695
    TabletIndexes _indexes;
696
697
    std::unique_ptr<NestedGroupWriteProvider> _nested_group_provider;
698
    VariantStatistics _statistics;
699
};
700
701
// Root VARIANT values and their physical subcolumns.
702
class VariantColumnWriter : public ColumnWriter {
703
public:
704
    explicit VariantColumnWriter(const ColumnWriterOptions& opts, TabletColumnPtr column);
705
706
558
    ~VariantColumnWriter() override = default;
707
708
    Status init() override;
709
710
    uint64_t estimate_buffer_size() override;
711
712
    Status finish() override;
713
    Status write_data() override;
714
    Status write_ordinal_index() override;
715
716
    Status write_zone_map() override;
717
718
    Status write_inverted_index() override;
719
    Status write_bloom_filter_index() override;
720
1
    ordinal_t get_next_rowid() const override { return _next_rowid; }
721
722
    uint64_t get_raw_data_bytes() const override;
723
724
    uint64_t get_total_uncompressed_data_pages_bytes() const override;
725
726
    uint64_t get_total_compressed_data_pages_bytes() const override;
727
728
0
    Status append_nulls(size_t num_rows) override {
729
0
        return Status::NotSupported("variant writer can not append_nulls");
730
0
    }
731
732
    // Splits off the null map and hands the nested column down to the
733
    // variant writers.
734
    Status append(const IColumn& column, size_t row_pos, size_t num_rows) override;
735
736
0
    Status finish_current_page() override {
737
0
        return Status::NotSupported("variant writer has no data, can not finish_current_page");
738
0
    }
739
740
0
    VariantColumnWriterImpl* impl_for_test() const { return _impl.get(); }
741
742
private:
743
    std::unique_ptr<VariantColumnWriterImpl> _impl;
744
    ordinal_t _next_rowid = 0;
745
};
746
747
} // namespace segment_v2
748
} // namespace doris