Skip to content

Commit

Permalink
[#15] Allow storing raw strings, not the whole Nebulex.Object.t()
Browse files Browse the repository at this point in the history
  • Loading branch information
cabol committed Nov 11, 2019
1 parent 96bd2d3 commit 180dedf
Show file tree
Hide file tree
Showing 7 changed files with 89 additions and 17 deletions.
7 changes: 4 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ services:
- docker

elixir:
- 1.8
- 1.9

otp_release:
- 22.1
- 21.3

env:
Expand All @@ -20,11 +21,11 @@ before_script:
- mix deps.get --only test

script:
- mix format --check-formatted
- mix credo --strict
- mix coveralls.travis
- mix dialyzer --plt
- mix dialyzer --halt-exit-status
- mix credo --strict
- mix format --check-formatted

after_script:
- MIX_ENV=docs mix deps.get
Expand Down
59 changes: 53 additions & 6 deletions lib/nebulex_redis_adapter.ex
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,47 @@ defmodule NebulexRedisAdapter do
next values: `:standalone | :cluster | :redis_cluster`. Defaults to
`:standalone`.
* `:pool_size` - number of connections to keep in the pool.
* `:pool_size` - Number of connections to keep in the pool.
Defaults to `System.schedulers_online()`.
* `:conn_opts` - Redis client options (`Redix` options in this case).
For more information about the options (Redis and connection options),
please check out `Redix` docs.
* `:default_data_type` - Sets the default data type for encoding Redis
values. Defaults to `:object`. For more information, check the
"Data Types" section below.
* `:dt` - This option is only valid for set-like operations and allows us
to change the Redis value encoding via this option. This option overrides
`:default_data_type`. Defaults to `:object`. For more information, check
the "Data Types" section below.
## Data Types
This adapter supports different ways to encode and store Redis values,
regarding the data type we are working with. Supported values:
* `:object` - By default, the value stored in Redis is the
`Nebulex.Object.t()` itself, before to insert it, it is encoded as binary
using `:erlang.term_to_binary/1`. The downside is it consumes more memory
since the object contains not only the value but also the key, the TTL,
and the version. This is the default.
* `:string` - If this option is set and the object value can be converted
to a valid string (e.g.: strings, integers, atoms), then that string is
stored directly in Redis without any encoding.
### Usage Example
MyCache.set("foo", "bar", dt: :string)
MyCache.set("int", 123, dt: :string)
MyCache.set("atom", :atom, dt: :string)
**NOTE:** Support for other Redis Data Types is in progress.
## Standalone Example
We can define our cache to use Redis adapter as follows:
Expand Down Expand Up @@ -187,7 +221,7 @@ defmodule NebulexRedisAdapter do
@behaviour Nebulex.Adapter
@behaviour Nebulex.Adapter.Queryable

import NebulexRedisAdapter.String
import NebulexRedisAdapter.Encoder

alias Nebulex.Object
alias NebulexRedisAdapter.{Cluster, Command, Connection, RedisCluster}
Expand All @@ -202,6 +236,7 @@ defmodule NebulexRedisAdapter do
mode = Keyword.get(config, :mode, :standalone)
pool_size = Keyword.get(config, :pool_size, @default_pool_size)
hash_slot = Keyword.get(config, :hash_slot)
default_dt = Keyword.get(config, :default_data_type, :object)

nodes =
for {node_name, node_opts} <- Keyword.get(config, :nodes, []) do
Expand All @@ -225,6 +260,10 @@ defmodule NebulexRedisAdapter do
true ->
def __hash_slot__, do: Cluster
end

def get_data_type(opts) do
Keyword.get(opts, :dt, unquote(default_dt))
end
end
end

Expand Down Expand Up @@ -277,7 +316,12 @@ defmodule NebulexRedisAdapter do
{keys, acc}

entry, {[key | keys], acc} ->
{keys, Map.put(acc, key, decode(entry))}
value =
entry
|> decode()
|> object(key, -1)

{keys, Map.put(acc, key, value)}
end)
|> elem(1)
end
Expand All @@ -286,8 +330,9 @@ defmodule NebulexRedisAdapter do
def set(cache, object, opts) do
cmd_opts = cmd_opts(opts, action: :set, ttl: nil)
redis_k = encode(object.key)
redis_v = encode(object, cache.get_data_type(opts))

case Command.exec!(cache, ["SET", redis_k, encode(object) | cmd_opts], redis_k) do
case Command.exec!(cache, ["SET", redis_k, redis_v | cmd_opts], redis_k) do
"OK" -> true
nil -> false
end
Expand All @@ -311,6 +356,8 @@ defmodule NebulexRedisAdapter do
end

defp do_set_many(hash_slot_or_key, cache, objects, opts) do
dt = cache.get_data_type(opts)

default_exp =
opts
|> Keyword.get(:ttl)
Expand All @@ -325,7 +372,7 @@ defmodule NebulexRedisAdapter do
do: [["EXPIRE", redis_k, Object.remaining_ttl(expire_at)] | acc2],
else: acc2

{[encode(object), redis_k | acc1], acc2}
{[encode(object, dt), redis_k | acc1], acc2}
end)

["OK" | _] = Command.pipeline!(cache, [Enum.reverse(mset) | expire], hash_slot_or_key)
Expand Down Expand Up @@ -482,7 +529,7 @@ defmodule NebulexRedisAdapter do
%{obj | expire_at: Object.expire_at(ttl)}
end

defp object(value, key, -1) do
defp object(value, key, _ttl) when is_binary(value) do
%Object{key: key, value: value}
end

Expand Down
2 changes: 1 addition & 1 deletion lib/nebulex_redis_adapter/cluster.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ defmodule NebulexRedisAdapter.Cluster do

use Nebulex.Adapter.HashSlot

import NebulexRedisAdapter.String
import NebulexRedisAdapter.Encoder

alias NebulexCluster.Pool

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
defmodule NebulexRedisAdapter.String do
defmodule NebulexRedisAdapter.Encoder do
@moduledoc false

@spec encode(term) :: binary
def encode(data) do
alias Nebulex.Object

@type dt :: :object | :string

@spec encode(term, dt) :: binary
def encode(data, dt \\ :object)

def encode(%Object{value: data}, :string) do
to_string(data)
end

def encode(data, _) do
to_string(data)
rescue
_e -> :erlang.term_to_binary(data)
Expand Down
2 changes: 1 addition & 1 deletion lib/nebulex_redis_adapter/redis_cluster.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ defmodule NebulexRedisAdapter.RedisCluster do

use Nebulex.Adapter.HashSlot

import NebulexRedisAdapter.String
import NebulexRedisAdapter.Encoder

alias NebulexCluster.Pool
alias NebulexRedisAdapter.Connection
Expand Down
6 changes: 3 additions & 3 deletions mix.exs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
defmodule NebulexRedisAdapter.MixProject do
use Mix.Project

@version "1.1.0"
@version "1.1.1-dev"

def project do
[
app: :nebulex_redis_adapter,
version: @version,
elixir: "~> 1.6",
elixir: "~> 1.8",
deps: deps(),

# Docs
Expand Down Expand Up @@ -66,7 +66,7 @@ defmodule NebulexRedisAdapter.MixProject do

defp nebulex_dep do
if System.get_env("NBX_TEST") do
[github: "cabol/nebulex", tag: "v1.1.0", override: true]
[github: "cabol/nebulex", tag: "v1.1.1", override: true]
else
"~> 1.1"
end
Expand Down
14 changes: 14 additions & 0 deletions test/shared/cache_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,20 @@ defmodule NebulexRedisAdapter.CacheTest do
"**name**" |> @cache.all() |> @cache.get_many()
end

test "string data type" do
assert "bar" == @cache.set("foo", "bar", dt: :string)
assert "bar" == @cache.get("foo")

assert 123 == @cache.set("int", 123, dt: :string)
assert "123" == @cache.get("int")

assert :atom == @cache.set("atom", :atom, dt: :string)
assert "atom" == @cache.get("atom")

assert :ok == @cache.set_many(%{"foo" => "bar", 1 => 1, :a => :a}, dt: :string)
assert %{"foo" => "bar", 1 => "1", :a => "a"} == @cache.get_many(["foo", 1, :a])
end

## Private Functions

defp to_int(keys), do: :lists.usort(for(k <- keys, do: String.to_integer(k)))
Expand Down

0 comments on commit 180dedf

Please sign in to comment.