Skip to content

Commit 94b2003

Browse files
authored
feat(core): introduce ColumnarRowRef with shared batch context (#120)
1 parent f18b4f1 commit 94b2003

8 files changed

Lines changed: 309 additions & 11 deletions

src/paimon/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ set(PAIMON_COMMON_SRCS
2929
common/data/columnar/columnar_array.cpp
3030
common/data/columnar/columnar_map.cpp
3131
common/data/columnar/columnar_row.cpp
32+
common/data/columnar/columnar_row_ref.cpp
3233
common/data/decimal.cpp
3334
common/data/internal_row.cpp
3435
common/data/record_batch.cpp

src/paimon/common/data/columnar/columnar_array.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,9 @@
2626
#include "arrow/util/checked_cast.h"
2727
#include "arrow/util/decimal.h"
2828
#include "fmt/format.h"
29+
#include "paimon/common/data/columnar/columnar_batch_context.h"
2930
#include "paimon/common/data/columnar/columnar_map.h"
30-
#include "paimon/common/data/columnar/columnar_row.h"
31+
#include "paimon/common/data/columnar/columnar_row_ref.h"
3132
#include "paimon/common/utils/date_time_utils.h"
3233

3334
namespace paimon {
@@ -84,7 +85,8 @@ std::shared_ptr<InternalMap> ColumnarArray::GetMap(int32_t pos) const {
8485
std::shared_ptr<InternalRow> ColumnarArray::GetRow(int32_t pos, int32_t num_fields) const {
8586
auto struct_array = arrow::internal::checked_cast<const arrow::StructArray*>(array_);
8687
assert(struct_array);
87-
return std::make_shared<ColumnarRow>(struct_array->fields(), pool_, offset_ + pos);
88+
auto row_ctx = std::make_shared<ColumnarBatchContext>(nullptr, struct_array->fields(), pool_);
89+
return std::make_shared<ColumnarRowRef>(std::move(row_ctx), offset_ + pos);
8890
}
8991

9092
Result<std::vector<char>> ColumnarArray::ToBooleanArray() const {
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright 2026-present Alibaba Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#pragma once
18+
19+
#include <memory>
20+
#include <vector>
21+
22+
#include "arrow/array/array_base.h"
23+
24+
namespace arrow {
25+
class StructArray;
26+
} // namespace arrow
27+
28+
namespace paimon {
29+
class MemoryPool;
30+
31+
struct ColumnarBatchContext {
32+
ColumnarBatchContext(const std::shared_ptr<arrow::StructArray>& struct_array_in,
33+
const arrow::ArrayVector& array_vec_holder_in,
34+
const std::shared_ptr<MemoryPool>& pool_in)
35+
: struct_array(struct_array_in), pool(pool_in), array_vec_holder(array_vec_holder_in) {
36+
array_ptrs.reserve(array_vec_holder.size());
37+
for (const auto& array : array_vec_holder) {
38+
array_ptrs.push_back(array.get());
39+
}
40+
}
41+
42+
std::shared_ptr<arrow::StructArray> struct_array;
43+
std::shared_ptr<MemoryPool> pool;
44+
arrow::ArrayVector array_vec_holder;
45+
std::vector<const arrow::Array*> array_ptrs;
46+
};
47+
} // namespace paimon
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/*
2+
* Copyright 2026-present Alibaba Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#include "paimon/common/data/columnar/columnar_row_ref.h"
18+
19+
#include <cassert>
20+
21+
#include "arrow/array/array_decimal.h"
22+
#include "arrow/array/array_nested.h"
23+
#include "arrow/array/array_primitive.h"
24+
#include "arrow/type_traits.h"
25+
#include "arrow/util/checked_cast.h"
26+
#include "arrow/util/decimal.h"
27+
#include "paimon/common/data/columnar/columnar_array.h"
28+
#include "paimon/common/data/columnar/columnar_map.h"
29+
#include "paimon/common/utils/date_time_utils.h"
30+
31+
namespace paimon {
32+
Decimal ColumnarRowRef::GetDecimal(int32_t pos, int32_t precision, int32_t scale) const {
33+
using ArrayType = typename arrow::TypeTraits<arrow::Decimal128Type>::ArrayType;
34+
auto array = arrow::internal::checked_cast<const ArrayType*>(ctx_->array_ptrs[pos]);
35+
assert(array);
36+
arrow::Decimal128 decimal(array->GetValue(row_id_));
37+
return Decimal(precision, scale,
38+
static_cast<Decimal::int128_t>(decimal.high_bits()) << 64 | decimal.low_bits());
39+
}
40+
41+
Timestamp ColumnarRowRef::GetTimestamp(int32_t pos, int32_t precision) const {
42+
using ArrayType = typename arrow::TypeTraits<arrow::TimestampType>::ArrayType;
43+
auto array = arrow::internal::checked_cast<const ArrayType*>(ctx_->array_ptrs[pos]);
44+
assert(array);
45+
int64_t data = array->Value(row_id_);
46+
auto timestamp_type =
47+
arrow::internal::checked_pointer_cast<arrow::TimestampType>(array->type());
48+
// for orc format, data is saved as nano, therefore, Timestamp convert should consider precision
49+
// in arrow array rather than input precision
50+
DateTimeUtils::TimeType time_type = DateTimeUtils::GetTimeTypeFromArrowType(timestamp_type);
51+
auto [milli, nano] = DateTimeUtils::TimestampConverter(
52+
data, time_type, DateTimeUtils::TimeType::MILLISECOND, DateTimeUtils::TimeType::NANOSECOND);
53+
return Timestamp(milli, nano);
54+
}
55+
56+
std::shared_ptr<InternalRow> ColumnarRowRef::GetRow(int32_t pos, int32_t num_fields) const {
57+
auto struct_array =
58+
arrow::internal::checked_cast<const arrow::StructArray*>(ctx_->array_ptrs[pos]);
59+
assert(struct_array);
60+
auto nested_ctx =
61+
std::make_shared<ColumnarBatchContext>(nullptr, struct_array->fields(), ctx_->pool);
62+
return std::make_shared<ColumnarRowRef>(std::move(nested_ctx), row_id_);
63+
}
64+
65+
std::shared_ptr<InternalArray> ColumnarRowRef::GetArray(int32_t pos) const {
66+
auto list_array = arrow::internal::checked_cast<const arrow::ListArray*>(ctx_->array_ptrs[pos]);
67+
assert(list_array);
68+
int32_t offset = list_array->value_offset(row_id_);
69+
int32_t length = list_array->value_length(row_id_);
70+
return std::make_shared<ColumnarArray>(list_array->values(), ctx_->pool, offset, length);
71+
}
72+
73+
std::shared_ptr<InternalMap> ColumnarRowRef::GetMap(int32_t pos) const {
74+
auto map_array = arrow::internal::checked_cast<const arrow::MapArray*>(ctx_->array_ptrs[pos]);
75+
assert(map_array);
76+
int32_t offset = map_array->value_offset(row_id_);
77+
int32_t length = map_array->value_length(row_id_);
78+
return std::make_shared<ColumnarMap>(map_array->keys(), map_array->items(), ctx_->pool, offset,
79+
length);
80+
}
81+
82+
} // namespace paimon
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
/*
2+
* Copyright 2026-present Alibaba Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#pragma once
18+
19+
#include <cstdint>
20+
#include <memory>
21+
#include <string>
22+
#include <string_view>
23+
24+
#include "fmt/format.h"
25+
#include "paimon/common/data/binary_string.h"
26+
#include "paimon/common/data/columnar/columnar_batch_context.h"
27+
#include "paimon/common/data/columnar/columnar_utils.h"
28+
#include "paimon/common/data/internal_array.h"
29+
#include "paimon/common/data/internal_map.h"
30+
#include "paimon/common/data/internal_row.h"
31+
#include "paimon/common/types/row_kind.h"
32+
#include "paimon/data/decimal.h"
33+
#include "paimon/data/timestamp.h"
34+
#include "paimon/result.h"
35+
36+
namespace paimon {
37+
class Bytes;
38+
39+
/// Columnar row view which shares batch-level context to reduce per-row overhead.
40+
class ColumnarRowRef : public InternalRow {
41+
public:
42+
ColumnarRowRef(std::shared_ptr<ColumnarBatchContext> ctx, int64_t row_id)
43+
: ctx_(std::move(ctx)), row_id_(row_id) {}
44+
45+
Result<const RowKind*> GetRowKind() const override {
46+
return row_kind_;
47+
}
48+
49+
void SetRowKind(const RowKind* kind) override {
50+
row_kind_ = kind;
51+
}
52+
53+
int32_t GetFieldCount() const override {
54+
return static_cast<int32_t>(ctx_->array_ptrs.size());
55+
}
56+
57+
bool IsNullAt(int32_t pos) const override {
58+
return ctx_->array_ptrs[pos]->IsNull(row_id_);
59+
}
60+
61+
bool GetBoolean(int32_t pos) const override {
62+
return ColumnarUtils::GetGenericValue<arrow::BooleanType, bool>(ctx_->array_ptrs[pos],
63+
row_id_);
64+
}
65+
66+
char GetByte(int32_t pos) const override {
67+
return ColumnarUtils::GetGenericValue<arrow::Int8Type, char>(ctx_->array_ptrs[pos],
68+
row_id_);
69+
}
70+
71+
int16_t GetShort(int32_t pos) const override {
72+
return ColumnarUtils::GetGenericValue<arrow::Int16Type, int16_t>(ctx_->array_ptrs[pos],
73+
row_id_);
74+
}
75+
76+
int32_t GetInt(int32_t pos) const override {
77+
return ColumnarUtils::GetGenericValue<arrow::Int32Type, int32_t>(ctx_->array_ptrs[pos],
78+
row_id_);
79+
}
80+
81+
int32_t GetDate(int32_t pos) const override {
82+
return ColumnarUtils::GetGenericValue<arrow::Date32Type, int32_t>(ctx_->array_ptrs[pos],
83+
row_id_);
84+
}
85+
86+
int64_t GetLong(int32_t pos) const override {
87+
return ColumnarUtils::GetGenericValue<arrow::Int64Type, int64_t>(ctx_->array_ptrs[pos],
88+
row_id_);
89+
}
90+
91+
float GetFloat(int32_t pos) const override {
92+
return ColumnarUtils::GetGenericValue<arrow::FloatType, float>(ctx_->array_ptrs[pos],
93+
row_id_);
94+
}
95+
96+
double GetDouble(int32_t pos) const override {
97+
return ColumnarUtils::GetGenericValue<arrow::DoubleType, double>(ctx_->array_ptrs[pos],
98+
row_id_);
99+
}
100+
101+
BinaryString GetString(int32_t pos) const override {
102+
auto bytes = ColumnarUtils::GetBytes<arrow::StringType>(ctx_->array_ptrs[pos], row_id_,
103+
ctx_->pool.get());
104+
return BinaryString::FromBytes(bytes);
105+
}
106+
107+
std::string_view GetStringView(int32_t pos) const override {
108+
return ColumnarUtils::GetView(ctx_->array_ptrs[pos], row_id_);
109+
}
110+
111+
Decimal GetDecimal(int32_t pos, int32_t precision, int32_t scale) const override;
112+
113+
Timestamp GetTimestamp(int32_t pos, int32_t precision) const override;
114+
115+
std::shared_ptr<Bytes> GetBinary(int32_t pos) const override {
116+
return ColumnarUtils::GetBytes<arrow::BinaryType>(ctx_->array_ptrs[pos], row_id_,
117+
ctx_->pool.get());
118+
}
119+
120+
std::shared_ptr<InternalRow> GetRow(int32_t pos, int32_t num_fields) const override;
121+
122+
std::shared_ptr<InternalArray> GetArray(int32_t pos) const override;
123+
124+
std::shared_ptr<InternalMap> GetMap(int32_t pos) const override;
125+
126+
std::string ToString() const override {
127+
return fmt::format("ColumnarRowRef, row_id {}", row_id_);
128+
}
129+
130+
private:
131+
std::shared_ptr<ColumnarBatchContext> ctx_;
132+
const RowKind* row_kind_ = RowKind::Insert();
133+
int64_t row_id_;
134+
};
135+
} // namespace paimon

src/paimon/common/data/columnar/columnar_row_test.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include "arrow/array/array_nested.h"
2525
#include "arrow/ipc/json_simple.h"
2626
#include "gtest/gtest.h"
27+
#include "paimon/common/data/columnar/columnar_row_ref.h"
2728
#include "paimon/common/utils/date_time_utils.h"
2829
#include "paimon/memory/bytes.h"
2930
#include "paimon/memory/memory_pool.h"
@@ -70,6 +71,32 @@ TEST(ColumnarRowTest, TestSimple) {
7071
ASSERT_EQ(std::string(row.GetStringView(7)), "Hello");
7172
}
7273

74+
TEST(ColumnarRowRefTest, TestSimple) {
75+
auto pool = GetDefaultPool();
76+
std::shared_ptr<arrow::DataType> target_type =
77+
arrow::struct_({arrow::field("f1", arrow::int32()), arrow::field("f2", arrow::utf8())});
78+
auto f1 =
79+
arrow::ipc::internal::json::ArrayFromJSON(arrow::int32(), R"([1, 2, 3])").ValueOrDie();
80+
auto f2 =
81+
arrow::ipc::internal::json::ArrayFromJSON(arrow::utf8(), R"(["alpha", "beta", "gamma"])")
82+
.ValueOrDie();
83+
auto data = arrow::StructArray::Make({f1, f2}, target_type->fields()).ValueOrDie();
84+
85+
auto ctx = std::make_shared<ColumnarBatchContext>(data, data->fields(), pool);
86+
ColumnarRowRef row(ctx, 1);
87+
ASSERT_EQ(row.GetFieldCount(), 2);
88+
ASSERT_EQ(row.GetInt(0), 2);
89+
ASSERT_EQ(std::string(row.GetStringView(1)), "beta");
90+
91+
auto row_kind = row.GetRowKind();
92+
ASSERT_TRUE(row_kind.ok());
93+
ASSERT_EQ(row_kind.value(), RowKind::Insert());
94+
row.SetRowKind(RowKind::Delete());
95+
auto updated_kind = row.GetRowKind();
96+
ASSERT_TRUE(updated_kind.ok());
97+
ASSERT_EQ(updated_kind.value(), RowKind::Delete());
98+
}
99+
73100
TEST(ColumnarRowTest, TestComplexAndNestedType) {
74101
auto pool = GetDefaultPool();
75102
std::shared_ptr<arrow::DataType> target_type = arrow::struct_({

src/paimon/core/io/key_value_data_file_record_reader.cpp

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,7 @@
2828
#include "arrow/type.h"
2929
#include "arrow/util/checked_cast.h"
3030
#include "fmt/format.h"
31-
#include "paimon/common/data/columnar/columnar_row.h"
32-
#include "paimon/common/data/internal_row.h"
31+
#include "paimon/common/data/columnar/columnar_row_ref.h"
3332
#include "paimon/common/table/special_fields.h"
3433
#include "paimon/common/types/row_kind.h"
3534
#include "paimon/common/utils/arrow/status_utils.h"
@@ -69,12 +68,11 @@ bool KeyValueDataFileRecordReader::Iterator::HasNext() const {
6968

7069
Result<KeyValue> KeyValueDataFileRecordReader::Iterator::Next() {
7170
assert(HasNext());
72-
// as key is only used in merge sort, do not hold the data in ColumnarRow
73-
auto key = std::make_unique<ColumnarRow>(reader_->key_fields_, reader_->pool_, cursor_);
74-
// as value is used in merge sort and projection (maybe async and multi-thread), hold the data
75-
// in ColumnarRow
76-
auto value = std::make_unique<ColumnarRow>(reader_->value_struct_array_, reader_->value_fields_,
77-
reader_->pool_, cursor_);
71+
// key is only used in merge sort; key context does not hold parent struct array
72+
auto key = std::make_unique<ColumnarRowRef>(reader_->key_ctx_, cursor_);
73+
// value is used in merge sort and projection (maybe async and multi-thread), so value context
74+
// holds parent struct array to ensure data remains valid
75+
auto value = std::make_unique<ColumnarRowRef>(reader_->value_ctx_, cursor_);
7876
PAIMON_ASSIGN_OR_RAISE(const RowKind* row_kind,
7977
RowKind::FromByteValue(reader_->row_kind_array_->Value(cursor_)));
8078
int64_t sequence_number = reader_->sequence_number_array_->Value(cursor_);
@@ -137,12 +135,16 @@ Result<std::unique_ptr<KeyValueRecordReader::Iterator>> KeyValueDataFileRecordRe
137135
arrow::StructArray::Make(value_fields_, value_names_));
138136
selection_bitmap_ = std::move(bitmap);
139137
value_fields_ = value_struct_array_->fields();
138+
key_ctx_ = std::make_shared<ColumnarBatchContext>(nullptr, key_fields_, pool_);
139+
value_ctx_ = std::make_shared<ColumnarBatchContext>(value_struct_array_, value_fields_, pool_);
140140
TraverseArray(value_struct_array_);
141141
return std::make_unique<KeyValueDataFileRecordReader::Iterator>(this);
142142
}
143143

144144
void KeyValueDataFileRecordReader::Reset() {
145145
selection_bitmap_ = RoaringBitmap32();
146+
key_ctx_.reset();
147+
value_ctx_.reset();
146148
key_fields_.clear();
147149
value_fields_.clear();
148150
value_struct_array_.reset();

0 commit comments

Comments
 (0)