Current section
Files
Jump to
Current section
Files
lib/elasticsearch.ex
defmodule Elasticsearch do
@moduledoc """
Entry-point for interacting with your Elasticsearch cluster(s).
You should configure at least one `Elasticsearch.Cluster` in order to
use the functions in this module, or else you'll need to pass all the
configuration for the cluster into each function call.
"""
alias Elasticsearch.{
Document,
Cluster,
Cluster.Config
}
@type index_name :: String.t()
@type url :: Path.t()
@type opts :: Keyword.t()
@type data :: map | String.t()
@type response :: {:ok, map} :: {:error, Elasticsearch.Exception.t()}
@doc """
Creates or updates a document in a given index.
The document must implement the `Elasticsearch.Document` protocol.
## Example
iex> Index.create_from_file(Cluster, "posts-1", "test/support/settings/posts.json")
...> struct = %Post{id: 123, title: "Post", author: "Author"}
...> Elasticsearch.put_document(Cluster, struct, "posts-1")
{:ok,
%{"_id" => "123", "_index" => "posts-1",
"_shards" => %{"failed" => 0, "successful" => 1, "total" => 2},
"_type" => "post", "_version" => 1, "created" => true,
"result" => "created"}}
"""
@spec put_document(Cluster.t(), Document.t(), index_name) :: response
def put_document(cluster, document, index) do
put(cluster, document_url(document, index), Document.encode(document))
end
@doc """
Same as `put_document/2`, but raises on errors.
"""
@spec put_document(Cluster.t(), Document.t(), index_name) :: map | no_return
def put_document!(cluster, document, index) do
put!(cluster, document_url(document, index), Document.encode(document))
end
@doc """
Deletes a document from a given index.
The document must implement the `Elasticsearch.Document` protocol.
"""
@spec delete_document(Cluster.t(), Document.t(), index_name) :: response
def delete_document(cluster, document, index) do
delete(cluster, document_url(document, index))
end
@doc """
Same as `delete_document/2`, but raises on errors.
"""
@spec delete_document!(Cluster.t(), Document.t(), index_name) :: map | no_return
def delete_document!(cluster, document, index) do
delete!(cluster, document_url(document, index))
end
defp document_url(document, index) do
"/#{index}/#{Document.type(document)}/#{Document.id(document)}"
end
@doc """
Waits for a given Elasticsearch cluster to be available.
It will try a given number of times, with 1sec delay between tries.
"""
@spec wait_for_boot(Cluster.t(), integer) ::
{:ok, map}
| {:error, RuntimeError.t()}
| {:error, Elasticsearch.Exception.t()}
def wait_for_boot(cluster, tries, count \\ 0)
def wait_for_boot(_cluster, tries, count) when count == tries do
{
:error,
RuntimeError.exception("""
Elasticsearch could not be found after #{count} tries. Make sure it's running?
""")
}
end
def wait_for_boot(cluster, tries, count) do
with {:error, _} <- get(cluster, "/_cat/health?format=json") do
:timer.sleep(1000)
wait_for_boot(cluster, tries, count + 1)
end
end
@doc """
Gets the contents of a path from the Elasticsearch API.
## Examples
iex> {:ok, resp} = Elasticsearch.get(Cluster, "/_cat/health?format=json")
...> is_list(resp)
true
iex> Elasticsearch.get(Cluster, "/nonexistent")
{:error,
%Elasticsearch.Exception{col: nil, line: nil,
message: "no such index", query: nil,
raw: %{"error" => %{"index" => "nonexistent",
"index_uuid" => "_na_", "reason" => "no such index",
"resource.id" => "nonexistent",
"resource.type" => "index_or_alias",
"root_cause" => [%{"index" => "nonexistent",
"index_uuid" => "_na_", "reason" => "no such index",
"resource.id" => "nonexistent",
"resource.type" => "index_or_alias",
"type" => "index_not_found_exception"}],
"type" => "index_not_found_exception"}, "status" => 404},
status: 404, type: "index_not_found_exception"}}
"""
@spec get(Cluster.t(), url) :: response
@spec get(Cluster.t(), url, opts) :: response
def get(cluster, url, opts \\ []) do
config = Config.get(cluster)
config
|> config.api.request(:get, url, "", opts)
|> format()
end
@doc """
The same as `get/1`, but returns the response instead of a tuple. Raises on
errors.
## Examples
iex> resp = Elasticsearch.get!(Cluster, "/_cat/health?format=json")
...> is_list(resp)
true
iex> Elasticsearch.get!(Cluster, "/nonexistent")
** (Elasticsearch.Exception) (index_not_found_exception) no such index
"""
@spec get!(Cluster.t(), url) :: map | no_return
@spec get!(Cluster.t(), url, opts) :: map | no_return
def get!(cluster, url, opts \\ []) do
cluster
|> get(url, opts)
|> unwrap!()
end
@doc """
Puts data to a given Elasticsearch API path.
## Examples
iex> Index.create_from_file(Cluster, "posts-1", "test/support/settings/posts.json")
...> Elasticsearch.put(Cluster, "/posts-1/post/id", %{"title" => "title", "author" => "author"})
{:ok,
%{"_id" => "id", "_index" => "posts-1",
"_shards" => %{"failed" => 0, "successful" => 1, "total" => 2},
"_type" => "post", "_version" => 1, "created" => true,
"result" => "created"}}
iex> Elasticsearch.put(Cluster, "/bad/url", %{"title" => "title", "author" => "author"})
{:error,
%Elasticsearch.Exception{col: nil, line: nil,
message: "No handler found for uri [/bad/url] and method [PUT]",
query: nil, raw: nil, status: nil, type: nil}}
"""
@spec put(Cluster.t(), url, data) :: response
@spec put(Cluster.t(), url, data, opts) :: response
def put(cluster, url, data, opts \\ []) do
config = Config.get(cluster)
config
|> config.api.request(:put, url, data, opts)
|> format()
end
@doc """
The same as `put/2`, but returns the response instead of a tuple. Raises on
errors.
## Examples
iex> Index.create_from_file(Cluster, "posts", "test/support/settings/posts.json")
...> Elasticsearch.put!(Cluster, "/posts/post/id", %{"name" => "name", "author" => "author"})
%{"_id" => "id", "_index" => "posts",
"_shards" => %{"failed" => 0, "successful" => 1, "total" => 2},
"_type" => "post", "_version" => 1, "created" => true,
"result" => "created"}
iex> Elasticsearch.put!(Cluster, "/bad/url", %{"data" => "here"})
** (Elasticsearch.Exception) No handler found for uri [/bad/url] and method [PUT]
"""
@spec put!(Cluster.t(), url, data) :: map | no_return
@spec put!(Cluster.t(), url, data, opts) :: map | no_return
def put!(cluster, url, data, opts \\ []) do
cluster
|> put(url, data, opts)
|> unwrap!()
end
@doc """
Posts data or queries to a given Elasticsearch path.
## Examples
iex> Index.create_from_file(Cluster, "posts", "test/support/settings/posts.json")
...> query = %{"query" => %{"match_all" => %{}}}
...> {:ok, resp} = Elasticsearch.post(Cluster, "/posts/_search", query)
...> resp["hits"]["hits"]
[]
"""
@spec post(Cluster.t(), url, data) :: response
@spec post(Cluster.t(), url, data, opts) :: response
def post(cluster, url, data, opts \\ []) do
config = Config.get(cluster)
config
|> config.api.request(:post, url, data, opts)
|> format()
end
@doc """
The same as `post/1`, but returns the response. Raises on errors.
## Examples
iex> Index.create_from_file(Cluster, "posts", "test/support/settings/posts.json")
...> query = %{"query" => %{"match_all" => %{}}}
...> resp = Elasticsearch.post!(Cluster, "/posts/_search", query)
...> is_map(resp)
true
Raises an error if the path is invalid or another error occurs:
iex> query = %{"query" => %{"match_all" => %{}}}
...> Elasticsearch.post!(Cluster, "/nonexistent/_search", query)
** (Elasticsearch.Exception) (index_not_found_exception) no such index
"""
@spec post!(Cluster.t(), url, data) :: map | no_return
@spec post!(Cluster.t(), url, data, opts) :: map | no_return
def post!(cluster, url, data, opts \\ []) do
cluster
|> post(url, data, opts)
|> unwrap!()
end
@doc """
Deletes data at a given Elasticsearch URL.
## Examples
iex> Index.create_from_file(Cluster, "posts", "test/support/settings/posts.json")
...> Elasticsearch.delete(Cluster, "/posts")
{:ok, %{"acknowledged" => true}}
It returns an error if the given resource does not exist.
iex> Elasticsearch.delete(Cluster, "/nonexistent")
{:error,
%Elasticsearch.Exception{col: nil, line: nil,
message: "no such index", query: nil,
raw: %{"error" => %{"index" => "nonexistent",
"index_uuid" => "_na_", "reason" => "no such index",
"resource.id" => "nonexistent",
"resource.type" => "index_or_alias",
"root_cause" => [%{"index" => "nonexistent",
"index_uuid" => "_na_", "reason" => "no such index",
"resource.id" => "nonexistent",
"resource.type" => "index_or_alias",
"type" => "index_not_found_exception"}],
"type" => "index_not_found_exception"}, "status" => 404},
status: 404, type: "index_not_found_exception"}}
"""
@spec delete(Cluster.t(), url) :: response
@spec delete(Cluster.t(), url, opts) :: response
def delete(cluster, url, opts \\ []) do
config = Config.get(cluster)
config
|> config.api.request(:delete, url, "", opts)
|> format()
end
@doc """
Same as `delete/1`, but returns the response and raises errors.
## Examples
iex> Index.create_from_file(Cluster, "posts", "test/support/settings/posts.json")
...> Elasticsearch.delete!(Cluster, "/posts")
%{"acknowledged" => true}
Raises an error if the resource is invalid.
iex> Elasticsearch.delete!(Cluster, "/nonexistent")
** (Elasticsearch.Exception) (index_not_found_exception) no such index
"""
@spec delete!(Cluster.t(), url) :: map | no_return
@spec delete!(Cluster.t(), url, opts) :: map | no_return
def delete!(cluster, url, opts \\ []) do
cluster
|> delete(url, opts)
|> unwrap!()
end
defp format({:ok, %{status_code: code, body: body}})
when code >= 200 and code < 300 do
{:ok, body}
end
defp format({:ok, %{body: body}}) do
error = Elasticsearch.Exception.exception(response: body)
{:error, error}
end
defp format(error), do: error
defp unwrap!({:ok, value}), do: value
defp unwrap!({:error, exception}), do: raise(exception)
end