diff --git a/.all-contributorsrc b/.all-contributorsrc
index c77780ee7d..2bfc1055b4 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -753,6 +753,24 @@
"contributions": [
"code"
]
+ },
+ {
+ "login": "imhuytq",
+ "name": "Huy TQ",
+ "avatar_url": "https://avatars.githubusercontent.com/u/5723282?v=4",
+ "profile": "https://huytq.com",
+ "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/.github/workflows/cd.yml b/.github/workflows/cd.yml
index e519b22764..01c089a62b 100644
--- a/.github/workflows/cd.yml
+++ b/.github/workflows/cd.yml
@@ -13,8 +13,8 @@ jobs:
name: docs
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2.4.0
- - uses: actions/setup-node@v2.5.1
+ - uses: actions/checkout@v3
+ - uses: actions/setup-node@v3
with:
node-version: 14
- name: Install Dependencies
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
diff --git a/dialect/sql/schema/atlas.go b/dialect/sql/schema/atlas.go
index 24a45dfe8b..fdff9a8e48 100644
--- a/dialect/sql/schema/atlas.go
+++ b/dialect/sql/schema/atlas.go
@@ -291,18 +291,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.withForeignKeys {
m.atlas.diff = append(m.atlas.diff, withoutForeignKeys)
@@ -329,7 +329,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
}
@@ -355,7 +355,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
@@ -385,7 +385,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, name, changes)
}
type db struct{ dialect.ExecQuerier }
diff --git a/dialect/sql/schema/migrate.go b/dialect/sql/schema/migrate.go
index 5be835bbf7..3484ff7991 100644
--- a/dialect/sql/schema/migrate.go
+++ b/dialect/sql/schema/migrate.go
@@ -165,14 +165,24 @@ 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
}
- return migrate.New(nil, m.atlas.dir, m.atlas.fmt).WritePlan(plan)
+ // 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)
}
func (m *Migrate) create(ctx context.Context, tables ...*Table) error {
diff --git a/doc/md/contributors.md b/doc/md/contributors.md
index a086fcc5ac..72fed9f512 100644
--- a/doc/md/contributors.md
+++ b/doc/md/contributors.md
@@ -114,6 +114,8 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
Pedro Henrique 💻 |
MrParano1d 💻 |
Thomas Prebble 💻 |
+ Huy TQ 💻 |
+ maorlipchuk 💻 |
diff --git a/doc/md/versioned-migrations.md b/doc/md/versioned-migrations.md
index a8e44cc715..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
@@ -82,6 +81,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)
}
@@ -93,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
@@ -137,6 +138,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/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)
+
+:::
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"])
}
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/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/entc/integration/migrate/migrate_test.go b/entc/integration/migrate/migrate_test.go
index a365e87cbf..c3e6b8a9fa 100644
--- a/entc/integration/migrate/migrate_test.go
+++ b/entc/integration/migrate/migrate_test.go
@@ -99,6 +99,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.
@@ -163,7 +164,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)
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 {
diff --git a/go.mod b/go.mod
index e9250a2093..08aa0c4283 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.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
@@ -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 8057272693..04664d0dbf 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=
+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=
@@ -356,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=
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
}