为什么需要自定义的数据类型?
需求:
系统中需要存储超过Postgres的int8
(8字节, 64位)的整数, 那只有把它存储为字符串了.原因:
因为, 在我的Elixir项目中这个hash
值用到了很多很多地方, 类型是整数, 并且用于二进制协议的解析和编码, 我想要的效果就是从数据库出来的数据到程序中就是我期望的类型, 而不是在程序中到处用:erlang.string_to_integer/1
来做转换, 这样可以让代码更加简洁一点. 就这么一个用途, 没别的了.
创建移植脚本
首先, 我们需要创建一个移植脚本, 来在Postgres中创建一张名为large_int
的表
➜ ecto_test git:(master) ✗ mix ecto.gen.migration create_table_large_int -r EctoTest.Repo
* creating priv/repo/migrations
* creating priv/repo/migrations/20160430092534_create_table_large_int.exs
内容如下:
defmodule EctoTest.Repo.Migrations.CreateTableLargeInt do
use Ecto.Migration
def up do
create table(:large_int) do
add :hash, :char, size: 32 # CHAR(32)
# 数据库的字段类型为CHAR(32),下图中也可以看到
end
end
def down do
drop table(:large_int)
end
end
然后切换到终端执行mix ecto.migrate -r EctoTest.Repo
创建数据库表
➜ ecto_test git:(master) ✗ mix ecto.migrate -r EctoTest.Repo
17:48:43.425 [info] == Running EctoTest.Repo.Migrations.CreateTableLargeInt.up/0 forward
17:48:43.425 [info] create table large_int
17:48:43.434 [info] == Migrated in 0.0s
如果看到上面的信息, 说明表以已经创建好了,我们用一张美图来瞅一下表的结构和定义
实现自定义数据类型
defmodule EctoTest.Types.LargeInt do
@behaviour Ecto.Type
def type, do: :string
def cast(string) when is_binary(string) do
{:ok, string}
end
def cast(integer) when is_integer(integer) do
{:ok, :erlang.integer_to_binary(integer)}
end
def load(string), do: {:ok, string |> String.strip |> String.to_integer()}
def dump(integer) when is_integer(integer), do: {:ok, Integer.to_string(integer)}
def dump(_), do: :error
end
如何实现自定义数据类型, 可以参考 Ecto.Type Callbacks 文档
创建模型
defmodule EctoTest.Model.LargeInt do
@moduledoc """
A large integer that can be store as a string to database
in postgres
"""
use Ecto.Schema
alias EctoTest.Repo
alias EctoTest.Types.LargeInt
schema "large_int" do
field :hash, LargeInt # 引用自定义类型
end
def test do
%__MODULE__{hash: 17620097757106081354}
|> Repo.insert!
end
def get(id) do
__MODULE__ |> Repo.get(id)
end
end
模型中包含了一个test/0
函数, 用于向数据库中插入数据, 现在切换到终端, 编译一下项目 mix compile
, 然后启动应用 iex -S mix
执行这个test/0
方法, 我们看一下结果, 我用一张美图来演示:
切换到数据库, 看看数据是怎么样的:
最后, 我们来看看, 读取出来的效果:
中间遇到一个错误, 数据库的字段是32
字符的CHAR
, 调用 :erlang.string_to_integer/1
出错, 修改自定义数据类型模块, 用 String.strip/1
把空格去掉.
好了, 自定义数据就说到这里了. 项目的仓库在 https://github.com/developerworks/ecto_test 上, 需要测试的可以去clone下来测试.
2016-05-03 补充
关于自定数据类型的实现, 入库和出库调用的函数.