leaves12138 commented on code in PR #17:
URL: 
https://github.com/apache/terraform-provider-paimon/pull/17#discussion_r3943504008


##########
internal/provider/resource_table.go:
##########
@@ -85,44 +87,97 @@ func (r *tableResource) Configure(_ context.Context, req 
resource.ConfigureReque
 }
 
 func (r *tableResource) ModifyPlan(ctx context.Context, req 
resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) {
-       if req.State.Raw.IsNull() || req.Plan.Raw.IsNull() {
+       if req.Plan.Raw.IsNull() {
                return
        }
 
        var config, state, plan tableResourceModel
        resp.Diagnostics.Append(req.Config.Get(ctx, &config)...)
-       resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
+       if !req.State.Raw.IsNull() {
+               resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
+       }
        resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
        if resp.Diagnostics.HasError() {
                return
        }
 
-       var configuredFields, stateFields, plannedFields []tableFieldModel
-       resp.Diagnostics.Append(config.Fields.ElementsAs(ctx, 
&configuredFields, false)...)
-       resp.Diagnostics.Append(state.Fields.ElementsAs(ctx, &stateFields, 
false)...)
-       resp.Diagnostics.Append(plan.Fields.ElementsAs(ctx, &plannedFields, 
false)...)
-       if resp.Diagnostics.HasError() {
+       stabilizeTableKeys(ctx, config, state, &plan, &resp.Diagnostics)
+       replacementPaths := make([]path.Path, 0)
+       if tableFieldsInspectable(config.Fields) && 
tableFieldsInspectable(plan.Fields) {
+               var configuredFields, stateFields, plannedFields 
[]tableFieldModel
+               resp.Diagnostics.Append(config.Fields.ElementsAs(ctx, 
&configuredFields, false)...)
+               if !req.State.Raw.IsNull() {
+                       resp.Diagnostics.Append(state.Fields.ElementsAs(ctx, 
&stateFields, false)...)
+               }
+               resp.Diagnostics.Append(plan.Fields.ElementsAs(ctx, 
&plannedFields, false)...)
+               if resp.Diagnostics.HasError() {
+                       return
+               }
+               if len(configuredFields) != len(plannedFields) {
+                       resp.Diagnostics.AddError("Unable to stabilize Paimon 
field identities", "The configured and planned field lists have different 
lengths. Please report this issue to the provider developers.")
+
+                       return
+               }
+
+               stabilizePlannedFieldIdentities(configuredFields, stateFields, 
plannedFields)
+               if !req.State.Raw.IsNull() {
+                       validateAddedFieldIDs(stateFields, configuredFields, 
&resp.Diagnostics)
+               }
+               stabilizeFieldNullability(ctx, configuredFields, stateFields, 
plannedFields, plan, state, &resp.Diagnostics)
+               plan.Fields = fieldsValueFromModels(ctx, plannedFields, 
&resp.Diagnostics)
+               if resp.Diagnostics.HasError() {
+                       return
+               }
+               if !req.State.Raw.IsNull() {
+                       keyFields := knownTableKeyNames(state.PartitionKeys, 
state.PrimaryKeys, plan.PartitionKeys, plan.PrimaryKeys)
+                       if compositeFieldTypesRequireReplace(stateFields, 
plannedFields) ||
+                               keyFieldTypesRequireReplace(stateFields, 
plannedFields, keyFields) ||
+                               newNonNullableFieldsRequireReplace(stateFields, 
plannedFields) {
+                               replacementPaths = append(replacementPaths, 
path.Root("fields"))
+                       }
+               }
+
+       }
+       resp.Diagnostics.Append(resp.Plan.Set(ctx, &plan)...)
+       if req.State.Raw.IsNull() || resp.Diagnostics.HasError() {
                return
        }
-       if len(configuredFields) != len(plannedFields) {
-               resp.Diagnostics.AddError("Unable to stabilize Paimon field 
identities", "The configured and planned field lists have different lengths. 
Please report this issue to the provider developers.")
+       if !plan.PrimaryKeys.IsUnknown() && 
!state.PrimaryKeys.Equal(plan.PrimaryKeys) {
+               replacementPaths = append(replacementPaths, 
path.Root("options").AtMapKey("primary-key"))
+       }
+       if !plan.PartitionKeys.IsUnknown() && 
!state.PartitionKeys.Equal(plan.PartitionKeys) {

Review Comment:
   **[P1] Enforce replacement opt-in for wholly unknown partition keys**
   
   When the entire `partition_keys` value is unknown, this condition skips the 
opt-in check, but the attribute's 
`listplanmodifier.RequiresReplaceIfConfigured()` has already requested 
replacement. Terraform can consequently destroy/recreate the table with 
`allow_replacement = false`; replanning the replacement as a create does not 
rescue the check because that path has null prior state.
   
   For example, apply this configuration against an existing `analytics` 
database, then change only `terraform_data.partition.input` from `["tenant"]` 
to `["region"]`:
   
   ```hcl
   resource "terraform_data" "partition" {
     input = ["tenant"]
   }
   
   resource "paimon_table" "events" {
     database = "analytics"
     name     = "events"
     fields = [
       { name = "tenant", type = "STRING" },
       { name = "region", type = "STRING" }
     ]
     partition_keys    = terraform_data.partition.output
     allow_replacement = false
   }
   ```
   
   I reproduced this at `df3f709` using `acceptanceCatalog` and Terraform 
1.13.5. The second apply succeeds instead of returning `Destructive table 
change is disabled`; the fixture records **two table creates**, confirming that 
the existing table was replaced without opt-in. The newly added test only 
covers a partially unknown list, for which `IsUnknown()` is false, so it misses 
this case.
   
   Please make the opt-in cover attribute-level replacement decisions as well, 
including wholly unknown values, and add a protocol acceptance test that 
verifies the second apply is rejected without replacing the original table. The 
same interaction should also be checked for the `name`/`database` replacement 
modifiers.
   



##########
internal/provider/resource_table.go:
##########
@@ -85,44 +87,97 @@ func (r *tableResource) Configure(_ context.Context, req 
resource.ConfigureReque
 }
 
 func (r *tableResource) ModifyPlan(ctx context.Context, req 
resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) {
-       if req.State.Raw.IsNull() || req.Plan.Raw.IsNull() {
+       if req.Plan.Raw.IsNull() {
                return
        }
 
        var config, state, plan tableResourceModel
        resp.Diagnostics.Append(req.Config.Get(ctx, &config)...)
-       resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
+       if !req.State.Raw.IsNull() {
+               resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
+       }
        resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
        if resp.Diagnostics.HasError() {
                return
        }
 
-       var configuredFields, stateFields, plannedFields []tableFieldModel
-       resp.Diagnostics.Append(config.Fields.ElementsAs(ctx, 
&configuredFields, false)...)
-       resp.Diagnostics.Append(state.Fields.ElementsAs(ctx, &stateFields, 
false)...)
-       resp.Diagnostics.Append(plan.Fields.ElementsAs(ctx, &plannedFields, 
false)...)
-       if resp.Diagnostics.HasError() {
+       stabilizeTableKeys(ctx, config, state, &plan, &resp.Diagnostics)
+       replacementPaths := make([]path.Path, 0)
+       if tableFieldsInspectable(config.Fields) && 
tableFieldsInspectable(plan.Fields) {
+               var configuredFields, stateFields, plannedFields 
[]tableFieldModel
+               resp.Diagnostics.Append(config.Fields.ElementsAs(ctx, 
&configuredFields, false)...)
+               if !req.State.Raw.IsNull() {
+                       resp.Diagnostics.Append(state.Fields.ElementsAs(ctx, 
&stateFields, false)...)
+               }
+               resp.Diagnostics.Append(plan.Fields.ElementsAs(ctx, 
&plannedFields, false)...)
+               if resp.Diagnostics.HasError() {
+                       return
+               }
+               if len(configuredFields) != len(plannedFields) {
+                       resp.Diagnostics.AddError("Unable to stabilize Paimon 
field identities", "The configured and planned field lists have different 
lengths. Please report this issue to the provider developers.")
+
+                       return
+               }
+
+               stabilizePlannedFieldIdentities(configuredFields, stateFields, 
plannedFields)
+               if !req.State.Raw.IsNull() {
+                       validateAddedFieldIDs(stateFields, configuredFields, 
&resp.Diagnostics)
+               }
+               stabilizeFieldNullability(ctx, configuredFields, stateFields, 
plannedFields, plan, state, &resp.Diagnostics)
+               keyFields := append(stringListFromValue(ctx, 
state.PartitionKeys, &resp.Diagnostics), stringListFromValue(ctx, 
state.PrimaryKeys, &resp.Diagnostics)...)
+               keyFields = append(keyFields, stringListFromValue(ctx, 
plan.PartitionKeys, &resp.Diagnostics)...)

Review Comment:
   Verified fixed at df3f709: my original protocol acceptance reproducer with 
known fields and `partition_keys = [terraform_data.partition.output]` now 
passes. The new null-element validation and mixed known/unknown replacement 
coverage also pass. I found a separate whole-list-unknown replacement opt-in 
bypass during follow-up testing and reported it in the new review; this 
original creation-conversion finding itself is addressed.



-- 
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]

Reply via email to