Skip to content

Commit

Permalink
[CLIENT-1246] Adds missing API for Context#list_index_create and Cont…
Browse files Browse the repository at this point in the history
…ext#map_key_create
  • Loading branch information
khaf committed Oct 15, 2020
1 parent 7101645 commit 9ddfe77
Show file tree
Hide file tree
Showing 8 changed files with 184 additions and 27 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

All notable changes to this project will be documented in this file.

## [2.17.0] - 2020-10-15

* **New Features**
* [CLIENT-1246] Adds missing API for Context#list_index_create and Context#map_key_create

* **Bug Fixes**
* Fixed an issue were MsgPack extensions were not recursively cleared from the CDTs during unpacking.

## [2.16.0] - 2020-10-12

* **New Features**
Expand Down
12 changes: 12 additions & 0 deletions lib/aerospike/cdt/context.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ def initialize(id, value)
@value = value
end

##
# Create list with given type at index offset, given an order and pad.
def self.list_index_create(index, order, pad)
Context.new(0x10 | ListOrder.flag(order, pad), index)
end

##
# Lookup list by index offset.
# If the index is negative, the resolved index starts backwards from end of list.
Expand Down Expand Up @@ -90,6 +96,12 @@ def self.map_key(key)
Context.new(0x22, key)
end

##
# Create map with given type at map key.
def self.map_key_create(key, order)
Context.new(0x22 | order[:flag], key)
end

##
# Lookup map by value.
def self.map_value(key)
Expand Down
43 changes: 36 additions & 7 deletions lib/aerospike/cdt/list_operation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -84,18 +84,33 @@ class ListOperation < Operation
REMOVE_BY_RANK_RANGE = 39
REMOVE_BY_VALUE_REL_RANK_RANGE = 40

attr_reader :list_op, :arguments, :policy, :return_type, :ctx
attr_reader :list_op, :arguments, :policy, :return_type, :ctx, :flag

def initialize(op_type, list_op, bin_name, *arguments, return_type: nil, ctx: nil)
def initialize(op_type, list_op, bin_name, *arguments, return_type: nil, ctx: nil, flag: nil)
@op_type = op_type
@bin_name = bin_name
@bin_value = nil
@list_op = list_op
@ctx = ctx
@flag = flag
@arguments = arguments
@return_type = return_type
end

##
# creates list create operation.
# Server creates list at given context level. The context is allowed to be beyond list
# boundaries only if pad is set to true. In that case, nil list entries will be inserted to
# satisfy the context position.
def self.create(bin_name, order, pad, ctx: nil)
# If context not defined, the set order for top-level bin list.
if !ctx || ctx.length == 0
self.set_order(bin_name, order)
else
ListOperation.new(Operation::CDT_MODIFY, SET_TYPE, bin_name, order, ctx: ctx, flag: ListOrder.flag(order, pad))
end
end

##
# Create a set list order operation.
# Server sets list order.
Expand Down Expand Up @@ -504,11 +519,7 @@ def pack_bin_value
packer.write_array_header(3)
Value.of(0xff).pack(packer)

packer.write_array_header(@ctx.length*2)
@ctx.each do |ctx|
Value.of(ctx.id).pack(packer)
Value.of(ctx.value).pack(packer)
end
pack_context(packer)

packer.write_array_header(args.length+1)
Value.of(@list_op).pack(packer)
Expand All @@ -529,6 +540,24 @@ def pack_bin_value
end
BytesValue.new(bytes)
end

def pack_context(packer)
packer.write_array_header(@ctx.length*2)
if @flag
(1...@ctx.length).each do |i|
Value.of(@ctx[i].id).pack(packer)
Value.of(@ctx[i].value).pack(packer)
end

Value.of(@ctx[-1].id | @flag).pack(packer)
Value.of(@ctx[-1].value).pack(packer)
else
@ctx.each do |ctx|
Value.of(ctx.id).pack(packer)
Value.of(ctx.value).pack(packer)
end
end
end
end

class InvertibleListOp < ListOperation
Expand Down
7 changes: 7 additions & 0 deletions lib/aerospike/cdt/list_order.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ module ListOrder
##
# Default order
DEFAULT = UNORDERED

private

def self.flag(attributes, pad)
(attributes == 1) ? 0xc0 : (pad ? 0x80 : 0x40)
end

end
end
end
58 changes: 42 additions & 16 deletions lib/aerospike/cdt/map_operation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -96,26 +96,39 @@ class MapOperation < Operation
GET_BY_KEY_REL_INDEX_RANGE = 109
GET_BY_VALUE_REL_RANK_RANGE = 110

attr_reader :map_op, :arguments, :return_type, :ctx
attr_reader :map_op, :arguments, :return_type, :ctx, :flag

def initialize(op_type, map_op, bin_name, *arguments, ctx: nil, return_type: nil)
def initialize(op_type, map_op, bin_name, *arguments, ctx: nil, return_type: nil, flag: nil)
@op_type = op_type
@bin_name = bin_name
@bin_value = nil
@map_op = map_op
@ctx = ctx
@flag = flag
@arguments = arguments
@return_type = return_type
self
end

##
# Creates a map create operation.
# Server creates map at given context level.
def self.create(bin_name, order, ctx: nil)
if !ctx || ctx.length == 0
# If context not defined, the set order for top-level bin map.
self.set_policy(MapPolicy.new(order: order, flag: 0), bin_name)
else
MapOperation.new(Operation::CDT_MODIFY, SET_TYPE, bin_name, order[:attr], ctx: ctx, flag: order[:flag])
end
end

##
# Create set map policy operation.
# Server sets map policy attributes. Server returns null.
#
# The required map policy attributes can be changed after the map is created.
def self.set_policy(bin_name, policy, ctx: nil)
MapOperation.new(Operation::CDT_MODIFY, SET_TYPE, bin_name, policy.order, ctx: ctx)
MapOperation.new(Operation::CDT_MODIFY, SET_TYPE, bin_name, policy.order[:attr], ctx: ctx)
end

##
Expand All @@ -126,16 +139,16 @@ def self.set_policy(bin_name, policy, ctx: nil)
# The map policy also specifies the flags used when writing items to the map.
def self.put(bin_name, key, value, ctx: nil, policy: MapPolicy::DEFAULT)
if policy.flags != MapWriteFlags::DEFAULT
MapOperation.new(Operation::CDT_MODIFY, PUT, bin_name, key, value, policy.order, policy.flags, ctx: ctx)
MapOperation.new(Operation::CDT_MODIFY, PUT, bin_name, key, value, policy.order[:attr], policy.flags, ctx: ctx)
else
case policy.write_mode
when MapWriteMode::UPDATE_ONLY
# Replace doesn't allow map order because it does not create on non-existing key.
MapOperation.new(Operation::CDT_MODIFY, REPLACE, bin_name, key, value, ctx: ctx)
when MapWriteMode::CREATE_ONLY
MapOperation.new(Operation::CDT_MODIFY, ADD, bin_name, key, value, policy.order, ctx: ctx)
MapOperation.new(Operation::CDT_MODIFY, ADD, bin_name, key, value, policy.order[:attr], ctx: ctx)
else
MapOperation.new(Operation::CDT_MODIFY, PUT, bin_name, key, value, policy.order, ctx: ctx)
MapOperation.new(Operation::CDT_MODIFY, PUT, bin_name, key, value, policy.order[:attr], ctx: ctx)
end
end
end
Expand All @@ -148,16 +161,16 @@ def self.put(bin_name, key, value, ctx: nil, policy: MapPolicy::DEFAULT)
# The map policy also specifies the flags used when writing items to the map.
def self.put_items(bin_name, values, ctx: nil, policy: MapPolicy::DEFAULT)
if policy.flags != MapWriteFlags::DEFAULT
MapOperation.new(Operation::CDT_MODIFY, PUT_ITEMS, bin_name, values, policy.order, policy.flags, ctx: ctx)
MapOperation.new(Operation::CDT_MODIFY, PUT_ITEMS, bin_name, values, policy.order[:attr], policy.flags, ctx: ctx)
else
case policy.write_mode
when MapWriteMode::UPDATE_ONLY
# Replace doesn't allow map order because it does not create on non-existing key.
MapOperation.new(Operation::CDT_MODIFY, REPLACE_ITEMS, bin_name, values, ctx: ctx)
when MapWriteMode::CREATE_ONLY
MapOperation.new(Operation::CDT_MODIFY, ADD_ITEMS, bin_name, values, policy.order, ctx: ctx)
MapOperation.new(Operation::CDT_MODIFY, ADD_ITEMS, bin_name, values, policy.order[:attr], ctx: ctx)
else
MapOperation.new(Operation::CDT_MODIFY, PUT_ITEMS, bin_name, values, policy.order, ctx: ctx)
MapOperation.new(Operation::CDT_MODIFY, PUT_ITEMS, bin_name, values, policy.order[:attr], ctx: ctx)
end
end
end
Expand All @@ -170,7 +183,7 @@ def self.put_items(bin_name, values, ctx: nil, policy: MapPolicy::DEFAULT)
# The map policy dictates the type of map to create when it does not exist.
# The map policy also specifies the mode used when writing items to the map.
def self.increment(bin_name, key, incr, ctx: nil, policy: MapPolicy::DEFAULT)
MapOperation.new(Operation::CDT_MODIFY, INCREMENT, bin_name, key, incr, policy.order, ctx: ctx)
MapOperation.new(Operation::CDT_MODIFY, INCREMENT, bin_name, key, incr, policy.order[:attr], ctx: ctx)
end

##
Expand All @@ -181,7 +194,7 @@ def self.increment(bin_name, key, incr, ctx: nil, policy: MapPolicy::DEFAULT)
# The map policy dictates the type of map to create when it does not exist.
# The map policy also specifies the mode used when writing items to the map.
def self.decrement(bin_name, key, decr, ctx: nil, policy: MapPolicy::DEFAULT)
MapOperation.new(Operation::CDT_MODIFY, DECREMENT, bin_name, key, decr, policy.order, ctx: ctx)
MapOperation.new(Operation::CDT_MODIFY, DECREMENT, bin_name, key, decr, policy.order[:attr], ctx: ctx)
end

##
Expand Down Expand Up @@ -626,11 +639,7 @@ def pack_bin_value
packer.write_array_header(3)
Value.of(0xff).pack(packer)

packer.write_array_header(@ctx.length*2)
@ctx.each do |ctx|
Value.of(ctx.id).pack(packer)
Value.of(ctx.value).pack(packer)
end
pack_context(packer)

packer.write_array_header(args.length+1)
Value.of(@map_op).pack(packer)
Expand All @@ -651,6 +660,23 @@ def pack_bin_value
BytesValue.new(bytes)
end

def pack_context(packer)
packer.write_array_header(@ctx.length*2)
if @flag
(1...@ctx.length).each do |i|
Value.of(@ctx[i].id).pack(packer)
Value.of(@ctx[i].value).pack(packer)
end

Value.of(@ctx[-1].id | @flag).pack(packer)
Value.of(@ctx[-1].value).pack(packer)
else
@ctx.each do |ctx|
Value.of(ctx.id).pack(packer)
Value.of(ctx.value).pack(packer)
end
end
end
end

end
Expand Down
6 changes: 3 additions & 3 deletions lib/aerospike/cdt/map_order.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,15 @@ module MapOrder

##
# Map is not ordered. This is the default.
UNORDERED = 0
UNORDERED = {attr: 0, flag: 0x40}

##
# Order map by key.
KEY_ORDERED = 1
KEY_ORDERED = {attr: 1, flag: 0x80}

##
# Order map by key, then value.
KEY_VALUE_ORDERED = 3
KEY_VALUE_ORDERED = {attr: 3, flag: 0xc0}

##
# Default order
Expand Down
22 changes: 22 additions & 0 deletions spec/aerospike/cdt_list_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -630,6 +630,28 @@ def list_post_op

describe "Context", skip: !Support.min_version?("4.6") do

it "appends a single item to the list and returns the list size" do
client.delete(key)

list = [
[7, 9, 5],
[1, 2, 3],
[6, 5, 4, 1],
]

client.put(key, Aerospike::Bin.new(list_bin, list))

# Append value to new list created after the original 3 lists.
operation = [
ListOperation.append(list_bin, 2, ctx: [Context.list_index_create(3, ListOrder::ORDERED, false)], policy: ListPolicy.new(order: ListOrder::ORDERED)),
Aerospike::Operation.get(list_bin),
]
record = client.operate(key, operation)

results = record.bins[list_bin]
expect(record.bins[list_bin]).to eq [ [7, 9, 5], [1, 2, 3], [6, 5, 4,1], [2] ]
end

it "is used to change nested list" do
client.delete(key)

Expand Down
55 changes: 54 additions & 1 deletion spec/aerospike/cdt_map_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

let(:client) { Support.client }
let(:key) { Support.gen_random_key }
let(:map_bin) { "map" }
let(:map_bin) { "map_bin" }
let(:map_value) { nil }

before(:each) do
Expand All @@ -37,6 +37,59 @@ def map_post_op

describe "MapOperation Context", skip: !Support.min_version?("4.6") do

it "should support Create Map ops" do
client.delete(key)

m = {
"key1" => [7, 9, 5],
}

client.put(key, Aerospike::Bin.new(map_bin, m))
expect(map_post_op).to eq(m)

ctx = [Context.map_key("key2")]
record = client.operate(key,
[
ListOperation.create(map_bin, ListOrder::ORDERED, false, ctx: ctx),
ListOperation.append(map_bin, 2, ctx: ctx),
ListOperation.append(map_bin, 1, ctx: ctx),
Operation.get(map_bin),
]
)

expect(record.bins[map_bin]).to eq({
"key1" => [7, 9, 5],
"key2" => [1, 2],
})
end

it "should support Nested Map ops with Lists" do
client.delete(key)

m = {
"key1" => {
"key11" => 9, "key12" => 4,
},
"key2" => {
"key21" => 3, "key22" => 5,
},
}

client.put(key, Aerospike::Bin.new(map_bin, m))
record = client.operate(key, [Aerospike::Operation.get(map_bin)])
expect(record.bins[map_bin]).to eq(m)

record = client.operate(key, [MapOperation.put(map_bin, "key21", 11, ctx: [Context::map_key("key2")]), Aerospike::Operation.get(map_bin)])
expect(record.bins[map_bin]).to eq({
"key1" => {
"key11" => 9, "key12" => 4,
},
"key2" => {
"key21" => 11, "key22" => 5,
},
})
end

it "should support Nested Map ops" do
client.delete(key)

Expand Down

0 comments on commit 9ddfe77

Please sign in to comment.