lib/elastix/bulk.ex

defmodule Elastix.Bulk do
  @moduledoc """
  The bulk API makes it possible to perform many index/delete operations in a single API call.

  [Elastic documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-bulk.html)
  """
  import Elastix.HTTP, only: [prepare_url: 2]
  alias Elastix.{HTTP, JSON}

  @doc """
  Excepts a list of actions and sources for the `lines` parameter.

  ## Examples

      iex> Elastix.Bulk.post("http://localhost:9200", [%{index: %{_id: "1"}}, %{user: "kimchy"}], index: "twitter", type: "tweet")
      {:ok, %HTTPoison.Response{...}}
  """
  @spec post(
          elastic_url :: String.t(),
          lines :: list,
          opts :: Keyword.t(),
          query_params :: Keyword.t()
        ) :: HTTP.resp()
  def post(elastic_url, lines, options \\ [], query_params \\ []) do
    data =
      Enum.reduce(lines, [], fn l, acc -> ["\n", JSON.encode!(l) | acc] end)
      |> Enum.reverse()
      |> IO.iodata_to_binary()

    path =
      Keyword.get(options, :index)
      |> make_path(Keyword.get(options, :type), query_params)

    httpoison_options = Keyword.get(options, :httpoison_options, [])

    elastic_url
    |> prepare_url(path)
    |> HTTP.put(data, [], httpoison_options)
  end

  @doc """
  Deprecated: use `post/4` instead.
  """
  @spec post_to_iolist(
          elastic_url :: String.t(),
          lines :: list,
          opts :: Keyword.t(),
          query_params :: Keyword.t()
        ) :: HTTP.resp()
  def post_to_iolist(elastic_url, lines, options \\ [], query_params \\ []) do
    IO.warn(
      "This function is deprecated and will be removed in future releases; use Elastix.Bulk.post/4 instead."
    )

    httpoison_options = Keyword.get(options, :httpoison_options, [])

    (elastic_url <>
       make_path(Keyword.get(options, :index), Keyword.get(options, :type), query_params))
    |> HTTP.put(Enum.map(lines, fn line -> JSON.encode!(line) <> "\n" end), [], httpoison_options)
  end

  @doc """
  Same as `post/4` but instead of sending a list of maps you must send raw binary data in
  the format described in the [Elasticsearch documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-bulk.html).
  """
  @spec post_raw(
          elastic_url :: String.t(),
          raw_data :: String.t(),
          opts :: Keyword.t(),
          query_params :: Keyword.t()
        ) :: HTTP.resp()
  def post_raw(elastic_url, raw_data, options \\ [], query_params \\ []) do

    httpoison_options = Keyword.get(options, :httpoison_options, [])

    (elastic_url <>
       make_path(Keyword.get(options, :index), Keyword.get(options, :type), query_params))
    |> HTTP.put(raw_data, [], httpoison_options)
  end

  @doc false
  def make_path(index_name, type_name, query_params) do
    path = make_base_path(index_name, type_name)

    case query_params do
      [] -> path
      _ -> HTTP.append_query_string(path, query_params)
    end
  end

  defp make_base_path(nil, nil), do: "/_bulk"
  defp make_base_path(index_name, nil), do: "/#{index_name}/_bulk"
  defp make_base_path(index_name, type_name), do: "/#{index_name}/#{type_name}/_bulk"
end