やりたいこと
現在、↓のようなmigrationファイルを元に
defmodule PhoenixSample.Repo.Migrations.CreatePost do
use Ecto.Migration
def change do
create table(:posts) do
add :title, :string
add :content, :string
timestamps
end
end
end
↓のようなテーブルが生成されている。
MariaDB [phoenix_sample_dev]> SHOW COLUMNS FROM posts;
+-------------+---------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+---------------------+------+-----+---------+----------------+
| id | bigint(20) unsigned | NO | PRI | NULL | auto_increment |
| title | varchar(255) | YES | | NULL | |
| content | varchar(255) | YES | | NULL | |
| inserted_at | datetime | NO | | NULL | |
| updated_at | datetime | NO | | NULL | |
+-------------+---------------------+------+-----+---------+----------------+
このままでも問題ないのだが、contentカラムの方をTEXT型に変更したい。
そこで、Phoenixの(Ectoの?)Migration機能を使って変更してみようと思う。
なお、Migrationについてのより詳しい情報はこちらのマニュアルを参照してください。
Migrationを行う
空のmigrationファイルを作成する
mixコマンドを使い、空のmigrationファイルを生成する。
名前は何でもいいのですが、出来る限り内容が想像できるような名前を選んでおくのがよいでしょう。
$ mix ecto.gen.migration modify_contents_to_post
* creating priv/repo/migrations
* creating priv/repo/migrations/20160210073915_modify_contents_to_post.exs
migrationファイルを編集する
生成されたmigrationファイルを以下のように編集。
defmodule PhoenixSample.Repo.Migrations.ModifyContentsToPost do
use Ecto.Migration
def change do
alter table(:posts) do
modify :content, :text
end
end
end
テーブル生成ではなくて変更なので、alter tableを、
カラムの型の変更なのでmodifyを使っています。
マニュアルによると、他にもadd, remove, renameなど様々な関数が定義されているようです。
ところで、マニュアルには、addやmodifyはFunctonsのセクションで説明されている一方で
create/alter tableについてはMacroのセクションで説明されている。
このMacroってやはPaulGrahamが「Lispの力強さの秘密はマクロにある」ってうたってた、あのマクロのことかな?
なんだか怖いものに出くわしてしまった気分だ…
migration実行
mixコマンドを使ってmigrationを実行します。
$ mix ecto.migrate
17:03:22.722 [info] == Running PhoenixSample.Repo.Migrations.ModifyContentsToPost.change/0 forward
17:03:22.722 [info] alter table posts
17:03:22.742 [info] == Migrated in 0.1s
変更後のテーブル定義を確認すると。
MariaDB [phoenix_sample_dev]> show columns from posts;
+-------------+---------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+---------------------+------+-----+---------+----------------+
| id | bigint(20) unsigned | NO | PRI | NULL | auto_increment |
| title | varchar(255) | YES | | NULL | |
| content | text | YES | | NULL | |
| inserted_at | datetime | NO | | NULL | |
| updated_at | datetime | NO | | NULL | |
+-------------+---------------------+------+-----+---------+----------------+
無事変更されていることが確認されました。
ところで、migrationファイルではどんな型を使用できるんだい?
型の定義を求めて
で、今回のつまづきどころ。
カラムの型をvarchar->textに変えるためには、migrationファイルでどんな型を指定してやればよいか調べて見たところ、いまいちまとまって説明されているところが見つからず(マニュアル含む)
結局適当に:textを指定したところ、うまく行ってしまったという経緯があった。
このままにしておくのも気持ちが悪いので、もう少し調べてみるとする。
色々検索していくうちに、Ecto.Migrationのソースのコメントにぶち当たる。
This function also accepts Ecto primitive types as column types
and they are normalized by the database adapter. For example,
string
is converted to varchar,datetime
to the underlying
datetime or timestamp type,binary
to bits or blob, and so on.However, the column type is not always the same as the type in your
model. For example, a model that has astring
field, can be
supported by columns of typeschar
,varchar
,text
and others.
For this reason, this function also acceptstext
and other columns,
which are sent as is to the underlying database.
この関数は、カラムの型をEctoのプリミティブ型として受け取り、DBごとの適切な型に変換する。
(例えば、EctoのstringかたはDBのVarchar型に変換されるし、Ectoのdatetime型はDBのdatatime型に変換される)
ここで定義されたカラムの型とモデルで定義されたフィールドの型は必ずしも一対一には対応しない。
(たとえば、DB上ではchar,varchar,textと異なる型で定義されたカラムがあったとしても、モデルのフィールドではすべてstring型として表現される)
訳は間違っているかもしれませんが、とりあえずadd関数が受け取るtypeはEctoのprimitive型のようである。
ecto+primitive+typeで検索を進めていくと、やはりソースにぶち当たる。
@type t :: primitive | custom
@typedoc "Primitive Ecto types (handled by Ecto)."
@type primitive :: base | composite
@typedoc "Custom types are represented by user-defined modules."
@type custom :: atom
@typep base :: :integer | :float | :boolean | :string | :map |
:binary | :decimal | :id | :binary_id |
:datetime | :date | :time | :any
@typep composite :: {:array, base} | {:embed, Ecto.Embedded.t}
@base ~w(integer float boolean string binary decimal datetime date time id binary_id map any)a
@composite ~w(array embed)a
まだ不慣れな言語なせいもあって、理解が追いつかない記述ですが、一見BNF記法に見えるので、そう考えて進めていきます。
となると、primitive型はこいつらのことになりそう。
@typep base :: :integer | :float | :boolean | :string | :map |
:binary | :decimal | :id | :binary_id |
:datetime | :date | :time | :any
実際に試してみた
試しに, :idと:binary_id(uuid用の型)以外の型を全部使ったテーブルを作ってみる。
migrationファイルを用意して…
defmodule PhoenixSample.Repo.Migrations.CreateSample do
use Ecto.Migration
def change do
create table(:hoges) do
add :one, :integer
add :two, :float
add :three, :boolean
add :four, :string
add :five, :map
add :six, :binary
add :seven, :decimal
add :eight, :datetime
add :nine, :date
add :ten, :time
end
end
end
maigration実行。
結果はこんな感じ。
MariaDB [phoenix_sample_dev]> show columns from hoges;
+-------+---------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+---------------------+------+-----+---------+----------------+
| id | bigint(20) unsigned | NO | PRI | NULL | auto_increment |
| one | int(11) | YES | | NULL | |
| two | double | YES | | NULL | |
| three | tinyint(1) | YES | | NULL | |
| four | varchar(255) | YES | | NULL | |
| five | text | YES | | NULL | |
| six | blob | YES | | NULL | |
| seven | decimal(10,0) | YES | | NULL | |
| eight | datetime | YES | | NULL | |
| nine | date | YES | | NULL | |
| ten | time | YES | | NULL | |
+-------+---------------------+------+-----+---------+----------------+
対応表をまとめると、こんな感じになります。
Ecto.type | Mysql.type |
---|---|
:integer | int(11) |
:float | double |
:boolean | tinyint(1) |
:string | varchar(255) |
:map | text |
:binary | blob |
:decimal | decimal(10, 0) |
:datetime | datetime |
:date | date |
:time | time |
試してないのですが、map型のデータをそのままDBに投入できるみたいで、便利そうですね。
ところで、キミが上の方で使っていた:text型はどこからきているの?
ここまで書いてきて、ふと気がつく。
自分が最初に使った:text型がectoのprimitive型として定義されていないことに。
さて、困った。
しばらく調べてみると、migrationファイルで使える型はectoのprimitive型だけではなく
自前で定義したカスタム型でも良いということがわかったが、この:text型がどこで定義されているのかは依然不明。
どなたか知っている方がおりましたら、情報お願いします。
追記:MigrationファイルでEctoのprimitive型以外を指定した場合、そのままDBの型だとみなされる
コメントの情報より。
@nikuさん、@uasiさん情報&解説ありがとうござます。
試しに
defmodule PhoenixSample.Repo.Migrations.AddSmallintToHoge do
use Ecto.Migration
def change do
alter table(:hoges) do
add :eleven, :smallint
end
end
end
こんな感じに、smallint型のカラムを追加してみると...
MariaDB [phoenix_sample_dev]> show columns from hoges;
+--------+---------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------+---------------------+------+-----+---------+----------------+
| id | bigint(20) unsigned | NO | PRI | NULL | auto_increment |
| one | int(11) | YES | | NULL | |
| two | double | YES | | NULL | |
| three | tinyint(1) | YES | | NULL | |
| four | varchar(255) | YES | | NULL | |
| five | text | YES | | NULL | |
| six | blob | YES | | NULL | |
| seven | decimal(10,0) | YES | | NULL | |
| eight | datetime | YES | | NULL | |
| nine | date | YES | | NULL | |
| ten | time | YES | | NULL | |
| eleven | smallint(6) | YES | | NULL | | <- 追加された
+--------+---------------------+------+-----+---------+----------------+
と期待通りの挙動をすることが確認されました。
当然存在しない型を指定すると
defmodule PhoenixSample.Repo.Migrations.AddIntervalToHoges do
use Ecto.Migration
def change do
alter table(:hoges) do
add :twelve, :interval
end
end
end
エラーになる。
$ mix ecto.migrate
10:25:34.354 [info] == Running PhoenixSample.Repo.Migrations.AddIntervalToHoges.change/0 forward
10:25:34.355 [info] alter table hoges
** (Mariaex.Error) (1064): You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'interval' at line 1
(ecto) lib/ecto/adapters/sql.ex:185: Ecto.Adapters.SQL.query!/5
(ecto) lib/ecto/adapters/mysql.ex:85: anonymous fn/4 in Ecto.Adapters.MySQL.execute_ddl/3
(elixir) lib/enum.ex:1493: Enum."-reduce/3-lists^foldl/2-0-"/3
...
上側そのままで、DBだけリプレースみたいな事案に遭遇したことはないのですが、一応DB固有のデータ型を使うのは避けておいたほうがよいようです。