laskoviymishka commented on code in PR #1486:
URL: https://github.com/apache/iceberg-go/pull/1486#discussion_r3610624123


##########
catalog/hive/schema.go:
##########
@@ -160,22 +161,51 @@ func constructHiveTable(dbName, tableName, location, 
metadataLocation string, sc
        }
 }
 
-func updateHiveTableForCommit(existing *hive_metastore.Table, 
newMetadataLocation string) *hive_metastore.Table {
-       // Copy the existing table
+func updateHiveTableForCommit(
+       existing *hive_metastore.Table,
+       currentProps, newProps iceberg.Properties,
+       newLocation string,
+       newSchema *iceberg.Schema,
+       newMetadataLocation string,
+) *hive_metastore.Table {
        updated := *existing
-
-       // Update parameters
+       updated.Parameters = maps.Clone(existing.Parameters)
        if updated.Parameters == nil {
                updated.Parameters = make(map[string]string)
        }
 
-       // Store previous metadata location
-       if oldLocation, ok := updated.Parameters[MetadataLocationKey]; ok {
+       for key := range currentProps {
+               delete(updated.Parameters, key)
+       }
+       for key, value := range newProps {
+               updated.Parameters[key] = value
+       }
+       delete(updated.Parameters, PreviousMetadataLocationKey)
+
+       if oldLocation, ok := existing.Parameters[MetadataLocationKey]; ok {

Review Comment:
   nice that this reads `existing.Parameters` and not `updated.Parameters` — if 
`newProps` ever carried `metadata_location`, reading the mutated map would 
capture the wrong previous location. It's subtle enough that a one-line comment 
on why would save the next person from "simplifying" it back.



##########
catalog/hive/hive.go:
##########
@@ -532,7 +532,14 @@ func (c *Catalog) CommitTable(ctx context.Context, 
identifier table.Identifier,
        }
 
        if current != nil {
-               updatedHiveTbl := updateHiveTableForCommit(currentHiveTbl, 
staged.MetadataLocation())
+               updatedHiveTbl := updateHiveTableForCommit(
+                       currentHiveTbl,
+                       current.Properties(),
+                       staged.Properties(),

Review Comment:
   six positional args with two adjacent `iceberg.Properties` (`currentProps`, 
`newProps`) is easy to transpose silently — swap them and the diff logic 
quietly does the wrong thing.
   
   Since there's a single call site, passing the current/staged `Metadata` (or 
a small delta struct) instead of deconstructed fields would make the swap 
impossible. Not blocking, but worth doing while it's fresh.



##########
catalog/hive/schema.go:
##########
@@ -160,22 +161,51 @@ func constructHiveTable(dbName, tableName, location, 
metadataLocation string, sc
        }
 }
 
-func updateHiveTableForCommit(existing *hive_metastore.Table, 
newMetadataLocation string) *hive_metastore.Table {
-       // Copy the existing table
+func updateHiveTableForCommit(
+       existing *hive_metastore.Table,
+       currentProps, newProps iceberg.Properties,
+       newLocation string,
+       newSchema *iceberg.Schema,
+       newMetadataLocation string,
+) *hive_metastore.Table {
        updated := *existing
-
-       // Update parameters
+       updated.Parameters = maps.Clone(existing.Parameters)
        if updated.Parameters == nil {
                updated.Parameters = make(map[string]string)
        }
 
-       // Store previous metadata location
-       if oldLocation, ok := updated.Parameters[MetadataLocationKey]; ok {
+       for key := range currentProps {
+               delete(updated.Parameters, key)

Review Comment:
   this assumes the HMS `Parameters` we're deleting are exactly the Iceberg 
user-properties we last wrote — but old commits only ever stamped 
`metadata_location`, so any property a table already had in HMS (or anything an 
operator set directly that happens to collide with an Iceberg key name) gets 
silently dropped on the first commit after this lands.
   
   I'd at minimum document that invariant right here as a known limitation, or 
gate the delete on an allow-list of Iceberg key prefixes so we're not reaching 
into keys we didn't write. wdyt?



##########
catalog/hive/schema.go:
##########
@@ -160,22 +161,51 @@ func constructHiveTable(dbName, tableName, location, 
metadataLocation string, sc
        }
 }
 
-func updateHiveTableForCommit(existing *hive_metastore.Table, 
newMetadataLocation string) *hive_metastore.Table {
-       // Copy the existing table
+func updateHiveTableForCommit(
+       existing *hive_metastore.Table,
+       currentProps, newProps iceberg.Properties,
+       newLocation string,
+       newSchema *iceberg.Schema,
+       newMetadataLocation string,
+) *hive_metastore.Table {
        updated := *existing
-
-       // Update parameters
+       updated.Parameters = maps.Clone(existing.Parameters)
        if updated.Parameters == nil {
                updated.Parameters = make(map[string]string)
        }
 
-       // Store previous metadata location
-       if oldLocation, ok := updated.Parameters[MetadataLocationKey]; ok {
+       for key := range currentProps {
+               delete(updated.Parameters, key)
+       }
+       for key, value := range newProps {
+               updated.Parameters[key] = value
+       }
+       delete(updated.Parameters, PreviousMetadataLocationKey)
+
+       if oldLocation, ok := existing.Parameters[MetadataLocationKey]; ok {
                updated.Parameters[PreviousMetadataLocationKey] = oldLocation
        }
 
-       // Set new metadata location
+       updated.Parameters[TableTypeKey] = TableTypeIceberg
        updated.Parameters[MetadataLocationKey] = newMetadataLocation
+       updated.Parameters[ExternalKey] = "TRUE"
+       updated.Parameters["storage_handler"] = 
"org.apache.iceberg.mr.hive.HiveIcebergStorageHandler"
+
+       if existing.Sd == nil {
+               updated.Sd = constructHiveTable(
+                       existing.DbName,
+                       existing.TableName,
+                       newLocation,
+                       newMetadataLocation,
+                       newSchema,
+                       newProps,
+               ).Sd
+       } else {
+               storageDescriptor := *existing.Sd
+               storageDescriptor.Cols = schemaToHiveColumns(newSchema)

Review Comment:
   `schemaToHiveColumns` calls `newSchema.Fields()` straight off the pointer, 
so a nil `newSchema` panics here (and in the `constructHiveTable` branch 
above). `staged.Schema()` is non-nil in practice so this is latent, but the 
six-arg signature makes a nil mis-call easy. A nil guard or a documented 
precondition would be enough.



##########
catalog/hive/schema.go:
##########
@@ -160,22 +161,51 @@ func constructHiveTable(dbName, tableName, location, 
metadataLocation string, sc
        }
 }
 
-func updateHiveTableForCommit(existing *hive_metastore.Table, 
newMetadataLocation string) *hive_metastore.Table {
-       // Copy the existing table
+func updateHiveTableForCommit(
+       existing *hive_metastore.Table,
+       currentProps, newProps iceberg.Properties,
+       newLocation string,
+       newSchema *iceberg.Schema,
+       newMetadataLocation string,
+) *hive_metastore.Table {
        updated := *existing
-
-       // Update parameters
+       updated.Parameters = maps.Clone(existing.Parameters)
        if updated.Parameters == nil {
                updated.Parameters = make(map[string]string)
        }
 
-       // Store previous metadata location
-       if oldLocation, ok := updated.Parameters[MetadataLocationKey]; ok {
+       for key := range currentProps {
+               delete(updated.Parameters, key)
+       }
+       for key, value := range newProps {
+               updated.Parameters[key] = value
+       }
+       delete(updated.Parameters, PreviousMetadataLocationKey)
+
+       if oldLocation, ok := existing.Parameters[MetadataLocationKey]; ok {
                updated.Parameters[PreviousMetadataLocationKey] = oldLocation
        }
 
-       // Set new metadata location
+       updated.Parameters[TableTypeKey] = TableTypeIceberg

Review Comment:
   Java's `HMSTablePropertyHelper` translates `gc.enabled` → 
`external.table.purge` when it syncs (`ICEBERG_TO_HMS_TRANSLATION`). We copy 
`gc.enabled` through verbatim but never write `external.table.purge`, so Hive's 
own `DROP TABLE` won't honour the GC intent — `gc.enabled=false` won't stop the 
purge, and `gc.enabled=true` won't trigger it.
   
   This was inert before because commits only touched `metadata_location`; now 
that we sync every property on commit it's live. I'd mirror `gc.enabled` into 
`external.table.purge` here, and match the same in `constructHiveTable` so both 
paths agree.



##########
catalog/hive/schema.go:
##########
@@ -160,22 +161,51 @@ func constructHiveTable(dbName, tableName, location, 
metadataLocation string, sc
        }
 }
 
-func updateHiveTableForCommit(existing *hive_metastore.Table, 
newMetadataLocation string) *hive_metastore.Table {
-       // Copy the existing table
+func updateHiveTableForCommit(
+       existing *hive_metastore.Table,
+       currentProps, newProps iceberg.Properties,
+       newLocation string,
+       newSchema *iceberg.Schema,
+       newMetadataLocation string,
+) *hive_metastore.Table {
        updated := *existing
-
-       // Update parameters
+       updated.Parameters = maps.Clone(existing.Parameters)
        if updated.Parameters == nil {
                updated.Parameters = make(map[string]string)
        }
 
-       // Store previous metadata location
-       if oldLocation, ok := updated.Parameters[MetadataLocationKey]; ok {
+       for key := range currentProps {
+               delete(updated.Parameters, key)
+       }
+       for key, value := range newProps {
+               updated.Parameters[key] = value
+       }
+       delete(updated.Parameters, PreviousMetadataLocationKey)
+
+       if oldLocation, ok := existing.Parameters[MetadataLocationKey]; ok {
                updated.Parameters[PreviousMetadataLocationKey] = oldLocation
        }
 
-       // Set new metadata location
+       updated.Parameters[TableTypeKey] = TableTypeIceberg
        updated.Parameters[MetadataLocationKey] = newMetadataLocation
+       updated.Parameters[ExternalKey] = "TRUE"
+       updated.Parameters["storage_handler"] = 
"org.apache.iceberg.mr.hive.HiveIcebergStorageHandler"

Review Comment:
   `storage_handler` and the handler class are string literals here and again 
in `constructHiveTable` (and now the test) — every other reserved key has a 
named constant in `options.go`. I'd pull `StorageHandlerKey` / 
`IcebergStorageHandler` out and use them in all three spots.



##########
catalog/hive/schema.go:
##########
@@ -160,22 +161,51 @@ func constructHiveTable(dbName, tableName, location, 
metadataLocation string, sc
        }
 }
 
-func updateHiveTableForCommit(existing *hive_metastore.Table, 
newMetadataLocation string) *hive_metastore.Table {
-       // Copy the existing table
+func updateHiveTableForCommit(
+       existing *hive_metastore.Table,
+       currentProps, newProps iceberg.Properties,
+       newLocation string,
+       newSchema *iceberg.Schema,
+       newMetadataLocation string,
+) *hive_metastore.Table {
        updated := *existing
-
-       // Update parameters
+       updated.Parameters = maps.Clone(existing.Parameters)
        if updated.Parameters == nil {
                updated.Parameters = make(map[string]string)
        }
 
-       // Store previous metadata location
-       if oldLocation, ok := updated.Parameters[MetadataLocationKey]; ok {
+       for key := range currentProps {
+               delete(updated.Parameters, key)
+       }
+       for key, value := range newProps {
+               updated.Parameters[key] = value
+       }
+       delete(updated.Parameters, PreviousMetadataLocationKey)
+
+       if oldLocation, ok := existing.Parameters[MetadataLocationKey]; ok {
                updated.Parameters[PreviousMetadataLocationKey] = oldLocation
        }
 
-       // Set new metadata location
+       updated.Parameters[TableTypeKey] = TableTypeIceberg
        updated.Parameters[MetadataLocationKey] = newMetadataLocation
+       updated.Parameters[ExternalKey] = "TRUE"
+       updated.Parameters["storage_handler"] = 
"org.apache.iceberg.mr.hive.HiveIcebergStorageHandler"
+
+       if existing.Sd == nil {
+               updated.Sd = constructHiveTable(
+                       existing.DbName,
+                       existing.TableName,
+                       newLocation,
+                       newMetadataLocation,
+                       newSchema,
+                       newProps,
+               ).Sd
+       } else {
+               storageDescriptor := *existing.Sd

Review Comment:
   `storageDescriptor := *existing.Sd` is a shallow copy — `SerdeInfo` (a 
`*SerDeInfo`) and the SD-level `Parameters` map stay aliased to `existing.Sd`. 
It's inert today since we only replace `Cols` and `Location` and discard 
`existing` after `AlterTable`, but a real HMS response populates 
`SerdeInfo.Parameters`, so this is a footgun waiting on the next edit.
   
   I'd clone `SerdeInfo` (and its `Parameters` map) when it's non-nil, 
consistent with the `maps.Clone` we already do for the table `Parameters`.



##########
catalog/hive/schema.go:
##########
@@ -160,22 +161,51 @@ func constructHiveTable(dbName, tableName, location, 
metadataLocation string, sc
        }
 }
 
-func updateHiveTableForCommit(existing *hive_metastore.Table, 
newMetadataLocation string) *hive_metastore.Table {
-       // Copy the existing table
+func updateHiveTableForCommit(
+       existing *hive_metastore.Table,
+       currentProps, newProps iceberg.Properties,
+       newLocation string,
+       newSchema *iceberg.Schema,
+       newMetadataLocation string,
+) *hive_metastore.Table {
        updated := *existing
-
-       // Update parameters
+       updated.Parameters = maps.Clone(existing.Parameters)
        if updated.Parameters == nil {
                updated.Parameters = make(map[string]string)
        }
 
-       // Store previous metadata location
-       if oldLocation, ok := updated.Parameters[MetadataLocationKey]; ok {
+       for key := range currentProps {
+               delete(updated.Parameters, key)
+       }
+       for key, value := range newProps {
+               updated.Parameters[key] = value
+       }
+       delete(updated.Parameters, PreviousMetadataLocationKey)
+
+       if oldLocation, ok := existing.Parameters[MetadataLocationKey]; ok {
                updated.Parameters[PreviousMetadataLocationKey] = oldLocation
        }
 
-       // Set new metadata location
+       updated.Parameters[TableTypeKey] = TableTypeIceberg
        updated.Parameters[MetadataLocationKey] = newMetadataLocation
+       updated.Parameters[ExternalKey] = "TRUE"
+       updated.Parameters["storage_handler"] = 
"org.apache.iceberg.mr.hive.HiveIcebergStorageHandler"
+
+       if existing.Sd == nil {
+               updated.Sd = constructHiveTable(

Review Comment:
   we call `constructHiveTable` here only to pull its `.Sd`, but `.Sd` is 
derived entirely from location and schema — `newProps` doesn't affect it, so 
passing it reads as if it does. A small `buildIcebergSD(location, schema)` 
helper called from both spots would be clearer.
   
   Also nothing covers this branch — `existing.Sd == nil` is never hit by the 
test. wdyt about a case that constructs `existing` with a nil `Sd`?



##########
catalog/hive/hive_test.go:
##########
@@ -888,6 +888,62 @@ func TestConstructHiveTablePreservesReservedParameters(t 
*testing.T) {
        assert.Equal("gzip", hiveTbl.Parameters["write.metadata.compression"])
 }
 
+func TestUpdateHiveTableForCommitSynchronizesMetadata(t *testing.T) {

Review Comment:
   the test drives `updateHiveTableForCommit` directly, which is great for the 
merge logic, but nothing exercises the real path through `CommitTable` — that 
`current.Properties()` maps to `currentProps` and `staged.Properties()` to 
`newProps`, and that `AlterTable` actually receives the merged table.
   
   Given the arg list grew to six positional params, a mock `CommitTable` test 
asserting the `AlterTable` payload has the new columns and merged params would 
lock the wiring down. Right now a swapped-arg regression would sail through.



##########
catalog/hive/hive_test.go:
##########
@@ -888,6 +888,62 @@ func TestConstructHiveTablePreservesReservedParameters(t 
*testing.T) {
        assert.Equal("gzip", hiveTbl.Parameters["write.metadata.compression"])
 }
 
+func TestUpdateHiveTableForCommitSynchronizesMetadata(t *testing.T) {
+       existing := constructHiveTable(
+               "test_database",
+               "test_table",
+               "s3://bucket/old-location",
+               "s3://bucket/old-location/metadata/v1.metadata.json",
+               testSchema,
+               iceberg.Properties{
+                       "changed": "old",
+                       "removed": "value",
+               },
+       )
+       existing.Parameters["hms-owned"] = "keep"
+       originalParameters := maps.Clone(existing.Parameters)
+       originalStorageDescriptor := *existing.Sd
+
+       newSchema := iceberg.NewSchema(
+               1,
+               iceberg.NestedField{ID: 1, Name: "renamed", Type: 
iceberg.PrimitiveTypes.String},
+       )
+       updated := updateHiveTableForCommit(
+               existing,
+               iceberg.Properties{"changed": "old", "removed": "value"},
+               iceberg.Properties{
+                       "changed":                   "new",
+                       "added":                     "value",
+                       TableTypeKey:                "HIVE",
+                       MetadataLocationKey:         "s3://wrong/metadata.json",
+                       PreviousMetadataLocationKey: "s3://wrong/previous.json",
+                       ExternalKey:                 "FALSE",
+                       "storage_handler":           "wrong.StorageHandler",
+               },
+               "s3://bucket/new-location",
+               newSchema,
+               "s3://bucket/new-location/metadata/v2.metadata.json",
+       )
+
+       require.Equal(t, "new", updated.Parameters["changed"])
+       require.Equal(t, "value", updated.Parameters["added"])
+       require.NotContains(t, updated.Parameters, "removed")
+       require.Equal(t, "keep", updated.Parameters["hms-owned"])
+       require.Equal(t, TableTypeIceberg, updated.Parameters[TableTypeKey])
+       require.Equal(t, "TRUE", updated.Parameters[ExternalKey])
+       require.Equal(t, 
"org.apache.iceberg.mr.hive.HiveIcebergStorageHandler", 
updated.Parameters["storage_handler"])
+       require.Equal(t, "s3://bucket/old-location/metadata/v1.metadata.json", 
updated.Parameters[PreviousMetadataLocationKey])
+       require.Equal(t, "s3://bucket/new-location/metadata/v2.metadata.json", 
updated.Parameters[MetadataLocationKey])
+       require.Equal(t, "s3://bucket/new-location", updated.Sd.Location)
+       require.Equal(t, schemaToHiveColumns(newSchema), updated.Sd.Cols)
+       require.Equal(t, originalStorageDescriptor.InputFormat, 
updated.Sd.InputFormat)
+       require.Equal(t, originalStorageDescriptor.OutputFormat, 
updated.Sd.OutputFormat)
+       require.Equal(t, originalStorageDescriptor.SerdeInfo, 
updated.Sd.SerdeInfo)

Review Comment:
   this passes whether `SerdeInfo` is a fresh copy or the same pointer — 
`require.Equal` is a deep-equal, and here it's literally the same pointer, so 
it doesn't prove the independence the copy is supposed to give us.
   
   To actually assert it, mutate `updated.Sd.SerdeInfo.Parameters` after the 
call and check `existing.Sd.SerdeInfo.Parameters` is untouched (or at least 
assert the pointers differ). Pairs with the shallow-copy note on the SD.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to