defmodule Ecto.Adapter.Schema do
  @moduledoc """
  Specifies the schema API required from adapters.
  """

  @typedoc "Proxy type to the adapter meta"
  @type adapter_meta :: Ecto.Adapter.adapter_meta()

  @typedoc "Ecto.Schema metadata fields"
  @type schema_meta :: %{
          autogenerate_id: {schema_field :: atom, source_field :: atom, Ecto.Type.t()},
          context: term,
          prefix: binary | nil,
          schema: atom,
          source: binary
        }

  @type fields :: Keyword.t()
  @type filters :: Keyword.t()
  @type constraints :: Keyword.t()
  @type returning :: [atom]
  @type placeholders :: [term]
  @type options :: Keyword.t()

  @type on_conflict ::
          {:raise, list(), []}
          | {:nothing, list(), [atom]}
          | {[atom], list(), [atom]}
          | {Ecto.Query.t(), list(), [atom]}

  @doc """
  Called to autogenerate a value for id/embed_id/binary_id.

  Returns the autogenerated value, or nil if it must be
  autogenerated inside the storage or raise if not supported.
  """
  @callback autogenerate(field_type :: :id | :binary_id | :embed_id) :: term | nil

  @doc """
  Inserts multiple entries into the data store.

  In case an `Ecto.Query` given as any of the field values by the user,
  it will be sent to the adapter as a tuple with in the shape of
  `{query, params}`.
  """
  @callback insert_all(
              adapter_meta,
              schema_meta,
              header :: [atom],
              [[{atom, term | {Ecto.Query.t(), list()}}]],
              on_conflict,
              returning,
              placeholders,
              options
            ) :: {non_neg_integer, [[term]] | nil}

  @doc """
  Inserts a single new struct in the data store.

  ## Autogenerate

  The primary key will be automatically included in `returning` if the
  field has type `:id` or `:binary_id` and no value was set by the
  developer or none was autogenerated by the adapter.
  """
  @callback insert(adapter_meta, schema_meta, fields, on_conflict, returning, options) ::
              {:ok, fields} | {:invalid, constraints}

  @doc """
  Updates a single struct with the given filters.

  While `filters` can be any record column, it is expected that
  at least the primary key (or any other key that uniquely
  identifies an existing record) be given as a filter. Therefore,
  in case there is no record matching the given filters,
  `{:error, :stale}` is returned.
  """
  @callback update(adapter_meta, schema_meta, fields, filters, returning, options) ::
              {:ok, fields} | {:invalid, constraints} | {:error, :stale}

  @doc """
  Deletes a single struct with the given filters.

  While `filters` can be any record column, it is expected that
  at least the primary key (or any other key that uniquely
  identifies an existing record) be given as a filter. Therefore,
  in case there is no record matching the given filters,
  `{:error, :stale}` is returned.
  """
  @callback delete(adapter_meta, schema_meta, filters, returning, options) ::
              {:ok, fields} | {:invalid, constraints} | {:error, :stale}
end
