|
19 | 19 |
|
20 | 20 | #include "iceberg/update/replace_partitions.h" |
21 | 21 |
|
| 22 | +#include <algorithm> |
| 23 | + |
22 | 24 | #include "iceberg/expression/expressions.h" |
23 | 25 | #include "iceberg/partition_spec.h" |
24 | 26 | #include "iceberg/snapshot.h" |
25 | 27 | #include "iceberg/table.h" // IWYU pragma: keep |
26 | 28 | #include "iceberg/table_metadata.h" |
27 | 29 | #include "iceberg/transaction.h" |
| 30 | +#include "iceberg/transform.h" |
28 | 31 | #include "iceberg/util/error_collector.h" |
29 | 32 | #include "iceberg/util/macros.h" |
30 | 33 |
|
@@ -53,7 +56,17 @@ ReplacePartitions& ReplacePartitions::AddFile(const std::shared_ptr<DataFile>& f |
53 | 56 | ICEBERG_BUILDER_ASSIGN_OR_RETURN(auto spec, base().PartitionSpecById(spec_id)); |
54 | 57 |
|
55 | 58 | ICEBERG_BUILDER_RETURN_IF_ERROR(AddDataFile(file)); |
56 | | - if (spec->fields().empty()) { |
| 59 | + // A spec is effectively unpartitioned if it has no fields, or every field |
| 60 | + // uses the void transform. Java's PartitionSpec.isUnpartitioned() covers |
| 61 | + // both cases; mirror that so an all-void spec triggers the table-wide |
| 62 | + // replace path instead of a DropPartition with void-valued partition keys. |
| 63 | + auto is_unpartitioned = [](const PartitionSpec& s) { |
| 64 | + return s.fields().empty() || |
| 65 | + std::all_of(s.fields().begin(), s.fields().end(), [](const PartitionField& f) { |
| 66 | + return f.transform()->transform_type() == TransformType::kVoid; |
| 67 | + }); |
| 68 | + }; |
| 69 | + if (is_unpartitioned(*spec)) { |
57 | 70 | // Unpartitioned spec: Java's BaseReplacePartitions treats this as a |
58 | 71 | // table-wide replace rather than a spec-scoped DropPartition with empty |
59 | 72 | // partition values. Mirror that so every existing data file is dropped |
@@ -91,12 +104,12 @@ std::string ReplacePartitions::operation() { return DataOperation::kOverwrite; } |
91 | 104 |
|
92 | 105 | Status ReplacePartitions::Validate(const TableMetadata& current_metadata, |
93 | 106 | const std::shared_ptr<Snapshot>& snapshot) { |
94 | | - // Match Java BaseReplacePartitions: require at least one staged data file. |
95 | | - // Use the flags AddFile() sets — `DataSpec()` would also error on multi-spec |
96 | | - // stages and is not the guard we want here. |
97 | | - if (!replace_by_row_filter_ && replaced_partitions_.empty()) { |
98 | | - return InvalidArgument( |
99 | | - "ReplacePartitions requires at least one data file; call AddFile() first"); |
| 107 | + // Match Java BaseReplacePartitions.validate: require at least one staged data |
| 108 | + // file and that all staged files share exactly one partition spec. |
| 109 | + // DataSpec() enforces both invariants; ignore the returned spec here since |
| 110 | + // replace_by_row_filter_ / replaced_partitions_ already scope validation. |
| 111 | + if (!replace_by_row_filter_) { |
| 112 | + ICEBERG_RETURN_UNEXPECTED(DataSpec()); |
100 | 113 | } |
101 | 114 |
|
102 | 115 | if (snapshot == nullptr) { |
|
0 commit comments