Packages

Aerospike driver for Elixir with an OTP-native cluster runtime

Current section

Files

Jump to
aerospike_driver lib aerospike command operate.ex
Raw

lib/aerospike/command/operate.ex

defmodule Aerospike.Command.Operate do
@moduledoc false
alias Aerospike.Command.UnaryCommand
alias Aerospike.Command.UnarySupport
alias Aerospike.Error
alias Aerospike.Key
alias Aerospike.Policy
alias Aerospike.Protocol.AsmMsg
alias Aerospike.Protocol.AsmMsg.Operation
alias Aerospike.Protocol.Message
alias Aerospike.Protocol.OperateFlags
alias Aerospike.Protocol.Response
alias Aerospike.Runtime.TxnSupport
@type simple_operation ::
{:read, String.t() | atom()}
| {:write, String.t() | atom(), term()}
| {:add, String.t() | atom(), integer() | float()}
| {:append, String.t() | atom(), String.t()}
| {:prepend, String.t() | atom(), String.t()}
| :touch
| :delete
@type operation_input :: simple_operation() | Operation.t()
@type option ::
{:timeout, non_neg_integer()}
| {:socket_timeout, non_neg_integer()}
| {:max_retries, non_neg_integer()}
| {:sleep_between_retries_ms, non_neg_integer()}
| {:ttl, non_neg_integer() | :default | :never_expire | :dont_update}
| {:generation, non_neg_integer()}
| {:generation_policy, :none | :expect_equal | :expect_gt}
| {:filter, Aerospike.Exp.t() | nil}
| {:read_mode_ap, :one | :all}
| {:read_mode_sc, :session | :linearize | :allow_replica | :allow_unavailable}
| {:read_touch_ttl_percent, -1 | 0..100}
| {:commit_level, :all | :master}
| {:durable_delete, boolean()}
| {:respond_per_op, boolean()}
| {:send_key, boolean()}
| {:use_compression, boolean()}
| {:exists, :update | :update_only | :create_or_replace | :replace_only | :create_only}
@type result ::
{:ok, Aerospike.Record.t()}
| {:error, Error.t()}
| {:error, :cluster_not_ready | :no_master | :unknown_node}
@spec execute(GenServer.server(), Key.t(), [operation_input()], [option()]) :: result()
def execute(tender, %Key{} = key, operations, opts \\ []) when is_list(opts) do
with {:ok, txn} <- TxnSupport.txn_from_opts(opts),
{:ok, built_operations} <- build_operations(operations),
{:ok, policy} <- UnarySupport.operate_policy(tender, opts) do
flags = OperateFlags.scan_ops(built_operations)
input = command_input(key, tender, txn, opts, built_operations, flags, policy)
with :ok <- TxnSupport.prepare_txn_for_operate(tender, txn, key, flags.has_write?, opts) do
tender
|> UnarySupport.run_command(key, policy, command(flags), input)
|> maybe_track_txn_in_doubt(flags, tender, txn, key)
end
end
end
defp command_input(
key,
tender,
txn,
opts,
built_operations,
flags,
%Policy.UnaryWrite{} = policy
) do
%{
key: key,
conn: tender,
txn: txn,
opts: opts,
kind: if(flags.has_write?, do: :write, else: :read),
operations: built_operations,
flags: flags,
policy: policy,
filter: policy.filter
}
end
defp maybe_track_txn_in_doubt(
{:error, %Error{} = err} = result,
%{has_write?: true},
tender,
txn,
key
) do
TxnSupport.track_txn_in_doubt(tender, txn, key, err)
result
end
defp maybe_track_txn_in_doubt(result, _flags, _tender, _txn, _key), do: result
defp command(%{has_write?: has_write?}) do
UnaryCommand.new!(
name: __MODULE__,
dispatch: if(has_write?, do: :write, else: :read),
retry_transport: false,
build_request: &encode_operate/1,
parse_response: &parse_record_response/2
)
end
defp build_operations(operations) when is_list(operations) and operations != [] do
operations
|> Enum.reduce_while({:ok, []}, fn operation, {:ok, acc} ->
case build_operation(operation) do
{:ok, built_operation} -> {:cont, {:ok, [built_operation | acc]}}
{:error, %Error{}} = err -> {:halt, err}
end
end)
|> case do
{:ok, built_operations} -> {:ok, Enum.reverse(built_operations)}
{:error, %Error{}} = err -> err
end
end
defp build_operations([]) do
{:error,
Error.from_result_code(:invalid_argument,
message: "operate requires at least one operation"
)}
end
defp build_operations(other) do
{:error,
Error.from_result_code(:invalid_argument,
message: "operate operations must be a list, got: #{inspect(other)}"
)}
end
defp build_operation(%Operation{} = operation), do: {:ok, operation}
defp build_operation({:read, bin_name}) do
case Operation.from_simple({:read, normalize_bin_name(bin_name)}) do
{:ok, operation} -> {:ok, operation}
{:error, %Error{}} = err -> err
end
end
defp build_operation({:write, bin_name, value}) do
case Operation.from_simple({:write, normalize_bin_name(bin_name), value}) do
{:ok, operation} -> {:ok, operation}
{:error, %Error{}} = err -> err
end
end
defp build_operation({:add, bin_name, value}) do
case Operation.from_simple({:add, normalize_bin_name(bin_name), value}) do
{:ok, operation} -> {:ok, operation}
{:error, %Error{}} = err -> err
end
end
defp build_operation({:append, bin_name, value}) do
case Operation.from_simple({:append, normalize_bin_name(bin_name), value}) do
{:ok, operation} -> {:ok, operation}
{:error, %Error{}} = err -> err
end
end
defp build_operation({:prepend, bin_name, value}) do
case Operation.from_simple({:prepend, normalize_bin_name(bin_name), value}) do
{:ok, operation} -> {:ok, operation}
{:error, %Error{}} = err -> err
end
end
defp build_operation(:touch) do
case Operation.from_simple(:touch) do
{:ok, operation} -> {:ok, operation}
{:error, %Error{}} = err -> err
end
end
defp build_operation(:delete) do
case Operation.from_simple(:delete) do
{:ok, operation} -> {:ok, operation}
{:error, %Error{}} = err -> err
end
end
defp build_operation(other) do
Operation.from_simple(other)
end
defp normalize_bin_name(bin_name) when is_atom(bin_name), do: Atom.to_string(bin_name)
defp normalize_bin_name(bin_name), do: bin_name
defp encode_operate(%{
key: %Key{} = key,
conn: conn,
opts: opts,
operations: operations,
flags: %{
has_write?: has_write?,
read_bin?: read_bin?,
read_header?: read_header?,
respond_all?: respond_all?
},
policy: policy,
use_compression: use_compression,
filter: filter
}) do
key
|> AsmMsg.key_command(
operations,
[
read: read_bin? or read_header?,
read_header: read_header?,
write: has_write?
] ++ UnarySupport.operate_header_opts(policy, has_write?, respond_all?, use_compression)
)
|> AsmMsg.maybe_add_filter_exp(filter)
|> TxnSupport.maybe_add_mrt_fields(conn, key, opts, has_write?)
|> AsmMsg.encode()
|> Message.encode_as_msg_iodata()
end
defp parse_record_response(body, %{key: %Key{} = key, conn: conn, txn: txn, kind: kind}) do
UnarySupport.parse_as_msg(body, fn msg ->
case Response.parse_record_response(msg, key) do
{:ok, _} = ok ->
TxnSupport.track_txn_response(conn, txn, key, kind, msg, ok)
ok
{:error, _} = err ->
err
end
end)
end
end