diff --git a/doc/md/migrate.md b/doc/md/migrate.md index adec1c2bf3..c0c6d2eb63 100755 --- a/doc/md/migrate.md +++ b/doc/md/migrate.md @@ -300,6 +300,8 @@ options for hooking into schema migration steps. ![atlas-migration-process](https://entgo.io/images/assets/migrate-atlas-process.png) +#### Atlas `Diff` and `Apply` Hooks + Here are two examples that show how to hook into the Atlas `Diff` and `Apply` steps. ```go @@ -363,3 +365,54 @@ func main() { } } ``` + +In case a field was renamed in the `ent/schema`, Ent won't detect this change as renaming and will propose `DropColumn` +and `AddColumn` changes in the diff stage. One way to get over this is to use the +[StorageKey](schema-fields.md#storage-key) option on the field and keep the old column name in the database table. +However, using Atlas `Diff` hooks allow replacing the `DropColumn` and `AddColumn` changes with a `RenameColumn` change. + +```go +func main() { + client, err := ent.Open("mysql", "root:pass@tcp(localhost:3306)/test") + if err != nil { + log.Fatalf("failed connecting to mysql: %v", err) + } + defer client.Close() + // ... + if err := client.Schema.Create(ctx, schema.WithDiffHook(renameColumnHook)); err != nil { + log.Fatalf("failed creating schema resources: %v", err) + } +} + +func renameColumnHook(next schema.Differ) schema.Differ { + return schema.DiffFunc(func(current, desired *atlas.Schema) ([]atlas.Change, error) { + changes, err := next.Diff(current, desired) + if err != nil { + return nil, err + } + for _, c := range changes { + m, ok := c.(*atlas.ModifyTable) + // Skip if the change is not a ModifyTable, + // or if the table is not the "users" table. + if !ok || m.T.Name != user.Table { + continue + } + changes := atlas.Changes(m.Changes) + switch i, j := changes.IndexDropColumn("old_name"), changes.IndexAddColumn("new_name"); { + case i != -1 && j != -1: + // Append a new renaming change. + changes = append(changes, &atlas.RenameColumn{ + From: changes[i].(*atlas.DropColumn).C, + To: changes[j].(*atlas.AddColumn).C, + }) + // Remove the drop and add changes. + changes.RemoveIndex(i, j) + m.Changes = changes + case i != -1 || j != -1: + return nil, errors.New("old_name and new_name must be present or absent") + } + } + return changes, nil + }) +} +``` \ No newline at end of file diff --git a/entc/integration/migrate/entv1/migrate/schema.go b/entc/integration/migrate/entv1/migrate/schema.go index 1dd1706bf0..f151b11742 100644 --- a/entc/integration/migrate/entv1/migrate/schema.go +++ b/entc/integration/migrate/entv1/migrate/schema.go @@ -71,6 +71,7 @@ var ( {Name: "nickname", Type: field.TypeString, Unique: true}, {Name: "address", Type: field.TypeString, Nullable: true}, {Name: "renamed", Type: field.TypeString, Nullable: true}, + {Name: "old_token", Type: field.TypeString}, {Name: "blob", Type: field.TypeBytes, Nullable: true, Size: 255}, {Name: "state", Type: field.TypeEnum, Nullable: true, Enums: []string{"logged_in", "logged_out"}, Default: "logged_in"}, {Name: "status", Type: field.TypeString, Nullable: true}, @@ -86,13 +87,13 @@ var ( ForeignKeys: []*schema.ForeignKey{ { Symbol: "users_users_children", - Columns: []*schema.Column{UsersColumns[11]}, + Columns: []*schema.Column{UsersColumns[12]}, RefColumns: []*schema.Column{UsersColumns[0]}, OnDelete: schema.SetNull, }, { Symbol: "users_users_spouse", - Columns: []*schema.Column{UsersColumns[12]}, + Columns: []*schema.Column{UsersColumns[13]}, RefColumns: []*schema.Column{UsersColumns[0]}, OnDelete: schema.SetNull, }, diff --git a/entc/integration/migrate/entv1/mutation.go b/entc/integration/migrate/entv1/mutation.go index 96fb483f26..ea6f3a7612 100644 --- a/entc/integration/migrate/entv1/mutation.go +++ b/entc/integration/migrate/entv1/mutation.go @@ -1892,6 +1892,7 @@ type UserMutation struct { nickname *string address *string renamed *string + old_token *string blob *[]byte state *user.State status *string @@ -2290,6 +2291,42 @@ func (m *UserMutation) ResetRenamed() { delete(m.clearedFields, user.FieldRenamed) } +// SetOldToken sets the "old_token" field. +func (m *UserMutation) SetOldToken(s string) { + m.old_token = &s +} + +// OldToken returns the value of the "old_token" field in the mutation. +func (m *UserMutation) OldToken() (r string, exists bool) { + v := m.old_token + if v == nil { + return + } + return *v, true +} + +// OldOldToken returns the old "old_token" field's value of the User entity. +// If the User object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *UserMutation) OldOldToken(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldOldToken is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldOldToken requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldOldToken: %w", err) + } + return oldValue.OldToken, nil +} + +// ResetOldToken resets all changes to the "old_token" field. +func (m *UserMutation) ResetOldToken() { + m.old_token = nil +} + // SetBlob sets the "blob" field. func (m *UserMutation) SetBlob(b []byte) { m.blob = &b @@ -2676,7 +2713,7 @@ func (m *UserMutation) Type() string { // order to get all numeric fields that were incremented/decremented, call // AddedFields(). func (m *UserMutation) Fields() []string { - fields := make([]string, 0, 10) + fields := make([]string, 0, 11) if m.age != nil { fields = append(fields, user.FieldAge) } @@ -2695,6 +2732,9 @@ func (m *UserMutation) Fields() []string { if m.renamed != nil { fields = append(fields, user.FieldRenamed) } + if m.old_token != nil { + fields = append(fields, user.FieldOldToken) + } if m.blob != nil { fields = append(fields, user.FieldBlob) } @@ -2727,6 +2767,8 @@ func (m *UserMutation) Field(name string) (ent.Value, bool) { return m.Address() case user.FieldRenamed: return m.Renamed() + case user.FieldOldToken: + return m.OldToken() case user.FieldBlob: return m.Blob() case user.FieldState: @@ -2756,6 +2798,8 @@ func (m *UserMutation) OldField(ctx context.Context, name string) (ent.Value, er return m.OldAddress(ctx) case user.FieldRenamed: return m.OldRenamed(ctx) + case user.FieldOldToken: + return m.OldOldToken(ctx) case user.FieldBlob: return m.OldBlob(ctx) case user.FieldState: @@ -2815,6 +2859,13 @@ func (m *UserMutation) SetField(name string, value ent.Value) error { } m.SetRenamed(v) return nil + case user.FieldOldToken: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetOldToken(v) + return nil case user.FieldBlob: v, ok := value.([]byte) if !ok { @@ -2970,6 +3021,9 @@ func (m *UserMutation) ResetField(name string) error { case user.FieldRenamed: m.ResetRenamed() return nil + case user.FieldOldToken: + m.ResetOldToken() + return nil case user.FieldBlob: m.ResetBlob() return nil diff --git a/entc/integration/migrate/entv1/runtime.go b/entc/integration/migrate/entv1/runtime.go index d30b226b8c..95860ebf8a 100644 --- a/entc/integration/migrate/entv1/runtime.go +++ b/entc/integration/migrate/entv1/runtime.go @@ -21,12 +21,16 @@ func init() { userDescName := userFields[2].Descriptor() // user.NameValidator is a validator for the "name" field. It is called by the builders before save. user.NameValidator = userDescName.Validators[0].(func(string) error) + // userDescOldToken is the schema descriptor for old_token field. + userDescOldToken := userFields[7].Descriptor() + // user.DefaultOldToken holds the default value on creation for the old_token field. + user.DefaultOldToken = userDescOldToken.Default.(func() string) // userDescBlob is the schema descriptor for blob field. - userDescBlob := userFields[7].Descriptor() + userDescBlob := userFields[8].Descriptor() // user.BlobValidator is a validator for the "blob" field. It is called by the builders before save. user.BlobValidator = userDescBlob.Validators[0].(func([]byte) error) // userDescWorkplace is the schema descriptor for workplace field. - userDescWorkplace := userFields[10].Descriptor() + userDescWorkplace := userFields[11].Descriptor() // user.WorkplaceValidator is a validator for the "workplace" field. It is called by the builders before save. user.WorkplaceValidator = userDescWorkplace.Validators[0].(func(string) error) } diff --git a/entc/integration/migrate/entv1/schema/user.go b/entc/integration/migrate/entv1/schema/user.go index 62bed364d4..35aaf28d68 100644 --- a/entc/integration/migrate/entv1/schema/user.go +++ b/entc/integration/migrate/entv1/schema/user.go @@ -11,6 +11,7 @@ import ( "entgo.io/ent/schema/edge" "entgo.io/ent/schema/field" "entgo.io/ent/schema/index" + "github.com/google/uuid" ) // User holds the schema definition for the User entity. @@ -34,6 +35,8 @@ func (User) Fields() []ent.Field { Optional(), field.String("renamed"). Optional(), + field.String("old_token"). + DefaultFunc(uuid.NewString), field.Bytes("blob"). Optional(). MaxLen(255), diff --git a/entc/integration/migrate/entv1/user.go b/entc/integration/migrate/entv1/user.go index 642e072a0f..08e6cb3361 100644 --- a/entc/integration/migrate/entv1/user.go +++ b/entc/integration/migrate/entv1/user.go @@ -32,6 +32,8 @@ type User struct { Address string `json:"address,omitempty"` // Renamed holds the value of the "renamed" field. Renamed string `json:"renamed,omitempty"` + // OldToken holds the value of the "old_token" field. + OldToken string `json:"old_token,omitempty"` // Blob holds the value of the "blob" field. Blob []byte `json:"blob,omitempty"` // State holds the value of the "state" field. @@ -122,7 +124,7 @@ func (*User) scanValues(columns []string) ([]interface{}, error) { values[i] = new([]byte) case user.FieldID, user.FieldAge: values[i] = new(sql.NullInt64) - case user.FieldName, user.FieldDescription, user.FieldNickname, user.FieldAddress, user.FieldRenamed, user.FieldState, user.FieldStatus, user.FieldWorkplace: + case user.FieldName, user.FieldDescription, user.FieldNickname, user.FieldAddress, user.FieldRenamed, user.FieldOldToken, user.FieldState, user.FieldStatus, user.FieldWorkplace: values[i] = new(sql.NullString) case user.ForeignKeys[0]: // user_children values[i] = new(sql.NullInt64) @@ -185,6 +187,12 @@ func (u *User) assignValues(columns []string, values []interface{}) error { } else if value.Valid { u.Renamed = value.String } + case user.FieldOldToken: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field old_token", values[i]) + } else if value.Valid { + u.OldToken = value.String + } case user.FieldBlob: if value, ok := values[i].(*[]byte); !ok { return fmt.Errorf("unexpected type %T for field blob", values[i]) @@ -283,6 +291,8 @@ func (u *User) String() string { builder.WriteString(u.Address) builder.WriteString(", renamed=") builder.WriteString(u.Renamed) + builder.WriteString(", old_token=") + builder.WriteString(u.OldToken) builder.WriteString(", blob=") builder.WriteString(fmt.Sprintf("%v", u.Blob)) builder.WriteString(", state=") diff --git a/entc/integration/migrate/entv1/user/user.go b/entc/integration/migrate/entv1/user/user.go index 8ffb3b11e9..c4bb94e5b7 100644 --- a/entc/integration/migrate/entv1/user/user.go +++ b/entc/integration/migrate/entv1/user/user.go @@ -27,6 +27,8 @@ const ( FieldAddress = "address" // FieldRenamed holds the string denoting the renamed field in the database. FieldRenamed = "renamed" + // FieldOldToken holds the string denoting the old_token field in the database. + FieldOldToken = "old_token" // FieldBlob holds the string denoting the blob field in the database. FieldBlob = "blob" // FieldState holds the string denoting the state field in the database. @@ -77,6 +79,7 @@ var Columns = []string{ FieldNickname, FieldAddress, FieldRenamed, + FieldOldToken, FieldBlob, FieldState, FieldStatus, @@ -108,6 +111,8 @@ func ValidColumn(column string) bool { var ( // NameValidator is a validator for the "name" field. It is called by the builders before save. NameValidator func(string) error + // DefaultOldToken holds the default value on creation for the "old_token" field. + DefaultOldToken func() string // BlobValidator is a validator for the "blob" field. It is called by the builders before save. BlobValidator func([]byte) error // WorkplaceValidator is a validator for the "workplace" field. It is called by the builders before save. diff --git a/entc/integration/migrate/entv1/user/where.go b/entc/integration/migrate/entv1/user/where.go index 4fb1e81fb2..a0b550aa54 100644 --- a/entc/integration/migrate/entv1/user/where.go +++ b/entc/integration/migrate/entv1/user/where.go @@ -137,6 +137,13 @@ func Renamed(v string) predicate.User { }) } +// OldToken applies equality check predicate on the "old_token" field. It's identical to OldTokenEQ. +func OldToken(v string) predicate.User { + return predicate.User(func(s *sql.Selector) { + s.Where(sql.EQ(s.C(FieldOldToken), v)) + }) +} + // Blob applies equality check predicate on the "blob" field. It's identical to BlobEQ. func Blob(v []byte) predicate.User { return predicate.User(func(s *sql.Selector) { @@ -831,6 +838,117 @@ func RenamedContainsFold(v string) predicate.User { }) } +// OldTokenEQ applies the EQ predicate on the "old_token" field. +func OldTokenEQ(v string) predicate.User { + return predicate.User(func(s *sql.Selector) { + s.Where(sql.EQ(s.C(FieldOldToken), v)) + }) +} + +// OldTokenNEQ applies the NEQ predicate on the "old_token" field. +func OldTokenNEQ(v string) predicate.User { + return predicate.User(func(s *sql.Selector) { + s.Where(sql.NEQ(s.C(FieldOldToken), v)) + }) +} + +// OldTokenIn applies the In predicate on the "old_token" field. +func OldTokenIn(vs ...string) predicate.User { + v := make([]interface{}, len(vs)) + for i := range v { + v[i] = vs[i] + } + return predicate.User(func(s *sql.Selector) { + // if not arguments were provided, append the FALSE constants, + // since we can't apply "IN ()". This will make this predicate falsy. + if len(v) == 0 { + s.Where(sql.False()) + return + } + s.Where(sql.In(s.C(FieldOldToken), v...)) + }) +} + +// OldTokenNotIn applies the NotIn predicate on the "old_token" field. +func OldTokenNotIn(vs ...string) predicate.User { + v := make([]interface{}, len(vs)) + for i := range v { + v[i] = vs[i] + } + return predicate.User(func(s *sql.Selector) { + // if not arguments were provided, append the FALSE constants, + // since we can't apply "IN ()". This will make this predicate falsy. + if len(v) == 0 { + s.Where(sql.False()) + return + } + s.Where(sql.NotIn(s.C(FieldOldToken), v...)) + }) +} + +// OldTokenGT applies the GT predicate on the "old_token" field. +func OldTokenGT(v string) predicate.User { + return predicate.User(func(s *sql.Selector) { + s.Where(sql.GT(s.C(FieldOldToken), v)) + }) +} + +// OldTokenGTE applies the GTE predicate on the "old_token" field. +func OldTokenGTE(v string) predicate.User { + return predicate.User(func(s *sql.Selector) { + s.Where(sql.GTE(s.C(FieldOldToken), v)) + }) +} + +// OldTokenLT applies the LT predicate on the "old_token" field. +func OldTokenLT(v string) predicate.User { + return predicate.User(func(s *sql.Selector) { + s.Where(sql.LT(s.C(FieldOldToken), v)) + }) +} + +// OldTokenLTE applies the LTE predicate on the "old_token" field. +func OldTokenLTE(v string) predicate.User { + return predicate.User(func(s *sql.Selector) { + s.Where(sql.LTE(s.C(FieldOldToken), v)) + }) +} + +// OldTokenContains applies the Contains predicate on the "old_token" field. +func OldTokenContains(v string) predicate.User { + return predicate.User(func(s *sql.Selector) { + s.Where(sql.Contains(s.C(FieldOldToken), v)) + }) +} + +// OldTokenHasPrefix applies the HasPrefix predicate on the "old_token" field. +func OldTokenHasPrefix(v string) predicate.User { + return predicate.User(func(s *sql.Selector) { + s.Where(sql.HasPrefix(s.C(FieldOldToken), v)) + }) +} + +// OldTokenHasSuffix applies the HasSuffix predicate on the "old_token" field. +func OldTokenHasSuffix(v string) predicate.User { + return predicate.User(func(s *sql.Selector) { + s.Where(sql.HasSuffix(s.C(FieldOldToken), v)) + }) +} + +// OldTokenEqualFold applies the EqualFold predicate on the "old_token" field. +func OldTokenEqualFold(v string) predicate.User { + return predicate.User(func(s *sql.Selector) { + s.Where(sql.EqualFold(s.C(FieldOldToken), v)) + }) +} + +// OldTokenContainsFold applies the ContainsFold predicate on the "old_token" field. +func OldTokenContainsFold(v string) predicate.User { + return predicate.User(func(s *sql.Selector) { + s.Where(sql.ContainsFold(s.C(FieldOldToken), v)) + }) +} + // BlobEQ applies the EQ predicate on the "blob" field. func BlobEQ(v []byte) predicate.User { return predicate.User(func(s *sql.Selector) { diff --git a/entc/integration/migrate/entv1/user_create.go b/entc/integration/migrate/entv1/user_create.go index ceacff449d..4ce37509f1 100644 --- a/entc/integration/migrate/entv1/user_create.go +++ b/entc/integration/migrate/entv1/user_create.go @@ -84,6 +84,20 @@ func (uc *UserCreate) SetNillableRenamed(s *string) *UserCreate { return uc } +// SetOldToken sets the "old_token" field. +func (uc *UserCreate) SetOldToken(s string) *UserCreate { + uc.mutation.SetOldToken(s) + return uc +} + +// SetNillableOldToken sets the "old_token" field if the given value is not nil. +func (uc *UserCreate) SetNillableOldToken(s *string) *UserCreate { + if s != nil { + uc.SetOldToken(*s) + } + return uc +} + // SetBlob sets the "blob" field. func (uc *UserCreate) SetBlob(b []byte) *UserCreate { uc.mutation.SetBlob(b) @@ -281,6 +295,10 @@ func (uc *UserCreate) ExecX(ctx context.Context) { // defaults sets the default values of the builder before save. func (uc *UserCreate) defaults() { + if _, ok := uc.mutation.OldToken(); !ok { + v := user.DefaultOldToken() + uc.mutation.SetOldToken(v) + } if _, ok := uc.mutation.State(); !ok { v := user.DefaultState uc.mutation.SetState(v) @@ -303,6 +321,9 @@ func (uc *UserCreate) check() error { if _, ok := uc.mutation.Nickname(); !ok { return &ValidationError{Name: "nickname", err: errors.New(`entv1: missing required field "User.nickname"`)} } + if _, ok := uc.mutation.OldToken(); !ok { + return &ValidationError{Name: "old_token", err: errors.New(`entv1: missing required field "User.old_token"`)} + } if v, ok := uc.mutation.Blob(); ok { if err := user.BlobValidator(v); err != nil { return &ValidationError{Name: "blob", err: fmt.Errorf(`entv1: validator failed for field "User.blob": %w`, err)} @@ -399,6 +420,14 @@ func (uc *UserCreate) createSpec() (*User, *sqlgraph.CreateSpec) { }) _node.Renamed = value } + if value, ok := uc.mutation.OldToken(); ok { + _spec.Fields = append(_spec.Fields, &sqlgraph.FieldSpec{ + Type: field.TypeString, + Value: value, + Column: user.FieldOldToken, + }) + _node.OldToken = value + } if value, ok := uc.mutation.Blob(); ok { _spec.Fields = append(_spec.Fields, &sqlgraph.FieldSpec{ Type: field.TypeBytes, diff --git a/entc/integration/migrate/entv1/user_update.go b/entc/integration/migrate/entv1/user_update.go index a704cd76c6..385b043025 100644 --- a/entc/integration/migrate/entv1/user_update.go +++ b/entc/integration/migrate/entv1/user_update.go @@ -117,6 +117,20 @@ func (uu *UserUpdate) ClearRenamed() *UserUpdate { return uu } +// SetOldToken sets the "old_token" field. +func (uu *UserUpdate) SetOldToken(s string) *UserUpdate { + uu.mutation.SetOldToken(s) + return uu +} + +// SetNillableOldToken sets the "old_token" field if the given value is not nil. +func (uu *UserUpdate) SetNillableOldToken(s *string) *UserUpdate { + if s != nil { + uu.SetOldToken(*s) + } + return uu +} + // SetBlob sets the "blob" field. func (uu *UserUpdate) SetBlob(b []byte) *UserUpdate { uu.mutation.SetBlob(b) @@ -475,6 +489,13 @@ func (uu *UserUpdate) sqlSave(ctx context.Context) (n int, err error) { Column: user.FieldRenamed, }) } + if value, ok := uu.mutation.OldToken(); ok { + _spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{ + Type: field.TypeString, + Value: value, + Column: user.FieldOldToken, + }) + } if value, ok := uu.mutation.Blob(); ok { _spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{ Type: field.TypeBytes, @@ -790,6 +811,20 @@ func (uuo *UserUpdateOne) ClearRenamed() *UserUpdateOne { return uuo } +// SetOldToken sets the "old_token" field. +func (uuo *UserUpdateOne) SetOldToken(s string) *UserUpdateOne { + uuo.mutation.SetOldToken(s) + return uuo +} + +// SetNillableOldToken sets the "old_token" field if the given value is not nil. +func (uuo *UserUpdateOne) SetNillableOldToken(s *string) *UserUpdateOne { + if s != nil { + uuo.SetOldToken(*s) + } + return uuo +} + // SetBlob sets the "blob" field. func (uuo *UserUpdateOne) SetBlob(b []byte) *UserUpdateOne { uuo.mutation.SetBlob(b) @@ -1172,6 +1207,13 @@ func (uuo *UserUpdateOne) sqlSave(ctx context.Context) (_node *User, err error) Column: user.FieldRenamed, }) } + if value, ok := uuo.mutation.OldToken(); ok { + _spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{ + Type: field.TypeString, + Value: value, + Column: user.FieldOldToken, + }) + } if value, ok := uuo.mutation.Blob(); ok { _spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{ Type: field.TypeBytes, diff --git a/entc/integration/migrate/entv2/migrate/schema.go b/entc/integration/migrate/entv2/migrate/schema.go index f49f7c042c..c88da1d2ce 100644 --- a/entc/integration/migrate/entv2/migrate/schema.go +++ b/entc/integration/migrate/entv2/migrate/schema.go @@ -141,6 +141,7 @@ var ( {Name: "buffer", Type: field.TypeBytes, Nullable: true}, {Name: "title", Type: field.TypeString, Default: "SWE"}, {Name: "renamed", Type: field.TypeString, Nullable: true}, + {Name: "new_token", Type: field.TypeString}, {Name: "blob", Type: field.TypeBytes, Nullable: true, Size: 1000}, {Name: "state", Type: field.TypeEnum, Nullable: true, Enums: []string{"logged_in", "logged_out", "online"}, Default: "logged_in"}, {Name: "status", Type: field.TypeEnum, Nullable: true, Enums: []string{"done", "pending"}}, diff --git a/entc/integration/migrate/entv2/mutation.go b/entc/integration/migrate/entv2/mutation.go index 8a2e768b62..18061d4f6f 100644 --- a/entc/integration/migrate/entv2/mutation.go +++ b/entc/integration/migrate/entv2/mutation.go @@ -2987,6 +2987,7 @@ type UserMutation struct { buffer *[]byte title *string new_name *string + new_token *string blob *[]byte state *user.State status *user.Status @@ -3529,6 +3530,42 @@ func (m *UserMutation) ResetNewName() { delete(m.clearedFields, user.FieldNewName) } +// SetNewToken sets the "new_token" field. +func (m *UserMutation) SetNewToken(s string) { + m.new_token = &s +} + +// NewToken returns the value of the "new_token" field in the mutation. +func (m *UserMutation) NewToken() (r string, exists bool) { + v := m.new_token + if v == nil { + return + } + return *v, true +} + +// OldNewToken returns the old "new_token" field's value of the User entity. +// If the User object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *UserMutation) OldNewToken(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldNewToken is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldNewToken requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldNewToken: %w", err) + } + return oldValue.NewToken, nil +} + +// ResetNewToken resets all changes to the "new_token" field. +func (m *UserMutation) ResetNewToken() { + m.new_token = nil +} + // SetBlob sets the "blob" field. func (m *UserMutation) SetBlob(b []byte) { m.blob = &b @@ -3927,7 +3964,7 @@ func (m *UserMutation) Type() string { // order to get all numeric fields that were incremented/decremented, call // AddedFields(). func (m *UserMutation) Fields() []string { - fields := make([]string, 0, 15) + fields := make([]string, 0, 16) if m.mixed_string != nil { fields = append(fields, user.FieldMixedString) } @@ -3958,6 +3995,9 @@ func (m *UserMutation) Fields() []string { if m.new_name != nil { fields = append(fields, user.FieldNewName) } + if m.new_token != nil { + fields = append(fields, user.FieldNewToken) + } if m.blob != nil { fields = append(fields, user.FieldBlob) } @@ -4001,6 +4041,8 @@ func (m *UserMutation) Field(name string) (ent.Value, bool) { return m.Title() case user.FieldNewName: return m.NewName() + case user.FieldNewToken: + return m.NewToken() case user.FieldBlob: return m.Blob() case user.FieldState: @@ -4040,6 +4082,8 @@ func (m *UserMutation) OldField(ctx context.Context, name string) (ent.Value, er return m.OldTitle(ctx) case user.FieldNewName: return m.OldNewName(ctx) + case user.FieldNewToken: + return m.OldNewToken(ctx) case user.FieldBlob: return m.OldBlob(ctx) case user.FieldState: @@ -4129,6 +4173,13 @@ func (m *UserMutation) SetField(name string, value ent.Value) error { } m.SetNewName(v) return nil + case user.FieldNewToken: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetNewToken(v) + return nil case user.FieldBlob: v, ok := value.([]byte) if !ok { @@ -4303,6 +4354,9 @@ func (m *UserMutation) ResetField(name string) error { case user.FieldNewName: m.ResetNewName() return nil + case user.FieldNewToken: + m.ResetNewToken() + return nil case user.FieldBlob: m.ResetBlob() return nil diff --git a/entc/integration/migrate/entv2/runtime.go b/entc/integration/migrate/entv2/runtime.go index 6144f37239..1ce242b69e 100644 --- a/entc/integration/migrate/entv2/runtime.go +++ b/entc/integration/migrate/entv2/runtime.go @@ -42,12 +42,16 @@ func init() { userDescTitle := userFields[7].Descriptor() // user.DefaultTitle holds the default value on creation for the title field. user.DefaultTitle = userDescTitle.Default.(string) + // userDescNewToken is the schema descriptor for new_token field. + userDescNewToken := userFields[9].Descriptor() + // user.DefaultNewToken holds the default value on creation for the new_token field. + user.DefaultNewToken = userDescNewToken.Default.(func() string) // userDescBlob is the schema descriptor for blob field. - userDescBlob := userFields[9].Descriptor() + userDescBlob := userFields[10].Descriptor() // user.BlobValidator is a validator for the "blob" field. It is called by the builders before save. user.BlobValidator = userDescBlob.Validators[0].(func([]byte) error) // userDescCreatedAt is the schema descriptor for created_at field. - userDescCreatedAt := userFields[13].Descriptor() + userDescCreatedAt := userFields[14].Descriptor() // user.DefaultCreatedAt holds the default value on creation for the created_at field. user.DefaultCreatedAt = userDescCreatedAt.Default.(func() time.Time) } diff --git a/entc/integration/migrate/entv2/schema/user.go b/entc/integration/migrate/entv2/schema/user.go index 08ee012050..daedce57bb 100644 --- a/entc/integration/migrate/entv2/schema/user.go +++ b/entc/integration/migrate/entv2/schema/user.go @@ -7,6 +7,8 @@ package schema import ( "time" + "github.com/google/uuid" + "entgo.io/ent/schema" "entgo.io/ent" @@ -70,10 +72,14 @@ func (User) Fields() []ent.Field { field.String("title"). Default("SWE"), // change column name and reference it to the - // previous one ("renamed"). + // previous one using storage-key ("renamed"). field.String("new_name"). Optional(). StorageKey("renamed"), + // change column name from "old_token" to "new_token" + // and use Atlas diff hook in the migration. + field.String("new_token"). + DefaultFunc(uuid.NewString), // extending the blob size. field.Bytes("blob"). Optional(). diff --git a/entc/integration/migrate/entv2/user.go b/entc/integration/migrate/entv2/user.go index 93d8ad0040..37d22b4ffb 100644 --- a/entc/integration/migrate/entv2/user.go +++ b/entc/integration/migrate/entv2/user.go @@ -41,6 +41,8 @@ type User struct { Title string `json:"title,omitempty"` // NewName holds the value of the "new_name" field. NewName string `json:"new_name,omitempty"` + // NewToken holds the value of the "new_token" field. + NewToken string `json:"new_token,omitempty"` // Blob holds the value of the "blob" field. Blob []byte `json:"blob,omitempty"` // State holds the value of the "state" field. @@ -110,7 +112,7 @@ func (*User) scanValues(columns []string) ([]interface{}, error) { values[i] = new([]byte) case user.FieldID, user.FieldAge: values[i] = new(sql.NullInt64) - case user.FieldMixedString, user.FieldMixedEnum, user.FieldName, user.FieldDescription, user.FieldNickname, user.FieldPhone, user.FieldTitle, user.FieldNewName, user.FieldState, user.FieldStatus, user.FieldWorkplace: + case user.FieldMixedString, user.FieldMixedEnum, user.FieldName, user.FieldDescription, user.FieldNickname, user.FieldPhone, user.FieldTitle, user.FieldNewName, user.FieldNewToken, user.FieldState, user.FieldStatus, user.FieldWorkplace: values[i] = new(sql.NullString) case user.FieldCreatedAt: values[i] = new(sql.NullTime) @@ -195,6 +197,12 @@ func (u *User) assignValues(columns []string, values []interface{}) error { } else if value.Valid { u.NewName = value.String } + case user.FieldNewToken: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field new_token", values[i]) + } else if value.Valid { + u.NewToken = value.String + } case user.FieldBlob: if value, ok := values[i].(*[]byte); !ok { return fmt.Errorf("unexpected type %T for field blob", values[i]) @@ -288,6 +296,8 @@ func (u *User) String() string { builder.WriteString(u.Title) builder.WriteString(", new_name=") builder.WriteString(u.NewName) + builder.WriteString(", new_token=") + builder.WriteString(u.NewToken) builder.WriteString(", blob=") builder.WriteString(fmt.Sprintf("%v", u.Blob)) builder.WriteString(", state=") diff --git a/entc/integration/migrate/entv2/user/user.go b/entc/integration/migrate/entv2/user/user.go index 50cad02c33..78e27c8db1 100644 --- a/entc/integration/migrate/entv2/user/user.go +++ b/entc/integration/migrate/entv2/user/user.go @@ -36,6 +36,8 @@ const ( FieldTitle = "title" // FieldNewName holds the string denoting the new_name field in the database. FieldNewName = "renamed" + // FieldNewToken holds the string denoting the new_token field in the database. + FieldNewToken = "new_token" // FieldBlob holds the string denoting the blob field in the database. FieldBlob = "blob" // FieldState holds the string denoting the state field in the database. @@ -89,6 +91,7 @@ var Columns = []string{ FieldBuffer, FieldTitle, FieldNewName, + FieldNewToken, FieldBlob, FieldState, FieldStatus, @@ -123,6 +126,8 @@ var ( DefaultBuffer func() []byte // DefaultTitle holds the default value on creation for the "title" field. DefaultTitle string + // DefaultNewToken holds the default value on creation for the "new_token" field. + DefaultNewToken func() string // BlobValidator is a validator for the "blob" field. It is called by the builders before save. BlobValidator func([]byte) error // DefaultCreatedAt holds the default value on creation for the "created_at" field. diff --git a/entc/integration/migrate/entv2/user/where.go b/entc/integration/migrate/entv2/user/where.go index de8227f3cb..d4ce62625b 100644 --- a/entc/integration/migrate/entv2/user/where.go +++ b/entc/integration/migrate/entv2/user/where.go @@ -160,6 +160,13 @@ func NewName(v string) predicate.User { }) } +// NewToken applies equality check predicate on the "new_token" field. It's identical to NewTokenEQ. +func NewToken(v string) predicate.User { + return predicate.User(func(s *sql.Selector) { + s.Where(sql.EQ(s.C(FieldNewToken), v)) + }) +} + // Blob applies equality check predicate on the "blob" field. It's identical to BlobEQ. func Blob(v []byte) predicate.User { return predicate.User(func(s *sql.Selector) { @@ -1200,6 +1207,117 @@ func NewNameContainsFold(v string) predicate.User { }) } +// NewTokenEQ applies the EQ predicate on the "new_token" field. +func NewTokenEQ(v string) predicate.User { + return predicate.User(func(s *sql.Selector) { + s.Where(sql.EQ(s.C(FieldNewToken), v)) + }) +} + +// NewTokenNEQ applies the NEQ predicate on the "new_token" field. +func NewTokenNEQ(v string) predicate.User { + return predicate.User(func(s *sql.Selector) { + s.Where(sql.NEQ(s.C(FieldNewToken), v)) + }) +} + +// NewTokenIn applies the In predicate on the "new_token" field. +func NewTokenIn(vs ...string) predicate.User { + v := make([]interface{}, len(vs)) + for i := range v { + v[i] = vs[i] + } + return predicate.User(func(s *sql.Selector) { + // if not arguments were provided, append the FALSE constants, + // since we can't apply "IN ()". This will make this predicate falsy. + if len(v) == 0 { + s.Where(sql.False()) + return + } + s.Where(sql.In(s.C(FieldNewToken), v...)) + }) +} + +// NewTokenNotIn applies the NotIn predicate on the "new_token" field. +func NewTokenNotIn(vs ...string) predicate.User { + v := make([]interface{}, len(vs)) + for i := range v { + v[i] = vs[i] + } + return predicate.User(func(s *sql.Selector) { + // if not arguments were provided, append the FALSE constants, + // since we can't apply "IN ()". This will make this predicate falsy. + if len(v) == 0 { + s.Where(sql.False()) + return + } + s.Where(sql.NotIn(s.C(FieldNewToken), v...)) + }) +} + +// NewTokenGT applies the GT predicate on the "new_token" field. +func NewTokenGT(v string) predicate.User { + return predicate.User(func(s *sql.Selector) { + s.Where(sql.GT(s.C(FieldNewToken), v)) + }) +} + +// NewTokenGTE applies the GTE predicate on the "new_token" field. +func NewTokenGTE(v string) predicate.User { + return predicate.User(func(s *sql.Selector) { + s.Where(sql.GTE(s.C(FieldNewToken), v)) + }) +} + +// NewTokenLT applies the LT predicate on the "new_token" field. +func NewTokenLT(v string) predicate.User { + return predicate.User(func(s *sql.Selector) { + s.Where(sql.LT(s.C(FieldNewToken), v)) + }) +} + +// NewTokenLTE applies the LTE predicate on the "new_token" field. +func NewTokenLTE(v string) predicate.User { + return predicate.User(func(s *sql.Selector) { + s.Where(sql.LTE(s.C(FieldNewToken), v)) + }) +} + +// NewTokenContains applies the Contains predicate on the "new_token" field. +func NewTokenContains(v string) predicate.User { + return predicate.User(func(s *sql.Selector) { + s.Where(sql.Contains(s.C(FieldNewToken), v)) + }) +} + +// NewTokenHasPrefix applies the HasPrefix predicate on the "new_token" field. +func NewTokenHasPrefix(v string) predicate.User { + return predicate.User(func(s *sql.Selector) { + s.Where(sql.HasPrefix(s.C(FieldNewToken), v)) + }) +} + +// NewTokenHasSuffix applies the HasSuffix predicate on the "new_token" field. +func NewTokenHasSuffix(v string) predicate.User { + return predicate.User(func(s *sql.Selector) { + s.Where(sql.HasSuffix(s.C(FieldNewToken), v)) + }) +} + +// NewTokenEqualFold applies the EqualFold predicate on the "new_token" field. +func NewTokenEqualFold(v string) predicate.User { + return predicate.User(func(s *sql.Selector) { + s.Where(sql.EqualFold(s.C(FieldNewToken), v)) + }) +} + +// NewTokenContainsFold applies the ContainsFold predicate on the "new_token" field. +func NewTokenContainsFold(v string) predicate.User { + return predicate.User(func(s *sql.Selector) { + s.Where(sql.ContainsFold(s.C(FieldNewToken), v)) + }) +} + // BlobEQ applies the EQ predicate on the "blob" field. func BlobEQ(v []byte) predicate.User { return predicate.User(func(s *sql.Selector) { diff --git a/entc/integration/migrate/entv2/user_create.go b/entc/integration/migrate/entv2/user_create.go index 1b2c5687ce..da3ea1fc93 100644 --- a/entc/integration/migrate/entv2/user_create.go +++ b/entc/integration/migrate/entv2/user_create.go @@ -134,6 +134,20 @@ func (uc *UserCreate) SetNillableNewName(s *string) *UserCreate { return uc } +// SetNewToken sets the "new_token" field. +func (uc *UserCreate) SetNewToken(s string) *UserCreate { + uc.mutation.SetNewToken(s) + return uc +} + +// SetNillableNewToken sets the "new_token" field if the given value is not nil. +func (uc *UserCreate) SetNillableNewToken(s *string) *UserCreate { + if s != nil { + uc.SetNewToken(*s) + } + return uc +} + // SetBlob sets the "blob" field. func (uc *UserCreate) SetBlob(b []byte) *UserCreate { uc.mutation.SetBlob(b) @@ -342,6 +356,10 @@ func (uc *UserCreate) defaults() { v := user.DefaultTitle uc.mutation.SetTitle(v) } + if _, ok := uc.mutation.NewToken(); !ok { + v := user.DefaultNewToken() + uc.mutation.SetNewToken(v) + } if _, ok := uc.mutation.State(); !ok { v := user.DefaultState uc.mutation.SetState(v) @@ -385,6 +403,9 @@ func (uc *UserCreate) check() error { if _, ok := uc.mutation.Title(); !ok { return &ValidationError{Name: "title", err: errors.New(`entv2: missing required field "User.title"`)} } + if _, ok := uc.mutation.NewToken(); !ok { + return &ValidationError{Name: "new_token", err: errors.New(`entv2: missing required field "User.new_token"`)} + } if v, ok := uc.mutation.Blob(); ok { if err := user.BlobValidator(v); err != nil { return &ValidationError{Name: "blob", err: fmt.Errorf(`entv2: validator failed for field "User.blob": %w`, err)} @@ -516,6 +537,14 @@ func (uc *UserCreate) createSpec() (*User, *sqlgraph.CreateSpec) { }) _node.NewName = value } + if value, ok := uc.mutation.NewToken(); ok { + _spec.Fields = append(_spec.Fields, &sqlgraph.FieldSpec{ + Type: field.TypeString, + Value: value, + Column: user.FieldNewToken, + }) + _node.NewToken = value + } if value, ok := uc.mutation.Blob(); ok { _spec.Fields = append(_spec.Fields, &sqlgraph.FieldSpec{ Type: field.TypeBytes, diff --git a/entc/integration/migrate/entv2/user_update.go b/entc/integration/migrate/entv2/user_update.go index 8e935a4607..3e0edd97ed 100644 --- a/entc/integration/migrate/entv2/user_update.go +++ b/entc/integration/migrate/entv2/user_update.go @@ -167,6 +167,20 @@ func (uu *UserUpdate) ClearNewName() *UserUpdate { return uu } +// SetNewToken sets the "new_token" field. +func (uu *UserUpdate) SetNewToken(s string) *UserUpdate { + uu.mutation.SetNewToken(s) + return uu +} + +// SetNillableNewToken sets the "new_token" field if the given value is not nil. +func (uu *UserUpdate) SetNillableNewToken(s *string) *UserUpdate { + if s != nil { + uu.SetNewToken(*s) + } + return uu +} + // SetBlob sets the "blob" field. func (uu *UserUpdate) SetBlob(b []byte) *UserUpdate { uu.mutation.SetBlob(b) @@ -558,6 +572,13 @@ func (uu *UserUpdate) sqlSave(ctx context.Context) (n int, err error) { Column: user.FieldNewName, }) } + if value, ok := uu.mutation.NewToken(); ok { + _spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{ + Type: field.TypeString, + Value: value, + Column: user.FieldNewToken, + }) + } if value, ok := uu.mutation.Blob(); ok { _spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{ Type: field.TypeBytes, @@ -912,6 +933,20 @@ func (uuo *UserUpdateOne) ClearNewName() *UserUpdateOne { return uuo } +// SetNewToken sets the "new_token" field. +func (uuo *UserUpdateOne) SetNewToken(s string) *UserUpdateOne { + uuo.mutation.SetNewToken(s) + return uuo +} + +// SetNillableNewToken sets the "new_token" field if the given value is not nil. +func (uuo *UserUpdateOne) SetNillableNewToken(s *string) *UserUpdateOne { + if s != nil { + uuo.SetNewToken(*s) + } + return uuo +} + // SetBlob sets the "blob" field. func (uuo *UserUpdateOne) SetBlob(b []byte) *UserUpdateOne { uuo.mutation.SetBlob(b) @@ -1327,6 +1362,13 @@ func (uuo *UserUpdateOne) sqlSave(ctx context.Context) (_node *User, err error) Column: user.FieldNewName, }) } + if value, ok := uuo.mutation.NewToken(); ok { + _spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{ + Type: field.TypeString, + Value: value, + Column: user.FieldNewToken, + }) + } if value, ok := uuo.mutation.Blob(); ok { _spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{ Type: field.TypeBytes, diff --git a/entc/integration/migrate/migrate_test.go b/entc/integration/migrate/migrate_test.go index bf5dd72fb9..ca0d2711d3 100644 --- a/entc/integration/migrate/migrate_test.go +++ b/entc/integration/migrate/migrate_test.go @@ -6,6 +6,7 @@ package migrate import ( "context" + "errors" "fmt" "io/fs" "math" @@ -110,6 +111,7 @@ func TestSQLite(t *testing.T) { migratev2.WithGlobalUniqueID(true), migratev2.WithDropIndex(true), migratev2.WithDropColumn(true), + schema.WithDiffHook(renameTokenColumn), schema.WithDiffHook(func(next schema.Differ) schema.Differ { return schema.DiffFunc(func(current, desired *atlas.Schema) ([]atlas.Change, error) { // Example to hook into the diff process. @@ -208,7 +210,7 @@ func V1ToV2(t *testing.T, dialect string, clientv1 *entv1.Client, clientv2 *entv SanityV1(t, dialect, clientv1) // Run migration and execute queries on v2. - require.NoError(t, clientv2.Schema.Create(ctx, migratev2.WithGlobalUniqueID(true), migratev2.WithDropIndex(true), migratev2.WithDropColumn(true), schema.WithAtlas(true))) + require.NoError(t, clientv2.Schema.Create(ctx, migratev2.WithGlobalUniqueID(true), migratev2.WithDropIndex(true), migratev2.WithDropColumn(true), schema.WithAtlas(true), schema.WithDiffHook(renameTokenColumn))) require.NoError(t, clientv2.Schema.Create(ctx, migratev2.WithGlobalUniqueID(true), migratev2.WithDropIndex(true), migratev2.WithDropColumn(true), schema.WithAtlas(true)), "should not create additional resources on multiple runs") SanityV2(t, dialect, clientv2) @@ -304,6 +306,9 @@ func SanityV1(t *testing.T, dbdialect string, client *entv1.Client) { func SanityV2(t *testing.T, dbdialect string, client *entv2.Client) { ctx := context.Background() + for _, u := range client.User.Query().AllX(ctx) { + require.NotEmpty(t, u.NewToken, "old_token column should be renamed to new_token") + } if dbdialect != dialect.SQLite { require.True(t, client.User.Query().ExistX(ctx), "table 'users' should contain rows after running the migration") users := client.User.Query().Select(user.FieldCreatedAt).AllX(ctx) @@ -452,3 +457,29 @@ func countFiles(t *testing.T, d migrate.Dir) int { require.NoError(t, err) return len(files) } + +func renameTokenColumn(next schema.Differ) schema.Differ { + return schema.DiffFunc(func(current, desired *atlas.Schema) ([]atlas.Change, error) { + // Example to hook into the diff process. + changes, err := next.Diff(current, desired) + if err != nil { + return nil, err + } + for _, c := range changes { + m, ok := c.(*atlas.ModifyTable) + if !ok || m.T.Name != user.Table { + continue + } + changes := atlas.Changes(m.Changes) + switch i, j := changes.IndexDropColumn("old_token"), changes.IndexAddColumn("new_token"); { + case i != -1 && j != -1: + rename := &atlas.RenameColumn{From: changes[i].(*atlas.DropColumn).C, To: changes[j].(*atlas.AddColumn).C} + changes.RemoveIndex(i, j) + m.Changes = append(changes, rename) + case i != -1 || j != -1: + return nil, errors.New("old_token and new_token must be present or absent") + } + } + return changes, nil + }) +} diff --git a/go.mod b/go.mod index 6791f99a6e..2ad717fe6b 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module entgo.io/ent go 1.17 require ( - ariga.io/atlas v0.3.8-0.20220413145352-b3e05926121b + ariga.io/atlas v0.3.8-0.20220424181913-f64001131c0e github.com/DATA-DOG/go-sqlmock v1.5.0 github.com/go-openapi/inflect v0.19.0 github.com/go-sql-driver/mysql v1.6.0 diff --git a/go.sum b/go.sum index 6b8ab11d36..cc85e6738a 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,9 @@ ariga.io/atlas v0.3.8-0.20220413145352-b3e05926121b h1:Y9KtcGuANwu5X8NNGoMnve3GDmV7ReTG44A8PshryMI= ariga.io/atlas v0.3.8-0.20220413145352-b3e05926121b/go.mod h1:2aAhlHuY8tr7rPNz2MgOBTJB/cVjK0sv1VvTIqeYSws= +ariga.io/atlas v0.3.8-0.20220424134008-c5b477051c00 h1:jXc5tIxiaAqfKFtI9UIygTu1kVH35QMJ01p5z/CDxzY= +ariga.io/atlas v0.3.8-0.20220424134008-c5b477051c00/go.mod h1:2aAhlHuY8tr7rPNz2MgOBTJB/cVjK0sv1VvTIqeYSws= +ariga.io/atlas v0.3.8-0.20220424181913-f64001131c0e h1:IVj9dxSeAC0CRxjM+AYLIKbNdCzAjUnsUjAp/td7kYo= +ariga.io/atlas v0.3.8-0.20220424181913-f64001131c0e/go.mod h1:2aAhlHuY8tr7rPNz2MgOBTJB/cVjK0sv1VvTIqeYSws= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=