@@ -1409,6 +1409,214 @@ async fn test_set_domain_metadata_unsupported_writer_feature(
14091409 Ok ( ( ) )
14101410}
14111411
1412+ #[ tokio:: test]
1413+ async fn test_remove_domain_metadata_non_existent_domain ( ) -> Result < ( ) , Box < dyn std:: error:: Error > >
1414+ {
1415+ let _ = tracing_subscriber:: fmt:: try_init ( ) ;
1416+
1417+ let schema = Arc :: new ( StructType :: try_new ( vec ! [ StructField :: nullable(
1418+ "number" ,
1419+ DataType :: INTEGER ,
1420+ ) ] ) ?) ;
1421+
1422+ let table_name = "test_domain_metadata_unsupported" ;
1423+
1424+ let ( store, engine, table_location) = engine_store_setup ( table_name, None ) ;
1425+ let table_url = create_table (
1426+ store. clone ( ) ,
1427+ table_location,
1428+ schema. clone ( ) ,
1429+ & [ ] ,
1430+ true ,
1431+ vec ! [ ] ,
1432+ vec ! [ "domainMetadata" ] ,
1433+ )
1434+ . await ?;
1435+
1436+ let snapshot = Snapshot :: builder_for ( table_url. clone ( ) ) . build ( & engine) ?;
1437+ let txn = snapshot. transaction ( Box :: new ( FileSystemCommitter :: new ( ) ) ) ?;
1438+
1439+ let domain = "app.deprecated" ;
1440+
1441+ // removing domain metadata that doesn't exist should NOT write a tombstone
1442+ let _ = txn
1443+ . with_domain_metadata_removed ( domain. to_string ( ) )
1444+ . commit ( & engine) ?;
1445+
1446+ let commit_data = store
1447+ . get ( & Path :: from ( format ! (
1448+ "/{table_name}/_delta_log/00000000000000000001.json"
1449+ ) ) )
1450+ . await ?
1451+ . bytes ( )
1452+ . await ?;
1453+ let actions: Vec < serde_json:: Value > = Deserializer :: from_slice ( & commit_data)
1454+ . into_iter ( )
1455+ . try_collect ( ) ?;
1456+
1457+ let domain_action = actions. iter ( ) . find ( |v| v. get ( "domainMetadata" ) . is_some ( ) ) ;
1458+ assert ! (
1459+ domain_action. is_none( ) ,
1460+ "No tombstone should be written for non-existent domain"
1461+ ) ;
1462+
1463+ let final_snapshot = Snapshot :: builder_for ( table_url. clone ( ) ) . build ( & engine) ?;
1464+ let config = final_snapshot. get_domain_metadata ( domain, & engine) ?;
1465+ assert_eq ! ( config, None ) ;
1466+
1467+ Ok ( ( ) )
1468+ }
1469+
1470+ #[ tokio:: test]
1471+ async fn test_domain_metadata_set_remove_conflicts ( ) -> Result < ( ) , Box < dyn std:: error:: Error > > {
1472+ let _ = tracing_subscriber:: fmt:: try_init ( ) ;
1473+
1474+ let schema = Arc :: new ( StructType :: try_new ( vec ! [ StructField :: nullable(
1475+ "number" ,
1476+ DataType :: INTEGER ,
1477+ ) ] ) ?) ;
1478+
1479+ let table_name = "test_domain_metadata_unsupported" ;
1480+
1481+ let ( store, engine, table_location) = engine_store_setup ( table_name, None ) ;
1482+ let table_url = create_table (
1483+ store. clone ( ) ,
1484+ table_location,
1485+ schema. clone ( ) ,
1486+ & [ ] ,
1487+ true ,
1488+ vec ! [ ] ,
1489+ vec ! [ "domainMetadata" ] ,
1490+ )
1491+ . await ?;
1492+
1493+ let snapshot = Snapshot :: builder_for ( table_url. clone ( ) ) . build ( & engine) ?;
1494+
1495+ // set then remove same domain
1496+ let txn = snapshot
1497+ . clone ( )
1498+ . transaction ( Box :: new ( FileSystemCommitter :: new ( ) ) ) ?;
1499+ let err = txn
1500+ . with_domain_metadata ( "app.config" . to_string ( ) , "v1" . to_string ( ) )
1501+ . with_domain_metadata_removed ( "app.config" . to_string ( ) )
1502+ . commit ( & engine)
1503+ . unwrap_err ( ) ;
1504+ assert ! ( err
1505+ . to_string( )
1506+ . contains( "already specified in this transaction" ) ) ;
1507+
1508+ // remove then set same domain
1509+ let txn2 = snapshot
1510+ . clone ( )
1511+ . transaction ( Box :: new ( FileSystemCommitter :: new ( ) ) ) ?;
1512+ let err = txn2
1513+ . with_domain_metadata_removed ( "test.domain" . to_string ( ) )
1514+ . with_domain_metadata ( "test.domain" . to_string ( ) , "v1" . to_string ( ) )
1515+ . commit ( & engine)
1516+ . unwrap_err ( ) ;
1517+ assert ! ( err
1518+ . to_string( )
1519+ . contains( "already specified in this transaction" ) ) ;
1520+
1521+ // remove same domain twice
1522+ let txn3 = snapshot
1523+ . clone ( )
1524+ . transaction ( Box :: new ( FileSystemCommitter :: new ( ) ) ) ?;
1525+ let err = txn3
1526+ . with_domain_metadata_removed ( "another.domain" . to_string ( ) )
1527+ . with_domain_metadata_removed ( "another.domain" . to_string ( ) )
1528+ . commit ( & engine)
1529+ . unwrap_err ( ) ;
1530+ assert ! ( err
1531+ . to_string( )
1532+ . contains( "already specified in this transaction" ) ) ;
1533+
1534+ // remove system domain
1535+ let txn4 = snapshot
1536+ . clone ( )
1537+ . transaction ( Box :: new ( FileSystemCommitter :: new ( ) ) ) ?;
1538+ let err = txn4
1539+ . with_domain_metadata_removed ( "delta.system" . to_string ( ) )
1540+ . commit ( & engine)
1541+ . unwrap_err ( ) ;
1542+ assert ! ( err
1543+ . to_string( )
1544+ . contains( "Cannot modify domains that start with 'delta.' as those are system controlled" ) ) ;
1545+
1546+ Ok ( ( ) )
1547+ }
1548+
1549+ #[ tokio:: test]
1550+ async fn test_domain_metadata_set_then_remove ( ) -> Result < ( ) , Box < dyn std:: error:: Error > > {
1551+ let _ = tracing_subscriber:: fmt:: try_init ( ) ;
1552+
1553+ let schema = Arc :: new ( StructType :: try_new ( vec ! [ StructField :: nullable(
1554+ "number" ,
1555+ DataType :: INTEGER ,
1556+ ) ] ) ?) ;
1557+
1558+ let table_name = "test_domain_metadata_unsupported" ;
1559+
1560+ let ( store, engine, table_location) = engine_store_setup ( table_name, None ) ;
1561+ let table_url = create_table (
1562+ store. clone ( ) ,
1563+ table_location,
1564+ schema. clone ( ) ,
1565+ & [ ] ,
1566+ true ,
1567+ vec ! [ ] ,
1568+ vec ! [ "domainMetadata" ] ,
1569+ )
1570+ . await ?;
1571+
1572+ let domain = "app.config" ;
1573+ let configuration = r#"{"version": 1}"# ;
1574+
1575+ // txn 1: set domain metadata
1576+ let snapshot = Snapshot :: builder_for ( table_url. clone ( ) ) . build ( & engine) ?;
1577+ let txn = snapshot. transaction ( Box :: new ( FileSystemCommitter :: new ( ) ) ) ?;
1578+ let _ = txn
1579+ . with_domain_metadata ( domain. to_string ( ) , configuration. to_string ( ) )
1580+ . commit ( & engine) ?;
1581+
1582+ // txn 2: remove the same domain metadata
1583+ let snapshot = Snapshot :: builder_for ( table_url. clone ( ) ) . build ( & engine) ?;
1584+ let txn = snapshot. transaction ( Box :: new ( FileSystemCommitter :: new ( ) ) ) ?;
1585+ let _ = txn
1586+ . with_domain_metadata_removed ( domain. to_string ( ) )
1587+ . commit ( & engine) ?;
1588+
1589+ // verify removal commit preserves the previous configuration
1590+ let commit_data = store
1591+ . get ( & Path :: from ( format ! (
1592+ "/{table_name}/_delta_log/00000000000000000002.json"
1593+ ) ) )
1594+ . await ?
1595+ . bytes ( )
1596+ . await ?;
1597+ let actions: Vec < serde_json:: Value > = Deserializer :: from_slice ( & commit_data)
1598+ . into_iter ( )
1599+ . try_collect ( ) ?;
1600+
1601+ let domain_action = actions
1602+ . iter ( )
1603+ . find ( |v| v. get ( "domainMetadata" ) . is_some ( ) )
1604+ . unwrap ( ) ;
1605+ assert_eq ! ( domain_action[ "domainMetadata" ] [ "domain" ] , domain) ;
1606+ assert_eq ! (
1607+ domain_action[ "domainMetadata" ] [ "configuration" ] ,
1608+ configuration
1609+ ) ;
1610+ assert_eq ! ( domain_action[ "domainMetadata" ] [ "removed" ] , true ) ;
1611+
1612+ // verify reads see the metadata removal
1613+ let final_snapshot = Snapshot :: builder_for ( table_url. clone ( ) ) . build ( & engine) ?;
1614+ let domain_config = final_snapshot. get_domain_metadata ( domain, & engine) ?;
1615+ assert_eq ! ( domain_config, None ) ;
1616+
1617+ Ok ( ( ) )
1618+ }
1619+
14121620async fn get_ict_at_version (
14131621 store : Arc < dyn ObjectStore > ,
14141622 table_url : & Url ,
0 commit comments