Skip to content

Commit e9ccec0

Browse files
authored
fix: Align PartialUpdateMergeFunction with Java: add initRow/meetInsert logic (alibaba#233)
1 parent 12cf1af commit e9ccec0

3 files changed

Lines changed: 157 additions & 19 deletions

File tree

src/paimon/core/mergetree/compact/partial_update_merge_function.cpp

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,18 @@ PartialUpdateMergeFunction::CreateFieldAggregators(
265265
return aggregators;
266266
}
267267

268+
void PartialUpdateMergeFunction::Reset() {
269+
current_key_.reset();
270+
meet_insert_ = false;
271+
not_null_column_filled_ = false;
272+
row_ = std::make_unique<GenericRow>(getters_.size());
273+
last_seq_num_ = 0;
274+
for (auto& [_, agg] : field_aggregators_) {
275+
assert(agg);
276+
agg->Reset();
277+
}
278+
}
279+
268280
Status PartialUpdateMergeFunction::Add(KeyValue&& moved_kv) {
269281
// refresh key object to avoid reference overwritten
270282
KeyValue kv = std::move(moved_kv);
@@ -275,33 +287,50 @@ Status PartialUpdateMergeFunction::Add(KeyValue&& moved_kv) {
275287
// In 0.7- versions, the delete records might be written into data file even when
276288
// ignore-delete configured, so ignoreDelete still needs to be checked
277289
if (ignore_delete_) {
290+
if (!not_null_column_filled_) {
291+
InitRowAndHoldData(std::move(kv.value));
292+
not_null_column_filled_ = true;
293+
}
278294
return Status::OK();
279295
}
296+
297+
last_seq_num_ = kv.sequence_number;
298+
280299
if (field_sequence_enabled_) {
300+
// RetractWithSequenceGroup handles InitRow and AddDataHolder internally
281301
PAIMON_RETURN_NOT_OK(RetractWithSequenceGroup(std::move(kv)));
282302
return Status::OK();
283303
}
284304
if (remove_record_on_delete_) {
285305
if (kv.value_kind == RowKind::Delete()) {
286306
current_delete_row_ = true;
287307
row_ = std::make_unique<GenericRow>(getters_.size());
308+
InitRowAndHoldData(std::move(kv.value));
309+
} else if (!not_null_column_filled_) {
310+
InitRowAndHoldData(std::move(kv.value));
311+
not_null_column_filled_ = true;
288312
}
289313
return Status::OK();
290314
}
291315

292316
return Status::Invalid(
293317
"By default, Partial update can not accept delete records, you can choose one of "
294-
"the following solutions:1. Configure ignore-delete to ignore delete records; 2. "
295-
"Configure partial-update.remove-record-on-delete to remove the whole row when "
296-
"receiving delete records; 3. Configure sequence-group to retract partial "
297-
"columns.");
318+
"the following solutions:\n"
319+
"1. Configure 'ignore-delete' to ignore delete records.\n"
320+
"2. Configure 'partial-update.remove-record-on-delete' to remove the whole row "
321+
"when receiving delete records.\n"
322+
"3. Configure 'sequence-group's to retract partial columns. Also configure "
323+
"'partial-update.remove-record-on-sequence-group' to remove the whole row when "
324+
"receiving deleted records of specified sequence group.");
298325
}
299326
last_seq_num_ = kv.sequence_number;
300327
if (field_comparators_.empty()) {
301328
UpdateNonNullFields(std::move(kv));
302329
} else {
303330
UpdateWithSequenceGroup(std::move(kv));
304331
}
332+
meet_insert_ = true;
333+
not_null_column_filled_ = true;
305334
return Status::OK();
306335
}
307336

@@ -356,6 +385,15 @@ void PartialUpdateMergeFunction::UpdateWithSequenceGroup(KeyValue&& kv) {
356385
}
357386

358387
Status PartialUpdateMergeFunction::RetractWithSequenceGroup(KeyValue&& kv) {
388+
// Initialize row with all field values if this is the first record.
389+
// InitRow only reads field values (string_view etc.) from kv.value without taking ownership.
390+
// kv.value remains alive throughout this method, so the views are safe until AddDataHolder
391+
// at the end transfers ownership to row_.
392+
if (!not_null_column_filled_) {
393+
InitRow(*(kv.value));
394+
not_null_column_filled_ = true;
395+
}
396+
359397
std::set<int32_t> updated_sequence_fields;
360398
for (size_t i = 0; i < getters_.size(); ++i) {
361399
auto cmp_iter = field_comparators_.find(i);
@@ -382,6 +420,7 @@ Status PartialUpdateMergeFunction::RetractWithSequenceGroup(KeyValue&& kv) {
382420
sequence_group_partial_delete_.end()) {
383421
current_delete_row_ = true;
384422
row_ = std::make_unique<GenericRow>(getters_.size());
423+
InitRowAndHoldData(std::move(kv.value));
385424
return Status::OK();
386425
} else {
387426
row_->SetField(field_idx, getters_[field_idx](*(kv.value)));
@@ -413,6 +452,18 @@ Status PartialUpdateMergeFunction::RetractWithSequenceGroup(KeyValue&& kv) {
413452
return Status::OK();
414453
}
415454

455+
void PartialUpdateMergeFunction::InitRow(const InternalRow& value) {
456+
for (size_t i = 0; i < getters_.size(); ++i) {
457+
VariantType field = getters_[i](value);
458+
row_->SetField(i, field);
459+
}
460+
}
461+
462+
void PartialUpdateMergeFunction::InitRowAndHoldData(std::unique_ptr<InternalRow>&& value) {
463+
InitRow(*value);
464+
row_->AddDataHolder(std::move(value));
465+
}
466+
416467
bool PartialUpdateMergeFunction::IsEmptySequenceGroup(
417468
const KeyValue& kv, const std::shared_ptr<FieldsComparator>& comparator) const {
418469
for (const auto& field_idx : comparator->CompareFields()) {

src/paimon/core/mergetree/compact/partial_update_merge_function.h

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -67,21 +67,15 @@ class PartialUpdateMergeFunction : public MergeFunction {
6767
const std::map<std::string, std::vector<std::string>>& value_field_to_seq_group_field,
6868
const std::set<std::string>& seq_group_key_set);
6969

70-
void Reset() override {
71-
current_key_.reset();
72-
row_ = std::make_unique<GenericRow>(getters_.size());
73-
for (auto& [_, agg] : field_aggregators_) {
74-
assert(agg);
75-
agg->Reset();
76-
}
77-
}
70+
void Reset() override;
7871

7972
Status Add(KeyValue&& kv) override;
8073

8174
Result<std::optional<KeyValue>> GetResult() override {
82-
return std::optional<KeyValue>(
83-
KeyValue(current_delete_row_ ? RowKind::Delete() : RowKind::Insert(), last_seq_num_,
84-
KeyValue::UNKNOWN_LEVEL, std::move(current_key_), std::move(row_)));
75+
const RowKind* row_kind =
76+
(current_delete_row_ || !meet_insert_) ? RowKind::Delete() : RowKind::Insert();
77+
return std::optional<KeyValue>(KeyValue(row_kind, last_seq_num_, KeyValue::UNKNOWN_LEVEL,
78+
std::move(current_key_), std::move(row_)));
8579
};
8680

8781
private:
@@ -105,6 +99,13 @@ class PartialUpdateMergeFunction : public MergeFunction {
10599
bool IsEmptySequenceGroup(const KeyValue& kv,
106100
const std::shared_ptr<FieldsComparator>& comparator) const;
107101

102+
/// Initialize row_ with all field values from the given InternalRow.
103+
/// This only reads field values without taking ownership of the source row.
104+
void InitRow(const InternalRow& value);
105+
106+
/// Initialize row_ with all field values and transfer data ownership to row_.
107+
void InitRowAndHoldData(std::unique_ptr<InternalRow>&& value);
108+
108109
void UpdateNonNullFields(KeyValue&& kv);
109110

110111
void UpdateWithSequenceGroup(KeyValue&& kv);
@@ -124,5 +125,13 @@ class PartialUpdateMergeFunction : public MergeFunction {
124125
std::unique_ptr<GenericRow> row_;
125126
int64_t last_seq_num_ = KeyValue::UNKNOWN_SEQUENCE;
126127
bool current_delete_row_ = false;
128+
129+
/// If the first value is retract, and no insert record is received, the row kind should be
130+
/// RowKind::Delete. (Partial update sequence group may not correctly set current_delete_row_
131+
/// if no RowKind::Insert value is received)
132+
bool meet_insert_ = false;
133+
134+
/// Whether the row has been initialized with non-null column values from the first record.
135+
bool not_null_column_filled_ = false;
127136
};
128137
} // namespace paimon

src/paimon/core/mergetree/compact/partial_update_merge_function_test.cpp

Lines changed: 82 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -177,13 +177,11 @@ TEST_F(PartialUpdateMergeFunctionTest, TestSequenceGroupPartialDelete) {
177177
Add(mfunc, RowKind::Delete(), {1, 1, 1, 3, 1, 1, NullType()});
178178
CheckResult(mfunc, {1, NullType(), NullType(), 3, 3, 3, 3});
179179
Add(mfunc, RowKind::Delete(), {1, 1, 1, 3, 1, 1, 4});
180-
CheckResult(mfunc, {NullType(), NullType(), NullType(), NullType(), NullType(), NullType(),
181-
NullType()});
180+
CheckResult(mfunc, {1, 1, 1, 3, 1, 1, 4});
182181
Add(mfunc, {1, 4, 4, 4, 5, 5, 5});
183182
CheckResult(mfunc, {1, 4, 4, 4, 5, 5, 5});
184183
Add(mfunc, RowKind::Delete(), {1, 1, 1, 6, 1, 1, 6});
185-
CheckResult(mfunc, {NullType(), NullType(), NullType(), NullType(), NullType(), NullType(),
186-
NullType()});
184+
CheckResult(mfunc, {1, 1, 1, 6, 1, 1, 6});
187185
}
188186

189187
TEST_F(PartialUpdateMergeFunctionTest, TestMultiSequenceFields) {
@@ -794,4 +792,84 @@ TEST_F(PartialUpdateMergeFunctionTest, TestCreateFieldAggregatorsWithoutDefaultA
794792
// test no agg: f2
795793
ASSERT_TRUE(aggs.find(3) == aggs.end());
796794
}
795+
796+
TEST_F(PartialUpdateMergeFunctionTest, TestSequenceGroupPartialDeleteWithProjection) {
797+
std::map<std::string, std::string> options_map = {
798+
{"fields.f3.sequence-group", "f1,f2"},
799+
{"fields.f6.sequence-group", "f4,f5"},
800+
{"partial-update.remove-record-on-sequence-group", "f3,f6"}};
801+
802+
auto table_fields = CreateDataFields(/*value_arity=*/7);
803+
// Reordered fields: f3, f6, f0, f1, f2, f4, f5
804+
std::vector<DataField> value_fields = {table_fields[3], table_fields[6], table_fields[0],
805+
table_fields[1], table_fields[2], table_fields[4],
806+
table_fields[5]};
807+
std::vector<DataField> expected_completed_value_fields = value_fields;
808+
809+
auto mfunc = CreateMergeFunctionWithProjection(table_fields, value_fields, options_map,
810+
expected_completed_value_fields);
811+
mfunc->Reset();
812+
// Reordered: f3=11, f6=22, f0=100, f1=200, f2=1, f4=12, f5=21
813+
Add(mfunc, {11, 22, 100, 200, 1, 12, 21});
814+
Add(mfunc, RowKind::Delete(), {11, 22, 100, 200, 1, 12, 21});
815+
CheckResult(mfunc, {11, 22, 100, 200, 1, 12, 21});
816+
}
817+
818+
TEST_F(PartialUpdateMergeFunctionTest, TestAdjustProjectionNonProject) {
819+
std::map<std::string, std::string> options_map = {{"fields.f4.sequence-group", "f1,f3"},
820+
{"fields.f5.sequence-group", "f7"}};
821+
auto table_fields = CreateDataFields(/*value_arity=*/8);
822+
// Non-projection: use all fields
823+
std::vector<DataField> value_fields = table_fields;
824+
std::vector<DataField> expected_completed_value_fields = table_fields;
825+
826+
auto mfunc = CreateMergeFunctionWithProjection(table_fields, value_fields, options_map,
827+
expected_completed_value_fields);
828+
mfunc->Reset();
829+
Add(mfunc, {1, 1, 1, 1, 1, 1, 1, 1});
830+
Add(mfunc, {4, 2, 4, 2, 2, 0, NullType(), 3});
831+
CheckResult(mfunc, {4, 2, 4, 2, 2, 1, 1, 1});
832+
}
833+
834+
TEST_F(PartialUpdateMergeFunctionTest, TestDeleteReproduceCorrectSequenceNumber) {
835+
std::map<std::string, std::string> options_map = {
836+
{"partial-update.remove-record-on-delete", "true"}};
837+
auto mfunc = CreateMergeFunction(/*value_arity=*/5, options_map);
838+
mfunc->Reset();
839+
840+
Add(mfunc, {1, 1, 1, 1, 1});
841+
Add(mfunc, RowKind::Delete(), {1, 1, 1, 1, 1});
842+
843+
ASSERT_OK_AND_ASSIGN(auto result, mfunc->GetResult());
844+
ASSERT_TRUE(result.has_value());
845+
ASSERT_EQ(result->sequence_number, 1);
846+
}
847+
848+
TEST_F(PartialUpdateMergeFunctionTest, TestInitRowWithNullableFieldOnDelete) {
849+
std::map<std::string, std::string> options_map = {
850+
{"partial-update.remove-record-on-delete", "true"}};
851+
// f0 and f1 are not nullable, f2 and f3 are nullable
852+
std::vector<DataField> data_fields = {
853+
DataField(0, arrow::field("f0", arrow::int32(), /*nullable=*/false)),
854+
DataField(1, arrow::field("f1", arrow::int32(), /*nullable=*/false)),
855+
DataField(2, arrow::field("f2", arrow::int32(), /*nullable=*/true)),
856+
DataField(3, arrow::field("f3", arrow::int32(), /*nullable=*/true))};
857+
auto value_schema = DataField::ConvertDataFieldsToArrowSchema(data_fields);
858+
ASSERT_OK_AND_ASSIGN(CoreOptions options, CoreOptions::FromMap(options_map));
859+
std::map<std::string, std::vector<std::string>> value_field_to_seq_group_field;
860+
std::set<std::string> seq_group_key_set;
861+
ASSERT_OK(PartialUpdateMergeFunction::ParseSequenceGroupFields(
862+
options, &value_field_to_seq_group_field, &seq_group_key_set));
863+
ASSERT_OK_AND_ASSIGN(auto mfunc, PartialUpdateMergeFunction::Create(
864+
value_schema, /*primary_keys=*/{"f0"}, options,
865+
value_field_to_seq_group_field, seq_group_key_set));
866+
mfunc->Reset();
867+
868+
// insert some data first
869+
Add(mfunc, {1, 3, 5, 7});
870+
// send a DELETE with nullable field as null, triggers initRow
871+
Add(mfunc, RowKind::Delete(), {1, 2, 2, NullType()});
872+
// after delete with removeRecordOnDelete, row is re-initialized via initRow
873+
CheckResult(mfunc, {1, 2, 2, NullType()});
874+
}
797875
} // namespace paimon::test

0 commit comments

Comments
 (0)