This is an automated email from the ASF dual-hosted git repository.

zeroshade pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/iceberg-go.git


The following commit(s) were added to refs/heads/main by this push:
     new 6f3b0b008 feat(table): add UpdateRequirements helpers for 
create/replace/update commits (#2220)
6f3b0b008 is described below

commit 6f3b0b008b2468b19f39897162d109724ff0028d
Author: Alex Stephen <[email protected]>
AuthorDate: Mon Oct 19 13:10:11 2026 -0700

    feat(table): add UpdateRequirements helpers for create/replace/update 
commits (#2220)
    
    * Update requirements
    
    * Use new functions
    
    * remove builder
---
 catalog/rest/rest.go              |   4 +-
 table/update_requirements.go      | 161 ++++++++++++++++
 table/update_requirements_test.go | 391 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 553 insertions(+), 3 deletions(-)

diff --git a/catalog/rest/rest.go b/catalog/rest/rest.go
index 33ffe5c60..2d6db178b 100644
--- a/catalog/rest/rest.go
+++ b/catalog/rest/rest.go
@@ -1774,9 +1774,7 @@ func (r *Catalog) commitStagedCreate(ctx context.Context, 
identifier table.Ident
        // Append user-provided staged updates at the end.
        updates = append(updates, stagedUpdates...)
 
-       requirements := []table.Requirement{table.AssertCreate()}
-
-       return r.CommitTable(ctx, identifier, requirements, updates)
+       return r.CommitTable(ctx, identifier, 
table.RequirementsForCreateTable(updates), updates)
 }
 
 func (r *Catalog) CommitTable(ctx context.Context, ident table.Identifier, 
requirements []table.Requirement, updates []table.Update) (table.Metadata, 
string, error) {
diff --git a/table/update_requirements.go b/table/update_requirements.go
new file mode 100644
index 000000000..cd076c7c4
--- /dev/null
+++ b/table/update_requirements.go
@@ -0,0 +1,161 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package table
+
+import (
+       "fmt"
+       "slices"
+
+       "github.com/apache/iceberg-go"
+)
+
+// RequirementsForCreateTable returns the requirements for committing updates
+// that create a new table, mirroring Java's UpdateRequirements.forCreateTable.
+func RequirementsForCreateTable(updates []Update) []Requirement {
+       return deriveRequirements(nil, false, AssertCreate(), updates)
+}
+
+// RequirementsForReplaceTable returns the requirements for committing updates
+// that replace the table described by base. A replace overwrites the current
+// schema, spec, sort order and refs, so only ID assignment is guarded.
+func RequirementsForReplaceTable(base Metadata, updates []Update) 
([]Requirement, error) {
+       if base == nil {
+               return nil, fmt.Errorf("%w: table metadata is nil", 
iceberg.ErrInvalidArgument)
+       }
+
+       return deriveRequirements(base, true, 
AssertTableUUID(base.TableUUID()), updates), nil
+}
+
+// RequirementsForUpdateTable returns the requirements for committing updates
+// against the table described by base, so a commit fails instead of
+// overwriting a concurrent change. Use it to build the requirements for
+// Catalog.CommitTable when not going through Transaction.
+func RequirementsForUpdateTable(base Metadata, updates []Update) 
([]Requirement, error) {
+       if base == nil {
+               return nil, fmt.Errorf("%w: table metadata is nil", 
iceberg.ErrInvalidArgument)
+       }
+
+       return deriveRequirements(base, false, 
AssertTableUUID(base.TableUUID()), updates), nil
+}
+
+// deriveRequirements asserts the base state each update depends on. Every
+// requirement is computed from base, so duplicates are dropped by type (and
+// ref name for ref assertions), like requirementSemanticKey.
+func deriveRequirements(base Metadata, isReplace bool, first Requirement, 
updates []Update) []Requirement {
+       reqs := []Requirement{first}
+       if base == nil {
+               return reqs
+       }
+
+       seen := make(map[string]bool)
+       add := func(key string, r Requirement) {
+               if !seen[key] {
+                       seen[key] = true
+                       reqs = append(reqs, r)
+               }
+       }
+       addRef := func(name string) {
+               add(reqAssertRefSnapshotID+"\x00"+name, 
AssertRefSnapshotID(name, refSnapshotID(base, name)))
+       }
+       // Old schemas and specs must not be written by a branch that changed 
concurrently.
+       addBranches := func() {
+               for _, name := range nonMainBranches(base) {
+                       addRef(name)
+               }
+       }
+       addCurrentSchema := func() {
+               add(reqAssertCurrentSchemaID, 
AssertCurrentSchemaID(base.CurrentSchema().ID))
+       }
+       addDefaultSpec := func() {
+               add(reqAssertDefaultSpecID, 
AssertDefaultSpecID(base.DefaultPartitionSpec()))
+       }
+
+       for _, u := range updates {
+               // A replace overwrites the current state, so only ID 
assignment is guarded.
+               switch u := u.(type) {
+               case *addSchemaUpdate:
+                       add(reqAssertLastAssignedFieldID, 
AssertLastAssignedFieldID(base.LastColumnID()))
+               case *addPartitionSpecUpdate:
+                       add(reqAssertLastAssignedPartitionID, 
AssertLastAssignedPartitionID(lastAssignedPartitionID(base)))
+               case *setSnapshotRefUpdate:
+                       if !isReplace {
+                               addRef(u.RefName)
+                       }
+               case *setCurrentSchemaUpdate:
+                       if !isReplace {
+                               addCurrentSchema()
+                       }
+               case *setDefaultSpecUpdate:
+                       if !isReplace {
+                               addDefaultSpec()
+                       }
+               case *setDefaultSortOrderUpdate:
+                       if !isReplace {
+                               add(reqAssertDefaultSortOrderID, 
AssertDefaultSortOrderID(base.DefaultSortOrder()))
+                       }
+               case *removeSpecUpdate:
+                       if !isReplace {
+                               addDefaultSpec()
+                               addBranches()
+                       }
+               case *removeSchemasUpdate:
+                       if !isReplace {
+                               addCurrentSchema()
+                               addBranches()
+                       }
+               }
+       }
+
+       return reqs
+}
+
+// refSnapshotID returns the snapshot the named ref points at, or nil if the
+// ref does not exist.
+func refSnapshotID(meta Metadata, name string) *int64 {
+       for n, ref := range meta.Refs() {
+               if n == name {
+                       return &ref.SnapshotID
+               }
+       }
+
+       return nil
+}
+
+// nonMainBranches returns the names of base's branches other than main, sorted
+// so the requirement order is deterministic.
+func nonMainBranches(base Metadata) []string {
+       var names []string
+       for name, ref := range base.Refs() {
+               if ref.SnapshotRefType == BranchRef && name != MainBranch {
+                       names = append(names, name)
+               }
+       }
+       slices.Sort(names)
+
+       return names
+}
+
+// lastAssignedPartitionID falls back to the ID before the first partition
+// field when base has none, as UpdateSpec does.
+func lastAssignedPartitionID(base Metadata) int {
+       if id := base.LastPartitionSpecID(); id != nil {
+               return *id
+       }
+
+       return iceberg.PartitionDataIDStart - 1
+}
diff --git a/table/update_requirements_test.go 
b/table/update_requirements_test.go
new file mode 100644
index 000000000..c4f8676a0
--- /dev/null
+++ b/table/update_requirements_test.go
@@ -0,0 +1,391 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package table_test
+
+import (
+       "strings"
+       "testing"
+
+       "github.com/apache/iceberg-go"
+       "github.com/apache/iceberg-go/table"
+       "github.com/google/uuid"
+       "github.com/stretchr/testify/assert"
+       "github.com/stretchr/testify/require"
+)
+
+const (
+       reqTestSnapshotID   int64 = 3051729675574597004
+       reqTestCurrentSnap  int64 = 3055729675574597004
+       reqTestLastColumnID       = 3
+       reqTestSchemaID           = 1
+       reqTestLastPartID         = 1000
+       reqTestSpecID             = 0
+       reqTestSortOrderID        = 3
+)
+
+var reqTestUUID = uuid.MustParse("9c12d441-03fe-4693-9a96-a0705ddf69c1")
+
+// requirementsTestMetadata is ExampleTableMetadataV2 plus a non-main branch,
+// so branch-sensitive updates have something to assert.
+func requirementsTestMetadata(t *testing.T) table.Metadata {
+       t.Helper()
+
+       js := strings.Replace(table.ExampleTableMetadataV2,
+               `"refs": {`,
+               `"refs": {"dev": {"snapshot-id": 3051729675574597004, "type": 
"branch"}, `, 1)
+       meta, err := table.ParseMetadataString(js)
+       require.NoError(t, err)
+
+       return meta
+}
+
+func newSchemaForRequirements() *iceberg.Schema {
+       return iceberg.NewSchema(2,
+               iceberg.NestedField{ID: 1, Name: "x", Type: 
iceberg.PrimitiveTypes.Int64, Required: true},
+               iceberg.NestedField{ID: 4, Name: "w", Type: 
iceberg.PrimitiveTypes.String},
+       )
+}
+
+func TestRequirementsForUpdateTableNilBase(t *testing.T) {
+       _, err := table.RequirementsForUpdateTable(nil, nil)
+       assert.ErrorIs(t, err, iceberg.ErrInvalidArgument)
+
+       _, err = table.RequirementsForReplaceTable(nil, nil)
+       assert.ErrorIs(t, err, iceberg.ErrInvalidArgument)
+}
+
+func TestRequirementsForCreateTable(t *testing.T) {
+       assert.Equal(t, []table.Requirement{table.AssertCreate()}, 
table.RequirementsForCreateTable(nil))
+
+       // With no base, schema/spec/order updates add nothing beyond 
assert-create.
+       spec := iceberg.NewPartitionSpec()
+       order := table.UnsortedSortOrder
+       reqs := table.RequirementsForCreateTable([]table.Update{
+               table.NewAssignUUIDUpdate(reqTestUUID),
+               table.NewAddSchemaUpdate(newSchemaForRequirements()),
+               table.NewSetCurrentSchemaUpdate(-1),
+               table.NewAddPartitionSpecUpdate(&spec, true),
+               table.NewSetDefaultSpecUpdate(-1),
+               table.NewAddSortOrderUpdate(&order),
+               table.NewSetDefaultSortOrderUpdate(-1),
+               table.NewSetSnapshotRefUpdate(table.MainBranch, 1, 
table.BranchRef, 0, 0, 0),
+       })
+       assert.Equal(t, []table.Requirement{table.AssertCreate()}, reqs)
+}
+
+func TestRequirementsForUpdateTable(t *testing.T) {
+       meta := requirementsTestMetadata(t)
+       uuidReq := table.AssertTableUUID(reqTestUUID)
+       spec := iceberg.NewPartitionSpec()
+       order := table.UnsortedSortOrder
+
+       tests := []struct {
+               name    string
+               updates []table.Update
+               want    []table.Requirement
+       }{
+               {
+                       name: "empty",
+                       want: []table.Requirement{uuidReq},
+               },
+               {
+                       name: "updates without requirements",
+                       updates: []table.Update{
+                               table.NewAssignUUIDUpdate(reqTestUUID),
+                               table.NewUpgradeFormatVersionUpdate(3),
+                               table.NewAddSortOrderUpdate(&order),
+                               
table.NewSetPropertiesUpdate(iceberg.Properties{"a": "b"}),
+                               table.NewRemovePropertiesUpdate([]string{"a"}),
+                               table.NewSetLocationUpdate("s3://bucket/other"),
+                               table.NewRemoveSnapshotRefUpdate("dev"),
+                       },
+                       want: []table.Requirement{uuidReq},
+               },
+               {
+                       name: "add-schema is asserted once",
+                       updates: []table.Update{
+                               
table.NewAddSchemaUpdate(newSchemaForRequirements()),
+                               
table.NewAddSchemaUpdate(newSchemaForRequirements()),
+                       },
+                       want: []table.Requirement{uuidReq, 
table.AssertLastAssignedFieldID(reqTestLastColumnID)},
+               },
+               {
+                       name: "set-current-schema is asserted once",
+                       updates: []table.Update{
+                               table.NewSetCurrentSchemaUpdate(0),
+                               table.NewSetCurrentSchemaUpdate(1),
+                       },
+                       want: []table.Requirement{uuidReq, 
table.AssertCurrentSchemaID(reqTestSchemaID)},
+               },
+               {
+                       name: "add-spec is asserted once",
+                       updates: []table.Update{
+                               table.NewAddPartitionSpecUpdate(&spec, false),
+                               table.NewAddPartitionSpecUpdate(&spec, false),
+                       },
+                       want: []table.Requirement{uuidReq, 
table.AssertLastAssignedPartitionID(reqTestLastPartID)},
+               },
+               {
+                       name: "set-default-spec is asserted once",
+                       updates: []table.Update{
+                               table.NewSetDefaultSpecUpdate(-1),
+                               table.NewSetDefaultSpecUpdate(0),
+                       },
+                       want: []table.Requirement{uuidReq, 
table.AssertDefaultSpecID(reqTestSpecID)},
+               },
+               {
+                       name: "set-default-sort-order is asserted once",
+                       updates: []table.Update{
+                               table.NewSetDefaultSortOrderUpdate(-1),
+                               table.NewSetDefaultSortOrderUpdate(0),
+                       },
+                       want: []table.Requirement{uuidReq, 
table.AssertDefaultSortOrderID(reqTestSortOrderID)},
+               },
+               {
+                       name: "remove-partition-specs asserts default spec and 
non-main branches",
+                       updates: []table.Update{
+                               table.NewRemoveSpecUpdate([]int{1}),
+                               table.NewRemoveSpecUpdate([]int{2}),
+                       },
+                       want: []table.Requirement{
+                               uuidReq,
+                               table.AssertDefaultSpecID(reqTestSpecID),
+                               table.AssertRefSnapshotID("dev", 
new(reqTestSnapshotID)),
+                       },
+               },
+               {
+                       name: "remove-schemas asserts current schema and 
non-main branches",
+                       updates: []table.Update{
+                               table.NewRemoveSchemasUpdate([]int{0}),
+                       },
+                       want: []table.Requirement{
+                               uuidReq,
+                               table.AssertCurrentSchemaID(reqTestSchemaID),
+                               table.AssertRefSnapshotID("dev", 
new(reqTestSnapshotID)),
+                       },
+               },
+               {
+                       name: "set-snapshot-ref asserts the base snapshot, or 
absence for a new ref",
+                       updates: []table.Update{
+                               table.NewSetSnapshotRefUpdate(table.MainBranch, 
1, table.BranchRef, 0, 0, 0),
+                               table.NewSetSnapshotRefUpdate(table.MainBranch, 
2, table.BranchRef, 0, 0, 0),
+                               table.NewSetSnapshotRefUpdate("new", 1, 
table.BranchRef, 0, 0, 0),
+                       },
+                       want: []table.Requirement{
+                               uuidReq,
+                               table.AssertRefSnapshotID(table.MainBranch, 
new(reqTestCurrentSnap)),
+                               table.AssertRefSnapshotID("new", nil),
+                       },
+               },
+               {
+                       name: "a branch asserted by set-snapshot-ref is not 
asserted again",
+                       updates: []table.Update{
+                               table.NewSetSnapshotRefUpdate("dev", 1, 
table.BranchRef, 0, 0, 0),
+                               table.NewRemoveSchemasUpdate([]int{0}),
+                       },
+                       want: []table.Requirement{
+                               uuidReq,
+                               table.AssertRefSnapshotID("dev", 
new(reqTestSnapshotID)),
+                               table.AssertCurrentSchemaID(reqTestSchemaID),
+                       },
+               },
+               {
+                       name: "schema and partition evolution",
+                       updates: []table.Update{
+                               
table.NewAddSchemaUpdate(newSchemaForRequirements()),
+                               table.NewSetCurrentSchemaUpdate(-1),
+                               table.NewAddPartitionSpecUpdate(&spec, false),
+                               table.NewSetDefaultSpecUpdate(-1),
+                               table.NewAddSortOrderUpdate(&order),
+                               table.NewSetDefaultSortOrderUpdate(-1),
+                       },
+                       want: []table.Requirement{
+                               uuidReq,
+                               
table.AssertLastAssignedFieldID(reqTestLastColumnID),
+                               table.AssertCurrentSchemaID(reqTestSchemaID),
+                               
table.AssertLastAssignedPartitionID(reqTestLastPartID),
+                               table.AssertDefaultSpecID(reqTestSpecID),
+                               
table.AssertDefaultSortOrderID(reqTestSortOrderID),
+                       },
+               },
+       }
+
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       got, err := table.RequirementsForUpdateTable(meta, 
tt.updates)
+                       require.NoError(t, err)
+                       assert.Equal(t, tt.want, got)
+
+                       for _, r := range got {
+                               assert.NoError(t, r.Validate(meta), "%s must 
hold against its own base", r.GetType())
+                       }
+               })
+       }
+}
+
+func TestRequirementsForReplaceTable(t *testing.T) {
+       meta := requirementsTestMetadata(t)
+       spec := iceberg.NewPartitionSpec()
+       order := table.UnsortedSortOrder
+
+       // A replace only guards ID assignment; it overwrites the current 
schema,
+       // default spec, sort order and refs.
+       got, err := table.RequirementsForReplaceTable(meta, []table.Update{
+               table.NewAddSchemaUpdate(newSchemaForRequirements()),
+               table.NewSetCurrentSchemaUpdate(-1),
+               table.NewAddPartitionSpecUpdate(&spec, false),
+               table.NewSetDefaultSpecUpdate(-1),
+               table.NewAddSortOrderUpdate(&order),
+               table.NewSetDefaultSortOrderUpdate(-1),
+               table.NewSetSnapshotRefUpdate(table.MainBranch, 1, 
table.BranchRef, 0, 0, 0),
+               table.NewRemoveSpecUpdate([]int{1}),
+               table.NewRemoveSchemasUpdate([]int{0}),
+       })
+       require.NoError(t, err)
+       assert.Equal(t, []table.Requirement{
+               table.AssertTableUUID(reqTestUUID),
+               table.AssertLastAssignedFieldID(reqTestLastColumnID),
+               table.AssertLastAssignedPartitionID(reqTestLastPartID),
+       }, got)
+}
+
+type noLastPartitionIDMetadata struct{ table.Metadata }
+
+func (noLastPartitionIDMetadata) LastPartitionSpecID() *int { return nil }
+
+func TestRequirementsForUpdateTableMissingLastPartitionID(t *testing.T) {
+       meta := noLastPartitionIDMetadata{requirementsTestMetadata(t)}
+       spec := iceberg.NewPartitionSpec()
+
+       got, err := table.RequirementsForUpdateTable(meta, []table.Update{
+               table.NewAddPartitionSpecUpdate(&spec, false),
+       })
+       require.NoError(t, err)
+       assert.Equal(t, []table.Requirement{
+               table.AssertTableUUID(reqTestUUID),
+               
table.AssertLastAssignedPartitionID(iceberg.PartitionDataIDStart - 1),
+       }, got)
+}
+
+// Each requirement must fail once a concurrent commit changes the state it
+// guards, so a stale commit is rejected instead of overwriting that change.
+func TestRequirementsForUpdateTableConcurrentChange(t *testing.T) {
+       meta := requirementsTestMetadata(t)
+
+       tests := []struct {
+               name       string
+               update     table.Update
+               concurrent func(*table.MetadataBuilder) error
+       }{
+               {
+                       name:   "add-schema",
+                       update: 
table.NewAddSchemaUpdate(newSchemaForRequirements()),
+                       concurrent: func(b *table.MetadataBuilder) error {
+                               return b.AddSchema(iceberg.NewSchema(2,
+                                       iceberg.NestedField{ID: 1, Name: "x", 
Type: iceberg.PrimitiveTypes.Int64, Required: true},
+                                       iceberg.NestedField{ID: 4, Name: "v", 
Type: iceberg.PrimitiveTypes.Int32},
+                               ))
+                       },
+               },
+               {
+                       name:   "set-current-schema",
+                       update: table.NewSetCurrentSchemaUpdate(0),
+                       concurrent: func(b *table.MetadataBuilder) error {
+                               if err := b.AddSchema(iceberg.NewSchema(2,
+                                       iceberg.NestedField{ID: 1, Name: "x", 
Type: iceberg.PrimitiveTypes.Int64, Required: true},
+                                       iceberg.NestedField{ID: 2, Name: "y", 
Type: iceberg.PrimitiveTypes.Int64, Required: true},
+                                       iceberg.NestedField{ID: 3, Name: "z", 
Type: iceberg.PrimitiveTypes.Int64, Required: true},
+                                       iceberg.NestedField{ID: 4, Name: "v", 
Type: iceberg.PrimitiveTypes.Int32},
+                               )); err != nil {
+                                       return err
+                               }
+
+                               return b.SetCurrentSchemaID(-1)
+                       },
+               },
+               {
+                       name:   "add-spec",
+                       update: 
table.NewAddPartitionSpecUpdate(new(iceberg.NewPartitionSpec()), false),
+                       concurrent: func(b *table.MetadataBuilder) error {
+                               spec := 
iceberg.NewPartitionSpec(iceberg.PartitionField{
+                                       SourceIDs: []int{2}, FieldID: 1001, 
Name: "y", Transform: iceberg.IdentityTransform{},
+                               })
+
+                               return b.AddPartitionSpec(&spec, false)
+                       },
+               },
+               {
+                       name:   "set-default-spec",
+                       update: table.NewSetDefaultSpecUpdate(-1),
+                       concurrent: func(b *table.MetadataBuilder) error {
+                               spec := iceberg.NewPartitionSpec()
+                               if err := b.AddPartitionSpec(&spec, false); err 
!= nil {
+                                       return err
+                               }
+
+                               return b.SetDefaultSpecID(-1)
+                       },
+               },
+               {
+                       name:   "set-default-sort-order",
+                       update: table.NewSetDefaultSortOrderUpdate(-1),
+                       concurrent: func(b *table.MetadataBuilder) error {
+                               order := table.UnsortedSortOrder
+                               if err := b.AddSortOrder(&order); err != nil {
+                                       return err
+                               }
+
+                               return b.SetDefaultSortOrderID(-1)
+                       },
+               },
+               {
+                       name:   "set-snapshot-ref",
+                       update: table.NewSetSnapshotRefUpdate(table.MainBranch, 
reqTestSnapshotID, table.BranchRef, 0, 0, 0),
+                       concurrent: func(b *table.MetadataBuilder) error {
+                               return b.SetSnapshotRef(table.MainBranch, 
reqTestSnapshotID, table.BranchRef)
+                       },
+               },
+               {
+                       name:   "remove-schemas with a moved branch",
+                       update: table.NewRemoveSchemasUpdate([]int{0}),
+                       concurrent: func(b *table.MetadataBuilder) error {
+                               return b.SetSnapshotRef("dev", 
reqTestCurrentSnap, table.BranchRef)
+                       },
+               },
+       }
+
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       reqs, err := table.RequirementsForUpdateTable(meta, 
[]table.Update{tt.update})
+                       require.NoError(t, err)
+
+                       b, err := table.MetadataBuilderFromBase(meta, "")
+                       require.NoError(t, err)
+                       require.NoError(t, tt.concurrent(b))
+                       changed, err := b.Build()
+                       require.NoError(t, err)
+
+                       // The table UUID is unchanged, so the failure must 
come from the
+                       // last requirement, which guards the state the 
concurrent commit changed.
+                       require.Greater(t, len(reqs), 1)
+                       assert.NoError(t, reqs[0].Validate(changed))
+                       last := reqs[len(reqs)-1]
+                       assert.Error(t, last.Validate(changed), "%s should 
reject the concurrent change", last.GetType())
+               })
+       }
+}

Reply via email to