From 9beea059f4acab0d076066d47c25173554bc8fc3 Mon Sep 17 00:00:00 2001 From: "newton@alisx.com" Date: Mon, 29 Jul 2024 13:03:01 +0300 Subject: [PATCH] chore(spanner): improve proto bundle creation, add default value to prevent_destroy --- go.mod | 9 +- go.sum | 16 ++- .../alis_google_spanner_table_resource.go | 5 +- internal/spanner/services/services_test.go | 56 +++++----- internal/spanner/services/utils.go | 104 +++++++++++++----- 5 files changed, 124 insertions(+), 66 deletions(-) diff --git a/go.mod b/go.mod index b110f7e..6bbf0f8 100644 --- a/go.mod +++ b/go.mod @@ -3,9 +3,9 @@ module terraform-provider-alis go 1.22 require ( - cloud.google.com/go/bigtable v1.26.0 - cloud.google.com/go/discoveryengine v1.9.0 - cloud.google.com/go/iam v1.1.11 + cloud.google.com/go/bigtable v1.27.1 + cloud.google.com/go/discoveryengine v1.10.0 + cloud.google.com/go/iam v1.1.12 cloud.google.com/go/spanner v1.64.0 cloud.google.com/go/storage v1.43.0 github.com/google/go-cmp v0.6.0 @@ -33,6 +33,7 @@ require ( cloud.google.com/go/auth/oauth2adapt v0.2.3 // indirect cloud.google.com/go/compute/metadata v0.5.0 // indirect cloud.google.com/go/longrunning v0.5.10 // indirect + cloud.google.com/go/monitoring v1.20.1 // indirect github.com/BurntSushi/toml v1.2.1 // indirect github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.0 // indirect github.com/Kunde21/markdownfmt/v3 v3.1.0 // indirect @@ -108,6 +109,8 @@ require ( go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect + go.opentelemetry.io/otel/sdk v1.24.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect golang.org/x/crypto v0.25.0 // indirect golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect diff --git a/go.sum b/go.sum index 6df6e20..ff37491 100644 --- a/go.sum +++ b/go.sum @@ -133,8 +133,8 @@ cloud.google.com/go/bigquery v1.47.0/go.mod h1:sA9XOgy0A8vQK9+MWhEQTY6Tix87M/Zur cloud.google.com/go/bigquery v1.48.0/go.mod h1:QAwSz+ipNgfL5jxiaK7weyOhzdoAy1zFm0Nf1fysJac= cloud.google.com/go/bigquery v1.49.0/go.mod h1:Sv8hMmTFFYBlt/ftw2uN6dFdQPzBlREY9yBh7Oy7/4Q= cloud.google.com/go/bigquery v1.50.0/go.mod h1:YrleYEh2pSEbgTBZYMJ5SuSr0ML3ypjRB1zgf7pvQLU= -cloud.google.com/go/bigtable v1.26.0 h1:Gxk6UA21+3uz6QUm8d9wpxHznf6hr29HkwqWekfZ8Rs= -cloud.google.com/go/bigtable v1.26.0/go.mod h1:nqDsH+YoPA1oTmCEfWfsUGYV7NDIaufhwJgs+eh5bGo= +cloud.google.com/go/bigtable v1.27.1 h1:SFKsPZF+ddGmUFcwsHsajVRP1gg8JfvEe7ExqZ4SQ+c= +cloud.google.com/go/bigtable v1.27.1/go.mod h1:AMREzzQzYjiWYan7JvJXINc8dfqemnNBWDHlYONtPLw= cloud.google.com/go/billing v1.4.0/go.mod h1:g9IdKBEFlItS8bTtlrZdVLWSSdSyFUZKXNS02zKMOZY= cloud.google.com/go/billing v1.5.0/go.mod h1:mztb1tBc3QekhjSgmpf/CV4LzWXLzCArwpLmP2Gm88s= cloud.google.com/go/billing v1.6.0/go.mod h1:WoXzguj+BeHXPbKfNWkqVtDdzORazmCjraY+vrxcyvI= @@ -254,8 +254,8 @@ cloud.google.com/go/dialogflow v1.19.0/go.mod h1:JVmlG1TwykZDtxtTXujec4tQ+D8SBFM cloud.google.com/go/dialogflow v1.29.0/go.mod h1:b+2bzMe+k1s9V+F2jbJwpHPzrnIyHihAdRFMtn2WXuM= cloud.google.com/go/dialogflow v1.31.0/go.mod h1:cuoUccuL1Z+HADhyIA7dci3N5zUssgpBJmCzI6fNRB4= cloud.google.com/go/dialogflow v1.32.0/go.mod h1:jG9TRJl8CKrDhMEcvfcfFkkpp8ZhgPz3sBGmAUYJ2qE= -cloud.google.com/go/discoveryengine v1.9.0 h1:4AXqQRiNtedoR8Ev/s5Rd2R4zaQ74PGNCQnDumCKbr8= -cloud.google.com/go/discoveryengine v1.9.0/go.mod h1:HsuZ6ZcRhS7TYaoJoWgcS17cOFBeDSprgsmh9FnQcpI= +cloud.google.com/go/discoveryengine v1.10.0 h1:LktMurW+kPEAHETzA5kGGG8v1wUW8ETtQahb86I1gw8= +cloud.google.com/go/discoveryengine v1.10.0/go.mod h1:KauPECIzETtH6VShNqCct4hU4ZtEJAoEz4cfIHkPpQc= cloud.google.com/go/dlp v1.6.0/go.mod h1:9eyB2xIhpU0sVwUixfBubDoRwP+GjeUoxxeueZmqvmM= cloud.google.com/go/dlp v1.7.0/go.mod h1:68ak9vCiMBjbasxeVD17hVPxDEck+ExiHavX8kiHG+Q= cloud.google.com/go/dlp v1.9.0/go.mod h1:qdgmqgTyReTz5/YNSSuueR8pl7hO0o9bQ39ZhtgkWp4= @@ -323,8 +323,8 @@ cloud.google.com/go/iam v0.8.0/go.mod h1:lga0/y3iH6CX7sYqypWJ33hf7kkfXJag67naqGE cloud.google.com/go/iam v0.11.0/go.mod h1:9PiLDanza5D+oWFZiH1uG+RnRCfEGKoyl6yo4cgWZGY= cloud.google.com/go/iam v0.12.0/go.mod h1:knyHGviacl11zrtZUoDuYpDgLjvr28sLQaG0YB2GYAY= cloud.google.com/go/iam v0.13.0/go.mod h1:ljOg+rcNfzZ5d6f1nAUJ8ZIxOaZUVoS14bKCtaLZ/D0= -cloud.google.com/go/iam v1.1.11 h1:0mQ8UKSfdHLut6pH9FM3bI55KWR46ketn0PuXleDyxw= -cloud.google.com/go/iam v1.1.11/go.mod h1:biXoiLWYIKntto2joP+62sd9uW5EpkZmKIvfNcTWlnQ= +cloud.google.com/go/iam v1.1.12 h1:JixGLimRrNGcxvJEQ8+clfLxPlbeZA6MuRJ+qJNQ5Xw= +cloud.google.com/go/iam v1.1.12/go.mod h1:9LDX8J7dN5YRyzVHxwQzrQs9opFFqn0Mxs9nAeB+Hhg= cloud.google.com/go/iap v1.4.0/go.mod h1:RGFwRJdihTINIe4wZ2iCP0zF/qu18ZwyKxrhMhygBEc= cloud.google.com/go/iap v1.5.0/go.mod h1:UH/CGgKd4KyohZL5Pt0jSKE4m3FR51qg6FKQ/z/Ix9A= cloud.google.com/go/iap v1.6.0/go.mod h1:NSuvI9C/j7UdjGjIde7t7HBz+QTwBcapPE07+sSRcLk= @@ -382,6 +382,8 @@ cloud.google.com/go/monitoring v1.7.0/go.mod h1:HpYse6kkGo//7p6sT0wsIC6IBDET0RhI cloud.google.com/go/monitoring v1.8.0/go.mod h1:E7PtoMJ1kQXWxPjB6mv2fhC5/15jInuulFdYYtlcvT4= cloud.google.com/go/monitoring v1.12.0/go.mod h1:yx8Jj2fZNEkL/GYZyTLS4ZtZEZN8WtDEiEqG4kLK50w= cloud.google.com/go/monitoring v1.13.0/go.mod h1:k2yMBAB1H9JT/QETjNkgdCGD9bPF712XiLTVr+cBrpw= +cloud.google.com/go/monitoring v1.20.1 h1:XmM6uk4+mI2ZhWdI2n/2GNhJdpeQN+1VdG2UWEDhX48= +cloud.google.com/go/monitoring v1.20.1/go.mod h1:FYSe/brgfuaXiEzOQFhTjsEsJv+WePyK71X7Y8qo6uQ= cloud.google.com/go/networkconnectivity v1.4.0/go.mod h1:nOl7YL8odKyAOtzNX73/M5/mGZgqqMeryi6UPZTk/rA= cloud.google.com/go/networkconnectivity v1.5.0/go.mod h1:3GzqJx7uhtlM3kln0+x5wyFvuVH1pIBJjhCpjzSt75o= cloud.google.com/go/networkconnectivity v1.6.0/go.mod h1:OJOoEXW+0LAxHh89nXd64uGG+FbQoeH8DtxCHVOMlaM= @@ -1115,6 +1117,8 @@ go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGX go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw= go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg= +go.opentelemetry.io/otel/sdk/metric v1.24.0 h1:yyMQrPzF+k88/DbH7o4FMAs80puqd+9osbiBrJrz/w8= +go.opentelemetry.io/otel/sdk/metric v1.24.0/go.mod h1:I6Y5FjH6rvEnTTAYQz3Mmv2kl6Ek5IIrmwTLqMrrOE0= go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= diff --git a/internal/spanner/alis_google_spanner_table_resource.go b/internal/spanner/alis_google_spanner_table_resource.go index a079111..72fb4c9 100644 --- a/internal/spanner/alis_google_spanner_table_resource.go +++ b/internal/spanner/alis_google_spanner_table_resource.go @@ -11,6 +11,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault" "github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" @@ -268,8 +269,10 @@ func (r *spannerTableResource) Schema(_ context.Context, _ resource.SchemaReques }, "prevent_destroy": schema.BoolAttribute{ Optional: true, + Computed: true, Description: "Prevent the table from being destroyed.\n" + "**This only applies to the terraform state and does not prevent the actual table from being deleted via another source.**", + Default: booldefault.StaticBool(true), }, }, Description: "A Google Cloud Spanner table resource.\n" + @@ -756,7 +759,7 @@ func (r *spannerTableResource) Delete(ctx context.Context, req resource.DeleteRe if state.PreventDestroy.ValueBool() { resp.Diagnostics.AddError( "Error Deleting Table", - "Table ("+state.Name.ValueString()+") is protected from deletion by terraform configuration.", + "Table ("+state.Name.ValueString()+") is protected from deletion by terraform configuration. Set `prevent_destroy` to false.", ) return } diff --git a/internal/spanner/services/services_test.go b/internal/spanner/services/services_test.go index aab9879..04f25d4 100644 --- a/internal/spanner/services/services_test.go +++ b/internal/spanner/services/services_test.go @@ -351,7 +351,7 @@ func TestCreateSpannerTable(t *testing.T) { Schema: &SpannerTableSchema{ Columns: []*SpannerTableColumn{ { - Name: "id", + Name: "key", IsPrimaryKey: wrapperspb.Bool(true), Unique: wrapperspb.Bool(false), Type: "INT64", @@ -359,27 +359,26 @@ func TestCreateSpannerTable(t *testing.T) { Required: wrapperspb.Bool(true), }, { - Name: "portfolio", + Name: "test", IsPrimaryKey: wrapperspb.Bool(false), Unique: wrapperspb.Bool(false), Type: "PROTO", ProtoFileDescriptorSet: &ProtoFileDescriptorSet{ - ProtoPackage: wrapperspb.String("alis.px.resources.portfolios.v1.Portfolio"), + ProtoPackage: wrapperspb.String("alis.px.services.data.v2.SpannerTest.NestedEnum"), FileDescriptorSetPath: wrapperspb.String("gcs:gs://internal.descriptorset.alis-px-product-g51dmvo.alis.services/descriptorset.pb"), FileDescriptorSetPathSource: ProtoFileDescriptorSetSourceGcs, }, }, - { - Name: "portfolio_name", - IsPrimaryKey: wrapperspb.Bool(true), - Unique: wrapperspb.Bool(false), - Type: "STRING", - IsComputed: wrapperspb.Bool(true), - ComputationDdl: wrapperspb.String("portfolio.name"), - Required: wrapperspb.Bool(true), - Size: wrapperspb.Int64(255), - DefaultValue: wrapperspb.String("default"), - }, + //{ + // Name: "branch_test", + // IsPrimaryKey: wrapperspb.Bool(false), + // Unique: wrapperspb.Bool(false), + // Type: "STRING", + // //IsComputed: wrapperspb.Bool(true), + // //ComputationDdl: wrapperspb.String("branch.name"), + // Required: wrapperspb.Bool(false), + // Size: wrapperspb.Int64(255), + //}, }, }, }, @@ -455,7 +454,7 @@ func TestUpdateSpannerTable(t *testing.T) { Schema: &SpannerTableSchema{ Columns: []*SpannerTableColumn{ { - Name: "id", + Name: "key", IsPrimaryKey: wrapperspb.Bool(true), Unique: wrapperspb.Bool(false), Type: "INT64", @@ -463,27 +462,26 @@ func TestUpdateSpannerTable(t *testing.T) { Required: wrapperspb.Bool(true), }, { - Name: "portfolio", + Name: "test", IsPrimaryKey: wrapperspb.Bool(false), Unique: wrapperspb.Bool(false), Type: "PROTO", ProtoFileDescriptorSet: &ProtoFileDescriptorSet{ - ProtoPackage: wrapperspb.String("alis.px.resources.portfolios.v1.Portfolio"), + ProtoPackage: wrapperspb.String("alis.px.resources.portfolios.v1.Branch"), //FileDescriptorSetPath: wrapperspb.String("gcs:gs://internal.descriptorset.alis-px-product-g51dmvo.alis.services/descriptorset.pb"), //FileDescriptorSetPathSource: ProtoFileDescriptorSetSourceGcs, }, }, - //{ - // Name: "portfolio_name", - // IsPrimaryKey: wrapperspb.Bool(true), - // Unique: wrapperspb.Bool(false), - // Type: "STRING", - // IsComputed: wrapperspb.Bool(true), - // ComputationDdl: wrapperspb.String("portfolio.name"), - // Required: wrapperspb.Bool(true), - // Size: wrapperspb.Int64(255), - // DefaultValue: wrapperspb.String("default"), - //}, + { + Name: "branch_tests", + //IsPrimaryKey: wrapperspb.Bool(false), + //Unique: wrapperspb.Bool(false), + Type: "ARRAY", + //IsComputed: wrapperspb.Bool(true), + //ComputationDdl: wrapperspb.String("test.name"), + Required: wrapperspb.Bool(true), + //Size: wrapperspb.Int64(255), + }, }, }, }, @@ -1314,7 +1312,7 @@ func TestCreateProtoBundle(t *testing.T) { args: args{ ctx: context.Background(), databaseName: fmt.Sprintf("projects/%s/instances/%s/databases/%s", TestProject, TestInstance, "tf-test"), - protoPackageName: "alis.px.resources.portfolios.v1.NAVCommit", + protoPackageName: "alis.px.services.data.v2.SpannerTest", }, }, } diff --git a/internal/spanner/services/utils.go b/internal/spanner/services/utils.go index 912bb65..ce8ab9e 100644 --- a/internal/spanner/services/utils.go +++ b/internal/spanner/services/utils.go @@ -4,7 +4,6 @@ import ( "context" "errors" "fmt" - "log" "sort" "strings" "time" @@ -504,56 +503,107 @@ func CreateProtoBundle(ctx context.Context, databaseName string, protoPackageNam return err } - var getNestedProtoPackageNames func(ctx context.Context, desc protoreflect.Descriptor) ([]string, error) - getNestedProtoPackageNames = func(ctx context.Context, desc protoreflect.Descriptor) ([]string, error) { + // Unmarshal the proto file descriptor set + fds := &descriptorpb.FileDescriptorSet{} + if err := proto.Unmarshal(descriptorSet, fds); err != nil { + return status.Errorf(codes.Internal, "Error unmarshalling proto file descriptor set: %v", err) + } + + files, err := protodesc.NewFiles(fds) + if err != nil { + return status.Errorf(codes.Internal, "Error creating proto files: %v", err) + } + + var getProtoPackageNamesFn func(ctx context.Context, parent string, desc protoreflect.Descriptor) ([]string, error) + getProtoPackageNamesFn = func(ctx context.Context, parent string, desc protoreflect.Descriptor) ([]string, error) { var protoPackageNames []string switch d := desc.(type) { case protoreflect.MessageDescriptor: // Add the proto package name protoPackageNames = append(protoPackageNames, fmt.Sprintf("%s", d.FullName())) - // Add the proto package names for the enums - for i := 0; i < d.Enums().Len(); i++ { - protoPackageNames = append(protoPackageNames, fmt.Sprintf("%s", d.Enums().Get(i).FullName())) + nestedProtoPackageParentNamesMap := map[string]protoreflect.Descriptor{} + + for i := 0; i < d.Fields().Len(); i++ { + field := d.Fields().Get(i) + + switch field.Kind() { + case protoreflect.MessageKind: + // Get nested proto package names + nestedProtoPackageNames, err := getProtoPackageNamesFn(ctx, parent, field.Message()) + if err != nil { + return nil, err + } + protoPackageNames = append(protoPackageNames, nestedProtoPackageNames...) + + // Get the nested proto package names of the parent + if field.Message().Parent() != nil { + nestedProtoPackageParentName := fmt.Sprintf("%s", field.Message().Parent().FullName()) + nestedProtoPackageParentNamesMap[nestedProtoPackageParentName] = field.Message().Parent() + } + case protoreflect.EnumKind: + protoPackageNames = append(protoPackageNames, fmt.Sprintf("%s", field.Enum().FullName())) + + // Get the nested proto package names of the parent + if field.Enum().Parent() != nil { + nestedProtoPackageParentName := fmt.Sprintf("%s", field.Enum().Parent().FullName()) + nestedProtoPackageParentNamesMap[nestedProtoPackageParentName] = field.Enum().Parent() + } + } } - // Add the proto package names for the nested messages - for i := 0; i < d.Messages().Len(); i++ { - nestedProtoPackageNames, err := getNestedProtoPackageNames(ctx, d.Messages().Get(i)) + // Get the nested proto package names of the parents + for nestedProtoPackageParentName, nestedDesc := range nestedProtoPackageParentNamesMap { + if nestedProtoPackageParentName == parent { + continue + } + + nestedProtoPackageNames, err := getProtoPackageNamesFn(ctx, nestedProtoPackageParentName, nestedDesc) if err != nil { return nil, err } protoPackageNames = append(protoPackageNames, nestedProtoPackageNames...) } - } + case protoreflect.EnumDescriptor: + // Add the proto package name + protoPackageNames = append(protoPackageNames, fmt.Sprintf("%s", d.FullName())) - return protoPackageNames, nil - } + if d.Parent() != nil { + // Get the nested proto package names of the parent + nestedProtoPackageParentName := fmt.Sprintf("%s", d.Parent().FullName()) - // Unmarshal the proto file descriptor set - fds := &descriptorpb.FileDescriptorSet{} - if err := proto.Unmarshal(descriptorSet, fds); err != nil { - return status.Errorf(codes.Internal, "Error unmarshalling proto file descriptor set: %v", err) - } + // Get the nested proto package names of the parents + nestedProtoPackageNames, err := getProtoPackageNamesFn(ctx, nestedProtoPackageParentName, d.Parent()) + if err != nil { + return nil, err + } - files, err := protodesc.NewFiles(fds) - if err != nil { - return status.Errorf(codes.Internal, "Error creating proto files: %v", err) + protoPackageNames = append(protoPackageNames, nestedProtoPackageNames...) + + } + } + + return protoPackageNames, nil } + // Get the message/enum descriptor desc, err := files.FindDescriptorByName(protoreflect.FullName(protoPackageName)) if err != nil { return status.Errorf(codes.Internal, "Error finding descriptor for %s: %v", protoPackageName, err) } - // TODO: Revisit later - nestedProtoPackageNames, err := getNestedProtoPackageNames(ctx, desc) + // Get the proto package names including nested messages and enums + protoPackageNames, err := getProtoPackageNamesFn(ctx, protoPackageName, desc) if err != nil { return err } - log.Printf("nestedProtoPackageNames: %v", nestedProtoPackageNames) + // Remove any duplicates + protoPackageNames = alUtils.Unique(protoPackageNames) + + // Sort the proto package names + sort.Strings(protoPackageNames) updateDatabaseDdl := func(ctx context.Context, databaseName string, statements []string, descriptorSet []byte) error { // Create the proto bundle @@ -569,11 +619,11 @@ func CreateProtoBundle(ctx context.Context, databaseName string, protoPackageNam return updateDdlOp.Wait(ctx) } - formattedNestedProtoPackageNames := alUtils.Transform(nestedProtoPackageNames, func(name string) string { + formattedProtoPackageNames := alUtils.Transform(protoPackageNames, func(name string) string { return fmt.Sprintf("`%s`", name) }) - createStatement := fmt.Sprintf("CREATE PROTO BUNDLE (%s)", strings.Join(formattedNestedProtoPackageNames, ", ")) + createStatement := fmt.Sprintf("CREATE PROTO BUNDLE (%s)", strings.Join(formattedProtoPackageNames, ", ")) err = updateDatabaseDdl(ctx, databaseName, []string{createStatement}, descriptorSet) if err != nil { if status.Code(err) != codes.AlreadyExists && status.Code(err) != codes.InvalidArgument { @@ -581,7 +631,7 @@ func CreateProtoBundle(ctx context.Context, databaseName string, protoPackageNam } // Try to insert the proto bundle - insertStatement := fmt.Sprintf("ALTER PROTO BUNDLE INSERT (%s)", strings.Join(formattedNestedProtoPackageNames, ", ")) + insertStatement := fmt.Sprintf("ALTER PROTO BUNDLE INSERT (%s)", strings.Join(formattedProtoPackageNames, ", ")) err = updateDatabaseDdl(ctx, databaseName, []string{insertStatement}, descriptorSet) if err != nil { if status.Code(err) != codes.AlreadyExists && status.Code(err) != codes.InvalidArgument { @@ -589,7 +639,7 @@ func CreateProtoBundle(ctx context.Context, databaseName string, protoPackageNam } // Try to update the proto bundle - updateStatement := fmt.Sprintf("ALTER PROTO BUNDLE UPDATE (%s)", strings.Join(formattedNestedProtoPackageNames, ", ")) + updateStatement := fmt.Sprintf("ALTER PROTO BUNDLE UPDATE (%s)", strings.Join(formattedProtoPackageNames, ", ")) err = updateDatabaseDdl(ctx, databaseName, []string{updateStatement}, descriptorSet) if err != nil { return err