Current section
Files
Jump to
Current section
Files
lib/aerospike/protocol/exp.ex
defmodule Aerospike.Protocol.Exp do
@moduledoc false
# Encodes Aerospike expression nodes to MessagePack wire bytes.
#
# Nodes are plain maps with fields:
# bytes: binary — pre-encoded; written directly (used for sub-expressions)
# val: term — literal value (integer, float, bool, nil, {:string, s}, {:blob, b})
# cmd: atom — operation code (see @op_* constants)
# val: term — scalar argument for :bin and :digest_modulo commands
# type: atom — exp type for :bin reads (:int, :string, :float, etc.)
# exps: [node] — sub-expression nodes for compound operations
#
# String vs blob literals: both are Elixir binaries, so they are tagged:
# {:string, s} -> msgpack str (raw UTF-8)
# {:blob, b} -> msgpack bin (0xC4/C5/C6 format)
#
# All string values in expression context (bin names, string literals) use plain
# msgpack str — no particle-type prefix. This differs from CDT payloads.
alias Aerospike.Protocol.MessagePack
# Operation codes (from expression.go)
@op_eq 1
@op_ne 2
@op_gt 3
@op_gte 4
@op_lt 5
@op_lte 6
@op_and 16
@op_or 17
@op_not 18
@op_digest_modulo 64
@op_device_size 65
@op_last_update 66
@op_void_time 68
@op_ttl 69
@op_set_name 70
@op_key_exists 71
@op_is_tombstone 72
@op_record_size 74
@op_bin 81
# Expression type codes for bin reads (from expression.go)
@exp_type_nil 0
@exp_type_bool 1
@exp_type_int 2
@exp_type_string 3
@exp_type_list 4
@exp_type_map 5
@exp_type_blob 6
@exp_type_float 7
@exp_type_geo 8
@exp_type_hll 9
@spec encode(map()) :: binary()
# Pre-encoded sub-expression — write bytes directly.
def encode(%{bytes: bytes}) when is_binary(bytes), do: bytes
# Bin read: [81, exp_type_int, raw_str_name]
# Bin name is packed as plain msgpack str (packRawString in Go).
def encode(%{cmd: :bin, val: name, type: type}) when is_binary(name) do
array_header(3) <>
MessagePack.pack!(@op_bin) <>
MessagePack.pack!(exp_type_int(type)) <>
MessagePack.pack!(name)
end
# Digest modulo: [64, value]
def encode(%{cmd: :digest_modulo, val: n}) when is_integer(n) do
array_header(2) <> MessagePack.pack!(@op_digest_modulo) <> MessagePack.pack!(n)
end
# Compound expression: [op_code, sub_expr_1, ..., sub_expr_N]
# Covers comparisons (:eq, :ne, :gt, :gte, :lt, :lte), boolean (:and_, :or_, :not_).
def encode(%{cmd: cmd, exps: exps}) when is_list(exps) do
encoded_exps = IO.iodata_to_binary(Enum.map(exps, &encode/1))
array_header(1 + length(exps)) <> MessagePack.pack!(op_code(cmd)) <> encoded_exps
end
# Metadata (no value): [op_code]
# Must come before the generic val-based literal clauses to avoid pattern overlap.
def encode(%{cmd: cmd})
when cmd in [
:ttl,
:void_time,
:last_update,
:key_exists,
:set_name,
:is_tombstone,
:device_size,
:record_size
] do
array_header(1) <> MessagePack.pack!(op_code(cmd))
end
# Literal nil
def encode(%{val: nil}), do: MessagePack.pack!(nil)
# Literal bool
def encode(%{val: false}), do: MessagePack.pack!(false)
def encode(%{val: true}), do: MessagePack.pack!(true)
# Literal integer
def encode(%{val: n}) when is_integer(n), do: MessagePack.pack!(n)
# Literal float
def encode(%{val: f}) when is_float(f), do: MessagePack.pack!(f)
# Literal string — plain msgpack str (raw UTF-8, no particle prefix)
def encode(%{val: {:string, s}}) when is_binary(s), do: MessagePack.pack!(s)
# Literal blob — msgpack bin format (0xC4/C5/C6)
def encode(%{val: {:blob, b}}) when is_binary(b), do: msgpack_bin(b)
# Op code lookup
defp op_code(:eq), do: @op_eq
defp op_code(:ne), do: @op_ne
defp op_code(:gt), do: @op_gt
defp op_code(:gte), do: @op_gte
defp op_code(:lt), do: @op_lt
defp op_code(:lte), do: @op_lte
defp op_code(:and_), do: @op_and
defp op_code(:or_), do: @op_or
defp op_code(:not_), do: @op_not
defp op_code(:ttl), do: @op_ttl
defp op_code(:void_time), do: @op_void_time
defp op_code(:last_update), do: @op_last_update
defp op_code(:key_exists), do: @op_key_exists
defp op_code(:set_name), do: @op_set_name
defp op_code(:is_tombstone), do: @op_is_tombstone
defp op_code(:device_size), do: @op_device_size
defp op_code(:record_size), do: @op_record_size
# Exp type integer lookup
defp exp_type_int(nil), do: @exp_type_nil
defp exp_type_int(:bool), do: @exp_type_bool
defp exp_type_int(:int), do: @exp_type_int
defp exp_type_int(:string), do: @exp_type_string
defp exp_type_int(:list), do: @exp_type_list
defp exp_type_int(:map), do: @exp_type_map
defp exp_type_int(:blob), do: @exp_type_blob
defp exp_type_int(:float), do: @exp_type_float
defp exp_type_int(:geo), do: @exp_type_geo
defp exp_type_int(:hll), do: @exp_type_hll
# Msgpack array header
defp array_header(len) when len <= 15, do: <<0x90 + len>>
defp array_header(len) when len <= 0xFFFF, do: <<0xDC, len::16-big>>
defp array_header(len), do: <<0xDD, len::32-big>>
# Msgpack bin format for blob literals (0xC4/C5/C6, not msgpack str)
defp msgpack_bin(b) when byte_size(b) <= 0xFF, do: <<0xC4, byte_size(b)::8, b::binary>>
defp msgpack_bin(b) when byte_size(b) <= 0xFFFF, do: <<0xC5, byte_size(b)::16-big, b::binary>>
defp msgpack_bin(b), do: <<0xC6, byte_size(b)::32-big, b::binary>>
end