defmodule Ecto.Adapters.ExSQL do
@moduledoc """
Ecto SQL adapter for ExSQL.
This module starts as a thin Ecto adapter shell over the pure-Elixir ExSQL
DBConnection driver. Raw SQL queries are supported first; query generation,
migration DDL, and SQLite-compatible type codecs are ported in later phases.
"""
use Ecto.Adapters.SQL, driver: :exsql
@behaviour Ecto.Adapter.Storage
@impl Ecto.Adapter
def loaders(:boolean, type), do: [&bool_decode/1, type]
def loaders(:date, type), do: [&date_decode/1, type]
def loaders(:time, type), do: [&time_decode/1, type]
def loaders(:time_usec, type), do: [&time_decode/1, type]
def loaders(:naive_datetime, type), do: [&naive_datetime_decode/1, type]
def loaders(:naive_datetime_usec, type), do: [&naive_datetime_decode/1, type]
def loaders(:utc_datetime, type), do: [&utc_datetime_decode/1, type]
def loaders(:utc_datetime_usec, type), do: [&utc_datetime_decode/1, type]
def loaders({:array, _}, type), do: [&json_decode/1, type]
def loaders({:map, _}, type), do: [&json_decode/1, &Ecto.Type.embedded_load(type, &1, :json)]
def loaders(:map, type), do: [&json_decode/1, type]
def loaders(:binary_id, type), do: [Ecto.UUID, type]
def loaders(_, type), do: [type]
@impl Ecto.Adapter
def dumpers(:boolean, type), do: [type, &bool_encode/1]
def dumpers({:array, _}, type), do: [&Ecto.Type.embedded_dump(type, &1, :json)]
def dumpers({:map, _}, type), do: [&Ecto.Type.embedded_dump(type, &1, :json)]
def dumpers(:binary_id, type), do: [type, Ecto.UUID]
def dumpers(_, type), do: [type]
@impl Ecto.Adapter.Storage
def storage_down(options) do
db_path = Keyword.fetch!(options, :database)
case File.rm(db_path) do
:ok ->
File.rm(db_path <> "-shm")
File.rm(db_path <> "-wal")
:ok
_otherwise ->
{:error, :already_down}
end
end
@impl Ecto.Adapter.Storage
def storage_status(options) do
db_path = Keyword.fetch!(options, :database)
if File.exists?(db_path), do: :up, else: :down
end
@impl Ecto.Adapter.Storage
def storage_up(options) do
database = Keyword.get(options, :database)
cond do
is_nil(database) ->
raise ArgumentError, """
No ExSQL database path specified. Please configure your Repo with:
config :my_app, MyApp.Repo,
adapter: Ecto.Adapters.ExSQL,
database: "/path/to/sqlite/database"
"""
database in [:memory, ":memory:"] ->
:ok
File.exists?(database) ->
{:error, :already_up}
true ->
database |> Path.dirname() |> File.mkdir_p!()
db = ExSQL.Database.new()
case ExSQL.FileFormat.write(db, database, journal_mode: :memory) do
{:ok, _path} -> :ok
{:error, reason} -> {:error, reason}
end
end
end
defp bool_decode(0), do: {:ok, false}
defp bool_decode(1), do: {:ok, true}
defp bool_decode(false), do: {:ok, false}
defp bool_decode(true), do: {:ok, true}
defp bool_decode(value), do: {:ok, value}
defp bool_encode(false), do: {:ok, 0}
defp bool_encode(true), do: {:ok, 1}
defp bool_encode(value), do: {:ok, value}
defp date_decode(value) when is_binary(value), do: Date.from_iso8601(value)
defp date_decode(value), do: {:ok, value}
defp time_decode(value) when is_binary(value), do: Time.from_iso8601(value)
defp time_decode(value), do: {:ok, value}
defp naive_datetime_decode(value) when is_binary(value), do: NaiveDateTime.from_iso8601(value)
defp naive_datetime_decode(value), do: {:ok, value}
defp utc_datetime_decode(value) when is_binary(value) do
case DateTime.from_iso8601(value) do
{:ok, datetime, _offset} -> {:ok, datetime}
{:error, _reason} -> naive_datetime_decode(value)
end
end
defp utc_datetime_decode(value), do: {:ok, value}
defp json_decode(value) when is_binary(value), do: Jason.decode(value)
defp json_decode(value), do: {:ok, value}
@impl Ecto.Adapter.Migration
def supports_ddl_transaction?, do: true
@impl Ecto.Adapter.Migration
def lock_for_migrations(_meta, _options, fun), do: fun.()
end