Current section
Files
Jump to
Current section
Files
lib/falkordb.ex
defmodule FalkorDB do
@moduledoc """
Elixir client for FalkorDB 4.16.x using Redix as the Redis transport.
V1 supports single-node and sentinel topologies.
"""
alias FalkorDB.CommandBuilder
alias FalkorDB.CommandError
alias FalkorDB.Connection
alias FalkorDB.Connection.RedixSentinel
alias FalkorDB.Connection.RedixSingle
alias FalkorDB.Graph
@type t :: %__MODULE__{
connection: Connection.t(),
topology: Connection.mode()
}
defstruct [:connection, :topology]
@spec connect(keyword()) :: {:ok, t()} | {:error, term()}
def connect(opts \\ []) do
mode = normalize_mode(Keyword.get(opts, :mode, :single))
adapter = adapter_for(mode)
with {:ok, connection} <- adapter.connect(opts) do
{:ok, %__MODULE__{connection: connection, topology: mode}}
end
end
@spec from_connection(Connection.t()) :: t()
def from_connection(%Connection{} = connection) do
%__MODULE__{connection: connection, topology: connection.mode}
end
@spec mode(t()) :: Connection.mode()
def mode(%__MODULE__{topology: mode}), do: mode
@spec stop(t()) :: :ok
def stop(%__MODULE__{connection: connection}), do: Connection.stop(connection)
@spec select_graph(t(), String.t()) :: Graph.t()
def select_graph(%__MODULE__{} = db, graph_name), do: Graph.new(db, graph_name)
@spec command(t(), [String.Chars.t()]) :: {:ok, term()} | {:error, term()}
def command(%__MODULE__{connection: connection}, command) do
case Connection.command(connection, command) do
{:error, %Redix.Error{} = error} ->
{:error, %CommandError{message: Exception.message(error), reason: error}}
{:error, %Redix.ConnectionError{} = error} ->
{:error, %FalkorDB.ConnectionError{message: Exception.message(error), reason: error}}
other ->
other
end
end
@spec pipeline(t(), [[String.Chars.t()]]) :: {:ok, [term()]} | {:error, term()}
def pipeline(%__MODULE__{connection: connection}, commands) do
case Connection.pipeline(connection, commands) do
{:error, %Redix.Error{} = error} ->
{:error, %CommandError{message: Exception.message(error), reason: error}}
{:error, %Redix.ConnectionError{} = error} ->
{:error, %FalkorDB.ConnectionError{message: Exception.message(error), reason: error}}
other ->
other
end
end
@spec list(t()) :: {:ok, [String.t()]} | {:error, term()}
def list(%__MODULE__{} = db) do
with {:ok, reply} <- command(db, ["GRAPH.LIST"]) do
{:ok, normalize_string_list(reply)}
end
end
@spec config_get(t(), String.t()) :: {:ok, term()} | {:error, term()}
def config_get(%__MODULE__{} = db, key) when is_binary(key) do
command(db, ["GRAPH.CONFIG", "GET", key])
end
@spec config_set(t(), String.t(), String.Chars.t()) :: {:ok, term()} | {:error, term()}
def config_set(%__MODULE__{} = db, key, value) when is_binary(key) do
command(db, ["GRAPH.CONFIG", "SET", key, to_string(value)])
end
@spec info(t(), [String.t()] | String.t() | nil) :: {:ok, term()} | {:error, term()}
def info(db, sections \\ nil)
def info(%__MODULE__{} = db, nil), do: command(db, ["GRAPH.INFO"])
def info(%__MODULE__{} = db, section) when is_binary(section),
do: command(db, ["GRAPH.INFO", section])
def info(%__MODULE__{} = db, sections) when is_list(sections) do
command(db, ["GRAPH.INFO" | Enum.map(sections, &to_string/1)])
end
@spec debug(t(), [String.Chars.t()]) :: {:ok, term()} | {:error, term()}
def debug(%__MODULE__{} = db, args) when is_list(args) do
command(db, ["GRAPH.DEBUG" | Enum.map(args, &to_string/1)])
end
@spec acl(t(), [String.Chars.t()]) :: {:ok, term()} | {:error, term()}
def acl(%__MODULE__{} = db, args) when is_list(args) do
command(db, ["GRAPH.ACL" | Enum.map(args, &to_string/1)])
end
@spec set_password(t(), String.t() | atom(), String.t()) :: {:ok, term()} | {:error, term()}
def set_password(%__MODULE__{} = db, action, password) when is_binary(password) do
[normalized_action, pass] =
CommandBuilder.password_arguments(normalize_action(action), password)
command(db, ["GRAPH.PASSWORD", normalized_action, pass])
end
@spec udf_load(t(), String.t(), String.t(), keyword()) :: {:ok, term()} | {:error, term()}
def udf_load(%__MODULE__{} = db, library_name, script, opts \\ []) do
replace = Keyword.get(opts, :replace, false)
command(db, ["GRAPH.UDF" | CommandBuilder.udf_load_arguments(library_name, script, replace)])
end
@spec udf_list(t(), String.t() | nil, keyword()) :: {:ok, term()} | {:error, term()}
def udf_list(%__MODULE__{} = db, library_name \\ nil, opts \\ []) do
with_code = Keyword.get(opts, :with_code, false)
command(db, ["GRAPH.UDF" | CommandBuilder.udf_list_arguments(library_name, with_code)])
end
@spec udf_flush(t()) :: {:ok, term()} | {:error, term()}
def udf_flush(%__MODULE__{} = db), do: command(db, ["GRAPH.UDF", "FLUSH"])
@spec udf_delete(t(), String.t()) :: {:ok, term()} | {:error, term()}
def udf_delete(%__MODULE__{} = db, library_name),
do: command(db, ["GRAPH.UDF", "DELETE", library_name])
defp adapter_for(:single), do: RedixSingle
defp adapter_for(:sentinel), do: RedixSentinel
defp normalize_mode(mode) when is_atom(mode) do
case mode do
:single -> :single
:sentinel -> :sentinel
_ -> raise ArgumentError, "unsupported mode: #{inspect(mode)}"
end
end
defp normalize_mode(mode) when is_binary(mode) do
case String.downcase(mode) do
"single" -> :single
"sentinel" -> :sentinel
_ -> raise ArgumentError, "unsupported mode: #{inspect(mode)}"
end
end
defp normalize_string_list(list) when is_list(list), do: Enum.map(list, &to_string/1)
defp normalize_string_list(_other), do: []
defp normalize_action(action) when is_atom(action),
do: action |> Atom.to_string() |> String.upcase()
defp normalize_action(action) when is_binary(action), do: String.upcase(action)
end