@@ -474,14 +474,6 @@ def test_replace_table_schema_id_reuse(
474474 assert replaced .metadata .last_column_id == expected_last_col_id
475475
476476
477- def test_replace_table_accepts_pyarrow_schema (catalog : Catalog , test_table_identifier : Identifier ) -> None :
478- """The schema argument may be a PyArrow schema; it must be converted and have IDs assigned."""
479- _create_simple_table (catalog , test_table_identifier )
480- pa_schema = pa .schema ([pa .field ("id" , pa .int64 ()), pa .field ("data" , pa .large_string ()), pa .field ("extra" , pa .bool_ ())])
481- replaced = catalog .replace_table (test_table_identifier , schema = pa_schema )
482- assert {f .name for f in replaced .schema ().fields } == {"id" , "data" , "extra" }
483-
484-
485477def test_replace_table_preserves_identifier_field_ids (catalog : Catalog , test_table_identifier : Identifier ) -> None :
486478 """Identifier-field IDs on the new schema are honored after reuse-by-name."""
487479 schema = Schema (
@@ -509,37 +501,42 @@ def test_replace_table_reuses_partition_spec_id(catalog: Catalog, test_table_ide
509501 assert replaced .metadata .default_spec_id == 0
510502
511503
512- @pytest .mark .parametrize (
513- "location_kwarg, expected" ,
514- [
515- ("explicit" , "explicit" ), # explicit location is used verbatim (minus any trailing slash)
516- (None , "existing" ), # no location → existing location is preserved
517- ("explicit-with-trailing-slash" , "explicit-with-trailing-slash-stripped" ),
518- ],
519- ids = ["explicit" , "inherited" , "trailing-slash-stripped" ],
520- )
521- def test_replace_table_location_resolution (
522- catalog : Catalog ,
523- test_table_identifier : Identifier ,
524- tmp_path : Path ,
525- location_kwarg : str | None ,
526- expected : str ,
527- ) -> None :
528- """`location=None` keeps the existing location; an explicit location is used (sans trailing slash)."""
504+ def test_replace_table_with_sort_order_changes (catalog : Catalog , test_table_identifier : Identifier ) -> None :
505+ """Replace can change the sort order. The new sort order is appended to the history and
506+ becomes the default; a follow-up replace back to unsorted reuses the unsorted order_id
507+ rather than appending a duplicate."""
508+ _ , schema = _create_simple_table (catalog , test_table_identifier )
509+ sort = SortOrder (SortField (source_id = 1 , transform = IdentityTransform (), direction = SortDirection .ASC ))
510+
511+ # unsorted → sorted: a new order is added and becomes the default.
512+ sorted_table = catalog .replace_table (test_table_identifier , schema = schema , sort_order = sort )
513+ assert sorted_table .sort_order ().fields == sort .fields
514+ assert sorted_table .metadata .default_sort_order_id != 0
515+
516+ # sorted → unsorted: reuses the unsorted order_id 0 from history.
517+ unsorted_table = catalog .replace_table (test_table_identifier , schema = schema )
518+ assert unsorted_table .sort_order ().is_unsorted
519+ assert unsorted_table .metadata .default_sort_order_id == 0
520+
521+
522+ def test_replace_table_inherits_existing_location (catalog : Catalog , test_table_identifier : Identifier ) -> None :
523+ """`location=None` keeps the existing table's location."""
529524 _ , schema = _create_simple_table (catalog , test_table_identifier )
530525 existing = catalog .load_table (test_table_identifier ).metadata .location
526+ replaced = catalog .replace_table (test_table_identifier , schema = schema )
527+ assert replaced .metadata .location == existing
531528
532- if location_kwarg is None :
533- replaced = catalog . replace_table ( test_table_identifier , schema = schema )
534- assert replaced . metadata . location == existing
535- elif location_kwarg == "explicit" :
536- new_location = f"file:// { tmp_path } /relocated"
537- replaced = catalog . replace_table ( test_table_identifier , schema = schema , location = new_location )
538- assert replaced . metadata . location == new_location
539- else :
540- new_location = f"file:// { tmp_path } /with-slash"
541- replaced = catalog .replace_table (test_table_identifier , schema = schema , location = new_location + "/" )
542- assert replaced .metadata .location == new_location
529+
530+ @ pytest . mark . parametrize ( "trailing_slash" , [ False , True ], ids = [ "no-slash" , "trailing-slash" ] )
531+ def test_replace_table_uses_explicit_location (
532+ catalog : Catalog , test_table_identifier : Identifier , tmp_path : Path , trailing_slash : bool
533+ ) -> None :
534+ """An explicit `location` is used verbatim; trailing slash is stripped."""
535+ _ , schema = _create_simple_table ( catalog , test_table_identifier )
536+ bare = f"file:// { tmp_path } /relocated"
537+ arg = bare + "/" if trailing_slash else bare
538+ replaced = catalog .replace_table (test_table_identifier , schema = schema , location = arg )
539+ assert replaced .metadata .location == bare
543540
544541
545542def test_replace_table_merges_properties_with_overrides_and_additions (
@@ -657,23 +654,22 @@ def run_failing_replace() -> None:
657654
658655
659656def test_concurrent_replace_table (catalog : Catalog , test_table_identifier : Identifier ) -> None :
660- """Two replace_table calls staged from the same base metadata cannot both commit.
661-
662- The first replace updates the catalog pointer to a new metadata location; the second
663- replace was built against the now-stale base, so its commit must be rejected with
664- `CommitFailedException`."""
665- _ , schema = _create_simple_table (catalog , test_table_identifier )
657+ """Two concurrent replace_table calls staged from the same base both adding a column
658+ must fail on the second commit with an `assert-last-assigned-field-id` violation —
659+ proving the `AssertLastAssignedFieldId` requirement actually guards against duplicate
660+ field-id assignment under concurrent writers."""
661+ _create_simple_table (catalog , test_table_identifier )
666662 new_schema = Schema (
667663 NestedField (field_id = 1 , name = "id" , field_type = LongType (), required = False ),
668664 NestedField (field_id = 2 , name = "data" , field_type = StringType (), required = False ),
669665 NestedField (field_id = 3 , name = "extra" , field_type = BooleanType (), required = False ),
670666 )
671- # Both transactions are built from the same base metadata.
667+ # Both transactions build from the same base metadata with the same new schema .
672668 txn_a = catalog .replace_table_transaction (test_table_identifier , schema = new_schema )
673- txn_b = catalog .replace_table_transaction (test_table_identifier , schema = schema )
669+ txn_b = catalog .replace_table_transaction (test_table_identifier , schema = new_schema )
674670
675671 txn_a .commit_transaction ()
676- with pytest .raises (CommitFailedException ):
672+ with pytest .raises (CommitFailedException , match = "last assigned field id" ):
677673 txn_b .commit_transaction ()
678674
679675
0 commit comments