Skip to content

Commit 90ae61d

Browse files
author
Xinli Shang
committed
address review: table-wide unpartitioned replace + ValidateDeletedDataFiles
- AddFile() now uses DeleteByRowFilter(AlwaysTrue()) for unpartitioned specs instead of DropPartition with empty partition values, matching Java BaseReplacePartitions which treats unpartitioned tables as a table-wide replace. - Validate() now also calls ValidateDeletedDataFiles when ValidateNoConflictingDeletes is enabled, mirroring Java where validateNewDeletes gates both checks. This rejects concurrent overwrite/delete commits in the replaced partitions. - New replace_by_row_filter_ flag drives the AlwaysTrue path in Validate() for the unpartitioned case.
1 parent 12dc230 commit 90ae61d

2 files changed

Lines changed: 49 additions & 19 deletions

File tree

src/iceberg/update/replace_partitions.cc

Lines changed: 41 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,17 @@ ReplacePartitions& ReplacePartitions::AddFile(const std::shared_ptr<DataFile>& f
5353
ICEBERG_BUILDER_ASSIGN_OR_RETURN(auto spec, base().PartitionSpecById(spec_id));
5454

5555
ICEBERG_BUILDER_RETURN_IF_ERROR(AddDataFile(file));
56-
// DropPartition(spec_id, partition) registers the (spec_id, partition_values)
57-
// tuple with both data and delete filter managers. For an unpartitioned spec
58-
// the partition values are empty and naturally match every file under that
59-
// spec — no separate AlwaysTrue path is needed, and validation stays scoped
60-
// to the spec rather than the whole table.
61-
ICEBERG_BUILDER_RETURN_IF_ERROR(DropPartition(spec_id, file->partition));
62-
replaced_partitions_.add(spec_id, file->partition);
56+
if (spec->fields().empty()) {
57+
// Unpartitioned spec: Java's BaseReplacePartitions treats this as a
58+
// table-wide replace rather than a spec-scoped DropPartition with empty
59+
// partition values. Mirror that so every existing data file is dropped
60+
// and conflict validation runs against AlwaysTrue.
61+
ICEBERG_BUILDER_RETURN_IF_ERROR(DeleteByRowFilter(Expressions::AlwaysTrue()));
62+
replace_by_row_filter_ = true;
63+
} else {
64+
ICEBERG_BUILDER_RETURN_IF_ERROR(DropPartition(spec_id, file->partition));
65+
replaced_partitions_.add(spec_id, file->partition);
66+
}
6367
return *this;
6468
}
6569

@@ -90,21 +94,43 @@ Status ReplacePartitions::Validate(const TableMetadata& current_metadata,
9094
if (snapshot == nullptr) {
9195
return {};
9296
}
93-
// No-op update: no partitions were staged, so there is nothing to conflict
94-
// with. Calling the validators with AlwaysTrue here would turn an empty
95-
// builder into a full-table conflict check.
96-
if (replaced_partitions_.empty()) {
97+
// No-op update: no partitions were staged and no table-wide replace was
98+
// requested, so there is nothing to conflict with. Calling the validators
99+
// with AlwaysTrue here would turn an empty builder into a full-table check.
100+
if (!replace_by_row_filter_ && replaced_partitions_.empty()) {
97101
return {};
98102
}
99103

100104
auto io = ctx_->table->io();
101105
if (validate_conflicting_data_) {
102-
ICEBERG_RETURN_UNEXPECTED(ValidateAddedDataFiles(
103-
current_metadata, starting_snapshot_id_, replaced_partitions_, snapshot, io));
106+
if (replace_by_row_filter_) {
107+
ICEBERG_RETURN_UNEXPECTED(ValidateAddedDataFiles(
108+
current_metadata, starting_snapshot_id_, Expressions::AlwaysTrue(), snapshot,
109+
io, IsCaseSensitive()));
110+
} else {
111+
ICEBERG_RETURN_UNEXPECTED(ValidateAddedDataFiles(
112+
current_metadata, starting_snapshot_id_, replaced_partitions_, snapshot, io));
113+
}
104114
}
105115
if (validate_conflicting_deletes_) {
106-
ICEBERG_RETURN_UNEXPECTED(ValidateNoNewDeleteFiles(
107-
current_metadata, starting_snapshot_id_, replaced_partitions_, snapshot, io));
116+
// Java's BaseReplacePartitions.validate gates both ValidateNoNewDeleteFiles
117+
// and ValidateDeletedDataFiles on the same validateNewDeletes flag. The
118+
// second check rejects concurrent overwrite/delete commits in the replaced
119+
// partitions; without it a concurrent delete in a replaced partition would
120+
// commit silently.
121+
if (replace_by_row_filter_) {
122+
ICEBERG_RETURN_UNEXPECTED(ValidateNoNewDeleteFiles(
123+
current_metadata, starting_snapshot_id_, Expressions::AlwaysTrue(), snapshot,
124+
io, IsCaseSensitive()));
125+
ICEBERG_RETURN_UNEXPECTED(ValidateDeletedDataFiles(
126+
current_metadata, starting_snapshot_id_, Expressions::AlwaysTrue(), snapshot,
127+
io, IsCaseSensitive()));
128+
} else {
129+
ICEBERG_RETURN_UNEXPECTED(ValidateNoNewDeleteFiles(
130+
current_metadata, starting_snapshot_id_, replaced_partitions_, snapshot, io));
131+
ICEBERG_RETURN_UNEXPECTED(ValidateDeletedDataFiles(
132+
current_metadata, starting_snapshot_id_, replaced_partitions_, snapshot, io));
133+
}
108134
}
109135
return {};
110136
}

src/iceberg/update/replace_partitions.h

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -116,10 +116,14 @@ class ICEBERG_EXPORT ReplacePartitions : public MergingSnapshotUpdate {
116116
std::optional<int64_t> starting_snapshot_id_;
117117
bool validate_conflicting_data_{false};
118118
bool validate_conflicting_deletes_{false};
119-
// Partitions touched by AddFile(); used to scope conflict validation to the
120-
// overwritten partitions instead of the whole table. For unpartitioned specs
121-
// the partition values are empty, and DropPartition(spec_id, {}) already
122-
// matches every file in that spec — no separate "whole table" path is needed.
119+
// True once an AddFile() call has staged a file whose partition spec is
120+
// unpartitioned. Java's BaseReplacePartitions treats this case as a
121+
// table-wide replace (DeleteByRowFilter(AlwaysTrue())) and runs conflict
122+
// validation against AlwaysTrue rather than a partition set — mirror that.
123+
bool replace_by_row_filter_{false};
124+
// Partitions touched by AddFile() in partitioned specs. Used to scope
125+
// conflict validation to the overwritten partitions in the partitioned
126+
// case; ignored when replace_by_row_filter_ is true.
123127
PartitionSet replaced_partitions_;
124128
};
125129

0 commit comments

Comments
 (0)