2020#include " iceberg/catalog/rest/types.h"
2121
2222#include < algorithm>
23+ #include < optional>
2324
25+ #include " iceberg/expression/expression.h"
26+ #include " iceberg/manifest/manifest_entry.h"
2427#include " iceberg/partition_spec.h"
2528#include " iceberg/schema.h"
2629#include " iceberg/sort_order.h"
2730#include " iceberg/table_metadata.h"
2831#include " iceberg/table_requirement.h"
32+ #include " iceberg/table_scan.h"
2933#include " iceberg/table_update.h"
3034
3135namespace iceberg ::rest {
3236
37+ std::string_view ToString (PlanStatus status) {
38+ switch (status) {
39+ case PlanStatus::kSubmitted :
40+ return " submitted" ;
41+ case PlanStatus::kCompleted :
42+ return " completed" ;
43+ case PlanStatus::kCancelled :
44+ return " cancelled" ;
45+ case PlanStatus::kFailed :
46+ return " failed" ;
47+ }
48+ return " unknown" ;
49+ }
50+
51+ Result<PlanStatus> PlanStatusFromString (std::string_view status_str) {
52+ if (status_str == " submitted" ) return PlanStatus::kSubmitted ;
53+ if (status_str == " completed" ) return PlanStatus::kCompleted ;
54+ if (status_str == " cancelled" ) return PlanStatus::kCancelled ;
55+ if (status_str == " failed" ) return PlanStatus::kFailed ;
56+ return JsonParseError (" Unknown plan status: {}" , status_str);
57+ }
58+
3359bool CreateTableRequest::operator ==(const CreateTableRequest& other) const {
3460 if (name != other.name || location != other.location ||
3561 stage_create != other.stage_create || properties != other.properties ) {
@@ -118,9 +144,22 @@ bool CommitTableResponse::operator==(const CommitTableResponse& other) const {
118144 return true ;
119145}
120146
147+ namespace {
148+
149+ bool ExpressionPtrEqual (const std::shared_ptr<Expression>& lhs,
150+ const std::shared_ptr<Expression>& rhs) {
151+ if (lhs == rhs) {
152+ return true ;
153+ }
154+ return lhs && rhs && lhs->Equals (*rhs);
155+ }
156+
157+ } // namespace
158+
121159bool PlanTableScanRequest::operator ==(const PlanTableScanRequest& other) const {
122160 return snapshot_id == other.snapshot_id && select == other.select &&
123- filter == other.filter && case_sensitive == other.case_sensitive &&
161+ ExpressionPtrEqual (filter, other.filter ) &&
162+ case_sensitive == other.case_sensitive &&
124163 use_snapshot_schema == other.use_snapshot_schema &&
125164 start_snapshot_id == other.start_snapshot_id &&
126165 end_snapshot_id == other.end_snapshot_id && stats_fields == other.stats_fields &&
@@ -129,86 +168,81 @@ bool PlanTableScanRequest::operator==(const PlanTableScanRequest& other) const {
129168
130169namespace {
131170
132- bool ScanTaskFieldsEqual (
133- const std::vector<std::string>& plan_tasks_a,
134- const std::vector<std::string>& plan_tasks_b,
135- const std::vector<std::shared_ptr<DataFile>>& delete_files_a,
136- const std::vector<std::shared_ptr<DataFile>>& delete_files_b,
137- const std::vector<std::shared_ptr<FileScanTask>>& file_scan_tasks_a,
138- const std::vector<std::shared_ptr<FileScanTask>>& file_scan_tasks_b) {
139- if (plan_tasks_a != plan_tasks_b) {
140- return false ;
171+ template <typename T>
172+ bool SharedPtrEqual (const std::shared_ptr<T>& lhs, const std::shared_ptr<T>& rhs) {
173+ if (lhs == rhs) {
174+ return true ;
141175 }
142- if (delete_files_a.size () != delete_files_b.size ()) {
143- return false ;
144- }
145- for (size_t i = 0 ; i < delete_files_a.size (); ++i) {
146- if (!delete_files_a[i] != !delete_files_b[i]) {
147- return false ;
148- }
149- if (delete_files_a[i] && *delete_files_a[i] != *delete_files_b[i]) {
150- return false ;
151- }
176+ return lhs && rhs && *lhs == *rhs;
177+ }
178+
179+ template <typename T>
180+ bool SharedPtrVectorEqual (const std::vector<std::shared_ptr<T>>& lhs,
181+ const std::vector<std::shared_ptr<T>>& rhs) {
182+ return std::ranges::equal (lhs, rhs, SharedPtrEqual<T>);
183+ }
184+
185+ bool FileScanTaskEqual (const std::shared_ptr<FileScanTask>& lhs,
186+ const std::shared_ptr<FileScanTask>& rhs) {
187+ if (lhs == rhs) {
188+ return true ;
152189 }
153- if (file_scan_tasks_a. size () != file_scan_tasks_b. size () ) {
190+ if (!lhs || !rhs ) {
154191 return false ;
155192 }
156- for (size_t i = 0 ; i < file_scan_tasks_a.size (); ++i) {
157- const auto & a = file_scan_tasks_a[i];
158- const auto & b = file_scan_tasks_b[i];
159- if (!a != !b) {
160- return false ;
161- }
162- if (!a) continue ;
163- if (!a->data_file () != !b->data_file ()) {
164- return false ;
165- }
166- if (a->data_file () && *a->data_file () != *b->data_file ()) {
167- return false ;
168- }
169- if (a->delete_files ().size () != b->delete_files ().size ()) {
170- return false ;
171- }
172- for (size_t j = 0 ; j < a->delete_files ().size (); ++j) {
173- if (!a->delete_files ()[j] != !b->delete_files ()[j]) {
174- return false ;
175- }
176- if (a->delete_files ()[j] && *a->delete_files ()[j] != *b->delete_files ()[j]) {
177- return false ;
178- }
179- }
180- if (a->residual_filter () != b->residual_filter ()) {
181- return false ;
182- }
193+ return SharedPtrEqual (lhs->data_file (), rhs->data_file ()) &&
194+ SharedPtrVectorEqual (lhs->delete_files (), rhs->delete_files ()) &&
195+ ExpressionPtrEqual (lhs->residual_filter (), rhs->residual_filter ());
196+ }
197+
198+ template <typename T>
199+ bool OptionalSharedPtrVectorEqual (
200+ const std::optional<std::vector<std::shared_ptr<T>>>& lhs,
201+ const std::optional<std::vector<std::shared_ptr<T>>>& rhs,
202+ bool (*eq)(const std::shared_ptr<T>&, const std::shared_ptr<T>&)) {
203+ if (lhs.has_value () != rhs.has_value ()) {
204+ return false ;
183205 }
184- return true ;
206+ return !lhs.has_value () || std::ranges::equal (*lhs, *rhs, eq);
207+ }
208+
209+ template <typename Response>
210+ bool ScanTaskFieldsEqual (const Response& lhs, const Response& rhs) {
211+ return lhs.plan_tasks == rhs.plan_tasks &&
212+ SharedPtrVectorEqual (lhs.delete_files , rhs.delete_files ) &&
213+ OptionalSharedPtrVectorEqual (lhs.file_scan_tasks , rhs.file_scan_tasks ,
214+ FileScanTaskEqual);
215+ }
216+
217+ template <typename Response>
218+ bool HasTaskFields (const Response& response) {
219+ return response.plan_tasks .has_value () || response.file_scan_tasks .has_value ();
220+ }
221+
222+ template <typename Response>
223+ bool HasNonEmptyFileScanTasks (const Response& response) {
224+ return response.file_scan_tasks .has_value () && !response.file_scan_tasks ->empty ();
185225}
186226
187227} // namespace
188228
189229bool PlanTableScanResponse::operator ==(const PlanTableScanResponse& other) const {
190- return ScanTaskFieldsEqual (plan_tasks, other.plan_tasks , delete_files,
191- other.delete_files , file_scan_tasks,
192- other.file_scan_tasks ) &&
193- plan_status == other.plan_status && plan_id == other.plan_id &&
194- error == other.error ;
230+ return ScanTaskFieldsEqual (*this , other) && plan_status == other.plan_status &&
231+ plan_id == other.plan_id && error == other.error ;
195232}
196233
197234bool FetchPlanningResultResponse::operator ==(
198235 const FetchPlanningResultResponse& other) const {
199- return ScanTaskFieldsEqual (plan_tasks, other.plan_tasks , delete_files,
200- other.delete_files , file_scan_tasks,
201- other.file_scan_tasks ) &&
202- plan_status == other.plan_status && error == other.error ;
236+ return ScanTaskFieldsEqual (*this , other) && plan_status == other.plan_status &&
237+ error == other.error ;
203238}
204239
205240bool FetchScanTasksRequest::operator ==(const FetchScanTasksRequest& other) const {
206241 return planTask == other.planTask ;
207242}
208243
209244bool FetchScanTasksResponse::operator ==(const FetchScanTasksResponse& other) const {
210- return ScanTaskFieldsEqual (plan_tasks, other.plan_tasks , delete_files,
211- other.delete_files , file_scan_tasks, other.file_scan_tasks );
245+ return ScanTaskFieldsEqual (*this , other);
212246}
213247
214248Status OAuthTokenResponse::Validate () const {
@@ -257,8 +291,7 @@ Status PlanTableScanResponse::Validate() const {
257291 return ValidationFailed (
258292 " Invalid response: 'cancelled' is not a valid status for planTableScan" );
259293 }
260- if (plan_status != PlanStatus::kCompleted &&
261- (!plan_tasks.empty () || !file_scan_tasks.empty ())) {
294+ if (plan_status != PlanStatus::kCompleted && HasTaskFields (*this )) {
262295 return ValidationFailed (
263296 " Invalid response: tasks can only be defined when status is 'completed'" );
264297 }
@@ -268,7 +301,7 @@ Status PlanTableScanResponse::Validate() const {
268301 " Invalid response: plan id can only be defined when status is 'submitted' or "
269302 " 'completed'" );
270303 }
271- if (file_scan_tasks. empty ( ) && !delete_files.empty ()) {
304+ if (! HasNonEmptyFileScanTasks (* this ) && !delete_files.empty ()) {
272305 return ValidationFailed (
273306 " Invalid response: deleteFiles should only be returned with fileScanTasks that "
274307 " reference them" );
@@ -285,12 +318,11 @@ Status PlanTableScanResponse::Validate() const {
285318}
286319
287320Status FetchPlanningResultResponse::Validate () const {
288- if (plan_status != PlanStatus::kCompleted &&
289- (!plan_tasks.empty () || !file_scan_tasks.empty ())) {
321+ if (plan_status != PlanStatus::kCompleted && HasTaskFields (*this )) {
290322 return ValidationFailed (
291323 " Invalid response: tasks can only be returned in a 'completed' status" );
292324 }
293- if (file_scan_tasks. empty ( ) && !delete_files.empty ()) {
325+ if (! HasNonEmptyFileScanTasks (* this ) && !delete_files.empty ()) {
294326 return ValidationFailed (
295327 " Invalid response: deleteFiles should only be returned with fileScanTasks that "
296328 " reference them" );
@@ -314,12 +346,12 @@ Status FetchScanTasksRequest::Validate() const {
314346}
315347
316348Status FetchScanTasksResponse::Validate () const {
317- if (file_scan_tasks. empty ( ) && !delete_files.empty ()) {
349+ if (! HasNonEmptyFileScanTasks (* this ) && !delete_files.empty ()) {
318350 return ValidationFailed (
319351 " Invalid response: deleteFiles should only be returned with fileScanTasks that "
320352 " reference them" );
321353 }
322- if (plan_tasks. empty () && file_scan_tasks. empty ( )) {
354+ if (! HasTaskFields (* this )) {
323355 return ValidationFailed (
324356 " Invalid response: planTasks and fileScanTask cannot both be null" );
325357 }
0 commit comments