Skip to content

Commit

Permalink
doc/md: example and test for migration apply hooks (#2514)
Browse files Browse the repository at this point in the history
  • Loading branch information
a8m committed May 2, 2022
1 parent 2d291df commit dab95be
Show file tree
Hide file tree
Showing 21 changed files with 676 additions and 16 deletions.
16 changes: 8 additions & 8 deletions dialect/sql/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,22 +21,22 @@ type Driver struct {
}

// NewDriver creates a new Driver with the given Conn and dialect.
func NewDriver(c Conn, d string) *Driver {
return &Driver{c, d}
func NewDriver(dialect string, c Conn) *Driver {
return &Driver{dialect: dialect, Conn: c}
}

// Open wraps the database/sql.Open method and returns a dialect.Driver that implements the an ent/dialect.Driver interface.
func Open(driver, source string) (*Driver, error) {
db, err := sql.Open(driver, source)
func Open(dialect, source string) (*Driver, error) {
db, err := sql.Open(dialect, source)
if err != nil {
return nil, err
}
return NewDriver(Conn{db}, driver), nil
return NewDriver(dialect, Conn{db}), nil
}

// OpenDB wraps the given database/sql.DB method with a Driver.
func OpenDB(driver string, db *sql.DB) *Driver {
return NewDriver(Conn{db}, driver)
func OpenDB(dialect string, db *sql.DB) *Driver {
return NewDriver(dialect, Conn{db})
}

// DB returns the underlying *sql.DB instance.
Expand All @@ -46,7 +46,7 @@ func (d Driver) DB() *sql.DB {

// Dialect implements the dialect.Dialect method.
func (d Driver) Dialect() string {
// If the underlying driver is wrapped with opencensus driver.
// If the underlying driver is wrapped with a telemetry driver.
for _, name := range []string{dialect.MySQL, dialect.SQLite, dialect.Postgres} {
if strings.HasPrefix(d.dialect, name) {
return name
Expand Down
2 changes: 1 addition & 1 deletion doc/md/dialects.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ supported by default by SQLite, and will be added in the future using a [tempora

Gremlin does not support migration nor indexes, and **<ins>it's considered experimental</ins>**.

## TiDB - **<ins>preview</ins>**
## TiDB **(<ins>preview</ins>)**

TiDB support is in preview and requires the [Atlas migration engine](#atlas-integration).
TiDB is MySQL compatible and thus any feature that works on MySQL _should_ work on TiDB as well.
Expand Down
63 changes: 63 additions & 0 deletions doc/md/migrate.md
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,8 @@ func main() {
}
```

#### `Diff` Hook Example

In case a field was renamed in the `ent/schema`, Ent won't detect this change as renaming and will propose `DropColumn`
and `AddColumn` changes in the diff stage. One way to get over this is to use the
[StorageKey](schema-fields.md#storage-key) option on the field and keep the old column name in the database table.
Expand Down Expand Up @@ -415,4 +417,65 @@ func renameColumnHook(next schema.Differ) schema.Differ {
return changes, nil
})
}
```

#### `Apply` Hook Example

The `Apply` hook allows accessing and mutating the migration plan and its raw changes (SQL statements), but in addition
to that it is also useful for executing custom SQL statements before or after the plan is applied. For example, changing
a nullable column to non-nullable without a default value is not allowed by default. However, we can work around this
using an `Apply` hook that `UPDATE`s all rows that contain `NULL` value in this column:

```go
func main() {
client, err := ent.Open("mysql", "root:pass@tcp(localhost:3306)/test")
if err != nil {
log.Fatalf("failed connecting to mysql: %v", err)
}
defer client.Close()
// ...
if err := client.Schema.Create(ctx, schema.WithApplyHook(fillNulls)); err != nil {
log.Fatalf("failed creating schema resources: %v", err)
}
}

func fillNulls(next schema.Applier) schema.Applier {
return schema.ApplyFunc(func(ctx context.Context, conn dialect.ExecQuerier, plan *migrate.Plan) error {
// There are three ways to UPDATE the NULL values to "Unknown" in this stage.
// Append a custom migrate.Change to the plan, execute an SQL statement directly
// on the dialect.ExecQuerier, or use the ent.Client used by the project.

// Execute a custom SQL statement.
query, args := sql.Dialect(dialect.MySQL).
Update(user.Table).
Set(user.FieldDropOptional, "Unknown").
Where(sql.IsNull(user.FieldDropOptional)).
Query()
if err := conn.Exec(ctx, query, args, nil); err != nil {
return err
}

// Append a custom statement to migrate.Plan.
//
// plan.Changes = append([]*migrate.Change{
// {
// Cmd: fmt.Sprintf("UPDATE users SET %[1]s = '%[2]s' WHERE %[1]s IS NULL", user.FieldDropOptional, "Unknown"),
// },
// }, plan.Changes...)

// Use the ent.Client used by the project.
//
// drv := sql.NewDriver(dialect.MySQL, sql.Conn{ExecQuerier: conn.(*sql.Tx)})
// if err := ent.NewClient(ent.Driver(drv)).
// User.
// Update().
// SetDropOptional("Unknown").
// Where(/* Add predicate to filter only rows with NULL values */).
// Exec(ctx); err != nil {
// return fmt.Errorf("fix default values to uppercase: %w", err)
// }

return next.Apply(ctx, conn, plan)
})
}
```
5 changes: 3 additions & 2 deletions entc/integration/migrate/entv1/migrate/schema.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

75 changes: 74 additions & 1 deletion entc/integration/migrate/entv1/mutation.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions entc/integration/migrate/entv1/schema/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ func (User) Fields() []ent.Field {
field.String("workplace").
MaxLen(30).
Optional(),
field.String("drop_optional").
Optional(),
}
}

Expand Down
12 changes: 11 additions & 1 deletion entc/integration/migrate/entv1/user.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions entc/integration/migrate/entv1/user/user.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit dab95be

Please sign in to comment.