Operate, CDT, And Geo

Copy Markdown View Source

Aerospike.operate/4 executes a list of server-side operations against one record. Use it for atomic primitive updates, complex data type operations, expression operations, bit operations, HyperLogLog operations, and readbacks.

Primitive Operations

Primitive operation builders live in Aerospike.Op.

key = Aerospike.key("test", "sessions", "session:1")

{:ok, _metadata} =
  Aerospike.put(:aerospike, key, %{
    "visits" => 1,
    "status" => "new"
  })

{:ok, record} =
  Aerospike.operate(:aerospike, key, [
    Aerospike.Op.add("visits", 1),
    Aerospike.Op.put("status", "active"),
    Aerospike.Op.get("visits")
  ])

record.bins["visits"]

List Operations

List operations live in Aerospike.Op.List. Selector operations can choose return shapes with return-type helpers.

key = Aerospike.key("test", "sessions", "session:events")

{:ok, _metadata} =
  Aerospike.put(:aerospike, key, %{"events" => ["created"]})

{:ok, record} =
  Aerospike.operate(:aerospike, key, [
    Aerospike.Op.List.append("events", "opened"),
    Aerospike.Op.List.size("events")
  ])

record.bins["events"]

Selector helpers can address list entries by index, rank, or value. Index is position in the list. Rank is position in value order, so -1 means the highest-ranked value. return_type: controls whether the server returns values, indexes, ranks, counts, or an existence flag.

{:ok, record} =
  Aerospike.operate(:aerospike, key, [
    Aerospike.Op.List.get_by_index("events", 0,
      return_type: :value
    ),
    Aerospike.Op.List.get_by_rank("events", -1,
      return_type: :value
    ),
    Aerospike.Op.List.get_by_value("events", "opened",
      return_type: :exists
    )
  ])

List write policies use mnemonic order and flag values. Defaults are the safest choice; set policy values only when your application needs ordered-list or write-flag behavior. Advanced compatibility callers can still pass integer values, including {:raw, integer} for unnamed server values.

Aerospike.Op.List.append("events", "clicked", policy: [order: :ordered, flags: :add_unique])

Map Operations

Map operations live in Aerospike.Op.Map.

key = Aerospike.key("test", "profiles", "user:stats")

{:ok, _metadata} =
  Aerospike.put(:aerospike, key, %{"stats" => %{"views" => 1}})

{:ok, record} =
  Aerospike.operate(:aerospike, key, [
    Aerospike.Op.Map.increment("stats", "views", 1),
    Aerospike.Op.Map.put("stats", "updated_by", "worker-1"),
    Aerospike.Op.Map.get_by_key("stats", "views",
      return_type: :value
    )
  ])

record.bins["stats"]

Map selectors can return keys, values, key/value pairs, indexes, ranks, counts, or existence flags. Key selectors default to keys, value/rank selectors default to values or key/value pairs depending on the operation; pass return_type: when the desired shape matters.

{:ok, record} =
  Aerospike.operate(:aerospike, key, [
    Aerospike.Op.Map.get_by_key("stats", "views",
      return_type: :value
    ),
    Aerospike.Op.Map.get_by_rank("stats", -1,
      return_type: :key_value
    ),
    Aerospike.Op.Map.get_by_value("stats", 1,
      return_type: :key
    )
  ])

record.bins["stats"]

Map write policies use mnemonic order and flag values. :order controls map ordering and :flags applies write flags when the operation supports them. Advanced compatibility callers can still pass integer values, including {:raw, integer} for unnamed server values.

Aerospike.Op.Map.put_items("stats", %{"likes" => 2},
  policy: [order: :key_ordered, flags: :update_only]
)

Nested CDT Paths

Nested CDT operations use Aerospike.Ctx path steps through the :ctx option.

key = Aerospike.key("test", "profiles", "user:nested")

{:ok, _metadata} =
  Aerospike.put(:aerospike, key, %{
    "profile" => %{"events" => []}
  })

{:ok, _record} =
  Aerospike.operate(:aerospike, key, [
    Aerospike.Op.List.append("profile", "signed-in",
      ctx: [Aerospike.Ctx.map_key("events")]
    )
  ])

Context paths can move through maps and lists by key, value, index, or rank. The operation bin is the top-level CDT bin; each context step navigates inside that value before the operation runs.

key = Aerospike.key("test", "profiles", "user:nested-scores")

{:ok, _metadata} =
  Aerospike.put(:aerospike, key, %{
    "profile" => %{
      "teams" => [
        %{"name" => "red", "scores" => [10, 20]},
        %{"name" => "blue", "scores" => [15]}
      ]
    }
  })

{:ok, record} =
  Aerospike.operate(:aerospike, key, [
    Aerospike.Op.List.append("profile", 25,
      ctx: [
        Aerospike.Ctx.map_key("teams"),
        Aerospike.Ctx.list_index(0),
        Aerospike.Ctx.map_key("scores")
      ]
    ),
    Aerospike.Op.List.get_by_rank("profile", -1,
      ctx: [
        Aerospike.Ctx.map_key("teams"),
        Aerospike.Ctx.list_index(0),
        Aerospike.Ctx.map_key("scores")
      ],
      return_type: :value
    )
  ])

record.bins["profile"]

See Aerospike.Op.List, Aerospike.Op.Map, and Aerospike.Ctx for the full operation reference.

Bit Operations

Bit operations require Aerospike blob bins. Plain Elixir binaries are encoded as strings by this client, so seed bit bins with {:blob, binary}.

key = Aerospike.key("test", "profiles", "user:flags")

{:ok, _metadata} =
  Aerospike.put(:aerospike, key, %{"flags" => {:blob, <<0>>}})

{:ok, record} =
  Aerospike.operate(:aerospike, key, [
    Aerospike.Op.Bit.set("flags", 0, 8, <<0b1010_0000>>),
    Aerospike.Op.Bit.count("flags", 0, 8)
  ])

record.bins["flags"]

HyperLogLog Operations

HyperLogLog operations live in Aerospike.Op.HLL and require server support for HLL bins.

key = Aerospike.key("test", "profiles", "visitors")

{:ok, _metadata} = Aerospike.put(:aerospike, key, %{"seed" => 0})

{:ok, record} =
  Aerospike.operate(:aerospike, key, [
    Aerospike.Op.HLL.init("visitors", 14, 0),
    Aerospike.Op.HLL.add("visitors", ["ada", "grace"], 14, 0),
    Aerospike.Op.HLL.get_count("visitors")
  ])

record.bins["visitors"]

Expression Operations

Expression operate helpers live in Aerospike.Op.Exp and return or write server-side expression values.

{:ok, record} =
  Aerospike.operate(:aerospike, key, [
    Aerospike.Op.Exp.read("projected", Aerospike.Exp.int_bin("score")),
    Aerospike.Op.Exp.write("score_copy", Aerospike.Exp.int_bin("score"))
  ])

record.bins["projected"]

Geo Values And Queries

Use Aerospike.Geo typed values for GeoJSON bins. Geo secondary-index queries use Aerospike.Filter.geo_within/2 or Aerospike.Filter.geo_contains/2.

key = Aerospike.key("test", "places", "pdx")
point = Aerospike.Geo.point(-122.6765, 45.5231)

{:ok, _metadata} = Aerospike.put(:aerospike, key, %{"loc" => point})

{:ok, task} =
  Aerospike.create_index(:aerospike, "test", "places",
    bin: "loc",
    name: "places_loc_geo_idx",
    type: :geo2dsphere
  )

:ok = Aerospike.IndexTask.wait(task)

region = Aerospike.Geo.circle(-122.6765, 45.5231, 10_000.0)

query =
  Aerospike.Query.new("test", "places")
  |> Aerospike.Query.where(Aerospike.Filter.geo_within("loc", region))
  |> Aerospike.Query.max_records(100)

{:ok, records} = Aerospike.query_all(:aerospike, query)

Geo filters require matching secondary indexes. query_all/3 and query_page/3 require query.max_records.