Current section
Files
Jump to
Current section
Files
lib/ex_redi.ex
defmodule ExRedi do
@moduledoc """
Documentation for ExRedi.
"""
use Application
alias ExRedi.{
Options,
Parser
}
@typedoc "The name of your RediSearch index"
@type index :: binary | atom
@typedoc "RediSearch query string"
@type query :: binary
@typedoc "RediSearch schema definition"
@type schema :: [binary]
@typedoc "RediSearch field definition"
@type fields :: [binary]
@typedoc "Document ID"
@type id :: binary | integer
@typedoc "Document format for multi-ops"
@type doc :: {id, fields}
@typedoc "Operation options"
@type opts :: keyword
@typedoc "Error returned from a failed operation"
@type error :: {:error, binary}
@score "1.0"
defguardp is_index(term) when is_binary(term) or is_atom(term)
@doc false
def start(_type, args) do
children = [
{ExRedi.Redis, args}
]
opts = [
strategy: :one_for_one,
name: ExRedi.Supervisor
]
Supervisor.start_link(children, opts)
end
@doc """
Creates an index with the given spec.
[More Info](http://redisearch.io/Commands/#ftcreate)
## Examples
iex> ExRedi.create("myIdx", ["title", "TEXT", "WEIGHT", "5.0", "body", "TEXT"])
:ok
iex> ExRedi.create("myIdx", ["title", "TEXT"])
{:error, "Index already exists. Drop it first!"}
"""
@spec create(index, schema, opts) :: :ok | error
def create(index, schema, opts \\ []) when is_index(index) do
args = ["FT.CREATE", index, build_opts(:create, opts), "SCHEMA", schema]
with {:ok, "OK"} <- command(args) do
:ok
end
end
@doc """
Deletes all the keys associated with the index.
[More Info](http://redisearch.io/Commands/#ftdrop)
## Examples
iex> ExRedi.create("myIdx", ["title", "TEXT"])
:ok
iex> ExRedi.drop("myIdx")
:ok
iex> ExRedi.info("myIdx")
{:error, "Unknown Index name"}
iex> ExRedi.drop("notMyIdx")
{:error, "Unknown Index name"}
"""
@spec drop(index, opts) :: :ok | error
def drop(index, opts \\ []) when is_index(index) do
args = ["FT.DROP", index | build_opts(:drop, opts)]
with {:ok, "OK"} <- command(args) do
:ok
end
end
@doc """
Checks if the index exists.
## Examples
iex> exists?("myIdx")
true
iex> exists?("notMyIdx")
false
"""
@spec exists?(index) :: boolean
def exists?(index) when is_index(index) do
with {:ok, data} <- info(index) do
Map.get(data, "index_name") === to_string(index)
else
_ ->
false
end
end
@doc """
Adds a document to the index.
[More Info](http://redisearch.io/Commands/#ftadd)
## Example
iex> ExRedi.add("myIdx", "1", ["title", "foo", "body", "bar"])
:ok
"""
@spec add(index, id, fields, opts) :: :ok | error
def add(index, id, fields, opts \\ []) do
{score, opts} = Keyword.pop(opts, :score, @score)
args = ["FT.ADD", index, id, score, build_opts(:add, opts), "FIELDS", fields]
with {:ok, "OK"} <- command(args) do
:ok
end
end
@doc """
Adds multiple document to the index via Redis 'MULTI'.
[More Info](http://redisearch.io/Commands/#ftadd)
## Example
iex> docs = [
...> {"1", ["title", "foo", "body", "hello"]},
...> {"2", ["title", "bar", "body", "world"]}
...> ]
...> ExRedi.add_multi("myIdx", docs)
[:ok, :ok]
"""
@spec add_multi(index, [doc], opts) :: [:ok, ...] | error
def add_multi(index, docs, opts \\ []) when is_index(index) do
Enum.map(docs, fn {id, fields} ->
# TODO: Actually use Redis MULTI
add(index, id, fields, opts)
end)
end
@doc """
Adds a document to the index from an existing HASH key in Redis.
[More Info](http://redisearch.io/Commands/#ftaddhash)
## Examples
iex> Redix.command(:ex_redi, ["HMSET", "doc-1", "title", "hello world"])
{:ok, "OK"}
iex> ExRedi.add_hash("myIdx", "doc-1")
:ok
iex> ExRedi.add_hash("myIdx", "nothing-here")
{:error, "Could not load document"}
"""
@spec add_hash(index, id, opts) :: :ok | error
def add_hash(index, id, opts \\ []) when is_index(index) do
{score, opts} = Keyword.pop(opts, :score, @score)
args = ["FT.ADDHASH", index, id, score | build_opts(:add_hash, opts)]
with {:ok, "OK"} <- command(args) do
:ok
end
end
@doc """
Returns the full contents of a document.
[More Info](http://redisearch.io/Commands/#ftget)
"""
@spec get(index, id) :: {:ok, map} | error
def get(index, id) when is_index(index) do
with {:ok, result} <- command(["FT.GET", index, id]) do
result
|> Parser.member(id)
|> wrap(:ok)
end
end
@doc """
Returns the full contents of multiple documents.
[More Info](http://redisearch.io/Commands/#ftmget)
"""
@spec mget(index, [id]) :: {:ok, [map]} | error
def mget(index, ids) when is_index(index) do
with {:ok, result} <- command(["FT.MGET", index, ids]) do
result
|> Enum.zip(ids)
|> Enum.map(fn
{nil, _} -> nil
{result, id} -> Parser.member(result, id)
end)
|> Enum.reject(&is_nil/1)
|> wrap(:ok)
end
end
@doc """
Deletes a document from the index.
Returns {:ok, 1} if the document was removed from the index, otherwise {:ok, 0}.
[More Info](http://redisearch.io/Commands/#ftdel)
## Examples
iex> ExRedi.del("myIdx", "1")
{:ok, 1}
iex> ExRedi.del("myIdx", "nothing-here")
{:ok, 0}
"""
@spec del(index, id, opts) :: {:ok, integer} | error
def del(index, id, opts \\ []) when is_index(index) do
command(["FT.DEL", index, id | build_opts(:del, opts)])
end
@doc """
Returns information and statistics on the index.
[More Info](http://redisearch.io/Commands/#ftinfo)
## Example
iex> ExRedi.info("myIdx")
%{
"bytes_per_record_avg" => "...",
"doc_table_size_mb" => "...",
"fields" => [...],
...
}
"""
@spec info(index) :: {:ok, map} | error
def info(index) when is_index(index) do
with {:ok, result} <- command(["FT.INFO", index]) do
result
|> Parser.info()
|> wrap(:ok)
end
end
@doc """
Searches the index with the given query, returning either documents or ids.
[More Info](http://redisearch.io/Commands/#ftsearch)
[Syntax](http://redisearch.io/Query_Syntax)
"""
@spec search(index, query, opts) :: {:ok, [map]} | error
def search(index, query, opts \\ []) when is_index(index) do
args = ["FT.SEARCH", index, query | build_opts(:search, opts)]
with {:ok, result} <- command(args) do
result
|> Parser.search(opts)
|> wrap(:ok)
end
end
@doc """
Runs a search query and performs aggregate transformations on the results.
[More Info](http://redisearch.io/Commands/#ftaggregate)
[Overview](http://redisearch.io/Aggregations)
"""
@spec aggregate(index, query, opts) :: {:ok, list} | error
def aggregate(index, query, opts \\ []) when is_index(index) do
command(["FT.AGGREGATE", index, query | build_opts(:aggregate, opts)])
end
@doc """
Prints the execution plan for a query
[More Info](http://redisearch.io/Commands/#ftexplain)
"""
@spec explain(index, query) :: :ok | error
def explain(index, query) when is_index(index) do
with {:ok, result} <- command(["FT.EXPLAIN", index, query]) do
IO.puts(result)
end
end
@doc """
Returns the distinct tags indexed in a Tag field.
[More Info](http://redisearch.io/Commands/#fttagvals)
"""
@spec tags(index, field :: binary) :: {:ok, list} | error
def tags(index, field) when is_index(index) do
command(["FT.TAGVALS", index, field])
end
defdelegate tag_vals(index, field), to: __MODULE__, as: :tags
@doc """
Adds a suggestion string to an auto-complete suggestion dictionary.
[More Info](http://redisearch.io/Commands/#ftsugadd)
"""
@spec add_suggestion(key :: binary, string :: binary, opts) :: {:ok, integer} | error
def add_suggestion(key, string, opts \\ []) do
{score, opts} = Keyword.pop(opts, :score, @score)
command(["FT.SUGADD", key, string, score | build_opts(:sugadd, opts)])
end
@doc """
Gets completion suggestions for a prefix.
[More Info](http://redisearch.io/Commands/#ftsugget)
"""
@spec get_suggestion(key :: binary, prefix :: binary, opts) :: {:ok, list} | error
def get_suggestion(key, prefix, opts \\ []) do
command(["FT.SUGGET", key, prefix | build_opts(:sugget, opts)])
end
@doc """
Deletes a string from a suggestion index.
Returns 1 if the string was removed, otherwise 0.
[More Info](http://redisearch.io/Commands/#ftsugdel)
"""
@spec delete_suggestion(key :: binary, string :: binary) :: {:ok, integer} | error
def delete_suggestion(key, string) do
command(["FT.SUGDEL", key, string])
end
@doc """
Gets the size of an auto-complete suggestion dictionary.
[More Info](http://redisearch.io/Commands/#ftsuglen)
"""
@spec suggestion_length(key :: binary) :: {:ok, integer} | error
def suggestion_length(key) do
command(["FT.SUGLEN", key])
end
# ======= #
# Private #
# ======= #
defp build_opts(key, opts), do: Options.build(key, opts)
defp wrap(term, tag), do: {tag, term}
defp command(args) do
ExRedi.Redis.command(args)
end
end