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