From 25ebdebcf1a6588c6d116c56376dc265a5cd1014 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Feb 2022 21:20:25 +0200 Subject: [PATCH 01/13] build(deps): bump actions/setup-node from 2.5.1 to 3 (#2365) Bumps [actions/setup-node](https://github.com/actions/setup-node) from 2.5.1 to 3. - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](https://github.com/actions/setup-node/compare/v2.5.1...v3) --- updated-dependencies: - dependency-name: actions/setup-node dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/cd.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index e519b22764..7ea43ee19c 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2.4.0 - - uses: actions/setup-node@v2.5.1 + - uses: actions/setup-node@v3 with: node-version: 14 - name: Install Dependencies From 8753baae78b609f09cf45129b70990118888bfb1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 3 Mar 2022 18:37:58 +0200 Subject: [PATCH 02/13] build(deps): bump actions/checkout from 2.4.0 to 3 (#2372) Bumps [actions/checkout](https://github.com/actions/checkout) from 2.4.0 to 3. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v2.4.0...v3) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/cd.yml | 2 +- .github/workflows/ci.yml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 7ea43ee19c..01c089a62b 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -13,7 +13,7 @@ jobs: name: docs runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2.4.0 + - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: node-version: 14 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b3444c48b4..0a22b20a2b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,7 +13,7 @@ jobs: lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2.4.0 + - uses: actions/checkout@v3 - name: Run linters uses: golangci/golangci-lint-action@v2.5.2 with: @@ -25,7 +25,7 @@ jobs: matrix: go: ['1.17', '1.16'] steps: - - uses: actions/checkout@v2.4.0 + - uses: actions/checkout@v3 - uses: actions/setup-go@v2 with: go-version: ${{ matrix.go }} @@ -54,7 +54,7 @@ jobs: generate: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2.4.0 + - uses: actions/checkout@v3 - uses: actions/setup-go@v2 with: go-version: '1.17' @@ -226,7 +226,7 @@ jobs: --health-timeout 5s --health-retries 5 steps: - - uses: actions/checkout@v2.4.0 + - uses: actions/checkout@v3 - uses: actions/setup-go@v2 with: go-version: '1.17' @@ -392,7 +392,7 @@ jobs: --health-timeout 5s --health-retries 5 steps: - - uses: actions/checkout@v2.4.0 + - uses: actions/checkout@v3 with: fetch-depth: 0 - uses: actions/setup-go@v2 From 35a098fdbb290d46a8a48c68a90363c3a8f9d371 Mon Sep 17 00:00:00 2001 From: Ariel Mashraki <7413593+a8m@users.noreply.github.com> Date: Thu, 3 Mar 2022 21:52:44 +0200 Subject: [PATCH 03/13] dialect/sql/schema: fix bug in atlas integration when working WithDropIndex/WithDropColumn (#2374) * dialect/sql/schema: fix no change condition in atlas * dialect/sql/schema: fix bug in atlas integration when working WithDropIndex/WithDropColumn Co-authored-by: Zeev Manilovich --- dialect/sql/schema/atlas.go | 13 +++++++------ entc/integration/migrate/migrate_test.go | 3 ++- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/dialect/sql/schema/atlas.go b/dialect/sql/schema/atlas.go index f4eadfaaaa..555f411008 100644 --- a/dialect/sql/schema/atlas.go +++ b/dialect/sql/schema/atlas.go @@ -14,6 +14,7 @@ import ( "ariga.io/atlas/sql/migrate" "ariga.io/atlas/sql/schema" + "entgo.io/ent/dialect" entsql "entgo.io/ent/dialect/sql" "entgo.io/ent/schema/field" @@ -272,18 +273,18 @@ func (m *Migrate) setupAtlas() error { if m.withFixture { return errors.New("sql/schema: WithFixture(true) does not work in Atlas migration") } - k := DropIndex | DropColumn + skip := DropIndex | DropColumn if m.atlas.skip != NoChange { - k = m.atlas.skip + skip = m.atlas.skip } if m.dropIndexes { - k |= ^DropIndex + skip &= ^DropIndex } if m.dropColumns { - k |= ^DropColumn + skip &= ^DropColumn } - if k == NoChange { - m.atlas.diff = append(m.atlas.diff, filterChanges(k)) + if skip != NoChange { + m.atlas.diff = append(m.atlas.diff, filterChanges(skip)) } if m.atlas.dir != nil && m.atlas.fmt == nil { m.atlas.fmt = migrate.DefaultFormatter diff --git a/entc/integration/migrate/migrate_test.go b/entc/integration/migrate/migrate_test.go index ea269a8a64..d1d39222c7 100644 --- a/entc/integration/migrate/migrate_test.go +++ b/entc/integration/migrate/migrate_test.go @@ -98,6 +98,7 @@ func TestSQLite(t *testing.T) { ctx, migratev2.WithGlobalUniqueID(true), migratev2.WithDropIndex(true), + migratev2.WithDropColumn(true), 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. @@ -162,7 +163,7 @@ func V1ToV2(t *testing.T, dialect string, clientv1 *entv1.Client, clientv2 *entv // 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), schema.WithAtlas(true)), "should not create additional resources on multiple runs") + 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) u := clientv2.User.Create().SetAge(1).SetName("foo").SetNickname("nick_foo").SetPhone("phone").SaveX(ctx) From 3aab4d91c248e49b2065c9d28d64349d77fb3f3d Mon Sep 17 00:00:00 2001 From: Ariel Mashraki <7413593+a8m@users.noreply.github.com> Date: Fri, 4 Mar 2022 22:53:37 +0200 Subject: [PATCH 04/13] all: update atlasgo.io to latest (#2376) --- dialect/sql/schema/migrate.go | 2 +- entc/integration/migrate/entv1/car/car.go | 6 ++--- .../migrate/entv1/migrate/schema.go | 25 +++++++++++-------- entc/integration/migrate/entv1/schema/user.go | 8 ++++++ entc/integration/migrate/entv1/user/user.go | 4 +-- entc/integration/migrate/entv2/car/car.go | 6 ++--- .../migrate/entv2/migrate/schema.go | 25 +++++++++++-------- entc/integration/migrate/entv2/schema/user.go | 9 +++++++ entc/integration/migrate/entv2/user/user.go | 4 +-- go.mod | 2 +- go.sum | 2 ++ 11 files changed, 59 insertions(+), 34 deletions(-) diff --git a/dialect/sql/schema/migrate.go b/dialect/sql/schema/migrate.go index 80c4b25152..9fac1a8069 100644 --- a/dialect/sql/schema/migrate.go +++ b/dialect/sql/schema/migrate.go @@ -171,7 +171,7 @@ func (m *Migrate) Diff(ctx context.Context, tables ...*Table) error { if err != nil { return err } - return migrate.New(nil, m.atlas.dir, m.atlas.fmt).WritePlan(plan) + return migrate.NewPlanner(nil, m.atlas.dir, migrate.WithFormatter(m.atlas.fmt)).WritePlan(plan) } func (m *Migrate) create(ctx context.Context, tables ...*Table) error { diff --git a/entc/integration/migrate/entv1/car/car.go b/entc/integration/migrate/entv1/car/car.go index 3e601f7dbd..e2dcfe8cbc 100644 --- a/entc/integration/migrate/entv1/car/car.go +++ b/entc/integration/migrate/entv1/car/car.go @@ -16,9 +16,9 @@ const ( // UserFieldID holds the string denoting the ID field of the User. UserFieldID = "oid" // Table holds the table name of the car in the database. - Table = "cars" + Table = "Car" // OwnerTable is the table that holds the owner relation/edge. - OwnerTable = "cars" + OwnerTable = "Car" // OwnerInverseTable is the table name for the User entity. // It exists in this package in order to avoid circular dependency with the "user" package. OwnerInverseTable = "users" @@ -31,7 +31,7 @@ var Columns = []string{ FieldID, } -// ForeignKeys holds the SQL foreign-keys that are owned by the "cars" +// ForeignKeys holds the SQL foreign-keys that are owned by the "Car" // table and are not defined as standalone fields in the schema. var ForeignKeys = []string{ "user_car", diff --git a/entc/integration/migrate/entv1/migrate/schema.go b/entc/integration/migrate/entv1/migrate/schema.go index aafe9c888b..2bb54ab2e0 100644 --- a/entc/integration/migrate/entv1/migrate/schema.go +++ b/entc/integration/migrate/entv1/migrate/schema.go @@ -13,20 +13,20 @@ import ( ) var ( - // CarsColumns holds the columns for the "cars" table. - CarsColumns = []*schema.Column{ + // CarColumns holds the columns for the "Car" table. + CarColumns = []*schema.Column{ {Name: "id", Type: field.TypeInt, Increment: true}, {Name: "user_car", Type: field.TypeInt, Unique: true, Nullable: true}, } - // CarsTable holds the schema information for the "cars" table. - CarsTable = &schema.Table{ - Name: "cars", - Columns: CarsColumns, - PrimaryKey: []*schema.Column{CarsColumns[0]}, + // CarTable holds the schema information for the "Car" table. + CarTable = &schema.Table{ + Name: "Car", + Columns: CarColumns, + PrimaryKey: []*schema.Column{CarColumns[0]}, ForeignKeys: []*schema.ForeignKey{ { - Symbol: "cars_users_car", - Columns: []*schema.Column{CarsColumns[1]}, + Symbol: "Car_users_car", + Columns: []*schema.Column{CarColumns[1]}, RefColumns: []*schema.Column{UsersColumns[0]}, OnDelete: schema.SetNull, }, @@ -115,7 +115,7 @@ var ( } // Tables holds all the tables in the schema. Tables = []*schema.Table{ - CarsTable, + CarTable, ConversionsTable, CustomTypesTable, UsersTable, @@ -123,7 +123,10 @@ var ( ) func init() { - CarsTable.ForeignKeys[0].RefTable = UsersTable + CarTable.ForeignKeys[0].RefTable = UsersTable + CarTable.Annotation = &entsql.Annotation{ + Table: "Car", + } UsersTable.ForeignKeys[0].RefTable = UsersTable UsersTable.ForeignKeys[1].RefTable = UsersTable } diff --git a/entc/integration/migrate/entv1/schema/user.go b/entc/integration/migrate/entv1/schema/user.go index 1e37776795..3aa4b2bed1 100644 --- a/entc/integration/migrate/entv1/schema/user.go +++ b/entc/integration/migrate/entv1/schema/user.go @@ -7,6 +7,7 @@ package schema import ( "entgo.io/ent" "entgo.io/ent/dialect/entsql" + "entgo.io/ent/schema" "entgo.io/ent/schema/edge" "entgo.io/ent/schema/field" "entgo.io/ent/schema/index" @@ -72,6 +73,13 @@ type Car struct { ent.Schema } +// Annotations of the Car. +func (Car) Annotations() []schema.Annotation { + return []schema.Annotation{ + entsql.Annotation{Table: "Car"}, + } +} + func (Car) Edges() []ent.Edge { return []ent.Edge{ edge.From("owner", User.Type). diff --git a/entc/integration/migrate/entv1/user/user.go b/entc/integration/migrate/entv1/user/user.go index a614853f3a..b515e20dda 100644 --- a/entc/integration/migrate/entv1/user/user.go +++ b/entc/integration/migrate/entv1/user/user.go @@ -60,10 +60,10 @@ const ( // SpouseColumn is the table column denoting the spouse relation/edge. SpouseColumn = "user_spouse" // CarTable is the table that holds the car relation/edge. - CarTable = "cars" + CarTable = "Car" // CarInverseTable is the table name for the Car entity. // It exists in this package in order to avoid circular dependency with the "car" package. - CarInverseTable = "cars" + CarInverseTable = "Car" // CarColumn is the table column denoting the car relation/edge. CarColumn = "user_car" ) diff --git a/entc/integration/migrate/entv2/car/car.go b/entc/integration/migrate/entv2/car/car.go index 3e601f7dbd..e2dcfe8cbc 100644 --- a/entc/integration/migrate/entv2/car/car.go +++ b/entc/integration/migrate/entv2/car/car.go @@ -16,9 +16,9 @@ const ( // UserFieldID holds the string denoting the ID field of the User. UserFieldID = "oid" // Table holds the table name of the car in the database. - Table = "cars" + Table = "Car" // OwnerTable is the table that holds the owner relation/edge. - OwnerTable = "cars" + OwnerTable = "Car" // OwnerInverseTable is the table name for the User entity. // It exists in this package in order to avoid circular dependency with the "user" package. OwnerInverseTable = "users" @@ -31,7 +31,7 @@ var Columns = []string{ FieldID, } -// ForeignKeys holds the SQL foreign-keys that are owned by the "cars" +// ForeignKeys holds the SQL foreign-keys that are owned by the "Car" // table and are not defined as standalone fields in the schema. var ForeignKeys = []string{ "user_car", diff --git a/entc/integration/migrate/entv2/migrate/schema.go b/entc/integration/migrate/entv2/migrate/schema.go index ec95e68751..14086c452c 100644 --- a/entc/integration/migrate/entv2/migrate/schema.go +++ b/entc/integration/migrate/entv2/migrate/schema.go @@ -13,20 +13,20 @@ import ( ) var ( - // CarsColumns holds the columns for the "cars" table. - CarsColumns = []*schema.Column{ + // CarColumns holds the columns for the "Car" table. + CarColumns = []*schema.Column{ {Name: "id", Type: field.TypeInt, Increment: true}, {Name: "user_car", Type: field.TypeInt}, } - // CarsTable holds the schema information for the "cars" table. - CarsTable = &schema.Table{ - Name: "cars", - Columns: CarsColumns, - PrimaryKey: []*schema.Column{CarsColumns[0]}, + // CarTable holds the schema information for the "Car" table. + CarTable = &schema.Table{ + Name: "Car", + Columns: CarColumns, + PrimaryKey: []*schema.Column{CarColumns[0]}, ForeignKeys: []*schema.ForeignKey{ { - Symbol: "cars_users_car", - Columns: []*schema.Column{CarsColumns[1]}, + Symbol: "Car_users_car", + Columns: []*schema.Column{CarColumns[1]}, RefColumns: []*schema.Column{UsersColumns[0]}, OnDelete: schema.NoAction, }, @@ -209,7 +209,7 @@ var ( } // Tables holds all the tables in the schema. Tables = []*schema.Table{ - CarsTable, + CarTable, ConversionsTable, CustomTypesTable, GroupsTable, @@ -221,7 +221,10 @@ var ( ) func init() { - CarsTable.ForeignKeys[0].RefTable = UsersTable + CarTable.ForeignKeys[0].RefTable = UsersTable + CarTable.Annotation = &entsql.Annotation{ + Table: "Car", + } MediaTable.Annotation = &entsql.Annotation{ Check: "text <> 'boring'", } diff --git a/entc/integration/migrate/entv2/schema/user.go b/entc/integration/migrate/entv2/schema/user.go index 4362e432bf..03de9c8cd7 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" + "entgo.io/ent/schema" + "entgo.io/ent" "entgo.io/ent/dialect" "entgo.io/ent/dialect/entsql" @@ -142,6 +144,13 @@ type Car struct { ent.Schema } +// Annotations of the Car. +func (Car) Annotations() []schema.Annotation { + return []schema.Annotation{ + entsql.Annotation{Table: "Car"}, + } +} + func (Car) Edges() []ent.Edge { return []ent.Edge{ edge.From("owner", User.Type). diff --git a/entc/integration/migrate/entv2/user/user.go b/entc/integration/migrate/entv2/user/user.go index dc383dabf0..4fc21fa058 100644 --- a/entc/integration/migrate/entv2/user/user.go +++ b/entc/integration/migrate/entv2/user/user.go @@ -59,10 +59,10 @@ const ( // Table holds the table name of the user in the database. Table = "users" // CarTable is the table that holds the car relation/edge. - CarTable = "cars" + CarTable = "Car" // CarInverseTable is the table name for the Car entity. // It exists in this package in order to avoid circular dependency with the "car" package. - CarInverseTable = "cars" + CarInverseTable = "Car" // CarColumn is the table column denoting the car relation/edge. CarColumn = "user_car" // PetsTable is the table that holds the pets relation/edge. diff --git a/go.mod b/go.mod index e9250a2093..7cde1045ff 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module entgo.io/ent go 1.17 require ( - ariga.io/atlas v0.3.5-0.20220215131223-8043663b4223 + ariga.io/atlas v0.3.7-0.20220303204946-787354f533c3 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 8057272693..ffebe355f4 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ ariga.io/atlas v0.3.5-0.20220215131223-8043663b4223 h1:kthcdfUZLRcoVeZetK8gJ0FGaw2D5zrr3h2nP/TuXCw= ariga.io/atlas v0.3.5-0.20220215131223-8043663b4223/go.mod h1:XcLUpQX7Cq4qtagEHIleq3MJaBeeJ76BS8doc4gkOJk= +ariga.io/atlas v0.3.7-0.20220303204946-787354f533c3 h1:fjG4oFCQEfGrRi0QoxWcH2OO28CE6VYa6DkIr3yDySU= +ariga.io/atlas v0.3.7-0.20220303204946-787354f533c3/go.mod h1:yWGf4VPiD4SW83+kAqzD624txN9VKoJC+bpVXr2pKJA= 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= From ddddc1d92ad3a4f1c33df20f1502188dd6d5c911 Mon Sep 17 00:00:00 2001 From: Huy TQ <5723282+imhuytq@users.noreply.github.com> Date: Mon, 7 Mar 2022 16:18:07 +0700 Subject: [PATCH 05/13] dialect/sql/schema: add name to versioned migration files (#2375) * add name to versioned migration files * Skip writing migration files if the plan has no changes --- dialect/sql/schema/atlas.go | 2 +- dialect/sql/schema/migrate.go | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/dialect/sql/schema/atlas.go b/dialect/sql/schema/atlas.go index 555f411008..23e3cff6de 100644 --- a/dialect/sql/schema/atlas.go +++ b/dialect/sql/schema/atlas.go @@ -364,7 +364,7 @@ func (m *Migrate) atDiff(ctx context.Context, conn dialect.ExecQuerier, tables . return nil, err } // Plan changes. - return drv.PlanChanges(ctx, "", changes) + return drv.PlanChanges(ctx, "changes", changes) } type db struct{ dialect.ExecQuerier } diff --git a/dialect/sql/schema/migrate.go b/dialect/sql/schema/migrate.go index 9fac1a8069..91adcdbc49 100644 --- a/dialect/sql/schema/migrate.go +++ b/dialect/sql/schema/migrate.go @@ -171,6 +171,10 @@ func (m *Migrate) Diff(ctx context.Context, tables ...*Table) error { if err != nil { return err } + // Skip if the plan has no changes + if len(plan.Changes) == 0 { + return nil + } return migrate.NewPlanner(nil, m.atlas.dir, migrate.WithFormatter(m.atlas.fmt)).WritePlan(plan) } From a1a7cd81146ae50bb00ff5c7c89aca9a57bb6b63 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Mon, 7 Mar 2022 10:19:30 +0100 Subject: [PATCH 06/13] docs: add imhuytq as a contributor for code (#2378) * docs: update doc/md/contributors.md [skip ci] * docs: update .all-contributorsrc [skip ci] Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> --- .all-contributorsrc | 9 +++++++++ doc/md/contributors.md | 1 + 2 files changed, 10 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index c77780ee7d..8ba32286b3 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -753,6 +753,15 @@ "contributions": [ "code" ] + }, + { + "login": "imhuytq", + "name": "Huy TQ", + "avatar_url": "https://avatars.githubusercontent.com/u/5723282?v=4", + "profile": "https://huytq.com", + "contributions": [ + "code" + ] } ], "contributorsPerLine": 7, diff --git a/doc/md/contributors.md b/doc/md/contributors.md index a086fcc5ac..beaae60d1e 100644 --- a/doc/md/contributors.md +++ b/doc/md/contributors.md @@ -114,6 +114,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
Pedro Henrique

💻
MrParano1d

💻
Thomas Prebble

💻 +
Huy TQ

💻 From 78a0fd9716066e94ec40f0d13f29b92c05c9b0bd Mon Sep 17 00:00:00 2001 From: Ariel Mashraki <7413593+a8m@users.noreply.github.com> Date: Mon, 7 Mar 2022 22:33:50 +0200 Subject: [PATCH 07/13] schema/field: expose RType.Implements method (#2379) Also, add both (T) and (*T) methods for RType --- dialect/sql/schema/migrate.go | 2 +- schema/field/field.go | 46 +++++++++++---------- schema/field/field_test.go | 75 +++++++++++++++++++++++++++++++++++ schema/field/type.go | 9 +++-- 4 files changed, 107 insertions(+), 25 deletions(-) diff --git a/dialect/sql/schema/migrate.go b/dialect/sql/schema/migrate.go index 91adcdbc49..57a8cda3b9 100644 --- a/dialect/sql/schema/migrate.go +++ b/dialect/sql/schema/migrate.go @@ -171,7 +171,7 @@ func (m *Migrate) Diff(ctx context.Context, tables ...*Table) error { if err != nil { return err } - // Skip if the plan has no changes + // Skip if the plan has no changes. if len(plan.Changes) == 0 { return nil } diff --git a/schema/field/field.go b/schema/field/field.go index 0c20eda2b3..c895b304e9 100644 --- a/schema/field/field.go +++ b/schema/field/field.go @@ -1165,37 +1165,43 @@ func (d *Descriptor) goType(typ interface{}, expectType reflect.Type) { Methods: make(map[string]struct{ In, Out []*RType }, t.NumMethod()), }, } + methods(t, info.RType) switch t.Kind() { case reflect.Slice, reflect.Ptr, reflect.Map: info.Nillable = true } switch pt := reflect.PtrTo(t); { - case pt.Implements(valueScannerType): - t = pt - fallthrough - case t.Implements(valueScannerType): - n := t.NumMethod() - for i := 0; i < n; i++ { - m := t.Method(i) - in := make([]*RType, m.Type.NumIn()-1) - for j := range in { - arg := m.Type.In(j + 1) - in[j] = &RType{Name: arg.Name(), Ident: arg.String(), Kind: arg.Kind(), PkgPath: arg.PkgPath()} - } - out := make([]*RType, m.Type.NumOut()) - for j := range out { - ret := m.Type.Out(j) - out[j] = &RType{Name: ret.Name(), Ident: ret.String(), Kind: ret.Kind(), PkgPath: ret.PkgPath()} - } - info.RType.Methods[m.Name] = struct{ In, Out []*RType }{in, out} - } - case t.Kind() == expectType.Kind() && t.ConvertibleTo(expectType): + case pt.Implements(valueScannerType), t.Implements(valueScannerType), + t.Kind() == expectType.Kind() && t.ConvertibleTo(expectType): default: d.Err = fmt.Errorf("GoType must be a %q type or ValueScanner", expectType) } d.Info = info } +func methods(t reflect.Type, rtype *RType) { + // For type T, add methods with + // pointer receiver as well (*T). + if t.Kind() != reflect.Ptr { + t = reflect.PtrTo(t) + } + n := t.NumMethod() + for i := 0; i < n; i++ { + m := t.Method(i) + in := make([]*RType, m.Type.NumIn()-1) + for j := range in { + arg := m.Type.In(j + 1) + in[j] = &RType{Name: arg.Name(), Ident: arg.String(), Kind: arg.Kind(), PkgPath: arg.PkgPath()} + } + out := make([]*RType, m.Type.NumOut()) + for j := range out { + ret := m.Type.Out(j) + out[j] = &RType{Name: ret.Name(), Ident: ret.String(), Kind: ret.Kind(), PkgPath: ret.PkgPath()} + } + rtype.Methods[m.Name] = struct{ In, Out []*RType }{in, out} + } +} + func (d *Descriptor) checkDefaultFunc(expectType reflect.Type) { for _, typ := range []reflect.Type{reflect.TypeOf(d.Default), reflect.TypeOf(d.UpdateDefault)} { if typ == nil || typ.Kind() != reflect.Func || d.Err != nil { diff --git a/schema/field/field_test.go b/schema/field/field_test.go index 84740d6fb2..88bd8b0d1a 100644 --- a/schema/field/field_test.go +++ b/schema/field/field_test.go @@ -8,14 +8,18 @@ import ( "database/sql" "database/sql/driver" "errors" + "fmt" + "io" "net" "net/http" "net/url" "reflect" "regexp" + "strconv" "testing" "time" + "entgo.io/ent" "entgo.io/ent/dialect" "entgo.io/ent/schema/field" @@ -680,6 +684,77 @@ func TestField_Other(t *testing.T) { assert.Error(t, fd.Err, "invalid default value") } +type UserRole string + +const ( + Admin UserRole = "ADMIN" + User UserRole = "USER" + Unknown UserRole = "UNKNOWN" +) + +func (UserRole) Values() (roles []string) { + for _, r := range []UserRole{Admin, User, Unknown} { + roles = append(roles, string(r)) + } + return +} + +func (e UserRole) String() string { + return string(e) +} + +// MarshalGQL implements graphql.Marshaler interface. +func (e UserRole) MarshalGQL(w io.Writer) { + _, _ = io.WriteString(w, strconv.Quote(e.String())) +} + +// UnmarshalGQL implements graphql.Unmarshaler interface. +func (e *UserRole) UnmarshalGQL(val interface{}) error { + str, ok := val.(string) + if !ok { + return fmt.Errorf("enum %T must be a string", val) + } + *e = UserRole(str) + switch *e { + case Admin, User, Unknown: + return nil + default: + return fmt.Errorf("%s is not a valid Role", str) + } +} + +type Scalar struct{} + +func (Scalar) MarshalGQL(io.Writer) {} +func (*Scalar) UnmarshalGQL(interface{}) error { return nil } +func (Scalar) Value() (driver.Value, error) { return nil, nil } + +func TestRType_Implements(t *testing.T) { + type ( + marshaler interface{ MarshalGQL(w io.Writer) } + unmarshaler interface{ UnmarshalGQL(v interface{}) error } + codec interface { + marshaler + unmarshaler + } + ) + var ( + codecType = reflect.TypeOf((*codec)(nil)).Elem() + marshalType = reflect.TypeOf((*marshaler)(nil)).Elem() + unmarshalType = reflect.TypeOf((*unmarshaler)(nil)).Elem() + ) + for _, f := range []ent.Field{ + field.Enum("role").GoType(Admin), + field.Other("scalar", &Scalar{}), + field.Other("scalar", Scalar{}), + } { + fd := f.Descriptor() + assert.True(t, fd.Info.RType.Implements(codecType)) + assert.True(t, fd.Info.RType.Implements(marshalType)) + assert.True(t, fd.Info.RType.Implements(unmarshalType)) + } +} + func TestTypeString(t *testing.T) { typ := field.TypeBool assert.Equal(t, "bool", typ.String()) diff --git a/schema/field/type.go b/schema/field/type.go index addbffc699..15e1964b12 100644 --- a/schema/field/type.go +++ b/schema/field/type.go @@ -120,12 +120,12 @@ func (t TypeInfo) ConstName() string { // ValueScanner indicates if this type implements the ValueScanner interface. func (t TypeInfo) ValueScanner() bool { - return t.RType.implements(valueScannerType) + return t.RType.Implements(valueScannerType) } // Valuer indicates if this type implements the driver.Valuer interface. func (t TypeInfo) Valuer() bool { - return t.RType.implements(valuerType) + return t.RType.Implements(valuerType) } // Comparable reports whether values of this type are comparable. @@ -147,7 +147,7 @@ var stringerType = reflect.TypeOf((*fmt.Stringer)(nil)).Elem() // Stringer indicates if this type implements the Stringer interface. func (t TypeInfo) Stringer() bool { - return t.RType.implements(stringerType) + return t.RType.Implements(stringerType) } var ( @@ -215,7 +215,8 @@ func (r *RType) IsPtr() bool { return r != nil && r.Kind == reflect.Ptr } -func (r *RType) implements(typ reflect.Type) bool { +// Implements reports whether the RType ~implements the given interface type. +func (r *RType) Implements(typ reflect.Type) bool { if r == nil { return false } From 0c7679e571be3299a5b273c4378cac9a37eada9b Mon Sep 17 00:00:00 2001 From: Ariel Mashraki Date: Tue, 8 Mar 2022 22:21:51 +0200 Subject: [PATCH 08/13] entc/gen: set Ref and Inverse for edge contains both From and To --- entc/gen/graph.go | 10 +++++++--- entc/gen/graph_test.go | 4 ++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/entc/gen/graph.go b/entc/gen/graph.go index 8293851102..e20e683a72 100644 --- a/entc/gen/graph.go +++ b/entc/gen/graph.go @@ -310,7 +310,7 @@ func (g *Graph) addEdges(schema *load.Schema) { ref := e.Ref expect(e.RefName == "", "reference name is derived from the assoc name: %s.%s <-> %s.%s", t.Name, ref.Name, t.Name, e.Name) expect(ref.Type == t.Name, "assoc-inverse edge allowed only as o2o relation of the same type") - t.Edges = append(t.Edges, &Edge{ + from := &Edge{ def: e, Type: typ, Name: e.Name, @@ -320,8 +320,10 @@ func (g *Graph) addEdges(schema *load.Schema) { Optional: !e.Required, StructTag: structTag(e.Name, e.Tag), Annotations: e.Annotations, - }, &Edge{ + } + to := &Edge{ def: ref, + Ref: from, Type: typ, Owner: t, Name: ref.Name, @@ -329,7 +331,9 @@ func (g *Graph) addEdges(schema *load.Schema) { Optional: !ref.Required, StructTag: structTag(ref.Name, ref.Tag), Annotations: ref.Annotations, - }) + } + from.Ref = to + t.Edges = append(t.Edges, from, to) default: panic(graphError{"edge must be either an assoc or inverse edge"}) } diff --git a/entc/gen/graph_test.go b/entc/gen/graph_test.go index 2d82f24c8d..61bea6c4a3 100644 --- a/entc/gen/graph_test.go +++ b/entc/gen/graph_test.go @@ -123,7 +123,11 @@ func TestNewGraph(t *testing.T) { require.Equal(graph.Nodes[0], e1.Type) require.Equal("t2_m2m_from", t2.Edges[5].Name) + require.Equal("t2_m2m_to", t2.Edges[5].Inverse) require.Equal("t2_m2m_to", t2.Edges[6].Name) + require.Empty(t2.Edges[6].Inverse) + require.Equal(t2.Edges[6], t2.Edges[5].Ref) + require.Equal(t2.Edges[5], t2.Edges[6].Ref) require.Equal(map[string]string{"Name": "From"}, t2.Edges[5].Annotations["GQL"]) require.Equal(map[string]string{"Name": "To"}, t2.Edges[6].Annotations["GQL"]) } From 342088fcce2a622bf50a267641317cabc0a61318 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Tue, 8 Mar 2022 23:32:13 +0200 Subject: [PATCH 09/13] docs: add maorlipchuk as a contributor for code (#2381) --- .all-contributorsrc | 9 +++++++++ doc/md/contributors.md | 1 + 2 files changed, 10 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index 8ba32286b3..2bfc1055b4 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -762,6 +762,15 @@ "contributions": [ "code" ] + }, + { + "login": "maorlipchuk", + "name": "maorlipchuk", + "avatar_url": "https://avatars.githubusercontent.com/u/7034637?v=4", + "profile": "https://github.com/maorlipchuk", + "contributions": [ + "code" + ] } ], "contributorsPerLine": 7, diff --git a/doc/md/contributors.md b/doc/md/contributors.md index beaae60d1e..72fed9f512 100644 --- a/doc/md/contributors.md +++ b/doc/md/contributors.md @@ -115,6 +115,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
MrParano1d

💻
Thomas Prebble

💻
Huy TQ

💻 +
maorlipchuk

💻 From 2853afc1dc2ea75f282a7c90595a9c9f0d0dcb91 Mon Sep 17 00:00:00 2001 From: MasseElch <12862103+masseelch@users.noreply.github.com> Date: Thu, 10 Mar 2022 16:40:57 +0100 Subject: [PATCH 10/13] =?UTF-8?q?dialect/sql/schema:=20add=20method=20to?= =?UTF-8?q?=20create=20a=20named=20versioned=20migration=20=E2=80=A6=20(#2?= =?UTF-8?q?385)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * dialect/sql/schema: add method to create a named versioned migration file * doc/md: documentation for named versioned migrations * entc/gen/template/dialect/sql/feature: add NamedDiff method to create named versioned migration files * all: go generate * doc/md: apply CR --- dialect/sql/schema/atlas.go | 6 +++--- dialect/sql/schema/migrate.go | 8 +++++++- doc/md/versioned-migrations.md | 6 ++++++ .../gen/template/dialect/sql/feature/migrate_diff.tmpl | 10 ++++++++++ entc/integration/migrate/versioned/migrate/migrate.go | 10 ++++++++++ 5 files changed, 36 insertions(+), 4 deletions(-) diff --git a/dialect/sql/schema/atlas.go b/dialect/sql/schema/atlas.go index 23e3cff6de..a4d68960a9 100644 --- a/dialect/sql/schema/atlas.go +++ b/dialect/sql/schema/atlas.go @@ -308,7 +308,7 @@ func (m *Migrate) atCreate(ctx context.Context, tables ...*Table) error { return err } } - plan, err := m.atDiff(ctx, tx, tables...) + plan, err := m.atDiff(ctx, tx, "", tables...) if err != nil { return err } @@ -334,7 +334,7 @@ func (m *Migrate) atCreate(ctx context.Context, tables ...*Table) error { return tx.Commit() } -func (m *Migrate) atDiff(ctx context.Context, conn dialect.ExecQuerier, tables ...*Table) (*migrate.Plan, error) { +func (m *Migrate) atDiff(ctx context.Context, conn dialect.ExecQuerier, name string, tables ...*Table) (*migrate.Plan, error) { drv, err := m.atOpen(conn) if err != nil { return nil, err @@ -364,7 +364,7 @@ func (m *Migrate) atDiff(ctx context.Context, conn dialect.ExecQuerier, tables . return nil, err } // Plan changes. - return drv.PlanChanges(ctx, "changes", changes) + return drv.PlanChanges(ctx, name, changes) } type db struct{ dialect.ExecQuerier } diff --git a/dialect/sql/schema/migrate.go b/dialect/sql/schema/migrate.go index 57a8cda3b9..291b051c5e 100644 --- a/dialect/sql/schema/migrate.go +++ b/dialect/sql/schema/migrate.go @@ -164,10 +164,16 @@ func (m *Migrate) Create(ctx context.Context, tables ...*Table) error { // Diff compares the state read from the StateReader with the state defined by Ent. // Changes will be written to migration files by the configures Planner. func (m *Migrate) Diff(ctx context.Context, tables ...*Table) error { + return m.NamedDiff(ctx, "changes", tables...) +} + +// NamedDiff compares the state read from the StateReader with the state defined by Ent. +// Changes will be written to migration files by the configures Planner. +func (m *Migrate) NamedDiff(ctx context.Context, name string, tables ...*Table) error { if m.atlas.dir == nil { return errors.New("no migration directory given") } - plan, err := m.atDiff(ctx, m, tables...) + plan, err := m.atDiff(ctx, m, name, tables...) if err != nil { return err } diff --git a/doc/md/versioned-migrations.md b/doc/md/versioned-migrations.md index a8e44cc715..d6b0d39c59 100644 --- a/doc/md/versioned-migrations.md +++ b/doc/md/versioned-migrations.md @@ -82,6 +82,8 @@ func main() { } // Write migration diff. err = client.Schema.Diff(ctx, schema.WithDir(dir)) + // You can use the following method to give the migration files a name. + // err = client.Schema.NamedDiff(ctx, "migration_name", schema.WithDir(dir)) if err != nil { log.Fatalf("failed creating schema resources: %v", err) } @@ -137,6 +139,10 @@ func main() { if err := m.Diff(context.Background(), tbls...); err != nil { log.Fatalln(err) } + // You can use the following method to give the migration files a name. + // if err := m.NamedDiff(context.Background(), "migration_name", tbls...); err != nil { + // log.Fatalln(err) + // } } ``` diff --git a/entc/gen/template/dialect/sql/feature/migrate_diff.tmpl b/entc/gen/template/dialect/sql/feature/migrate_diff.tmpl index df879f873b..9fa3a732ea 100644 --- a/entc/gen/template/dialect/sql/feature/migrate_diff.tmpl +++ b/entc/gen/template/dialect/sql/feature/migrate_diff.tmpl @@ -16,4 +16,14 @@ func (s *Schema) Diff(ctx context.Context, opts ...schema.MigrateOption) error { } return migrate.Diff(ctx, Tables...) } + +// NamedDiff creates a named migration file containing the statements to resolve the diff +// between the Ent schema and the connected database. +func (s *Schema) NamedDiff(ctx context.Context, name string, opts ...schema.MigrateOption) error { + migrate, err := schema.NewMigrate(s.drv, opts...) + if err != nil { + return fmt.Errorf("ent/migrate: %w", err) + } + return migrate.NamedDiff(ctx, name, Tables...) +} {{ end }} \ No newline at end of file diff --git a/entc/integration/migrate/versioned/migrate/migrate.go b/entc/integration/migrate/versioned/migrate/migrate.go index 9641fc2616..0a227e7534 100644 --- a/entc/integration/migrate/versioned/migrate/migrate.go +++ b/entc/integration/migrate/versioned/migrate/migrate.go @@ -66,6 +66,16 @@ func (s *Schema) Diff(ctx context.Context, opts ...schema.MigrateOption) error { return migrate.Diff(ctx, Tables...) } +// NamedDiff creates a named migration file containing the statements to resolve the diff +// between the Ent schema and the connected database. +func (s *Schema) NamedDiff(ctx context.Context, name string, opts ...schema.MigrateOption) error { + migrate, err := schema.NewMigrate(s.drv, opts...) + if err != nil { + return fmt.Errorf("ent/migrate: %w", err) + } + return migrate.NamedDiff(ctx, name, Tables...) +} + // WriteTo writes the schema changes to w instead of running them against the database. // // if err := client.Schema.WriteTo(context.Background(), os.Stdout); err != nil { From a27f753bc800ce0a20d3158fc1cdb4d284e9b068 Mon Sep 17 00:00:00 2001 From: Amit Shani Date: Sun, 13 Mar 2022 17:08:50 +0200 Subject: [PATCH 11/13] go: upgrade atlas for tidb support (#2394) --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 7cde1045ff..f896a320b8 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module entgo.io/ent go 1.17 require ( - ariga.io/atlas v0.3.7-0.20220303204946-787354f533c3 + ariga.io/atlas v0.3.8-0.20220313134928-770640fc02bf 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 ffebe355f4..5326718a4e 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,8 @@ ariga.io/atlas v0.3.5-0.20220215131223-8043663b4223 h1:kthcdfUZLRcoVeZetK8gJ0FGa ariga.io/atlas v0.3.5-0.20220215131223-8043663b4223/go.mod h1:XcLUpQX7Cq4qtagEHIleq3MJaBeeJ76BS8doc4gkOJk= ariga.io/atlas v0.3.7-0.20220303204946-787354f533c3 h1:fjG4oFCQEfGrRi0QoxWcH2OO28CE6VYa6DkIr3yDySU= ariga.io/atlas v0.3.7-0.20220303204946-787354f533c3/go.mod h1:yWGf4VPiD4SW83+kAqzD624txN9VKoJC+bpVXr2pKJA= +ariga.io/atlas v0.3.8-0.20220313134928-770640fc02bf h1:bAt5AUvr91QI8yXHME6qTsMTNM4BtfSB3M9o1cmt51E= +ariga.io/atlas v0.3.8-0.20220313134928-770640fc02bf/go.mod h1:ipw7dUlFanAylr9nvs8lCvOUC8hFG6PGd/gtr+uJMvk= 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= From 7328355e17c8bc137449f8f7772788aa9bc26ae9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Mar 2022 10:21:43 +0200 Subject: [PATCH 12/13] build(deps): bump github.com/spf13/cobra from 1.3.0 to 1.4.0 (#2386) Bumps [github.com/spf13/cobra](https://github.com/spf13/cobra) from 1.3.0 to 1.4.0. - [Release notes](https://github.com/spf13/cobra/releases) - [Changelog](https://github.com/spf13/cobra/blob/master/CHANGELOG.md) - [Commits](https://github.com/spf13/cobra/compare/v1.3.0...v1.4.0) --- updated-dependencies: - dependency-name: github.com/spf13/cobra dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index f896a320b8..08aa0c4283 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/mitchellh/mapstructure v1.4.3 github.com/modern-go/reflect2 v1.0.2 github.com/olekukonko/tablewriter v0.0.5 - github.com/spf13/cobra v1.3.0 + github.com/spf13/cobra v1.4.0 github.com/stretchr/objx v0.2.0 // indirect github.com/stretchr/testify v1.7.1-0.20210427113832-6241f9ab9942 go.opencensus.io v0.23.0 diff --git a/go.sum b/go.sum index 5326718a4e..04664d0dbf 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,3 @@ -ariga.io/atlas v0.3.5-0.20220215131223-8043663b4223 h1:kthcdfUZLRcoVeZetK8gJ0FGaw2D5zrr3h2nP/TuXCw= -ariga.io/atlas v0.3.5-0.20220215131223-8043663b4223/go.mod h1:XcLUpQX7Cq4qtagEHIleq3MJaBeeJ76BS8doc4gkOJk= ariga.io/atlas v0.3.7-0.20220303204946-787354f533c3 h1:fjG4oFCQEfGrRi0QoxWcH2OO28CE6VYa6DkIr3yDySU= ariga.io/atlas v0.3.7-0.20220303204946-787354f533c3/go.mod h1:yWGf4VPiD4SW83+kAqzD624txN9VKoJC+bpVXr2pKJA= ariga.io/atlas v0.3.8-0.20220313134928-770640fc02bf h1:bAt5AUvr91QI8yXHME6qTsMTNM4BtfSB3M9o1cmt51E= @@ -360,8 +358,9 @@ github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasO github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v1.3.0 h1:R7cSvGu+Vv+qX0gW5R/85dx2kmmJT5z5NM8ifdYjdn0= github.com/spf13/cobra v1.3.0/go.mod h1:BrRVncBjOJa/eUcVVm9CE+oC6as8k+VYr4NY7WCi9V4= +github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q= +github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= From 41db628df3cf6f8b6a927d1adacff060a79a8926 Mon Sep 17 00:00:00 2001 From: MasseElch <12862103+masseelch@users.noreply.github.com> Date: Mon, 14 Mar 2022 14:47:13 +0100 Subject: [PATCH 13/13] doc/website/blog: versioned migrations (#2387) * doc/md: clarify when feature flag is needed for versioned migrations * doc/website/blog: introduction * doc/md: more blog post * Apply suggestions from code review Co-authored-by: Hila Kashai <73284641+hilakashai@users.noreply.github.com> * doc/website/blog: move explanation about versioned migration purpose up * doc/website/blog: apply CR * doc/website/blog: reword desription of versioned migrations * doc/website/blog: typos * doc/website/blog: last minute changes pre publish Co-authored-by: Hila Kashai <73284641+hilakashai@users.noreply.github.com> --- doc/md/versioned-migrations.md | 17 +- ...2-03-14-announcing-versioned-migrations.md | 364 ++++++++++++++++++ 2 files changed, 372 insertions(+), 9 deletions(-) create mode 100644 doc/website/blog/2022-03-14-announcing-versioned-migrations.md diff --git a/doc/md/versioned-migrations.md b/doc/md/versioned-migrations.md index d6b0d39c59..b8a47e4866 100644 --- a/doc/md/versioned-migrations.md +++ b/doc/md/versioned-migrations.md @@ -10,10 +10,13 @@ tool you like (like golang-migrate, Flyway, liquibase). ![atlas-versioned-migration-process](https://entgo.io/images/assets/migrate-atlas-versioned.png) -## Configuration +## Generating Versioned Migration Files + +### From Client -In order to have Ent make the necessary changes to your code, you have to enable this feature with one of the two -options: +If you want to use an instantiated Ent client to create new migration files, you have to enable the versioned +migrations feature flag in order to have Ent make the necessary changes to the generated code. Depending on how you +execute the Ent code generator, you have to use one of the two options: 1. If you are using the default go generate configuration, simply add the `--feature sql/versioned-migration` to the `ent/generate.go` file as follows: @@ -47,12 +50,8 @@ func main() { } ``` -## Generating Versioned Migration Files - -### From Client - After regenerating the project, there will be an extra `Diff` method on the Ent client that you can use to inspect the -connected database, compare it with the schema definitions and create sql statements needed to migrate the database to +connected database, compare it with the schema definitions and create SQL statements needed to migrate the database to the graph. ```go @@ -95,7 +94,7 @@ You can then create a new set of migration files by simply calling `go run -mod= ### From Graph You can also generate new migration files without an instantiated Ent client. This can be useful if you want to make the -migration file creation part of a go generate workflow. +migration file creation part of a go generate workflow. Note, that in this case enabling the feature flag is optional. ```go package main diff --git a/doc/website/blog/2022-03-14-announcing-versioned-migrations.md b/doc/website/blog/2022-03-14-announcing-versioned-migrations.md new file mode 100644 index 0000000000..98b5bac561 --- /dev/null +++ b/doc/website/blog/2022-03-14-announcing-versioned-migrations.md @@ -0,0 +1,364 @@ +--- +title: Announcing Versioned Migrations Authoring +author: MasseElch +authorURL: "https://github.com/masseelch" +authorImageURL: "https://avatars.githubusercontent.com/u/12862103?v=4" +image: "https://entgo.io/images/assets/migrate/versioned-share.png" +--- + +When [Ariel](https://github.com/a8m) released Ent v0.10.0 at the end of January, +he [introduced](2022-01-20-announcing-new-migration-engine.md) a new migration engine for Ent based on another +open-source project called [Atlas](https://github.com/ariga/atlas). + +Initially, Atlas supported a style of managing database schemas that we call "declarative migrations". With declarative +migrations, the desired state of the database schema is given as input to the migration engine, which plans and executes +a set of actions to change the database to its desired state. This approach has been popularized in the field of +cloud native infrastructure by projects such as Kubernetes and Terraform. It works great in many cases, in +fact it has served the Ent framework very well in the past few years. However, database migrations are a very sensitive +topic, and many projects require a more controlled approach. + +For this reason, most industry standard solutions, like [Flyway](https://flywaydb.org/) +, [Liquibase](https://liquibase.org/), or [golang-migrate/migrate](https://github.com/golang-migrate/migrate) (which is +common in the Go ecosystem), support a workflow that they call "versioned migrations". + +With versioned migrations (sometimes called "change base migrations") instead of describing the desired state ("what the +database should look like"), you describe the changes itself ("how to reach the state"). Most of the time this is done +by creating a set of SQL files containing the statements needed. Each of the files is assigned a unique version and a +description of the changes. Tools like the ones mentioned earlier are then able to interpret the migration files and to +apply (some of) them in the correct order to transition to the desired database structure. + +In this post, I want to showcase a new kind of migration workflow that has recently been added to Atlas and Ent. We call +it "versioned migration authoring" and it's an attempt to combine the simplicity and expressiveness of the declarative +approach with the safety and explicitness of versioned migrations. With versioned migration authoring, users still +declare their desired state and use the Atlas engine to plan a safe migration from the existing to the new state. +However, instead of coupling the planning and execution, it is instead written into a file which can be checked into +source control, fine-tuned manually and reviewed in normal code review processes. + +As an example, I will demonstrate the workflow with `golang-migrate/migrate`. + +### Getting Started + +The very first thing to do, is to make sure you have an up-to-date Ent version: + +```shell +go get -u entgo.io/ent@master +``` + +There are two ways to have Ent generate migration files for schema changes. The first one is to use an instantiated Ent +client and the second one to generate the changes from a parsed schema graph. This post will take the second approach, +if you want to learn how to use the first one you can have a look at +the [documentation](./docs/versioned-migrations#from-client). + +### Generating Versioned Migration Files + +Since we have enabled the versioned migrations feature now, let's create a small schema and generate the initial set of +migration files. Consider the following schema for a fresh Ent project: + +```go title="ent/schema/user.go" +package schema + +import ( + "entgo.io/ent" + "entgo.io/ent/schema/field" + "entgo.io/ent/schema/index" +) + +// User holds the schema definition for the User entity. +type User struct { + ent.Schema +} + +// Fields of the User. +func (User) Fields() []ent.Field { + return []ent.Field{ + field.String("username"), + } +} + +// Indexes of the User. +func (User) Indexes() []ent.Index { + return []ent.Index{ + index.Fields("username").Unique(), + } +} + +``` + +As I stated before, we want to use the parsed schema graph to compute the difference between our schema and the +connected database. Here is an example of a (semi-)persistent MySQL docker container to use if you want to follow along: + +```shell +docker run --rm --name ent-versioned-migrations --detach --env MYSQL_ROOT_PASSWORD=pass --env MYSQL_DATABASE=ent -p 3306:3306 mysql +``` + +Once you are done, you can shut down the container and remove all resources with `docker stop ent-versioned-migrations`. + +Now, let's create a small function that loads the schema graph and generates the migration files. Create a new Go file +named `main.go` and copy the following contents: + +```go title="main.go" +package main + +import ( + "context" + "log" + "os" + + "ariga.io/atlas/sql/migrate" + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/schema" + "entgo.io/ent/entc" + "entgo.io/ent/entc/gen" + _ "github.com/go-sql-driver/mysql" +) + +func main() { + // We need a name for the new migration file. + if len(os.Args) < 2 { + log.Fatalln("no name given") + } + // Create a local migration directory. + dir, err := migrate.NewLocalDir("migrations") + if err != nil { + log.Fatalln(err) + } + // Load the graph. + graph, err := entc.LoadGraph("./ent/schema", &gen.Config{}) + if err != nil { + log.Fatalln(err) + } + tbls, err := graph.Tables() + if err != nil { + log.Fatalln(err) + } + // Open connection to the database. + drv, err := sql.Open("mysql", "root:pass@tcp(localhost:3306)/ent") + if err != nil { + log.Fatalln(err) + } + // Inspect the current database state and compare it with the graph. + m, err := schema.NewMigrate(drv, schema.WithDir(dir)) + if err != nil { + log.Fatalln(err) + } + if err := m.NamedDiff(context.Background(), os.Args[1], tbls...); err != nil { + log.Fatalln(err) + } +} +``` + +All we have to do now is create the migration directory and execute the above Go file: + +```shell +mkdir migrations +go run -mod=mod main.go initial +``` + +You will now see two new files in the `migrations` directory: `_initial.down.sql` +and `_initial.up.sql`. The `x.up.sql` files are used to create the database version `x` and `x.down.sql` to +roll back to the previous version. + +```sql title="_initial.up.sql" +CREATE TABLE `users` (`id` bigint NOT NULL AUTO_INCREMENT, `username` varchar(191) NOT NULL, PRIMARY KEY (`id`), UNIQUE INDEX `user_username` (`username`)) CHARSET utf8mb4 COLLATE utf8mb4_bin; +``` + +```sql title="_initial.down.sql" +DROP TABLE `users`; +``` + +### Applying Migrations + +To apply these migrations on your database, install the `golang-migrate/migrate` tool as described in +their [README](https://github.com/golang-migrate/migrate/blob/master/cmd/migrate/README.md). Then run the following +command to check if everything went as it should. + +```shell +migrate -help +``` +```text +Usage: migrate OPTIONS COMMAND [arg...] + migrate [ -version | -help ] + +Options: + -source Location of the migrations (driver://url) + -path Shorthand for -source=file://path + -database Run migrations against this database (driver://url) + -prefetch N Number of migrations to load in advance before executing (default 10) + -lock-timeout N Allow N seconds to acquire database lock (default 15) + -verbose Print verbose logging + -version Print version + -help Print usage + +Commands: + create [-ext E] [-dir D] [-seq] [-digits N] [-format] NAME + Create a set of timestamped up/down migrations titled NAME, in directory D with extension E. + Use -seq option to generate sequential up/down migrations with N digits. + Use -format option to specify a Go time format string. + goto V Migrate to version V + up [N] Apply all or N up migrations + down [N] Apply all or N down migrations + drop Drop everything inside database + force V Set version V but don't run migration (ignores dirty state) + version Print current migration version +``` + +Now we can execute our initial migration and sync the database with our schema: + +```shell +migrate -source 'file://migrations' -database 'mysql://root:pass@tcp(localhost:3306)/ent' up +``` +```text +/u initial (349.256951ms) +``` + +### Workflow + +To demonstrate the usual workflow when using versioned migrations we will both edit our schema graph and generate the +migration changes for it, and manually create a set of migration files to seed the database with some data. First, we +will add a Group schema and a many-to-many relation to the existing User schema, next create an admin Group with an +admin User in it. Go ahead and make the following changes: + +```go title="ent/schema/user.go" {22-28} +package schema + +import ( + "entgo.io/ent" + "entgo.io/ent/schema/edge" + "entgo.io/ent/schema/field" + "entgo.io/ent/schema/index" +) + +// User holds the schema definition for the User entity. +type User struct { + ent.Schema +} + +// Fields of the User. +func (User) Fields() []ent.Field { + return []ent.Field{ + field.String("username"), + } +} + +// Edges of the User. +func (User) Edges() []ent.Edge { + return []ent.Edge{ + edge.From("groups", Group.Type). + Ref("users"), + } +} + +// Indexes of the User. +func (User) Indexes() []ent.Index { + return []ent.Index{ + index.Fields("username").Unique(), + } +} +``` + +```go title="ent/schema/group.go" +package schema + +import ( + "entgo.io/ent" + "entgo.io/ent/schema/edge" + "entgo.io/ent/schema/field" + "entgo.io/ent/schema/index" +) + +// Group holds the schema definition for the Group entity. +type Group struct { + ent.Schema +} + +// Fields of the Group. +func (Group) Fields() []ent.Field { + return []ent.Field{ + field.String("name"), + } +} + +// Edges of the Group. +func (Group) Edges() []ent.Edge { + return []ent.Edge{ + edge.To("users", User.Type), + } +} + +// Indexes of the Group. +func (Group) Indexes() []ent.Index { + return []ent.Index{ + index.Fields("name").Unique(), + } +} +``` +Once the schema is updated, create a new set of migration files. + +```shell +go run -mod=mod main.go add_group_schema +``` + +Once again there will be two new files in the `migrations` directory: `_add_group_schema.down.sql` +and `_add_group_schema.up.sql`. + +```sql title="_add_group_schema.up.sql" +CREATE TABLE `groups` (`id` bigint NOT NULL AUTO_INCREMENT, `name` varchar(191) NOT NULL, PRIMARY KEY (`id`), UNIQUE INDEX `group_name` (`name`)) CHARSET utf8mb4 COLLATE utf8mb4_bin; +CREATE TABLE `group_users` (`group_id` bigint NOT NULL, `user_id` bigint NOT NULL, PRIMARY KEY (`group_id`, `user_id`), CONSTRAINT `group_users_group_id` FOREIGN KEY (`group_id`) REFERENCES `groups` (`id`) ON DELETE CASCADE, CONSTRAINT `group_users_user_id` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE) CHARSET utf8mb4 COLLATE utf8mb4_bin; +``` + +```sql title="_add_group_schema.down.sql" +DROP TABLE `group_users`; +DROP TABLE `groups`; +``` + +Now you can either edit the generated files to add the seed data or create new files for it. I chose the latter: + +```shell +migrate create -format unix -ext sql -dir migrations seed_admin +``` +```text +[...]/ent-versioned-migrations/migrations/_seed_admin.up.sql +[...]/ent-versioned-migrations/migrations/_seed_admin.down.sql +``` + +You can now edit those files and add statements to create an admin Group and User. + +```sql title="migrations/_seed_admin.up.sql" +INSERT INTO `groups` (`id`, `name`) VALUES (1, 'Admins'); +INSERT INTO `users` (`id`, `username`) VALUES (1, 'admin'); +INSERT INTO `group_users` (`group_id`, `user_id`) VALUES (1, 1); +``` + +```sql title="migrations/_seed_admin.down.sql" +DELETE FROM `group_users` where `group_id` = 1 and `user_id` = 1; +DELETE FROM `groups` where id = 1; +DELETE FROM `users` where id = 1; +``` + +Apply the migrations once more, and you are done: + +```shell +migrate -source file://migrations -database 'mysql://root:pass@tcp(localhost:3306)/ent' up +``` + +```text +/u add_group_schema (417.434415ms) +/u seed_admin (674.189872ms) +``` + +### Wrapping Up + +In this post, we demonstrated the general workflow when using Ent Versioned Migrations with `golang-migate/migrate`. We +created a small example schema, generated the migration files for it and learned how to apply them. We now know the +workflow and how to add custom migration files. + +Have questions? Need help with getting started? Feel free to [join our Slack channel](https://entgo.io/docs/slack/). + +:::note For more Ent news and updates: + +- Subscribe to our [Newsletter](https://www.getrevue.co/profile/ent) +- Follow us on [Twitter](https://twitter.com/entgo_io) +- Join us on #ent on the [Gophers Slack](https://entgo.io/docs/slack) +- Join us on the [Ent Discord Server](https://discord.gg/qZmPgTE6RX) + +:::