defmodule Defql do
  @moduledoc """
  Module provides macros to create function with SQL as a body.

  ## Installation

  If [available in Hex](https://hex.pm/docs/publish), the package can be installed
  by adding `defql` to your list of dependencies in `mix.exs`:

  ```elixir
  defp deps do
    [
      {:defql, "~> 0.1.1"},
      {:postgrex, ">= 0.13.0"}, # optional
    ]
  end
  ```


  ### Configuration

  It requires `adapter` key, and adapter specific options.

  Use with ecto:

  ```elixir
  config :defql, connection: [
    adapter: Defql.Adapter.Ecto.Postgres,
    repo: Taped.Repo
  ]
  ```

  Use standalone connection:

  ```elixir
  config :defql, connection: [
    adapter: Defql.Adapter.Postgres,
    hostname: "localhost",
    username: "username",
    password: "password",
    database: "my_db",
    pool: DBConnection.Poolboy,
    pool_size: 1
  ]
  ```

  ## Usage

  We can define module to have access to our database:

  ```elixir
  defmodule UserQuery do
    use Defql

    defselect get(conds), table: :users
    definsert add(params), table: :users
    defupdate update(params, conds), table: :users
    defdelete delete(conds), table: :users

    defquery get_by_name(name, limit) do
      "SELECT * FROM users WHERE name = $name LIMIT $limit"
    end
  end
  ```

  Right now we have easy access to `users` in database:

  ```elixir
  UserQuery.get(id: "3") # => {:ok, [%{...}]}
  UserQuery.add(name: "Smbdy") # => {:ok, [%{...}]}
  UserQuery.update([name: "Other"],[id: "2"]) # => {:ok, [%{...}]}
  UserQuery.delete(id: "2") # => {:ok, [%{...}]}

  UserQuery.get_by_name("Ela", 4) # => {:ok, [%{...}, %{...}]}
  ```

  We can also define common table for the whole module.

  ```elixir
  defmodule UserQuery do
    use Defql, table: :users

    defselect get(conds)
    definsert add(params)
    defupdate update(params, conds)
    defdelete delete(conds)
  end
  ```

  `%{...}` It's a hash with user properties straight from database.

  Supported condition statements:
  - `[user_id: [1,2,3,4]]`
  - `[user_id: {:in, [1,2,3,4,5]}]`
  - `[name: {:like, "%john%"}]`
  - `[name: {:ilike, "%john"}]`
  """

  @doc false
  defmacro __using__(opts) do
    quote do
      Module.put_attribute(__MODULE__,
                           :table,
                           Keyword.get(unquote(opts),
                           :table))

      def resolve_table(opts) do
        Keyword.get(opts, :table) ||
        @table ||
        raise(ArgumentError, "table wasn't specified")
      end

      import Defql.Macros.Defquery
      import Defql.Macros.Definsert
      import Defql.Macros.Defdelete
      import Defql.Macros.Defupdate
      import Defql.Macros.Defselect
    end
  end
end
