Skip to main content

lib/ex_sql/ecto/query.ex

defmodule ExSQL.Ecto.Query do
  @moduledoc false

  defstruct statement: nil, name: nil, command: nil

  @type t :: %__MODULE__{
          statement: iodata(),
          name: String.t() | nil,
          command: atom() | nil
        }

  def build(opts) do
    statement = Keyword.fetch!(opts, :statement)

    %__MODULE__{
      statement: statement,
      name: Keyword.get(opts, :name),
      command: Keyword.get(opts, :command) || extract_command(statement)
    }
  end

  @commands %{
    "alter" => :alter,
    "analyze" => :analyze,
    "attach" => :attach,
    "begin" => :begin,
    "commit" => :commit,
    "create" => :create,
    "delete" => :delete,
    "detach" => :detach,
    "drop" => :drop,
    "explain" => :explain,
    "insert" => :insert,
    "pragma" => :pragma,
    "reindex" => :reindex,
    "release" => :release,
    "replace" => :replace,
    "rollback" => :rollback,
    "savepoint" => :savepoint,
    "select" => :select,
    "update" => :update,
    "vacuum" => :vacuum,
    "values" => :select,
    "with" => :select
  }

  defp extract_command(statement) do
    statement
    |> IO.iodata_to_binary()
    |> String.trim_leading()
    |> String.split(~r/\s+/, parts: 2)
    |> List.first()
    |> case do
      nil -> nil
      command -> Map.get(@commands, String.downcase(command))
    end
  end

  def encode_params(params) when is_list(params), do: Enum.map(params, &encode_param/1)

  def encode_params(params) when is_map(params) do
    Map.new(params, fn {key, value} -> {key, encode_param(value)} end)
  end

  def encode_params(params), do: params

  defp encode_param(true), do: 1
  defp encode_param(false), do: 0
  defp encode_param(%Date{} = value), do: Date.to_iso8601(value)
  defp encode_param(%NaiveDateTime{} = value), do: NaiveDateTime.to_iso8601(value)
  defp encode_param(%DateTime{} = value), do: DateTime.to_iso8601(value)
  defp encode_param(%Time{} = value), do: Time.to_iso8601(value)
  defp encode_param(value) when is_list(value) or is_map(value), do: Jason.encode!(value)
  defp encode_param(value), do: value

  defimpl DBConnection.Query do
    def parse(query, _opts), do: query
    def describe(query, _opts), do: query
    def encode(_query, params, _opts), do: ExSQL.Ecto.Query.encode_params(params)
    def decode(_query, result, _opts), do: result
  end

  defimpl String.Chars do
    def to_string(query), do: IO.iodata_to_binary(query.statement)
  end
end