Skip to main content

lib/ferric_store.ex

defmodule FerricStore do
  @moduledoc """
  Elixir SDK for FerricStore and FerricFlow over the native `ferric://` protocol.
  """

  alias FerricStore.Client

  defdelegate start_link(opts \\ []), to: Client
  defdelegate connect!(opts \\ []), to: Client
  defdelegate close(client), to: Client
  defdelegate pipeline(client, commands, opts \\ []), to: Client
  defdelegate async_pipeline(client, commands, opts \\ []), to: Client
  defdelegate async_native(client, opcode, payload, opts \\ []), to: Client
  defdelegate await(ref, timeout \\ 5_000), to: Client
  defdelegate yield(ref, timeout \\ 0), to: Client

  def command(client, command, args \\ [], opts \\ []) do
    Client.command(client, command, args, opts)
  end

  def ping(client), do: command(client, "PING")

  def get(client, key),
    do: Client.native(client, FerricStore.Protocol.opcode(:get), %{"key" => key})

  def set(client, key, value, opts \\ []) do
    response =
      Client.native(
        client,
        FerricStore.Protocol.opcode(:set),
        %{"key" => key, "value" => value} |> put_if_present("ttl_ms", Keyword.get(opts, :ttl_ms))
      )

    if response == "OK", do: :ok, else: response
  end

  def delete(client, keys) when is_list(keys), do: command(client, "DEL", keys)
  def delete(client, key), do: delete(client, [key])

  def mget(client, keys),
    do: Client.native(client, FerricStore.Protocol.opcode(:mget), %{"keys" => keys})

  def mset(client, pairs) when is_map(pairs), do: mset(client, Map.to_list(pairs))

  def mset(client, pairs) when is_list(pairs) do
    Client.native(client, FerricStore.Protocol.opcode(:mset), %{
      "pairs" => Enum.map(pairs, &kv_pair/1)
    })
  end

  def hset(client, key, field, value), do: command(client, "HSET", [key, field, value])
  def hget(client, key, field), do: command(client, "HGET", [key, field])
  def hmget(client, key, fields), do: command(client, "HMGET", [key | fields])
  def hgetall(client, key), do: command(client, "HGETALL", [key])

  def lpush(client, key, values), do: command(client, "LPUSH", [key | List.wrap(values)])
  def rpush(client, key, values), do: command(client, "RPUSH", [key | List.wrap(values)])
  def lpop(client, key), do: command(client, "LPOP", [key])
  def rpop(client, key), do: command(client, "RPOP", [key])
  def lrange(client, key, start, stop), do: command(client, "LRANGE", [key, start, stop])

  def sadd(client, key, members), do: command(client, "SADD", [key | List.wrap(members)])
  def srem(client, key, members), do: command(client, "SREM", [key | List.wrap(members)])
  def smembers(client, key), do: command(client, "SMEMBERS", [key])
  def sismember(client, key, member), do: command(client, "SISMEMBER", [key, member])

  def zadd(client, key, score, member), do: command(client, "ZADD", [key, score, member])
  def zrem(client, key, members), do: command(client, "ZREM", [key | List.wrap(members)])

  def zrange(client, key, start, stop, opts \\ []),
    do: command(client, "ZRANGE", [key, start, stop] ++ zrange_opts(opts))

  def zscore(client, key, member), do: command(client, "ZSCORE", [key, member])

  defp zrange_opts(opts) do
    []
    |> append_flag("WITHSCORES", Keyword.get(opts, :with_scores))
    |> append_flag("REV", Keyword.get(opts, :rev))
  end

  defp append_flag(args, _name, nil), do: args
  defp append_flag(args, _name, false), do: args
  defp append_flag(args, name, true), do: args ++ [name]
  defp put_if_present(map, _key, nil), do: map
  defp put_if_present(map, key, value), do: Map.put(map, key, value)

  defp kv_pair({key, value}), do: [key, value]
  defp kv_pair([key, value]), do: [key, value]
end