# Ecto

> ### Requirement {: .tip}
>
> This guide expects that you have:
>
> * Gone through the [introductory guides](installation.html)
> * Got a Phoenix application [up and running](up_and_running.html)

Most web applications today need some form of data validation and persistence. In the Elixir ecosystem, we have `Ecto` to enable this. Before we jump into building database-backed web features, we're going to focus on the finer details of Ecto to give a solid base to build our web features on top of. Let's get started!

Phoenix uses Ecto to provide builtin support to the following databases:

* PostgreSQL (via [`postgrex`](https://github.com/elixir-ecto/postgrex))
* MySQL (via [`myxql`](https://github.com/elixir-ecto/myxql))
* MSSQL (via [`tds`](https://github.com/livehelpnow/tds))
* ETS (via [`etso`](https://github.com/evadne/etso))
* SQLite3 (via [`ecto_sqlite3`](https://github.com/elixir-sqlite/ecto_sqlite3))

Newly generated Phoenix projects include Ecto with the PostgreSQL adapter by default. You can pass the `--database` option to change or `--no-ecto` flag to exclude this.

Ecto also provides support for other databases and it has many learning resources available. Please check out [Ecto's README](https://github.com/elixir-ecto/ecto) for general information.

This guide assumes that we have generated our new application with Ecto integration and that we will be using PostgreSQL. The introductory guides cover how to get your first application up and running. For using other databases, see the [Swapping Databases](swapping_databases.html) how-to guide.

## Using `phx.gen.schema`

Once we have Ecto and PostgreSQL installed and configured, the easiest way to use Ecto is to generate an Ecto *schema* through the `phx.gen.schema` task. Ecto schemas are a way for us to specify how Elixir data types map to and from external sources, such as database tables. Let's generate a `User` schema with `name`, `email`, `bio`, and `number_of_pets` fields.

```console
$ mix phx.gen.schema User users name:string email:string \
bio:string number_of_pets:integer

* creating ./lib/hello/user.ex
* creating priv/repo/migrations/20170523151118_create_users.exs

Remember to update your repository by running migrations:

   $ mix ecto.migrate
```

A couple of files were generated with this task. First, we have a `user.ex` file, containing our Ecto schema with our schema definition of the fields we passed to the task. Next, a migration file was generated inside `priv/repo/migrations/` which will create our database table that our schema maps to.

With our files in place, let's follow the instructions and run our migration:

```console
$ mix ecto.migrate
Compiling 1 file (.ex)
Generated hello app

[info] == Running Hello.Repo.Migrations.CreateUsers.change/0 forward

[info] create table users

[info] == Migrated in 0.0s
```

Mix assumes that we are in the development environment unless we tell it otherwise with `MIX_ENV=prod mix ecto.migrate`.

If we log in to our database server, and connect to our `hello_dev` database, we should see our `users` table. Ecto assumes that we want an integer column called `id` as our primary key, so we should see a sequence generated for that as well.

```console
$ psql -U postgres

Type "help" for help.

postgres=# \connect hello_dev
You are now connected to database "hello_dev" as user "postgres".
hello_dev=# \d
                List of relations
 Schema |       Name        |   Type   |  Owner
--------+-------------------+----------+----------
 public | schema_migrations | table    | postgres
 public | users             | table    | postgres
 public | users_id_seq      | sequence | postgres
(3 rows)
hello_dev=# \q
```

If we take a look at the migration generated by `phx.gen.schema` in `priv/repo/migrations/`, we'll see that it will add the columns we specified. It will also add timestamp columns for `inserted_at` and `updated_at` which come from the [`timestamps/1`] function.

```elixir
defmodule Hello.Repo.Migrations.CreateUsers do
  use Ecto.Migration

  def change do
    create table(:users) do
      add :name, :string
      add :email, :string
      add :bio, :string
      add :number_of_pets, :integer

      timestamps(type: :utc_datetime)
    end
  end
end
```

And here's what that translates to in the actual `users` table.

```console
$ psql
hello_dev=# \d users
Table "public.users"
Column         |            Type                | Modifiers
---------------+--------------------------------+----------------------------------------------------
id             | bigint                         | not null default nextval('users_id_seq'::regclass)
name           | character varying(255)         |
email          | character varying(255)         |
bio            | character varying(255)         |
number_of_pets | integer                        |
inserted_at    | timestamp(0) without time zone | not null
updated_at     | timestamp(0) without time zone | not null
Indexes:
"users_pkey" PRIMARY KEY, btree (id)
```

Notice that we do get an `id` column as our primary key by default, even though it isn't listed as a field in our migration.

## Repo configuration

Our `Hello.Repo` module is the foundation we need to work with databases in a Phoenix application. Phoenix generated it for us in `lib/hello/repo.ex`, and this is what it looks like.

```elixir
defmodule Hello.Repo do
  use Ecto.Repo,
    otp_app: :hello,
    adapter: Ecto.Adapters.Postgres
end
```

It begins by defining the repository module. Then it configures our `otp_app` name, and the `adapter` – `Postgres`, in our case.

Our repo has three main tasks - to bring in all the common query functions from [`Ecto.Repo`], to set the `otp_app` name equal to our application name, and to configure our database adapter. We'll talk more about how to use `Hello.Repo` in a bit.

When `phx.new` generated our application, it included some basic repository configuration as well. Let's look at `config/dev.exs`.

```elixir
...
# Configure your database
config :hello, Hello.Repo,
  username: "postgres",
  password: "postgres",
  hostname: "localhost",
  database: "hello_dev",
  show_sensitive_data_on_connection_error: true,
  pool_size: 10
...
```

We also have similar configuration in `config/test.exs` and `config/runtime.exs` which can also be changed to match your actual credentials.

## The schema

Ecto schemas are responsible for mapping Elixir values to external data sources, as well as mapping external data back into Elixir data structures. We can also define relationships to other schemas in our applications. For example, our `User` schema might have many posts, and each post would belong to a user. Ecto also handles data validation and type casting with changesets, which we'll discuss in a moment.

Here's the `User` schema that Phoenix generated for us.

```elixir
defmodule Hello.User do
  use Ecto.Schema
  import Ecto.Changeset

  schema "users" do
    field :bio, :string
    field :email, :string
    field :name, :string
    field :number_of_pets, :integer

    timestamps(type: :utc_datetime)
  end

  @doc false
  def changeset(user, attrs) do
    user
    |> cast(attrs, [:name, :email, :bio, :number_of_pets])
    |> validate_required([:name, :email, :bio, :number_of_pets])
  end
end
```

Ecto schemas at their core are simply Elixir structs. Our `schema` block is what tells Ecto how to cast our `%User{}` struct fields to and from the external `users` table. Often, the ability to simply cast data to and from the database isn't enough and extra data validation is required. This is where Ecto changesets come in. Let's dive in!

## Changesets and validations

Changesets define a pipeline of transformations our data needs to undergo before it will be ready for our application to use. These transformations might include type-casting, user input validation, and filtering out any extraneous parameters. Often we'll use changesets to validate user input before writing it to the database. Ecto repositories are also changeset-aware, which allows them not only to refuse invalid data, but also perform the minimal database updates possible by inspecting the changeset to know which fields have changed.

Let's take a closer look at our default changeset function.

```elixir
def changeset(user, attrs) do
  user
  |> cast(attrs, [:name, :email, :bio, :number_of_pets])
  |> validate_required([:name, :email, :bio, :number_of_pets])
end
```

Right now, we have two transformations in our pipeline. In the first call, we invoke `Ecto.Changeset.cast/3`, passing in our external parameters and marking which fields are required for validation.

[`cast/3`] first takes a struct, then the parameters (the proposed updates), and then the final field is the list of columns to be updated. [`cast/3`] also will only take fields that exist in the schema.

Next, `Ecto.Changeset.validate_required/3` checks that this list of fields is present in the changeset that [`cast/3`] returns. By default with the generator, all fields are required.

We can verify this functionality in `IEx`. Let's fire up our application inside IEx by running `iex -S mix`. In order to minimize typing and make this easier to read, let's alias our `Hello.User` struct.

```console
$ iex -S mix

iex> alias Hello.User
Hello.User
```

Next, let's build a changeset from our schema with an empty `User` struct, and an empty map of parameters.

```elixir
iex> changeset = User.changeset(%User{}, %{})
#Ecto.Changeset<
  action: nil,
  changes: %{},
  errors: [
    name: {"can't be blank", [validation: :required]},
    email: {"can't be blank", [validation: :required]},
    bio: {"can't be blank", [validation: :required]},
    number_of_pets: {"can't be blank", [validation: :required]}
  ],
  data: #Hello.User<>,
  valid?: false
>
```

Once we have a changeset, we can check if it is valid.

```elixir
iex> changeset.valid?
false
```

Since this one is not valid, we can ask it what the errors are.

```elixir
iex> changeset.errors
[
  name: {"can't be blank", [validation: :required]},
  email: {"can't be blank", [validation: :required]},
  bio: {"can't be blank", [validation: :required]},
  number_of_pets: {"can't be blank", [validation: :required]}
]
```

Now, let's make `number_of_pets` optional. In order to do this, we simply remove it from the list in the `changeset/2` function, in `Hello.User`.

```elixir
|> validate_required([:name, :email, :bio])
```

Now casting the changeset should tell us that only `name`, `email`, and `bio` can't be blank. We can test that by running `recompile()` inside IEx and then rebuilding our changeset.

```elixir
iex> recompile()
Compiling 1 file (.ex)
:ok

iex> changeset = User.changeset(%User{}, %{})
#Ecto.Changeset<
  action: nil,
  changes: %{},
  errors: [
    name: {"can't be blank", [validation: :required]},
    email: {"can't be blank", [validation: :required]},
    bio: {"can't be blank", [validation: :required]}
  ],
  data: #Hello.User<>,
  valid?: false
>

iex> changeset.errors
[
  name: {"can't be blank", [validation: :required]},
  email: {"can't be blank", [validation: :required]},
  bio: {"can't be blank", [validation: :required]}
]
```

What happens if we pass a key-value pair that is neither defined in the schema nor required?

Inside our existing IEx shell, let's create a `params` map with valid values plus an extra `random_key: "random value"`.

```elixir
iex> params = %{name: "Joe Example", email: "joe@example.com", bio: "An example to all", number_of_pets: 5, random_key: "random value"}
%{
  bio: "An example to all",
  email: "joe@example.com",
  name: "Joe Example",
  number_of_pets: 5,
  random_key: "random value"
}
```

Next, let's use our new `params` map to create another changeset.

```elixir
iex> changeset = User.changeset(%User{}, params)
#Ecto.Changeset<
  action: nil,
  changes: %{
    bio: "An example to all",
    email: "joe@example.com",
    name: "Joe Example",
    number_of_pets: 5
  },
  errors: [],
  data: #Hello.User<>,
  valid?: true
>
```

Our new changeset is valid.

```elixir
iex> changeset.valid?
true
```

We can also check the changeset's changes - the map we get after all of the transformations are complete.

```elixir
iex(9)> changeset.changes
%{bio: "An example to all", email: "joe@example.com", name: "Joe Example",
  number_of_pets: 5}
```

Notice that our `random_key` key and `"random_value"` value have been removed from the final changeset. Changesets allow us to cast external data, such as user input on a web form or data from a CSV file into valid data into our system. Invalid parameters will be stripped and bad data that is unable to be cast according to our schema will be highlighted in the changeset errors.

We can validate more than just whether a field is required or not. Let's take a look at some finer-grained validations.

What if we had a requirement that all biographies in our system must be at least two characters long? We can do this easily by adding another transformation to the pipeline in our changeset which validates the length of the `bio` field.

```elixir
def changeset(user, attrs) do
  user
  |> cast(attrs, [:name, :email, :bio, :number_of_pets])
  |> validate_required([:name, :email, :bio, :number_of_pets])
  |> validate_length(:bio, min: 2)
end
```

Now, if we try to cast data containing a value of `"A"` for our user's `bio`, we should see the failed validation in the changeset's errors.

```elixir
iex> recompile()

iex> changeset = User.changeset(%User{}, %{bio: "A"})

iex> changeset.errors[:bio]
{"should be at least %{count} character(s)",
 [count: 2, validation: :length, kind: :min, type: :string]}
```

If we also have a requirement for the maximum length that a bio can have, we can simply add another validation.

```elixir
def changeset(user, attrs) do
  user
  |> cast(attrs, [:name, :email, :bio, :number_of_pets])
  |> validate_required([:name, :email, :bio, :number_of_pets])
  |> validate_length(:bio, min: 2)
  |> validate_length(:bio, max: 140)
end
```

Let's say we want to perform at least some rudimentary format validation on the `email` field. All we want to check for is the presence of the `@`. The `Ecto.Changeset.validate_format/3` function is just what we need.

```elixir
def changeset(user, attrs) do
  user
  |> cast(attrs, [:name, :email, :bio, :number_of_pets])
  |> validate_required([:name, :email, :bio, :number_of_pets])
  |> validate_length(:bio, min: 2)
  |> validate_length(:bio, max: 140)
  |> validate_format(:email, ~r/@/)
end
```

If we try to cast a user with an email of `"example.com"`, we should see an error message like the following:

```elixir
iex> recompile()

iex> changeset = User.changeset(%User{}, %{email: "example.com"})

iex> changeset.errors[:email]
{"has invalid format", [validation: :format]}
```

There are many more validations and transformations we can perform in a changeset. Please see the [Ecto Changeset documentation](https://ecto.hexdocs.pm/Ecto.Changeset.html) for more information.

## Data persistence

We've explored migrations and schemas, but we haven't yet persisted any of our schemas or changesets. We briefly looked at our repository module in `lib/hello/repo.ex` earlier, and now it's time to put it to use.

Ecto repositories are the interface into a storage system, be it a database like PostgreSQL or an external service like a RESTful API. The `Repo` module's purpose is to take care of the finer details of persistence and data querying for us. As the caller, we only care about fetching and persisting data. The `Repo` module takes care of the underlying database adapter communication, connection pooling, and error translation for database constraint violations.

Let's head back over to IEx with `iex -S mix`, and insert a couple of users into the database.

```elixir
iex> alias Hello.{Repo, User}
[Hello.Repo, Hello.User]

iex> Repo.insert(%User{email: "user1@example.com"})
[debug] QUERY OK db=6.5ms queue=0.5ms idle=1358.3ms
INSERT INTO "users" ("email","inserted_at","updated_at") VALUES ($1,$2,$3) RETURNING "id" ["user1@example.com", ~U[2021-02-25 01:58:55Z], ~U[2021-02-25 01:58:55Z]]
{:ok,
 %Hello.User{
   __meta__: #Ecto.Schema.Metadata<:loaded, "users">,
   bio: nil,
   email: "user1@example.com",
   id: 1,
   inserted_at: ~U[2021-02-25 01:58:55Z],
   name: nil,
   number_of_pets: nil,
   updated_at: ~U[2021-02-25 01:58:55Z]
 }}

iex> Repo.insert(%User{email: "user2@example.com"})
[debug] QUERY OK db=1.3ms idle=1402.7ms
INSERT INTO "users" ("email","inserted_at","updated_at") VALUES ($1,$2,$3) RETURNING "id" ["user2@example.com", ~U[2021-02-25 02:03:28Z], ~U[2021-02-25 02:03:28Z]]
{:ok,
 %Hello.User{
   __meta__: #Ecto.Schema.Metadata<:loaded, "users">,
   bio: nil,
   email: "user2@example.com",
   id: 2,
   inserted_at: ~U[2021-02-25 02:03:28Z],
   name: nil,
   number_of_pets: nil,
   updated_at: ~U[2021-02-25 02:03:28Z]
 }}
```

We started by aliasing our `User` and `Repo` modules for easy access. Next, we called [`Repo.insert/2`] with a User struct. Since we are in the `dev` environment, we can see the debug logs for the query our repository performed when inserting the underlying `%User{}` data. We received a two-element tuple back with `{:ok, %User{}}`, which lets us know the insertion was successful.

We could also insert a user by passing a changeset to [`Repo.insert/2`]. If the changeset is valid, the repository will use an optimized database query to insert the record, and return a two-element tuple back, as above. If the changeset is not valid, we receive a two-element tuple consisting of `:error` plus the invalid changeset.

With a couple of users inserted, let's fetch them back out of the repo.

```elixir
iex> Repo.all(User)
[debug] QUERY OK source="users" db=5.8ms queue=1.4ms idle=1672.0ms
SELECT u0."id", u0."bio", u0."email", u0."name", u0."number_of_pets", u0."inserted_at", u0."updated_at" FROM "users" AS u0 []
[
  %Hello.User{
    __meta__: #Ecto.Schema.Metadata<:loaded, "users">,
    bio: nil,
    email: "user1@example.com",
    id: 1,
    inserted_at: ~U[2021-02-25 01:58:55Z],
    name: nil,
    number_of_pets: nil,
    updated_at: ~U[2021-02-25 01:58:55Z]
  },
  %Hello.User{
    __meta__: #Ecto.Schema.Metadata<:loaded, "users">,
    bio: nil,
    email: "user2@example.com",
    id: 2,
    inserted_at: ~U[2021-02-25 02:03:28Z],
    name: nil,
    number_of_pets: nil,
    updated_at: ~U[2021-02-25 02:03:28Z]
  }
]
```

That was easy! `Repo.all/1` takes a data source, our `User` schema in this case, and translates that to an underlying SQL query against our database. After it fetches the data, the Repo then uses our Ecto schema to map the database values back into Elixir data structures according to our `User` schema. We're not just limited to basic querying – Ecto includes a full-fledged query DSL for advanced SQL generation. In addition to a natural Elixir DSL, Ecto's query engine gives us multiple great features, such as SQL injection protection and compile-time optimization of queries. Let's try it out.

```elixir
iex> import Ecto.Query
Ecto.Query

iex> Repo.all(from u in User, select: u.email)
[debug] QUERY OK source="users" db=0.8ms queue=0.9ms idle=1634.0ms
SELECT u0."email" FROM "users" AS u0 []
["user1@example.com", "user2@example.com"]
```

First, we imported `Ecto.Query`, which imports the [`from/2`] macro of Ecto's Query DSL. Next, we built a query which selects all the email addresses in our users table. Let's try another example.

```elixir
iex> Repo.one(from u in User, where: ilike(u.email, "%1%"),
                               select: count(u.id))
[debug] QUERY OK source="users" db=1.6ms SELECT count(u0."id") FROM "users" AS u0 WHERE (u0."email" ILIKE '%1%') []
1
```

Now we're starting to get a taste of Ecto's rich querying capabilities. We used [`Repo.one/2`] to fetch the count of all users with an email address containing `1`, and received the expected count in return. This just scratches the surface of Ecto's query interface, and much more is supported such as sub-querying, interval queries, and advanced select statements. For example, let's build a query to fetch a map of all user id's to their email addresses.

```elixir
iex> Repo.all(from u in User, select: %{u.id => u.email})
[debug] QUERY OK source="users" db=0.9ms
SELECT u0."id", u0."email" FROM "users" AS u0 []
[
  %{1 => "user1@example.com"},
  %{2 => "user2@example.com"}
]
```

That little query packed a big punch. It both fetched all user emails from the database and efficiently built a map of the results in one go. You should browse the [Ecto.Query documentation](https://ecto.hexdocs.pm/Ecto.Query.html#content) to see the breadth of supported query features.

In addition to inserts, we can also perform updates and deletes with [`Repo.update/2`] and [`Repo.delete/2`] to update or delete a single schema. Ecto also supports bulk persistence with the [`Repo.insert_all/3`], [`Repo.update_all/3`], and [`Repo.delete_all/2`] functions.

There is quite a bit more that Ecto can do and we've only barely scratched the surface. With a solid Ecto foundation in place, we're now ready to continue building our app and integrate the web-facing application with our backend persistence. Along the way, we'll expand our Ecto knowledge and learn how to properly isolate our web interface from the underlying details of our system. Please take a look at the [Ecto documentation](https://ecto.hexdocs.pm/) for the rest of the story.

In our [Data modelling guides](contexts.html), we'll find out how to wrap up our Ecto access and business logic behind modules that group related functionality. We'll see how Phoenix helps us design maintainable applications, and we'll find out about other neat Ecto features along the way.

## Mix tasks

Ecto comes with a collection of Mix tasks to make it easier to manage your database and your application. Here is a quick look into the most important ones.

### `mix ecto.create`

This task will create the database specified by our application repositories, but we can pass in another repo if we want.

Here's what it looks like in action.

```console
$ mix ecto.create
The database for Hello.Repo has been created.
```

There are a few things that can go wrong with `ecto.create`. If our Postgres database doesn't have a "postgres" role (user), we'll get an error like this one.

```console
$ mix ecto.create
** (Mix) The database for Hello.Repo couldn't be created, reason given: psql: FATAL:  role "postgres" does not exist
```

We can fix this by creating the "postgres" role in the `psql` console with the permissions needed to log in and create a database.

```console
=# CREATE ROLE postgres LOGIN CREATEDB;
CREATE ROLE
```

If the "postgres" role does not have permission to log in to the application, we'll get this error.

```console
$ mix ecto.create
** (Mix) The database for Hello.Repo couldn't be created, reason given: psql: FATAL:  role "postgres" is not permitted to log in
```

To fix this, we need to change the permissions on our "postgres" user to allow login.

```console
=# ALTER ROLE postgres LOGIN;
ALTER ROLE
```

If the "postgres" role does not have permission to create a database, we'll get this error.

```console
$ mix ecto.create
** (Mix) The database for Hello.Repo couldn't be created, reason given: ERROR:  permission denied to create database
```

To fix this, we need to change the permissions on our "postgres" user in the `psql` console  to allow database creation.

```console
=# ALTER ROLE postgres CREATEDB;
ALTER ROLE
```

If the "postgres" role is using a password different from the default "postgres", we'll get this error.

```console
$ mix ecto.create
** (Mix) The database for Hello.Repo couldn't be created, reason given: psql: FATAL:  password authentication failed for user "postgres"
```

To fix this, we can change the password in the environment specific configuration file. For the development environment the password used can be found at the bottom of the `config/dev.exs` file.

Finally, if we happen to have another repo called `OurCustom.Repo` that we want to create the database for, we can run this.

```console
$ mix ecto.create -r OurCustom.Repo
The database for OurCustom.Repo has been created.
```

### `mix ecto.drop`

This task will drop the database specified in our repo. By default it will look for the repo named after our application (the one generated with our app unless we opted out of Ecto). It will not prompt us to check if we're sure we want to drop the database, so do exercise caution.

```console
$ mix ecto.drop
The database for Hello.Repo has been dropped.
```

### `mix ecto.gen.migration`

Migrations are a programmatic, repeatable way to affect changes to a database schema. Phoenix generators take care of generating migrations for us whenever we create a new context or schema, but if you want to generate a migration from scratch, `mix ecto.gen.migration` has our back. Let's see an example.

We simply need to invoke the task with a `snake_case` version of the module name that we want. Preferably, the name will describe what we want the migration to do.

```console
$ mix ecto.gen.migration add_comments_table
* creating priv/repo/migrations
* creating priv/repo/migrations/20150318001628_add_comments_table.exs
```

Notice that the migration's filename begins with a string representation of the date and time the file was created.

Let's take a look at the file `ecto.gen.migration` has generated for us at `priv/repo/migrations/20150318001628_add_comments_table.exs`.

```elixir
defmodule Hello.Repo.Migrations.AddCommentsTable do
  use Ecto.Migration

  def change do
  end
end
```

Notice that there is a single function `change/0` which will handle both forward migrations and rollbacks. We'll define the schema changes that we want using Ecto's handy DSL, and Ecto will figure out what to do depending on whether we are rolling forward or rolling back. Very nice indeed.

What we want to do is create a `comments` table with a `body` column, a `word_count` column, and timestamp columns for `inserted_at` and `updated_at`.

```elixir
...
def change do
  create table(:comments) do
    add :body, :string
    add :word_count, :integer

    timestamps(type: :utc_datetime)
  end
end
...
```

For more information on how to modify your database schema please refer to the
[Ecto's migration DSL docs](https://ecto-sql.hexdocs.pm/Ecto.Migration.html).
For example, to alter an existing schema see the documentation on Ecto’s
[`alter/2`](`Ecto.Migration.alter/2`) function.

That's it! We're ready to run our migration.

### `mix ecto.migrate`

Once we have our migration module ready, we can simply run `mix ecto.migrate` to have our changes applied to the database. We have already used it earlier in this chapter, but let's take it for a spin once more for our newly generated migration.

```console
$ mix ecto.migrate
[info] == Running Hello.Repo.Migrations.AddCommentsTable.change/0 forward
[info] create table comments
[info] == Migrated in 0.1s
```

When we first run `ecto.migrate`, it will create a table for us called `schema_migrations`. This will keep track of all the migrations which we run by storing the timestamp portion of the migration's filename.

Here's what the `schema_migrations` table looks like.

```console
hello_dev=# select * from schema_migrations;
version        |     inserted_at
---------------+---------------------
20250317170448 | 2025-03-17 21:07:26
20250318001628 | 2025-03-18 01:45:00
(2 rows)
```

When we roll back a migration, `mix ecto.rollback`, to be discussed next, we will remove the record representing this migration from `schema_migrations`.

By default, `ecto.migrate` will execute all pending migrations. We can exercise more control over which migrations we run by specifying some options when we run the task.

We can specify the number of pending migrations we would like to run with the `-n` or `--step` options.

```console
$ mix ecto.migrate -n 2
[info] == Running Hello.Repo.Migrations.CreatePost.change/0 forward
[info] create table posts
[info] == Migrated in 0.0s
[info] == Running Hello.Repo.Migrations.AddCommentsTable.change/0 forward
[info] create table comments
[info] == Migrated in 0.0s
```

The `--step` option will behave the same way.

```console
$ mix ecto.migrate --step 2
```

The `--to` option will run all migrations up to and including given version.

```console
$ mix ecto.migrate --to 20150317170448
```

### `mix ecto.rollback`

The `mix ecto.rollback` task will reverse the last migration we have run, undoing the schema changes. `ecto.migrate` and `ecto.rollback` are mirror images of each other.

```console
$ mix ecto.rollback
[info] == Running Hello.Repo.Migrations.AddCommentsTable.change/0 backward
[info] drop table comments
[info] == Migrated in 0.0s
```

`ecto.rollback` will handle the same options as `ecto.migrate`, so `-n`, `--step`, `-v`, and `--to` will behave as they do for `ecto.migrate`.

[`cast/3`]: `Ecto.Changeset.cast/3`
[`from/2`]: `Ecto.Query.from/2`
[`Repo.delete_all/2`]: `c:Ecto.Repo.delete_all/2`
[`Repo.delete/2`]: `c:Ecto.Repo.delete/2`
[`Repo.insert_all/3`]: `c:Ecto.Repo.insert_all/3`
[`Repo.insert/2`]: `c:Ecto.Repo.insert/2`
[`Repo.one/2`]: `c:Ecto.Repo.one/2`
[`Repo.update_all/3`]: `c:Ecto.Repo.update_all/3`
[`Repo.update/2`]: `c:Ecto.Repo.update/2`
[`timestamps/1`]: `Ecto.Migration.timestamps/1`
