| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| #pragma once |
| |
| #include <deque> |
| #include <memory> |
| #include <vector> |
| |
| #include "arrow/type.h" |
| #include "paimon/memory/memory_pool.h" |
| #include "paimon/reader/batch_reader.h" |
| #include "paimon/reader/file_batch_reader.h" |
| #include "paimon/result.h" |
| #include "paimon/status.h" |
| #include "paimon/utils/range.h" |
| |
| namespace arrow { |
| class Array; |
| class StructArray; |
| } // namespace arrow |
| |
| namespace paimon { |
| |
| /// Merges the blob files of one data-evolution blob bunch that span multiple max sequence |
| /// number layers, resolving placeholder entries row by row. Aligned with Java's |
| /// BlobFallbackRecordReader / AllPlaceholdersRecordReader. |
| /// |
| /// A data-evolution partial update rewrites only the touched rows of a blob column; the new blob |
| /// file records every untouched row as a placeholder entry. Reading therefore needs, per row, the |
| /// value from the newest layer that holds a real (non-placeholder) entry: |
| /// |
| /// 1. The caller groups the blob files by max sequence number (one group per layer, newest |
| /// first) and, inside each group, orders them by first row id. Row id ranges the group's |
| /// files do not cover are represented by gap segments, which stand for all-placeholder rows. |
| /// 2. All groups span the same overall row id range, so with the same row-ranges selection |
| /// applied they yield the same number of rows and can be stepped in lockstep. |
| /// 3. Each output row takes the first group, in max-sequence order, whose row is not a |
| /// placeholder. Placeholder rows are identified by exact equality with the |
| /// BlobDefs::kPlaceholderSentinel bytes, emitted by the blob format reader when |
| /// BlobDefs::kEmitPlaceholderSentinelKey is set. |
| /// 4. A row that is a placeholder in every group degrades to a null blob: it keeps its |
| /// _ROW_ID, reports -1 as its _SEQUENCE_NUMBER, and returns null for every other field. |
| class BlobFallbackBatchReader : public BatchReader { |
| public: |
| /// One piece of a sequence group: either a reader over a single blob file (already wrapped |
| /// with the usual per-file mapping readers and row selection), or a virtual gap standing for |
| /// row ids the group's files do not cover. Segments must be ordered by ascending row id. |
| struct Segment { |
| /// File segment: emits the rows of one blob file. Null for a gap segment. |
| std::unique_ptr<FileBatchReader> reader; |
| /// Gap segment only: the selected row ids the gap emits (all placeholders), as sorted |
| /// disjoint ranges. Must not be empty for a gap segment. |
| std::vector<Range> gap_selected_ranges; |
| }; |
| |
| /// `sequence_groups` must be ordered by descending max sequence number and contain at least |
| /// two groups (a single group needs no fallback). `read_schema` is the schema every file |
| /// reader emits; it must contain exactly one blob field and may additionally contain the |
| /// row-tracking fields _ROW_ID and _SEQUENCE_NUMBER (completed per file by |
| /// CompleteRowTrackingFieldsBatchReader), which stay correct for rows that are a |
| /// placeholder in every layer. |
| static Result<std::unique_ptr<BlobFallbackBatchReader>> Create( |
| std::vector<std::vector<Segment>>&& sequence_groups, |
| const std::shared_ptr<arrow::Schema>& read_schema, int32_t read_batch_size, |
| const std::shared_ptr<MemoryPool>& pool); |
| |
| Result<ReadBatch> NextBatch() override; |
| |
| Result<ReadBatchWithBitmap> NextBatchWithBitmap() override; |
| |
| void Close() override; |
| |
| std::shared_ptr<Metrics> GetReaderMetrics() const override; |
| |
| private: |
| /// A run of consecutive rows already fetched from one group: `array` rows |
| /// [offset, offset + length) for a file segment, or `length` placeholder rows for a gap |
| /// segment (array is null). |
| struct Chunk { |
| std::shared_ptr<arrow::StructArray> array; |
| int64_t offset = 0; |
| int64_t length = 0; |
| /// Gap chunk only: the row id of each of the `length` rows, filled when the read |
| /// schema contains _ROW_ID so all-placeholder rows can keep their row id. |
| std::vector<int64_t> gap_row_ids; |
| }; |
| |
| /// Read progress of one sequence group. Move-only, matching Segment's unique_ptr member. |
| struct GroupCursor { |
| GroupCursor() = default; |
| GroupCursor(const GroupCursor&) = delete; |
| GroupCursor& operator=(const GroupCursor&) = delete; |
| GroupCursor(GroupCursor&&) = default; |
| GroupCursor& operator=(GroupCursor&&) = default; |
| |
| std::vector<Segment> segments; |
| size_t segment_idx = 0; |
| /// Position inside the current gap segment: index into gap_selected_ranges and the |
| /// number of rows already emitted from that range. |
| size_t gap_range_idx = 0; |
| int64_t gap_range_pos = 0; |
| /// Rows fetched from the current file segment but not yet consumed. |
| std::deque<std::shared_ptr<arrow::StructArray>> pending; |
| int64_t pending_pos = 0; |
| }; |
| |
| BlobFallbackBatchReader(std::vector<GroupCursor>&& groups, |
| const std::shared_ptr<arrow::Schema>& read_schema, |
| int32_t blob_field_idx, int32_t row_id_field_idx, |
| int32_t seq_num_field_idx, int32_t read_batch_size, |
| const std::shared_ptr<MemoryPool>& pool); |
| |
| /// Collects up to `want` rows from the group into chunks. Only the first group may come up |
| /// short (which defines the window size); any later group ending early is a misalignment. |
| Result<int64_t> FillWindow(size_t group_idx, int64_t want, std::vector<Chunk>* chunks); |
| |
| /// Flags each of the `row_count` window rows of the given chunks as placeholder or not. |
| Result<std::vector<bool>> ComputePlaceholderFlags(const std::vector<Chunk>& chunks, |
| int64_t row_count) const; |
| |
| /// Assembles one output column by stitching, per run of rows choosing the same group, |
| /// slices of that group's chunks. For rows choosing no group (placeholder in every layer), |
| /// _ROW_ID is kept, _SEQUENCE_NUMBER becomes -1, and every other field becomes null. |
| Result<std::shared_ptr<arrow::Array>> AssembleColumn( |
| int32_t field_idx, const std::vector<int32_t>& group_choice, |
| const std::vector<std::vector<Chunk>>& group_chunks) const; |
| |
| /// Assembles the _ROW_ID values of rows [run_start, run_end) from the given group's chunks; |
| /// gap chunks contribute their synthesized row ids. |
| Result<std::shared_ptr<arrow::Array>> AssembleRowIdRun(const std::vector<Chunk>& chunks, |
| int64_t run_start, |
| int64_t run_end) const; |
| |
| std::vector<GroupCursor> groups_; |
| std::shared_ptr<arrow::Schema> read_schema_; |
| const int32_t blob_field_idx_; |
| /// Index of _ROW_ID / _SEQUENCE_NUMBER in the read schema, -1 when not read. |
| const int32_t row_id_field_idx_; |
| const int32_t seq_num_field_idx_; |
| const int32_t read_batch_size_; |
| std::shared_ptr<arrow::MemoryPool> arrow_pool_; |
| bool closed_ = false; |
| }; |
| |
| } // namespace paimon |