1919
2020#include < memory>
2121#include < string>
22+ #include < vector>
2223
2324#include < gtest/gtest.h>
2425
2526#include " iceberg/partition_spec.h"
27+ #include " iceberg/schema.h"
2628#include " iceberg/snapshot.h"
29+ #include " iceberg/sort_field.h"
2730#include " iceberg/sort_order.h"
2831#include " iceberg/table_metadata.h"
2932#include " iceberg/table_update.h"
3033#include " iceberg/test/matchers.h"
34+ #include " iceberg/transform.h"
35+ #include " iceberg/type.h"
3136
3237namespace iceberg {
3338
3439namespace {
3540
41+ // Helper function to create a simple schema for testing
42+ std::shared_ptr<Schema> CreateTestSchema () {
43+ auto field1 = SchemaField::MakeRequired (1 , " id" , int32 ());
44+ auto field2 = SchemaField::MakeRequired (2 , " data" , string ());
45+ auto field3 = SchemaField::MakeRequired (3 , " ts" , timestamp ());
46+ return std::make_shared<Schema>(std::vector<SchemaField>{field1, field2, field3}, 0 );
47+ }
48+
3649// Helper function to create base metadata for tests
3750std::unique_ptr<TableMetadata> CreateBaseMetadata () {
3851 auto metadata = std::make_unique<TableMetadata>();
@@ -41,11 +54,14 @@ std::unique_ptr<TableMetadata> CreateBaseMetadata() {
4154 metadata->location = " s3://bucket/test" ;
4255 metadata->last_sequence_number = 0 ;
4356 metadata->last_updated_ms = TimePointMs{std::chrono::milliseconds (1000 )};
44- metadata->last_column_id = 0 ;
57+ metadata->last_column_id = 3 ;
58+ metadata->current_schema_id = 0 ;
59+ metadata->schemas .push_back (CreateTestSchema ());
4560 metadata->default_spec_id = PartitionSpec::kInitialSpecId ;
4661 metadata->last_partition_id = 0 ;
4762 metadata->current_snapshot_id = Snapshot::kInvalidSnapshotId ;
4863 metadata->default_sort_order_id = SortOrder::kInitialSortOrderId ;
64+ metadata->sort_orders .push_back (SortOrder::Unsorted ());
4965 metadata->next_row_id = TableMetadata::kInitialRowId ;
5066 return metadata;
5167}
@@ -124,17 +140,121 @@ TEST(TableMetadataBuilderTest, AssignUUID) {
124140 EXPECT_EQ (metadata->table_uuid , " TEST-UUID-ABCD" ); // Original case preserved
125141}
126142
143+ // Test AddSortOrder
144+ TEST (TableMetadataBuilderTest, AddSortOrderBasic) {
145+ auto base = CreateBaseMetadata ();
146+ auto builder = TableMetadataBuilder::BuildFrom (base.get ());
147+ auto schema = CreateTestSchema ();
148+
149+ // 1. Add unsorted - should reuse existing unsorted order
150+ builder->AddSortOrder (SortOrder::Unsorted ());
151+ ICEBERG_UNWRAP_OR_FAIL (auto metadata, builder->Build ());
152+ ASSERT_EQ (metadata->sort_orders .size (), 1 );
153+ EXPECT_TRUE (metadata->sort_orders [0 ]->is_unsorted ());
154+
155+ // 2. Add basic sort order
156+ builder = TableMetadataBuilder::BuildFrom (base.get ());
157+ SortField field1 (1 , Transform::Identity (), SortDirection::kAscending ,
158+ NullOrder::kFirst );
159+ ICEBERG_UNWRAP_OR_FAIL (auto order1,
160+ SortOrder::Make (*schema, 1 , std::vector<SortField>{field1}));
161+ builder->AddSortOrder (std::move (order1));
162+ ICEBERG_UNWRAP_OR_FAIL (metadata, builder->Build ());
163+ ASSERT_EQ (metadata->sort_orders .size (), 2 );
164+ EXPECT_EQ (metadata->sort_orders [1 ]->order_id (), 1 );
165+
166+ // 3. Add duplicate - should be idempotent
167+ builder = TableMetadataBuilder::BuildFrom (base.get ());
168+ ICEBERG_UNWRAP_OR_FAIL (auto order2,
169+ SortOrder::Make (*schema, 1 , std::vector<SortField>{field1}));
170+ ICEBERG_UNWRAP_OR_FAIL (auto order3,
171+ SortOrder::Make (*schema, 1 , std::vector<SortField>{field1}));
172+ builder->AddSortOrder (std::move (order2));
173+ builder->AddSortOrder (std::move (order3)); // Duplicate
174+ ICEBERG_UNWRAP_OR_FAIL (metadata, builder->Build ());
175+ ASSERT_EQ (metadata->sort_orders .size (), 2 ); // Only one added
176+
177+ // 4. Add multiple different orders + verify ID reassignment
178+ builder = TableMetadataBuilder::BuildFrom (base.get ());
179+ SortField field2 (2 , Transform::Identity (), SortDirection::kDescending ,
180+ NullOrder::kLast );
181+ // User provides ID=99, Builder should reassign to ID=1
182+ ICEBERG_UNWRAP_OR_FAIL (auto order4,
183+ SortOrder::Make (*schema, 99 , std::vector<SortField>{field1}));
184+ ICEBERG_UNWRAP_OR_FAIL (
185+ auto order5, SortOrder::Make (*schema, 2 , std::vector<SortField>{field1, field2}));
186+ builder->AddSortOrder (std::move (order4));
187+ builder->AddSortOrder (std::move (order5));
188+ ICEBERG_UNWRAP_OR_FAIL (metadata, builder->Build ());
189+ ASSERT_EQ (metadata->sort_orders .size (), 3 );
190+ EXPECT_EQ (metadata->sort_orders [1 ]->order_id (), 1 ); // Reassigned from 99
191+ EXPECT_EQ (metadata->sort_orders [2 ]->order_id (), 2 );
192+ }
193+
194+ TEST (TableMetadataBuilderTest, AddSortOrderInvalid) {
195+ auto base = CreateBaseMetadata ();
196+ auto schema = CreateTestSchema ();
197+
198+ // 1. Invalid field ID
199+ auto builder = TableMetadataBuilder::BuildFrom (base.get ());
200+ SortField invalid_field (999 , Transform::Identity (), SortDirection::kAscending ,
201+ NullOrder::kFirst );
202+ ICEBERG_UNWRAP_OR_FAIL (auto order1,
203+ SortOrder::Make (1 , std::vector<SortField>{invalid_field}));
204+ builder->AddSortOrder (std::move (order1));
205+ ASSERT_THAT (builder->Build (), IsError (ErrorKind::kCommitFailed ));
206+ ASSERT_THAT (builder->Build (),
207+ HasErrorMessage (" Cannot find source column for sort field" ));
208+
209+ // 2. Invalid transform (Day transform on string type)
210+ builder = TableMetadataBuilder::BuildFrom (base.get ());
211+ SortField invalid_transform (2 , Transform::Day (), SortDirection::kAscending ,
212+ NullOrder::kFirst );
213+ ICEBERG_UNWRAP_OR_FAIL (auto order2,
214+ SortOrder::Make (1 , std::vector<SortField>{invalid_transform}));
215+ builder->AddSortOrder (std::move (order2));
216+ ASSERT_THAT (builder->Build (), IsError (ErrorKind::kCommitFailed ));
217+ ASSERT_THAT (builder->Build (), HasErrorMessage (" Invalid source type" ));
218+
219+ // 3. Without schema
220+ builder = TableMetadataBuilder::BuildFromEmpty (2 );
221+ builder->AssignUUID (" test-uuid" );
222+ SortField field (1 , Transform::Identity (), SortDirection::kAscending , NullOrder::kFirst );
223+ ICEBERG_UNWRAP_OR_FAIL (auto order3,
224+ SortOrder::Make (*schema, 1 , std::vector<SortField>{field}));
225+ builder->AddSortOrder (std::move (order3));
226+ ASSERT_THAT (builder->Build (), IsError (ErrorKind::kCommitFailed ));
227+ ASSERT_THAT (builder->Build (), HasErrorMessage (" Cannot find current schema" ));
228+ }
229+
127230// Test applying TableUpdate to builder
128231TEST (TableMetadataBuilderTest, ApplyUpdate) {
232+ // Use base metadata with schema for all update tests
233+ auto base = CreateBaseMetadata ();
234+ auto builder = TableMetadataBuilder::BuildFrom (base.get ());
235+
129236 // Apply AssignUUID update
130- auto builder = TableMetadataBuilder::BuildFromEmpty (2 );
131- table::AssignUUID update (" apply-uuid" );
132- update.ApplyTo (*builder);
133- // TODO(Li Feiyang): Add more update and `apply` once other build methods are
134- // implemented
237+ table::AssignUUID uuid_update (" apply-uuid" );
238+ uuid_update.ApplyTo (*builder);
239+
240+ // Apply AddSortOrder update
241+ auto schema = CreateTestSchema ();
242+ SortField sort_field (1 , Transform::Identity (), SortDirection::kAscending ,
243+ NullOrder::kFirst );
244+ ICEBERG_UNWRAP_OR_FAIL (auto sort_order_unique,
245+ SortOrder::Make (*schema, 1 , std::vector<SortField>{sort_field}));
246+ auto sort_order = std::shared_ptr<SortOrder>(std::move (sort_order_unique));
247+
248+ table::AddSortOrder sort_order_update (sort_order);
249+ sort_order_update.ApplyTo (*builder);
250+
251+ // TODO(Li Feiyang): Apply more build methods once they are implemented
135252
253+ // Verify all updates were applied
136254 ICEBERG_UNWRAP_OR_FAIL (auto metadata, builder->Build ());
137255 EXPECT_EQ (metadata->table_uuid , " apply-uuid" );
256+ ASSERT_EQ (metadata->sort_orders .size (), 2 );
257+ EXPECT_EQ (metadata->sort_orders [1 ]->order_id (), 1 );
138258}
139259
140260} // namespace iceberg
0 commit comments