Current section

Files

Jump to
sqlite_ecto2 lib sqlite_ecto.ex
Raw

lib/sqlite_ecto.ex

defmodule Sqlite.Ecto2 do
@moduledoc ~S"""
Ecto Adapter module for SQLite.
It uses Sqlitex and Esqlite for accessing the SQLite database.
## Configuration Options
When creating an `Ecto.Repo` that uses a SQLite database, you should configure
it as follows:
```elixir
# In your config/config.exs file
config :my_app, Repo,
adapter: Sqlite.Ecto2,
database: "ecto_simple.sqlite3"
# In your application code
defmodule Repo do
use Ecto.Repo,
otp_app: :my_app,
adapter: Sqlite.Ecto2
end
```
You may use other options as specified in the `Ecto.Repo` documentation.
Note that the `:database` option is passed as the `filename` argument to
[`sqlite3_open_v2`](http://sqlite.org/c3ref/open.html). This implies that you
may use `:memory:` to create a private, temporary in-memory database.
See also [SQLite's interpretation of URI "filenames"](https://sqlite.org/uri.html)
for more options such as shared memory caches.
"""
# Inherit all behaviour from Ecto.Adapters.SQL
use Ecto.Adapters.SQL, :sqlitex
import String, only: [to_integer: 1]
# And provide a custom storage implementation
@behaviour Ecto.Adapter.Storage
## Custom SQLite Types
def loaders(:boolean, type), do: [&bool_decode/1, type]
def loaders(:binary_id, type), do: [Ecto.UUID, type]
def loaders(:utc_datetime, type), do: [&date_decode/1, type]
def loaders(:naive_datetime, type), do: [&date_decode/1, type]
def loaders({:embed, _} = type, _),
do: [&json_decode/1, &Ecto.Adapters.SQL.load_embed(type, &1)]
def loaders(:map, type), do: [&json_decode/1, type]
def loaders({:map, _}, type), do: [&json_decode/1, type]
def loaders(:float, type), do: [&float_decode/1, type]
def loaders(_primitive, type), do: [type]
defp bool_decode(0), do: {:ok, false}
defp bool_decode(1), do: {:ok, true}
defp bool_decode(x), do: {:ok, x}
defp date_decode(<<year :: binary-size(4), "-",
month :: binary-size(2), "-",
day :: binary-size(2)>>)
do
{:ok, {to_integer(year), to_integer(month), to_integer(day)}}
end
defp date_decode(<<year :: binary-size(4), "-",
month :: binary-size(2), "-",
day :: binary-size(2), " ",
hour :: binary-size(2), ":",
minute :: binary-size(2), ":",
second :: binary-size(2), ".",
microsecond :: binary-size(6)>>)
do
{:ok, {{to_integer(year), to_integer(month), to_integer(day)},
{to_integer(hour), to_integer(minute), to_integer(second), to_integer(microsecond)}}}
end
defp date_decode(x), do: {:ok, x}
defp json_decode(x) when is_binary(x),
do: {:ok, Application.get_env(:ecto, :json_library).decode!(x)}
defp json_decode(x),
do: {:ok, x}
defp float_decode(x) when is_integer(x), do: {:ok, x / 1}
defp float_decode(x), do: {:ok, x}
def dumpers(:binary, type), do: [type, &blob_encode/1]
def dumpers(:binary_id, type), do: [type, Ecto.UUID]
def dumpers(:boolean, type), do: [type, &bool_encode/1]
def dumpers({:embed, _} = type, _), do: [&Ecto.Adapters.SQL.dump_embed(type, &1)]
def dumpers(:time, type), do: [type, &time_encode/1]
def dumpers(_primitive, type), do: [type]
defp blob_encode(value), do: {:ok, {:blob, value}}
defp bool_encode(false), do: {:ok, 0}
defp bool_encode(true), do: {:ok, 1}
defp time_encode(value) do
{:ok, value}
end
## Storage API
@doc false
def storage_up(opts) do
database = Keyword.get(opts, :database)
if File.exists?(database) do
{:error, :already_up}
else
database |> Path.dirname |> File.mkdir_p!
{:ok, db} = Sqlitex.open(database)
:ok = Sqlitex.exec(db, "PRAGMA journal_mode = WAL")
{:ok, [[journal_mode: "wal"]]} = Sqlitex.query(db, "PRAGMA journal_mode")
Sqlitex.close(db)
:ok
end
end
@doc false
def storage_down(opts) do
database = Keyword.get(opts, :database)
case File.rm(database) do
{:error, :enoent} ->
{:error, :already_down}
result ->
File.rm(database <> "-shm") # ignore results for these files
File.rm(database <> "-wal")
result
end
end
@doc false
def supports_ddl_transaction?, do: true
end