From bf71c68bed180032b52647fbf565a0c88c73e7cb Mon Sep 17 00:00:00 2001 From: Andrew Pett Date: Tue, 5 Mar 2019 08:48:05 +1300 Subject: [PATCH 1/5] Add writing flags --- lib/memcache/connection.ex | 54 ++++++++++++-- test/memcache/connection_test.exs | 117 ++++++++++++++++-------------- 2 files changed, 110 insertions(+), 61 deletions(-) diff --git a/lib/memcache/connection.ex b/lib/memcache/connection.ex index 561942e..4ccb6ee 100644 --- a/lib/memcache/connection.ex +++ b/lib/memcache/connection.ex @@ -5,6 +5,7 @@ defmodule Memcache.Connection do """ require Logger use Connection + use Bitwise alias Memcache.Protocol alias Memcache.Receiver alias Memcache.Utils @@ -90,7 +91,35 @@ defmodule Memcache.Connection do """ @spec execute(GenServer.server(), atom, [binary], Keyword.t()) :: Memcache.result() def execute(pid, command, args, options \\ []) do - Connection.call(pid, {:execute, command, args, %{cas: Keyword.get(options, :cas, false)}}) + if options[:flags] do + IO.inspect(options) + end + + translate_flags = fn flags -> + Enum.reduce(flags, 0, fn + :serialize, flag_bits -> + flag_bits ||| 1 + :compressed, flag_bits -> + flag_bits ||| 2 + end) + end + + flags = + options + |> Keyword.get(:flags, []) + |> translate_flags.() + + opts = + %{} + |> Map.put(:cas, Keyword.get(options, :cas, false)) + |> Map.put(:flags, flags) + + if options[:flags] do + IO.inspect(args) + IO.inspect(opts) + end + + Connection.call(pid, {:execute, command, args, opts}) end @doc """ @@ -283,7 +312,7 @@ defmodule Memcache.Connection do end defp send_and_receive(%State{sock: sock} = s, from, command, args, opts) do - packet = serialize(command, args) + packet = serialize(command, args, opts) case :gen_tcp.send(sock, packet) do :ok -> @@ -298,7 +327,7 @@ defmodule Memcache.Connection do defp send_and_receive_quiet(%State{sock: sock} = s, from, commands) do {packet, commands, i} = Enum.reduce(commands, {[], [], 1}, &accumulate_commands/2) - packet = [packet | serialize(:NOOP, [], i)] + packet = [packet | serialize(:NOOP, [], [], i)] case :gen_tcp.send(sock, packet) do :ok -> @@ -317,12 +346,12 @@ defmodule Memcache.Connection do end defp accumulate_commands({command, args}, {packet, commands, i}) do - {[packet | serialize(command, args, i)], [{i, command, args, %{cas: false}} | commands], + {[packet | serialize(command, args, [], i)], [{i, command, args, %{cas: false}} | commands], i + 1} end defp accumulate_commands({command, args, options}, {packet, commands, i}) do - {[packet | serialize(command, args, i)], + {[packet | serialize(command, args, options, i)], [{i, command, args, %{cas: Keyword.get(options, :cas, false)}} | commands], i + 1} end @@ -362,7 +391,7 @@ defmodule Memcache.Connection do end defp execute_command(sock, command, args) do - packet = serialize(command, args) + packet = serialize(command, args, []) case :gen_tcp.send(sock, packet) do :ok -> recv_response(sock, command) @@ -413,7 +442,18 @@ defmodule Memcache.Connection do end end - defp serialize(command, args, opaque \\ 0) do + @flag_commands [:SET, :SETQ, :ADD, :ADDQ, :REPLACE, :REPLACEQ] + defp serialize(command, args, opts, opaque \\ 0) do + args = if command in @flag_commands do + args ++ [Map.get(opts, :flags, 0)] + else + args + end + + do_serialize(command, args, opaque) + end + + defp do_serialize(command, args, opaque) do apply(Protocol, :to_binary, [command | [opaque | args]]) end end diff --git a/test/memcache/connection_test.exs b/test/memcache/connection_test.exs index bdfa13b..4a2113c 100644 --- a/test/memcache/connection_test.exs +++ b/test/memcache/connection_test.exs @@ -19,63 +19,72 @@ defmodule Memcache.ConnectionTest do cases = [ {:FLUSH, [], {:ok}}, - {:GET, ["unknown"], {:error, "Key not found"}}, - {:SET, ["hello", "world"], {:ok}}, + # {:GET, ["unknown"], {:error, "Key not found"}}, + # {:SET, ["hello", "world"], {:ok}}, + # {:GET, ["hello"], {:ok, "world"}}, + # {:SET, ["hello", ['w', 'o', "rl", 'd']], {:ok}}, + # {:GET, ["hello"], {:ok, "world"}}, + # {:SET, ["hello", "move on"], {:ok}}, + # {:SET, [<<0x56::size(32)>>, <<0x56::size(32)>>], {:ok}}, + # {:GET, [<<0x56::size(32)>>], {:ok, <<0x56::size(32)>>}}, + # {:GET, ["hello"], {:ok, "move on"}}, + # {:GETK, ["hello"], {:ok, "hello", "move on"}}, + # {:GETK, ["unknown"], {:error, "Key not found"}}, + # {:ADD, ["hello", "world"], {:error, "Key exists"}}, + # {:ADD, ["add", "world"], {:ok}}, + # {:DELETE, ["add"], {:ok}}, + # {:REPLACE, ["add", "world"], {:error, "Key not found"}}, + # {:ADD, ["add", "world"], {:ok}}, + # {:REPLACE, ["add", "world"], {:ok}}, + # {:DELETE, ["add"], {:ok}}, + # {:DELETE, ["hello"], {:ok}}, + # {:DELETE, ["unkown"], {:error, "Key not found"}}, + # {:INCREMENT, ["count", 1, 5], {:ok, 5}}, + # {:INCREMENT, ["count", 1, 5], {:ok, 6}}, + # {:INCREMENT, ["count", 5, 1], {:ok, 11}}, + # {:DELETE, ["count"], {:ok}}, + # {:SET, ["hello", "world"], {:ok}}, + # {:INCREMENT, ["hello"], {:error, "Incr/Decr on non-numeric value"}}, + # {:DELETE, ["hello"], {:ok}}, + # {:DECREMENT, ["count", 1, 5], {:ok, 5}}, + # {:DECREMENT, ["count", 1, 5], {:ok, 4}}, + # {:DECREMENT, ["count", 6, 5], {:ok, 0}}, + # {:DELETE, ["count"], {:ok}}, + # {:SET, ["hello", "world"], {:ok}}, + # {:DECREMENT, ["hello"], {:error, "Incr/Decr on non-numeric value"}}, + # {:DELETE, ["hello"], {:ok}}, + # {:INCREMENT, ["count", 6, 5, 0, 0xFFFFFFFF], {:error, "Key not found"}}, + # {:INCREMENT, ["count", 6, 5, 0, 0x05], {:ok, 5}}, + # {:DELETE, ["count"], {:ok}}, + # {:NOOP, [], {:ok}}, + # {:APPEND, ["new", "hope"], {:error, "Item not stored"}}, + # {:SET, ["new", "new "], {:ok}}, + # {:APPEND, ["new", "hope"], {:ok}}, + # {:GET, ["new"], {:ok, "new hope"}}, + # {:DELETE, ["new"], {:ok}}, + # {:PREPEND, ["new", "hope"], {:error, "Item not stored"}}, + # {:SET, ["new", "hope"], {:ok}}, + # {:PREPEND, ["new", "new "], {:ok}}, + # {:GET, ["new"], {:ok, "new hope"}}, + # {:DELETE, ["new"], {:ok}}, + # {:SET, ["name", "ananth"], {:ok}}, + # {:FLUSH, [0xFFFF], {:ok}}, + # {:GET, ["name"], {:ok, "ananth"}}, + # {:FLUSH, [], {:ok}}, + # {:GET, ["name"], {:error, "Key not found"}}, + {:SET, ["hello", "world", 0, 0], {:ok}}, {:GET, ["hello"], {:ok, "world"}}, - {:SET, ["hello", ['w', 'o', "rl", 'd']], {:ok}}, - {:GET, ["hello"], {:ok, "world"}}, - {:SET, ["hello", "move on"], {:ok}}, - {:SET, [<<0x56::size(32)>>, <<0x56::size(32)>>], {:ok}}, - {:GET, [<<0x56::size(32)>>], {:ok, <<0x56::size(32)>>}}, - {:GET, ["hello"], {:ok, "move on"}}, - {:GETK, ["hello"], {:ok, "hello", "move on"}}, - {:GETK, ["unknown"], {:error, "Key not found"}}, - {:ADD, ["hello", "world"], {:error, "Key exists"}}, - {:ADD, ["add", "world"], {:ok}}, - {:DELETE, ["add"], {:ok}}, - {:REPLACE, ["add", "world"], {:error, "Key not found"}}, - {:ADD, ["add", "world"], {:ok}}, - {:REPLACE, ["add", "world"], {:ok}}, - {:DELETE, ["add"], {:ok}}, - {:DELETE, ["hello"], {:ok}}, - {:DELETE, ["unkown"], {:error, "Key not found"}}, - {:INCREMENT, ["count", 1, 5], {:ok, 5}}, - {:INCREMENT, ["count", 1, 5], {:ok, 6}}, - {:INCREMENT, ["count", 5, 1], {:ok, 11}}, - {:DELETE, ["count"], {:ok}}, - {:SET, ["hello", "world"], {:ok}}, - {:INCREMENT, ["hello"], {:error, "Incr/Decr on non-numeric value"}}, - {:DELETE, ["hello"], {:ok}}, - {:DECREMENT, ["count", 1, 5], {:ok, 5}}, - {:DECREMENT, ["count", 1, 5], {:ok, 4}}, - {:DECREMENT, ["count", 6, 5], {:ok, 0}}, - {:DELETE, ["count"], {:ok}}, - {:SET, ["hello", "world"], {:ok}}, - {:DECREMENT, ["hello"], {:error, "Incr/Decr on non-numeric value"}}, - {:DELETE, ["hello"], {:ok}}, - {:INCREMENT, ["count", 6, 5, 0, 0xFFFFFFFF], {:error, "Key not found"}}, - {:INCREMENT, ["count", 6, 5, 0, 0x05], {:ok, 5}}, - {:DELETE, ["count"], {:ok}}, - {:NOOP, [], {:ok}}, - {:APPEND, ["new", "hope"], {:error, "Item not stored"}}, - {:SET, ["new", "new "], {:ok}}, - {:APPEND, ["new", "hope"], {:ok}}, - {:GET, ["new"], {:ok, "new hope"}}, - {:DELETE, ["new"], {:ok}}, - {:PREPEND, ["new", "hope"], {:error, "Item not stored"}}, - {:SET, ["new", "hope"], {:ok}}, - {:PREPEND, ["new", "new "], {:ok}}, - {:GET, ["new"], {:ok, "new hope"}}, - {:DELETE, ["new"], {:ok}}, - {:SET, ["name", "ananth"], {:ok}}, - {:FLUSH, [0xFFFF], {:ok}}, - {:GET, ["name"], {:ok, "ananth"}}, - {:FLUSH, [], {:ok}}, - {:GET, ["name"], {:error, "Key not found"}} + {:SET, ["hello", "world", 0, 0], [flags: [:serialize]], {:ok}}, + {:GET, ["hello"], [flags: [:serialize]], {:ok, "world"}}, + {:SET, ["hello", "world", 0, 0], [flags: [:serialize, :compressed]], {:ok}}, + {:GET, ["hello"], {:ok, "world"}} ] - Enum.each(cases, fn {command, args, response} -> - assert(execute(pid, command, args) == response) + Enum.each(cases, fn + {command, args, response} -> + assert(execute(pid, command, args) == response) + {command, args, opts, response} -> + assert(execute(pid, command, args, opts) == response) end) {:ok} = close(pid) From 3a45473a03bb93a116d046fd0fc18379f78c79db Mon Sep 17 00:00:00 2001 From: Andrew Pett Date: Tue, 5 Mar 2019 11:38:08 +1300 Subject: [PATCH 2/5] Add reading flags, change to Coder behaviour --- lib/memcache.ex | 41 +++++-- lib/memcache/binary_utils.ex | 12 ++ lib/memcache/coder.ex | 37 ++++++ lib/memcache/coder/erlang.ex | 2 +- lib/memcache/coder/json.ex | 3 +- lib/memcache/coder/raw.ex | 2 +- lib/memcache/coder/zip.ex | 2 +- lib/memcache/connection.ex | 68 ++++++----- lib/memcache/protocol.ex | 50 ++++++-- lib/memcache/receiver.ex | 1 + test/memcache/connection_test.exs | 187 ++++++++++++++++-------------- test/memcache_test.exs | 4 + 12 files changed, 268 insertions(+), 141 deletions(-) diff --git a/lib/memcache.ex b/lib/memcache.ex index 17aad3e..bd898df 100644 --- a/lib/memcache.ex +++ b/lib/memcache.ex @@ -544,26 +544,45 @@ defmodule Memcache do apply(elem(coder, 0), :encode, [value, elem(coder, 1)]) end - defp decode(server_options, value) do + defp encoder_flags(server_options, value) do coder = server_options.coder - apply(elem(coder, 0), :decode, [value, elem(coder, 1)]) + apply(elem(coder, 0), :encode_flags, [value, elem(coder, 1)]) end - defp decode_response({:ok, value}, server_options) when is_binary(value) do - {:ok, decode(server_options, value)} + defp opts_with_flags(opts, encoder_flags) do + given_flags = Keyword.get(opts, :flags, []) + Keyword.put(opts, :flags, merge_flags(encoder_flags, given_flags)) end - defp decode_response({:ok, value, cas}, server_options) when is_binary(value) do - {:ok, decode(server_options, value), cas} + defp merge_flags(encoder_flags, []), do: encoder_flags + defp merge_flags(_encoder_flags, [_head | []] = given_flags), do: given_flags + + defp decode(server_options, {value, flags}) do + coder = server_options.coder + module = elem(coder, 0) + coder_options = elem(coder, 1) ++ [flags: flags] + apply(module, :decode, [value, coder_options]) + end + + defp decode_response({:ok, value, flags}, server_options) when is_binary(value) and is_list(flags) do + {:ok, decode(server_options, {value, flags})} end - defp decode_response(rest, _server_options), do: rest + defp decode_response({:ok, value, cas, flags}, server_options) when is_binary(value) and is_list(flags) do + {:ok, decode(server_options, {value, flags}), cas} + end + + defp decode_response(rest, _server_options) do + rest + end defp decode_multi_response({:ok, values}, server_options) when is_list(values) do {:ok, Enum.map(values, &decode_response(&1, server_options))} end - defp decode_multi_response(rest, _server_options), do: rest + defp decode_multi_response(rest, _server_options) do + rest + end defp ttl_or_default(server_options, opts) do if Keyword.has_key?(opts, :ttl) do @@ -604,7 +623,7 @@ defmodule Memcache do |> execute( command, [key_with_namespace(server_options, key) | [encode(server_options, value) | rest]], - opts + opts_with_flags(opts, encoder_flags(server_options, value)) ) |> decode_response(server_options) end @@ -631,7 +650,9 @@ defmodule Memcache do commands = Enum.map(commands, fn {command, [key | [value | rest]], opts} -> {command, - [key_with_namespace(server_options, key) | [encode(server_options, value) | rest]], opts} + [key_with_namespace(server_options, key) | [encode(server_options, value) | rest]], + opts_with_flags(opts, encoder_flags(server_options, value)) + } end) server diff --git a/lib/memcache/binary_utils.ex b/lib/memcache/binary_utils.ex index 6ac0136..446ef63 100644 --- a/lib/memcache/binary_utils.ex +++ b/lib/memcache/binary_utils.ex @@ -34,6 +34,12 @@ defmodule Memcache.BinaryUtils do AUTH_STEP: 0x22 ] + # http://www.hjp.at/zettel/m/memcached_flags.rxml + @flags [ + serialised: 0x1, + compressed: 0x2 + ] + defmacro opb(x) do quote do <> @@ -46,6 +52,12 @@ defmodule Memcache.BinaryUtils do end end + def flag_bit(x) do + Keyword.fetch!(@flags, x) + end + + def flags, do: @flags + defmodule Header do @moduledoc false diff --git a/lib/memcache/coder.ex b/lib/memcache/coder.ex index 41ea091..4b0f251 100644 --- a/lib/memcache/coder.ex +++ b/lib/memcache/coder.ex @@ -11,9 +11,46 @@ defmodule Memcache.Coder do """ @callback encode(any, options :: Keyword.t()) :: iodata + @doc """ + Called before the value is sent to the server. It should return + the flags to set by default for this coder as a list + + Valid flags: + - :serialised + - :compressed + + Example: + iex> value = %{} + iex> options = [flags: [:compressed]] + iex> encode_flags(value, options) + [:compressed, :serialised] + + iex> encode_flags(value) + [:serialised] + """ + @callback encode_flags(any, options :: Keyword.t()) :: list(atom) + @doc """ Called after the value is loaded from the server. It can return any type. """ @callback decode(iodata, options :: Keyword.t()) :: any + + defmacro __using__(_opts) do + quote do + @behaviour unquote(__MODULE__) + + def encode_flags(value, options) do + flags = options[:flags] + + if !is_nil(flags) do + flags + else + [] + end + end + + defoverridable encode_flags: 2 + end + end end diff --git a/lib/memcache/coder/erlang.ex b/lib/memcache/coder/erlang.ex index a1da984..ecf2f17 100644 --- a/lib/memcache/coder/erlang.ex +++ b/lib/memcache/coder/erlang.ex @@ -3,7 +3,7 @@ defmodule Memcache.Coder.Erlang do Uses `:erlang.term_to_binary/2` and `:erlang.binary_to_term/1` to encode and decode value. """ - @behaviour Memcache.Coder + use Memcache.Coder def encode(value, options), do: :erlang.term_to_binary(value, options) def decode(value, _options), do: :erlang.binary_to_term(value) diff --git a/lib/memcache/coder/json.ex b/lib/memcache/coder/json.ex index 6cc8f0d..b638226 100644 --- a/lib/memcache/coder/json.ex +++ b/lib/memcache/coder/json.ex @@ -4,9 +4,10 @@ if Code.ensure_loaded?(Poison) do Uses the `Poison` module to encode and decode value. To use this coder add `poison` as a dependency in `mix.exs`. """ - @behaviour Memcache.Coder + use Memcache.Coder def encode(value, options), do: Poison.encode_to_iodata!(value, options) + def encode_flags(_value, _options), do: [:serialised] def decode(value, options), do: Poison.decode!(value, options) end end diff --git a/lib/memcache/coder/raw.ex b/lib/memcache/coder/raw.ex index d40fa6a..77247a3 100644 --- a/lib/memcache/coder/raw.ex +++ b/lib/memcache/coder/raw.ex @@ -2,7 +2,7 @@ defmodule Memcache.Coder.Raw do @moduledoc """ Doesn't do any conversion. Stores the value as it is in the server. """ - @behaviour Memcache.Coder + use Memcache.Coder def encode(value, _options), do: value def decode(value, _options), do: value diff --git a/lib/memcache/coder/zip.ex b/lib/memcache/coder/zip.ex index 6622ee1..a6354f1 100644 --- a/lib/memcache/coder/zip.ex +++ b/lib/memcache/coder/zip.ex @@ -3,7 +3,7 @@ defmodule Memcache.Coder.ZIP do Uses `:zlib.zip/1` and `:zlib.unzip/1` to compress and decompress value. """ - @behaviour Memcache.Coder + use Memcache.Coder def encode(value, _options), do: :zlib.zip(value) def decode(value, _options), do: :zlib.unzip(value) diff --git a/lib/memcache/connection.ex b/lib/memcache/connection.ex index 4ccb6ee..4c829a7 100644 --- a/lib/memcache/connection.ex +++ b/lib/memcache/connection.ex @@ -6,6 +6,7 @@ defmodule Memcache.Connection do require Logger use Connection use Bitwise + import Memcache.BinaryUtils alias Memcache.Protocol alias Memcache.Receiver alias Memcache.Utils @@ -91,34 +92,16 @@ defmodule Memcache.Connection do """ @spec execute(GenServer.server(), atom, [binary], Keyword.t()) :: Memcache.result() def execute(pid, command, args, options \\ []) do - if options[:flags] do - IO.inspect(options) - end - - translate_flags = fn flags -> - Enum.reduce(flags, 0, fn - :serialize, flag_bits -> - flag_bits ||| 1 - :compressed, flag_bits -> - flag_bits ||| 2 - end) - end - flags = options |> Keyword.get(:flags, []) - |> translate_flags.() + |> translate_flags() opts = %{} |> Map.put(:cas, Keyword.get(options, :cas, false)) |> Map.put(:flags, flags) - if options[:flags] do - IO.inspect(args) - IO.inspect(opts) - end - Connection.call(pid, {:execute, command, args, opts}) end @@ -136,6 +119,7 @@ defmodule Memcache.Connection do @spec execute_quiet(GenServer.server(), [{atom, [binary]} | {atom, [binary], Keyword.t()}]) :: {:ok, [Memcache.result()]} | {:error, atom} def execute_quiet(pid, commands) do + commands = normalise_flags(commands) Connection.call(pid, {:execute_quiet, commands}) end @@ -289,6 +273,26 @@ defmodule Memcache.Connection do :ok end + @spec normalise_flags([{atom, [binary]} | {atom, [binary], Keyword.t()}]) :: + [{atom, [binary]} | {atom, [binary], Keyword.t()}] + defp normalise_flags(commands) do + Enum.map(commands, fn + {_command, _args} = command -> + command + + {command, args, opts} -> + opts = Keyword.update(opts, :flags, 0, &translate_flags/1) + {command, args, opts} + end) + end + + @spec translate_flags(list(atom)) :: pos_integer + defp translate_flags(flags) do + Enum.reduce(flags, 0x0, fn flag, flag_bits -> + flag_bit(flag) ||| flag_bits + end) + end + defp maybe_activate_sock(state) do if Enum.empty?(state.receiver_queue) do case :inet.setopts(state.sock, active: :once) do @@ -327,7 +331,7 @@ defmodule Memcache.Connection do defp send_and_receive_quiet(%State{sock: sock} = s, from, commands) do {packet, commands, i} = Enum.reduce(commands, {[], [], 1}, &accumulate_commands/2) - packet = [packet | serialize(:NOOP, [], [], i)] + packet = [packet | serialize(:NOOP, [], %{}, i)] case :gen_tcp.send(sock, packet) do :ok -> @@ -346,7 +350,7 @@ defmodule Memcache.Connection do end defp accumulate_commands({command, args}, {packet, commands, i}) do - {[packet | serialize(command, args, [], i)], [{i, command, args, %{cas: false}} | commands], + {[packet | serialize(command, args, %{}, i)], [{i, command, args, %{cas: false}} | commands], i + 1} end @@ -391,7 +395,7 @@ defmodule Memcache.Connection do end defp execute_command(sock, command, args) do - packet = serialize(command, args, []) + packet = serialize(command, args, %{}) case :gen_tcp.send(sock, packet) do :ok -> recv_response(sock, command) @@ -442,17 +446,27 @@ defmodule Memcache.Connection do end end + @default_expiry 0 + @default_cas 0 @flag_commands [:SET, :SETQ, :ADD, :ADDQ, :REPLACE, :REPLACEQ] - defp serialize(command, args, opts, opaque \\ 0) do - args = if command in @flag_commands do - args ++ [Map.get(opts, :flags, 0)] - else - args + defp serialize(command, args, opts, opaque \\ 0) + defp serialize(command, args, opts, opaque) when command in @flag_commands do + opts = Map.new(opts) + flags = Map.get(opts, :flags, 0) + + args = case length(args) do + 2 -> args ++ [@default_expiry, @default_cas, flags] + 3 -> args ++ [@default_cas, flags] + 4 -> args ++ [flags] end do_serialize(command, args, opaque) end + defp serialize(command, args, _opts, opaque) do + do_serialize(command, args, opaque) + end + defp do_serialize(command, args, opaque) do apply(Protocol, :to_binary, [command | [opaque | args]]) end diff --git a/lib/memcache/protocol.ex b/lib/memcache/protocol.ex index 8a48c0a..5af036e 100644 --- a/lib/memcache/protocol.ex +++ b/lib/memcache/protocol.ex @@ -2,6 +2,7 @@ defmodule Memcache.Protocol do @moduledoc false + use Bitwise import Memcache.BinaryUtils alias Memcache.BinaryUtils.Header @@ -377,6 +378,10 @@ defmodule Memcache.Protocol do end def to_binary(:SET, opaque, key, value, cas, expiry, flag) do + if cas == [] do + raise "can't have a list here. you need to fix something." + end + [ bcat([request(), opb(:SET)]), <>, @@ -496,7 +501,7 @@ defmodule Memcache.Protocol do def parse_body( %Header{ status: 0x0000, - opcode: op(:GET), + opcode: op(:GET) = op, extra_length: extra_length, total_body_length: total_body_length, opaque: opaque @@ -504,14 +509,17 @@ defmodule Memcache.Protocol do rest ) do value_size = total_body_length - extra_length - <<_extra::binary-size(extra_length), value::binary-size(value_size)>> = rest - {opaque, {:ok, value}} + <> = rest + + flags = parse_flags(op, extra) + + {opaque, {:ok, value, flags}} end def parse_body( %Header{ status: 0x0000, - opcode: op(:GETQ), + opcode: op(:GETQ) = op, extra_length: extra_length, total_body_length: total_body_length, opaque: opaque @@ -519,14 +527,17 @@ defmodule Memcache.Protocol do rest ) do value_size = total_body_length - extra_length - <<_extra::binary-size(extra_length), value::binary-size(value_size)>> = rest - {opaque, {:ok, value}} + <> = rest + + flags = parse_flags(op, extra) + + {opaque, {:ok, value, flags}} end def parse_body( %Header{ status: 0x0000, - opcode: op(:GETK), + opcode: op(:GETK) = op, extra_length: extra_length, key_length: key_length, total_body_length: total_body_length, @@ -536,16 +547,18 @@ defmodule Memcache.Protocol do ) do value_size = total_body_length - extra_length - key_length - <<_extra::binary-size(extra_length), key::binary-size(key_length), + <> = rest - {opaque, {:ok, key, value}} + flags = parse_flags(op, extra) + + {opaque, {:ok, key, value, flags}} end def parse_body( %Header{ status: 0x0000, - opcode: op(:GETKQ), + opcode: op(:GETKQ) = op, extra_length: extra_length, key_length: key_length, total_body_length: total_body_length, @@ -555,10 +568,12 @@ defmodule Memcache.Protocol do ) do value_size = total_body_length - extra_length - key_length - <<_extra::binary-size(extra_length), key::binary-size(key_length), + <> = rest - {opaque, {:ok, key, value}} + flags = parse_flags(op, extra) + + {opaque, {:ok, key, value, flags}} end def parse_body(%Header{status: 0x0000, opcode: op(:VERSION), opaque: opaque}, rest) do @@ -625,6 +640,17 @@ defmodule Memcache.Protocol do {opaque, {:error, :auth_step, body}} end + @get_commands [op(:GET), op(:GETQ), op(:GETK), op(:GETKQ)] + def parse_flags(command, <>) when command in @get_commands do + Enum.reduce(flags(), [], fn {flag_name, flag_bits}, flags -> + if (extra &&& flag_bits) != 0, do: [flag_name | flags], else: flags + end) + end + + def parse_flags(_command, _extra) do + [] + end + defparse_empty(:SET) defparse_empty(:ADD) defparse_empty(:REPLACE) diff --git a/lib/memcache/receiver.ex b/lib/memcache/receiver.ex index da60a4b..99ed444 100644 --- a/lib/memcache/receiver.ex +++ b/lib/memcache/receiver.ex @@ -107,6 +107,7 @@ defmodule Memcache.Receiver do defp append_cas_version({:ok}, %{cas: cas_version}), do: {:ok, cas_version} defp append_cas_version({:ok, value}, %{cas: cas_version}), do: {:ok, value, cas_version} + defp append_cas_version({:ok, value, flags}, %{cas: cas_version}), do: {:ok, value, cas_version, flags} defp append_cas_version(error, %{cas: _cas_version}), do: error defp recv_response_quiet([], _sock, results, buffer) do diff --git a/test/memcache/connection_test.exs b/test/memcache/connection_test.exs index 4a2113c..b37a070 100644 --- a/test/memcache/connection_test.exs +++ b/test/memcache/connection_test.exs @@ -19,65 +19,65 @@ defmodule Memcache.ConnectionTest do cases = [ {:FLUSH, [], {:ok}}, - # {:GET, ["unknown"], {:error, "Key not found"}}, - # {:SET, ["hello", "world"], {:ok}}, - # {:GET, ["hello"], {:ok, "world"}}, - # {:SET, ["hello", ['w', 'o', "rl", 'd']], {:ok}}, - # {:GET, ["hello"], {:ok, "world"}}, - # {:SET, ["hello", "move on"], {:ok}}, - # {:SET, [<<0x56::size(32)>>, <<0x56::size(32)>>], {:ok}}, - # {:GET, [<<0x56::size(32)>>], {:ok, <<0x56::size(32)>>}}, - # {:GET, ["hello"], {:ok, "move on"}}, - # {:GETK, ["hello"], {:ok, "hello", "move on"}}, - # {:GETK, ["unknown"], {:error, "Key not found"}}, - # {:ADD, ["hello", "world"], {:error, "Key exists"}}, - # {:ADD, ["add", "world"], {:ok}}, - # {:DELETE, ["add"], {:ok}}, - # {:REPLACE, ["add", "world"], {:error, "Key not found"}}, - # {:ADD, ["add", "world"], {:ok}}, - # {:REPLACE, ["add", "world"], {:ok}}, - # {:DELETE, ["add"], {:ok}}, - # {:DELETE, ["hello"], {:ok}}, - # {:DELETE, ["unkown"], {:error, "Key not found"}}, - # {:INCREMENT, ["count", 1, 5], {:ok, 5}}, - # {:INCREMENT, ["count", 1, 5], {:ok, 6}}, - # {:INCREMENT, ["count", 5, 1], {:ok, 11}}, - # {:DELETE, ["count"], {:ok}}, - # {:SET, ["hello", "world"], {:ok}}, - # {:INCREMENT, ["hello"], {:error, "Incr/Decr on non-numeric value"}}, - # {:DELETE, ["hello"], {:ok}}, - # {:DECREMENT, ["count", 1, 5], {:ok, 5}}, - # {:DECREMENT, ["count", 1, 5], {:ok, 4}}, - # {:DECREMENT, ["count", 6, 5], {:ok, 0}}, - # {:DELETE, ["count"], {:ok}}, - # {:SET, ["hello", "world"], {:ok}}, - # {:DECREMENT, ["hello"], {:error, "Incr/Decr on non-numeric value"}}, - # {:DELETE, ["hello"], {:ok}}, - # {:INCREMENT, ["count", 6, 5, 0, 0xFFFFFFFF], {:error, "Key not found"}}, - # {:INCREMENT, ["count", 6, 5, 0, 0x05], {:ok, 5}}, - # {:DELETE, ["count"], {:ok}}, - # {:NOOP, [], {:ok}}, - # {:APPEND, ["new", "hope"], {:error, "Item not stored"}}, - # {:SET, ["new", "new "], {:ok}}, - # {:APPEND, ["new", "hope"], {:ok}}, - # {:GET, ["new"], {:ok, "new hope"}}, - # {:DELETE, ["new"], {:ok}}, - # {:PREPEND, ["new", "hope"], {:error, "Item not stored"}}, - # {:SET, ["new", "hope"], {:ok}}, - # {:PREPEND, ["new", "new "], {:ok}}, - # {:GET, ["new"], {:ok, "new hope"}}, - # {:DELETE, ["new"], {:ok}}, - # {:SET, ["name", "ananth"], {:ok}}, - # {:FLUSH, [0xFFFF], {:ok}}, - # {:GET, ["name"], {:ok, "ananth"}}, - # {:FLUSH, [], {:ok}}, - # {:GET, ["name"], {:error, "Key not found"}}, - {:SET, ["hello", "world", 0, 0], {:ok}}, - {:GET, ["hello"], {:ok, "world"}}, - {:SET, ["hello", "world", 0, 0], [flags: [:serialize]], {:ok}}, - {:GET, ["hello"], [flags: [:serialize]], {:ok, "world"}}, - {:SET, ["hello", "world", 0, 0], [flags: [:serialize, :compressed]], {:ok}}, - {:GET, ["hello"], {:ok, "world"}} + {:GET, ["unknown"], {:error, "Key not found"}}, + {:SET, ["hello", "world"], {:ok}}, + {:GET, ["hello"], {:ok, "world", []}}, + {:SET, ["hello", ['w', 'o', "rl", 'd']], {:ok}}, + {:GET, ["hello"], {:ok, "world", []}}, + {:SET, ["hello", "move on"], {:ok}}, + {:SET, [<<0x56::size(32)>>, <<0x56::size(32)>>], {:ok}}, + {:GET, [<<0x56::size(32)>>], {:ok, <<0x56::size(32)>>, []}}, + {:GET, ["hello"], {:ok, "move on", []}}, + {:GETK, ["hello"], {:ok, "hello", "move on", []}}, + {:GETK, ["unknown"], {:error, "Key not found"}}, + {:ADD, ["hello", "world"], {:error, "Key exists"}}, + {:ADD, ["add", "world"], {:ok}}, + {:DELETE, ["add"], {:ok}}, + {:REPLACE, ["add", "world"], {:error, "Key not found"}}, + {:ADD, ["add", "world"], {:ok}}, + {:REPLACE, ["add", "world"], {:ok}}, + {:DELETE, ["add"], {:ok}}, + {:DELETE, ["hello"], {:ok}}, + {:DELETE, ["unkown"], {:error, "Key not found"}}, + {:INCREMENT, ["count", 1, 5], {:ok, 5}}, + {:INCREMENT, ["count", 1, 5], {:ok, 6}}, + {:INCREMENT, ["count", 5, 1], {:ok, 11}}, + {:DELETE, ["count"], {:ok}}, + {:SET, ["hello", "world"], {:ok}}, + {:INCREMENT, ["hello"], {:error, "Incr/Decr on non-numeric value"}}, + {:DELETE, ["hello"], {:ok}}, + {:DECREMENT, ["count", 1, 5], {:ok, 5}}, + {:DECREMENT, ["count", 1, 5], {:ok, 4}}, + {:DECREMENT, ["count", 6, 5], {:ok, 0}}, + {:DELETE, ["count"], {:ok}}, + {:SET, ["hello", "world"], {:ok}}, + {:DECREMENT, ["hello"], {:error, "Incr/Decr on non-numeric value"}}, + {:DELETE, ["hello"], {:ok}}, + {:INCREMENT, ["count", 6, 5, 0, 0xFFFFFFFF], {:error, "Key not found"}}, + {:INCREMENT, ["count", 6, 5, 0, 0x05], {:ok, 5}}, + {:DELETE, ["count"], {:ok}}, + {:NOOP, [], {:ok}}, + {:APPEND, ["new", "hope"], {:error, "Item not stored"}}, + {:SET, ["new", "new "], {:ok}}, + {:APPEND, ["new", "hope"], {:ok}}, + {:GET, ["new"], {:ok, "new hope", []}}, + {:DELETE, ["new"], {:ok}}, + {:PREPEND, ["new", "hope"], {:error, "Item not stored"}}, + {:SET, ["new", "hope"], {:ok}}, + {:PREPEND, ["new", "new "], {:ok}}, + {:GET, ["new"], {:ok, "new hope", []}}, + {:DELETE, ["new"], {:ok}}, + {:SET, ["name", "ananth"], {:ok}}, + {:FLUSH, [0xFFFF], {:ok}}, + {:GET, ["name"], {:ok, "ananth", []}}, + {:FLUSH, [], {:ok}}, + {:GET, ["name"], {:error, "Key not found"}}, + {:SET, ["hello", "world1", 0, 0], {:ok}}, + {:GET, ["hello"], {:ok, "world1", []}}, + {:SET, ["hello", "world2", 0, 0], [flags: [:serialised]], {:ok}}, + {:GET, ["hello"], {:ok, "world2", [:serialised]}}, + {:SET, ["hello", "world3", 0, 0], [flags: [:compressed, :serialised]], {:ok}}, + {:GET, ["hello"], {:ok, "world3", [:compressed, :serialised]}} ] Enum.each(cases, fn @@ -103,7 +103,7 @@ defmodule Memcache.ConnectionTest do {:GET, ["hello"], [cas: true], {:ok, "another", :cas}}, {:SET, ["hello", "world", :cas], [cas: true], {:ok, :cas}}, {:SET, ["hello", "move on", :cas], [], {:ok}}, - {:GET, ["hello"], [], {:ok, "move on"}}, + {:GET, ["hello"], [], {:ok, "move on", []}}, {:ADD, ["add", "world"], [cas: true], {:ok, :cas}}, {:DELETE, ["add", :cas], [], {:ok}}, {:ADD, ["add", "world"], [], {:ok}}, @@ -112,7 +112,7 @@ defmodule Memcache.ConnectionTest do {:REPLACE, ["add", "world", :cas], [], {:ok}}, {:REPLACE, ["add", "world", :cas], [], @cas_error}, {:DELETE, ["add", :cas], [], @cas_error}, - {:GET, ["add"], [cas: true], {:ok, "world", :cas}}, + {:GET, ["add"], [cas: true], {:ok, "world", :cas, []}}, {:DELETE, ["add", :cas], [], {:ok}}, {:INCREMENT, ["count", 1, 5], [cas: true], {:ok, 5, :cas}}, {:INCREMENT, ["count", 1, 5], [], {:ok, 6}}, @@ -126,7 +126,7 @@ defmodule Memcache.ConnectionTest do {:APPEND, ["new", "hope", :cas], [], {:ok}}, {:APPEND, ["new", "hope", :cas], [], @cas_error}, {:APPEND, ["new", "hope"], [cas: true], {:ok, :cas}}, - {:GET, ["new"], [], {:ok, "new hopehope"}}, + {:GET, ["new"], [], {:ok, "new hopehope", []}}, {:SET, ["new", "hope"], [cas: true], {:ok, :cas}}, {:PREPEND, ["new", "new ", :cas], [], {:ok}}, {:PREPEND, ["new", "new ", :cas], [], @cas_error}, @@ -148,7 +148,18 @@ defmodule Memcache.ConnectionTest do cas {:ok, value, :cas} -> - assert {:ok, ^value, cas} = execute(pid, command, args, opts) + result = execute(pid, command, args, opts) + + if tuple_size(result) == 3 do + assert {:ok, ^value, cas} = result + cas + else + assert {:ok, ^value, cas, []} = result + cas + end + + {:ok, value, :cas, flags} -> + assert {:ok, ^value, cas, ^flags} = execute(pid, command, args, opts) cas rest -> @@ -168,9 +179,9 @@ defmodule Memcache.ConnectionTest do cases = [ {[{:GETQ, ["hello"]}, {:GETQ, ["hello"]}], {:ok, [{:error, "Key not found"}, {:error, "Key not found"}]}}, - {[{:GETQ, ["new"]}, {:GETQ, ["new"]}], {:ok, [{:ok, "hope"}, {:ok, "hope"}]}}, + {[{:GETQ, ["new"]}, {:GETQ, ["new"]}], {:ok, [{:ok, "hope", []}, {:ok, "hope", []}]}}, {[{:GETKQ, ["new"]}, {:GETKQ, ["unknown"]}], - {:ok, [{:ok, "new", "hope"}, {:error, "Key not found"}]}}, + {:ok, [{:ok, "new", "hope", []}, {:error, "Key not found"}]}}, {[ {:SETQ, ["hello", "WORLD"]}, {:GETQ, ["hello"]}, @@ -179,7 +190,7 @@ defmodule Memcache.ConnectionTest do {:DELETEQ, ["hello"]}, {:GETQ, ["hello"]} ], - {:ok, [{:ok}, {:ok, "WORLD"}, {:ok}, {:ok, "world"}, {:ok}, {:error, "Key not found"}]}}, + {:ok, [{:ok}, {:ok, "WORLD", []}, {:ok}, {:ok, "world", []}, {:ok}, {:error, "Key not found"}]}}, {[ {:SETQ, ["hello", "WORLD"]}, {:FLUSHQ, []}, @@ -197,7 +208,7 @@ defmodule Memcache.ConnectionTest do {:error, "Key not found"}, {:ok}, {:ok}, - {:ok, "world"}, + {:ok, "world", []}, {:ok}, {:error, "Key not found"} ]}}, @@ -210,7 +221,7 @@ defmodule Memcache.ConnectionTest do {:DELETEQ, ["unknown"]} ], {:ok, - [{:ok}, {:error, "Key exists"}, {:ok}, {:ok, "world"}, {:ok}, {:error, "Key not found"}]}}, + [{:ok}, {:error, "Key exists"}, {:ok}, {:ok, "world", []}, {:ok}, {:error, "Key not found"}]}}, {[ {:INCREMENTQ, ["count", 1, 5]}, {:INCREMENTQ, ["count", 1, 5]}, @@ -226,9 +237,9 @@ defmodule Memcache.ConnectionTest do [ {:ok}, {:ok}, - {:ok, "6"}, + {:ok, "6", []}, {:ok}, - {:ok, "11"}, + {:ok, "11", []}, {:ok}, {:ok}, {:error, "Incr/Decr on non-numeric value"}, @@ -249,9 +260,9 @@ defmodule Memcache.ConnectionTest do [ {:ok}, {:ok}, - {:ok, "4"}, + {:ok, "4", []}, {:ok}, - {:ok, "0"}, + {:ok, "0", []}, {:ok}, {:ok}, {:error, "Incr/Decr on non-numeric value"}, @@ -263,7 +274,7 @@ defmodule Memcache.ConnectionTest do {:REPLACEQ, ["add", "new"]}, {:GETQ, ["add"]}, {:DELETEQ, ["add"]} - ], {:ok, [{:error, "Key not found"}, {:ok}, {:ok}, {:ok, "new"}, {:ok}]}}, + ], {:ok, [{:error, "Key not found"}, {:ok}, {:ok}, {:ok, "new", []}, {:ok}]}}, {[ {:SETQ, ["new", "new "]}, {:DELETEQ, ["new"]}, @@ -285,12 +296,12 @@ defmodule Memcache.ConnectionTest do {:error, "Item not stored"}, {:ok}, {:ok}, - {:ok, "new hope"}, + {:ok, "new hope", []}, {:ok}, {:error, "Item not stored"}, {:ok}, {:ok}, - {:ok, "new hope"}, + {:ok, "new hope", []}, {:ok} ]}} ] @@ -307,7 +318,7 @@ defmodule Memcache.ConnectionTest do {:ok} = execute(pid, :FLUSH, []) {:ok} = execute(pid, :SET, ["new", "hope"]) - assert {:ok, [{:ok, "hope", cas}, {:ok, "hope", cas}]} = + assert {:ok, [{:ok, "hope", cas, []}, {:ok, "hope", cas, []}]} = execute_quiet(pid, [{:GETQ, ["new"], [cas: true]}, {:GETQ, ["new"], [cas: true]}]) assert {:ok, [{:ok, cas}, @cas_error]} = @@ -316,7 +327,7 @@ defmodule Memcache.ConnectionTest do {:SETQ, ["new", "hope", cas]} ]) - assert {:ok, [@cas_error, {:ok}, {:error, "Key not found"}, {:ok}, {:ok, "hope", cas}]} = + assert {:ok, [@cas_error, {:ok}, {:error, "Key not found"}, {:ok}, {:ok, "hope", cas, []}]} = execute_quiet(pid, [ {:DELETEQ, ["new", 1492]}, {:DELETEQ, ["new", cas]}, @@ -339,7 +350,7 @@ defmodule Memcache.ConnectionTest do {:INCREMENT, ["count", 1, 5], [cas: true]} ]) - assert {:ok, [{:ok}, {:ok, "7"}, @cas_error, {:ok, "7"}, {:ok}, {:ok, "12"}, {:ok}]} = + assert {:ok, [{:ok}, {:ok, "7", []}, @cas_error, {:ok, "7", []}, {:ok}, {:ok, "12", []}, {:ok}]} = execute_quiet(pid, [ {:INCREMENTQ, ["count", 1, 5, cas]}, {:GETQ, ["count"]}, @@ -356,7 +367,7 @@ defmodule Memcache.ConnectionTest do {:DECREMENT, ["count", 1, 5], [cas: true]} ]) - assert {:ok, [{:ok}, {:ok, "3"}, @cas_error, {:ok, "3"}, {:ok}, {:ok, "0"}, {:ok}]} == + assert {:ok, [{:ok}, {:ok, "3", []}, @cas_error, {:ok, "3", []}, {:ok}, {:ok, "0", []}, {:ok}]} == execute_quiet(pid, [ {:DECREMENTQ, ["count", 1, 5, cas]}, {:GETQ, ["count"]}, @@ -374,7 +385,7 @@ defmodule Memcache.ConnectionTest do {:REPLACE, ["add", "world"], [cas: true]} ]) - assert {:ok, [{:ok}, {:ok, "world"}, @cas_error, {:ok, "world"}, {:ok}]} == + assert {:ok, [{:ok}, {:ok, "world", []}, @cas_error, {:ok, "world", []}, {:ok}]} == execute_quiet(pid, [ {:REPLACEQ, ["add", "world", cas]}, {:GETQ, ["add"]}, @@ -394,13 +405,13 @@ defmodule Memcache.ConnectionTest do assert {:ok, [ {:ok}, - {:ok, "new hope"}, + {:ok, "new hope", []}, @cas_error, - {:ok, "new hope"}, + {:ok, "new hope", []}, {:ok}, - {:ok, "new hope"}, + {:ok, "new hope", []}, @cas_error, - {:ok, "new hope"}, + {:ok, "new hope", []}, {:ok}, {:ok} ]} == @@ -434,7 +445,7 @@ defmodule Memcache.ConnectionTest do test "named process" do {:ok, pid} = start_link([port: 21_211, hostname: "localhost"], name: :memcachex) {:ok} = execute(:memcachex, :SET, ["hello", "world"]) - {:ok, "world"} = execute(:memcachex, :GET, ["hello"]) + {:ok, "world", []} = execute(:memcachex, :GET, ["hello"]) {:ok} = close(pid) end @@ -453,7 +464,7 @@ defmodule Memcache.ConnectionTest do up("memcache") :timer.sleep(1000) {:ok} = execute(pid, :SET, ["hello", "world"]) - {:ok, "world"} = execute(pid, :GET, ["hello"]) + {:ok, "world", []} = execute(pid, :GET, ["hello"]) {:ok} = close(pid) end @@ -465,7 +476,7 @@ defmodule Memcache.ConnectionTest do start_hammering( fn -> assert_value_or_error({:ok}, execute(pid, :SET, ["hello", "world"])) - assert_value_or_error({:ok, "world"}, execute(pid, :GET, ["hello"])) + assert_value_or_error({:ok, "world", []}, execute(pid, :GET, ["hello"])) end, 8 ) @@ -512,7 +523,7 @@ defmodule Memcache.ConnectionTest do up("memcache_sasl") :timer.sleep(1000) {:ok} = execute(pid, :SET, ["hello", "world"]) - {:ok, "world"} = execute(pid, :GET, ["hello"]) + {:ok, "world", []} = execute(pid, :GET, ["hello"]) {:ok} = close(pid) end diff --git a/test/memcache_test.exs b/test/memcache_test.exs index dcf7b65..63c6dd5 100644 --- a/test/memcache_test.exs +++ b/test/memcache_test.exs @@ -35,6 +35,8 @@ defmodule MemcacheTest do assert {:ok} = Memcache.flush(pid) assert {:ok} == Memcache.set(pid, "hello", "world") assert {:ok, "world"} == Memcache.get(pid, "hello") + assert {:ok} == Memcache.set(pid, "yellow", "world", flags: [:serialised]) + assert {:ok, "world"} == Memcache.get(pid, "yellow") assert {:error, "Key exists"} == Memcache.add(pid, "hello", "world") assert {:ok} == Memcache.replace(pid, "hello", "again") assert {:ok} == Memcache.delete(pid, "hello") @@ -286,6 +288,8 @@ defmodule MemcacheTest do assert {:ok} == Memcache.set(pid, "hello", ["list", 1]) assert {:ok, ["list", 1]} == Memcache.get(pid, "hello") + assert {:ok} == Memcache.set(pid, "yellow", %{test: "test"}) + assert {:ok, %{"test" => "test"}} == Memcache.get(pid, "yellow") assert {:ok, [{:ok}]} == Memcache.multi_set(pid, [{"hello", %{"a" => 1}}]) assert {:ok, %{"a" => 1}} == Memcache.get(pid, "hello") assert {:ok, %{"hello" => %{"a" => 1}}} == Memcache.multi_get(pid, ["hello"]) From dd9eb9142f72194351e681501a45193c9d1aa125 Mon Sep 17 00:00:00 2001 From: Andrew Pett Date: Wed, 6 Mar 2019 14:07:53 +1300 Subject: [PATCH 3/5] Update result typespecs --- lib/memcache.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/memcache.ex b/lib/memcache.ex index bd898df..3bbd0c7 100644 --- a/lib/memcache.ex +++ b/lib/memcache.ex @@ -53,9 +53,9 @@ defmodule Memcache do """ @type error :: {:error, binary | atom} - @type result :: {:ok} | {:ok, integer} | {:ok, any} | {:ok, any, integer} | error + @type result :: {:ok} | {:ok, integer} | {:ok, any} | {:ok, any, keyword} | {:ok, any, integer} | {:ok, any, integer, keyword} | error - @type fetch_result :: {:ok, any} | {:ok, any, integer} | error + @type fetch_result :: {:ok, any} | {:ok, any, keyword} | {:ok, any, integer} | {:ok, any, integer, keyword} | error @type fetch_integer_result :: {:ok, integer} | {:ok, integer, integer} | error From d179f43727a8b84aedbe7ae0b603cecb1f59ab1b Mon Sep 17 00:00:00 2001 From: Andrew Pett Date: Wed, 13 Mar 2019 09:27:00 +1300 Subject: [PATCH 4/5] Run mix format --- lib/memcache.ex | 27 ++++++++++++++++++++------- lib/memcache/connection.ex | 16 +++++++++------- lib/memcache/receiver.ex | 5 ++++- test/memcache/connection_test.exs | 19 +++++++++++++++---- 4 files changed, 48 insertions(+), 19 deletions(-) diff --git a/lib/memcache.ex b/lib/memcache.ex index 3bbd0c7..fe9478e 100644 --- a/lib/memcache.ex +++ b/lib/memcache.ex @@ -53,9 +53,21 @@ defmodule Memcache do """ @type error :: {:error, binary | atom} - @type result :: {:ok} | {:ok, integer} | {:ok, any} | {:ok, any, keyword} | {:ok, any, integer} | {:ok, any, integer, keyword} | error - - @type fetch_result :: {:ok, any} | {:ok, any, keyword} | {:ok, any, integer} | {:ok, any, integer, keyword} | error + @type result :: + {:ok} + | {:ok, integer} + | {:ok, any} + | {:ok, any, keyword} + | {:ok, any, integer} + | {:ok, any, integer, keyword} + | error + + @type fetch_result :: + {:ok, any} + | {:ok, any, keyword} + | {:ok, any, integer} + | {:ok, any, integer, keyword} + | error @type fetch_integer_result :: {:ok, integer} | {:ok, integer, integer} | error @@ -564,11 +576,13 @@ defmodule Memcache do apply(module, :decode, [value, coder_options]) end - defp decode_response({:ok, value, flags}, server_options) when is_binary(value) and is_list(flags) do + defp decode_response({:ok, value, flags}, server_options) + when is_binary(value) and is_list(flags) do {:ok, decode(server_options, {value, flags})} end - defp decode_response({:ok, value, cas, flags}, server_options) when is_binary(value) and is_list(flags) do + defp decode_response({:ok, value, cas, flags}, server_options) + when is_binary(value) and is_list(flags) do {:ok, decode(server_options, {value, flags}), cas} end @@ -651,8 +665,7 @@ defmodule Memcache do Enum.map(commands, fn {command, [key | [value | rest]], opts} -> {command, [key_with_namespace(server_options, key) | [encode(server_options, value) | rest]], - opts_with_flags(opts, encoder_flags(server_options, value)) - } + opts_with_flags(opts, encoder_flags(server_options, value))} end) server diff --git a/lib/memcache/connection.ex b/lib/memcache/connection.ex index 4c829a7..4c1af0e 100644 --- a/lib/memcache/connection.ex +++ b/lib/memcache/connection.ex @@ -274,7 +274,7 @@ defmodule Memcache.Connection do end @spec normalise_flags([{atom, [binary]} | {atom, [binary], Keyword.t()}]) :: - [{atom, [binary]} | {atom, [binary], Keyword.t()}] + [{atom, [binary]} | {atom, [binary], Keyword.t()}] defp normalise_flags(commands) do Enum.map(commands, fn {_command, _args} = command -> @@ -282,7 +282,7 @@ defmodule Memcache.Connection do {command, args, opts} -> opts = Keyword.update(opts, :flags, 0, &translate_flags/1) - {command, args, opts} + {command, args, opts} end) end @@ -450,15 +450,17 @@ defmodule Memcache.Connection do @default_cas 0 @flag_commands [:SET, :SETQ, :ADD, :ADDQ, :REPLACE, :REPLACEQ] defp serialize(command, args, opts, opaque \\ 0) + defp serialize(command, args, opts, opaque) when command in @flag_commands do opts = Map.new(opts) flags = Map.get(opts, :flags, 0) - args = case length(args) do - 2 -> args ++ [@default_expiry, @default_cas, flags] - 3 -> args ++ [@default_cas, flags] - 4 -> args ++ [flags] - end + args = + case length(args) do + 2 -> args ++ [@default_expiry, @default_cas, flags] + 3 -> args ++ [@default_cas, flags] + 4 -> args ++ [flags] + end do_serialize(command, args, opaque) end diff --git a/lib/memcache/receiver.ex b/lib/memcache/receiver.ex index 99ed444..c4a8741 100644 --- a/lib/memcache/receiver.ex +++ b/lib/memcache/receiver.ex @@ -107,7 +107,10 @@ defmodule Memcache.Receiver do defp append_cas_version({:ok}, %{cas: cas_version}), do: {:ok, cas_version} defp append_cas_version({:ok, value}, %{cas: cas_version}), do: {:ok, value, cas_version} - defp append_cas_version({:ok, value, flags}, %{cas: cas_version}), do: {:ok, value, cas_version, flags} + + defp append_cas_version({:ok, value, flags}, %{cas: cas_version}), + do: {:ok, value, cas_version, flags} + defp append_cas_version(error, %{cas: _cas_version}), do: error defp recv_response_quiet([], _sock, results, buffer) do diff --git a/test/memcache/connection_test.exs b/test/memcache/connection_test.exs index b37a070..b2fc068 100644 --- a/test/memcache/connection_test.exs +++ b/test/memcache/connection_test.exs @@ -83,6 +83,7 @@ defmodule Memcache.ConnectionTest do Enum.each(cases, fn {command, args, response} -> assert(execute(pid, command, args) == response) + {command, args, opts, response} -> assert(execute(pid, command, args, opts) == response) end) @@ -190,7 +191,8 @@ defmodule Memcache.ConnectionTest do {:DELETEQ, ["hello"]}, {:GETQ, ["hello"]} ], - {:ok, [{:ok}, {:ok, "WORLD", []}, {:ok}, {:ok, "world", []}, {:ok}, {:error, "Key not found"}]}}, + {:ok, + [{:ok}, {:ok, "WORLD", []}, {:ok}, {:ok, "world", []}, {:ok}, {:error, "Key not found"}]}}, {[ {:SETQ, ["hello", "WORLD"]}, {:FLUSHQ, []}, @@ -221,7 +223,14 @@ defmodule Memcache.ConnectionTest do {:DELETEQ, ["unknown"]} ], {:ok, - [{:ok}, {:error, "Key exists"}, {:ok}, {:ok, "world", []}, {:ok}, {:error, "Key not found"}]}}, + [ + {:ok}, + {:error, "Key exists"}, + {:ok}, + {:ok, "world", []}, + {:ok}, + {:error, "Key not found"} + ]}}, {[ {:INCREMENTQ, ["count", 1, 5]}, {:INCREMENTQ, ["count", 1, 5]}, @@ -350,7 +359,8 @@ defmodule Memcache.ConnectionTest do {:INCREMENT, ["count", 1, 5], [cas: true]} ]) - assert {:ok, [{:ok}, {:ok, "7", []}, @cas_error, {:ok, "7", []}, {:ok}, {:ok, "12", []}, {:ok}]} = + assert {:ok, + [{:ok}, {:ok, "7", []}, @cas_error, {:ok, "7", []}, {:ok}, {:ok, "12", []}, {:ok}]} = execute_quiet(pid, [ {:INCREMENTQ, ["count", 1, 5, cas]}, {:GETQ, ["count"]}, @@ -367,7 +377,8 @@ defmodule Memcache.ConnectionTest do {:DECREMENT, ["count", 1, 5], [cas: true]} ]) - assert {:ok, [{:ok}, {:ok, "3", []}, @cas_error, {:ok, "3", []}, {:ok}, {:ok, "0", []}, {:ok}]} == + assert {:ok, + [{:ok}, {:ok, "3", []}, @cas_error, {:ok, "3", []}, {:ok}, {:ok, "0", []}, {:ok}]} == execute_quiet(pid, [ {:DECREMENTQ, ["count", 1, 5, cas]}, {:GETQ, ["count"]}, From 92eaafedabd34c30f4daabc4b1ca6851cea3d2b7 Mon Sep 17 00:00:00 2001 From: Andrew Pett Date: Wed, 13 Mar 2019 09:39:09 +1300 Subject: [PATCH 5/5] Mix format against elixir 1.7 --- lib/memcache/connection.ex | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/memcache/connection.ex b/lib/memcache/connection.ex index 4c1af0e..c04ba7b 100644 --- a/lib/memcache/connection.ex +++ b/lib/memcache/connection.ex @@ -273,8 +273,9 @@ defmodule Memcache.Connection do :ok end - @spec normalise_flags([{atom, [binary]} | {atom, [binary], Keyword.t()}]) :: - [{atom, [binary]} | {atom, [binary], Keyword.t()}] + @spec normalise_flags([{atom, [binary]} | {atom, [binary], Keyword.t()}]) :: [ + {atom, [binary]} | {atom, [binary], Keyword.t()} + ] defp normalise_flags(commands) do Enum.map(commands, fn {_command, _args} = command ->