diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index 6b842918..1c105bc3 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -6,40 +6,31 @@ jobs: test: runs-on: ${{matrix.os}}-latest continue-on-error: ${{matrix.experimental}} - + strategy: matrix: os: - ubuntu - + # - macos + ruby: - - 2.6 - - 2.7 - + - "2.6" + - "3.3" + experimental: [false] env: [""] - - include: - - os: ubuntu - ruby: head - experimental: true steps: - - uses: actions/checkout@v2 - - uses: ruby/setup-ruby@v1 - with: - ruby-version: ${{matrix.ruby}} - bundler-cache: true - - - name: Start server - timeout-minutes: 5 - env: - TERM: dumb - run: - .github/workflows/start_cluster.sh 2 - - - name: Run tests - timeout-minutes: 30 - env: - AEROSPIKE_HOSTS: "127.0.0.1:3000,127.0.0.1:3100" - run: ${{matrix.env}} bundle exec rspec + - name: Set up Aerospike Database + uses: reugn/github-action-aerospike@v1 + - uses: actions/checkout@v4 + - uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{matrix.ruby}} + bundler-cache: true + - name: Run tests + timeout-minutes: 30 + env: + AEROSPIKE_HOSTS: "127.0.0.1:3000" + CODECOV_ENABLED: "false" + run: ${{matrix.env}} bundle exec rspec diff --git a/CHANGELOG.md b/CHANGELOG.md index f841a7b4..69198bf9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,27 @@ All notable changes to this project will be documented in this file. +## [4.0.0] 2024-08-14 + +- **New Features** + - [CLIENT-2177] Add support for `MapReturnType#MAP_ORDERED` and `MapReturnType#MAP_UNORDERED`. + - [CLIENT-2308] Add `Exp#infinity_val` and `Exp#wildcard_val`. + - [CLIENT_1731] Support Batch Operations. + +- **Updates** + - [CLIENT-3055] Remove Unsupported Server Features (`Predexp` and `BatchDirect`). + +- **Improvements** + - [CLIENT-3056] Fix Github Actions Workflow. Tests still fail due to runner constraints, but we are now on the right track. + - [CLIENT-2682] Code Coverage - obtain current code coverage numbers for automated unit/integration functional tests. + +- **Fixes** + - [CLIENT-3080] Set correct return types in list/map read expressions. + Set `bool` return type for list read expressions with `ListReturnType.EXISTS`. + Set `bool` return type for map read expressions with `MapReturnType.EXISTS`. + Set `map` return type for map read expressions with `MapReturnType.UNORDERED_MAP` or `MapReturnType.ORDERED_MAP`. + - [CLIENT-3072] Fix an issue where `Statement#return_data` is not respected. + ## [3.0.0] 2023-12-15 Notice: This version of the client only supports Aerospike Server v6.0 and later. Some features will work for the older server versions. - **new_features** diff --git a/Gemfile b/Gemfile index 084d1765..ad605391 100644 --- a/Gemfile +++ b/Gemfile @@ -17,6 +17,7 @@ group :development do gem "rubocop-rspec", require: false end +gem "base64" gem "bcrypt" gem "msgpack", "~> 1.2" gem "rake" diff --git a/README.md b/README.md index 55ea697b..8f75d441 100644 --- a/README.md +++ b/README.md @@ -11,14 +11,18 @@ An Aerospike library for Ruby. This library is compatible with Ruby 2.3+ and supports Linux, Mac OS X and various other BSDs. -- [Usage](#Usage) -- [Prerequisites](#Prerequisites) -- [Installation](#Installation) -- [Benchmarks](#Benchmarks) -- [API Documentaion](#API-Documentation) -- [Tests](#Tests) -- [Examples](#Examples) - - [Tools](#Tools) +- [Aerospike Ruby Client ](#aerospike-ruby-client---) + - [Usage:](#usage) + - [Prerequisites](#prerequisites) + - [Installation](#installation) + - [Installation from Ruby gems](#installation-from-ruby-gems) + - [Installation from source](#installation-from-source) + - [Tests](#tests) + - [Examples](#examples) + - [Tools](#tools) + - [Benchmarks](#benchmarks) + - [API Documentation](#api-documentation) + - [License](#license) ## Usage: @@ -97,7 +101,7 @@ This library is packaged with a number of tests. To run all the test cases: - $ AEROSPIKE_HOSTS="[,]" AEROSPIKE_USER="" AEROSPIKE_PASSWORD="" bundle exec rspec + $ AEROSPIKE_HOSTS="[,]" AEROSPIKE_USER="" AEROSPIKE_PASSWORD="" bundle exec rspec ## Examples diff --git a/examples/pred_exp.rb b/examples/pred_exp.rb deleted file mode 100644 index 647d9ad5..00000000 --- a/examples/pred_exp.rb +++ /dev/null @@ -1,206 +0,0 @@ -# frozen_string_literal: true - -require "rubygems" -require "aerospike" -require './shared/shared' - -include Aerospike -include Shared - -def main - Shared.init - setup(Shared.client) - teardown(Shared.client) - - run_integer_predexp_example(Shared.client) - run_string_predexp_example(Shared.client) - run_regex_predexp_example(Shared.client) - run_mapval_predexp_example(Shared.client) - run_list_predexp_example(Shared.client) - run_geojson_predexp_example(Shared.client) - run_void_time_predexp_example(Shared.client) - - Shared.logger.info("Example finished successfully.") -end - -def setup(client) - records = 100 - Shared.logger.info("Creating #{records} records with random properties.") - records.times do |idx| - key = Key.new(Shared.namespace, Shared.set_name, "user#{idx}") - record = { - 'name' => %w[Timmy Alice John Arthur Mike Diana Emily Laura Nicole].sample + "_#{idx}", - 'race' => %w[Squid Octopus].sample, - 'level' => rand(1..100), - 'rank' => ['C-', 'C', 'C+', 'B-', 'B', 'B+', 'A-', 'A', 'A+', 'S', 'S+', 'X'].sample, - 'gear' => { - 'clothes' => ['Green Hoodie', 'White Tee', 'Blue Jersey', 'Black Tee', 'Mountain Coat'].sample - }, - 'weapons' => ['Water Gun', 'Paint Roller', 'Paintbrush', 'Aerospray', 'Bucket'].sample(3), - 'loc' => GeoJSON.new(type: 'Point', coordinates: [(3 + (idx * 0.003)), (4 + (idx * 0.003))]) - } - client.put(key, record, ttl: (idx + 1) * 5) - end - - task = client.create_index(Shared.namespace, Shared.set_name, "name_index", "name", :string) - task.wait_till_completed or fail "Could not create secondary 'name' index" - - task = client.create_index(Shared.namespace, Shared.set_name, "level_index", "level", :numeric) - task.wait_till_completed or fail "Could not create secondary 'level' index" - - task = client.create_index(Shared.namespace, Shared.set_name, "loc_index", "loc", :geo2dsphere) - task.wait_till_completed or fail "Could not create secondary 'loc' index" -end - -def teardown(client) - client.drop_index(Shared.namespace, Shared.set_name, "name_index") - client.drop_index(Shared.namespace, Shared.set_name, "level_index") - client.drop_index(Shared.namespace, Shared.set_name, "loc_index") -end - -def run_integer_predexp_example(client) - Shared.logger.info("Querying set using predicate expressions to return users with level > 30") - - statement = Statement.new(Shared.namespace, Shared.set_name) - statement.predexp = [ - PredExp.integer_bin('level'), - PredExp.integer_value(30), - PredExp.integer_greater - ] - - records = client.query(statement) - results = [] - records.each do |r| - results << r.bins['name'] - end - - Shared.logger.info("Found #{results.length} records with level > 30.") -end - -def run_string_predexp_example(client) - Shared.logger.info("Querying set using predicate expressions to return Squids") - - statement = Statement.new(Shared.namespace, Shared.set_name) - statement.predexp = [ - PredExp.string_bin('race'), - PredExp.string_value('Squid'), - PredExp.string_equal - ] - - records = client.query(statement) - results = [] - records.each do |r| - results << r.bins['name'] - end - - Shared.logger.info("Found #{results.length} Squids.") -end - -def run_regex_predexp_example(client) - Shared.logger.info("Querying set using predicate expressions to return B rank users") - - statement = Statement.new(Shared.namespace, Shared.set_name) - statement.predexp = [ - PredExp.string_bin('rank'), - PredExp.string_value('B'), - PredExp.string_regex(PredExp::RegexFlags::NONE) - ] - - records = client.query(statement) - results = [] - records.each do |r| - results << r.bins['name'] - end - - Shared.logger.info("Found #{results.length} users with B rank.") -end - -def run_mapval_predexp_example(client) - Shared.logger.info("Querying set using predicate expressions to return all users wearing White Tees") - - statement = Statement.new(Shared.namespace, Shared.set_name) - statement.predexp = [ - PredExp.string_value('White Tee'), - PredExp.string_var('x'), - PredExp.string_equal, - PredExp.map_bin('gear'), - PredExp.mapval_iterate_or('x') - ] - - records = client.query(statement) - results = [] - records.each do |r| - results << r.bins['name'] - end - - Shared.logger.info("Found #{results.length} users wearing White Tees.") -end - -def run_list_predexp_example(client) - Shared.logger.info("Querying set using predicate expressions to return users using buckets") - - statement = Statement.new(Shared.namespace, Shared.set_name) - statement.predexp = [ - PredExp.string_value('Bucket'), - PredExp.string_var('x'), - PredExp.string_equal, - PredExp.list_bin('weapons'), - PredExp.list_iterate_or('x') - ] - - records = client.query(statement) - results = [] - records.each do |r| - results << r.bins['name'] - end - - Shared.logger.info("Found #{results.length} users using buckets.") -end - -def run_geojson_predexp_example(client) - Shared.logger.info("Querying set using predicate expressions to return users in range of circle") - - circle_range = 1_000 - # circle with range of 1000 meters - circle = GeoJSON.new(type: 'AeroCircle', coordinates: [[3,4], circle_range]) - - statement = Statement.new(Shared.namespace, Shared.set_name) - statement.predexp = [ - PredExp.geojson_bin('loc'), - PredExp.geojson_value(circle), - PredExp.geojson_contains - ] - - records = client.query(statement) - results = [] - records.each do |r| - results << r.bins['name'] - end - - Shared.logger.info("Found #{results.length} users in a circle.") -end - -def run_void_time_predexp_example(client) - Shared.logger.info("Querying set using predicate expressions to return records expiring in less than a minute") - - minute_from_now = Time.now + 60 - # Provided time must be an Epoch in nanoseconds - minute_from_now = ("%10.9f" % minute_from_now.to_f).gsub('.', '').to_i - - statement = Statement.new(Shared.namespace, Shared.set_name) - statement.predexp = [ - Aerospike::PredExp.integer_value(minute_from_now), - Aerospike::PredExp.void_time, - Aerospike::PredExp.integer_greater - ] - - records = client.query(statement) - results = [] - records.each do |r| - results << r.bins['name'] - end - - Shared.logger.info("Found #{results.length} records expiring in less than a minute.") -end - -main diff --git a/lib/aerospike.rb b/lib/aerospike.rb index 17c1939f..b6cc426c 100644 --- a/lib/aerospike.rb +++ b/lib/aerospike.rb @@ -43,8 +43,8 @@ require "aerospike/value/particle_type" require "aerospike/value/value" require "aerospike/command/single_command" -require "aerospike/command/batch_direct_node" require "aerospike/command/batch_index_node" +require "aerospike/command/batch_operate_node" require "aerospike/command/field_type" require "aerospike/command/command" require "aerospike/command/execute_command" @@ -53,9 +53,8 @@ require "aerospike/command/operate_command" require "aerospike/command/exists_command" require "aerospike/command/multi_command" -require "aerospike/command/batch_direct_command" -require "aerospike/command/batch_direct_exists_command" require "aerospike/command/batch_index_command" +require "aerospike/command/batch_operate_command" require "aerospike/command/batch_index_exists_command" require "aerospike/command/read_header_command" require "aerospike/command/touch_command" @@ -97,6 +96,10 @@ require "aerospike/policy/generation_policy" require "aerospike/policy/policy" require "aerospike/policy/batch_policy" +require "aerospike/policy/batch_delete_policy" +require "aerospike/policy/batch_read_policy" +require "aerospike/policy/batch_udf_policy" +require "aerospike/policy/batch_write_policy" require "aerospike/policy/write_policy" require "aerospike/policy/scan_policy" require "aerospike/policy/query_policy" @@ -105,6 +108,13 @@ require "aerospike/policy/admin_policy" require "aerospike/policy/auth_mode" +require "aerospike/batch_record" +require "aerospike/batch_attr" +require "aerospike/batch_read" +require "aerospike/batch_write" +require "aerospike/batch_delete" +require "aerospike/batch_udf" + require "aerospike/socket/base" require "aerospike/socket/ssl" require "aerospike/socket/tcp" @@ -160,7 +170,6 @@ require "aerospike/query/query_command" require "aerospike/query/scan_command" require "aerospike/query/statement" -require "aerospike/query/pred_exp" require "aerospike/query/partition_tracker" require "aerospike/query/partition_status" require "aerospike/query/partition_filter" @@ -178,14 +187,6 @@ require "aerospike/exp/exp_hll" require "aerospike/exp/operation" -require "aerospike/query/pred_exp/and_or" -require "aerospike/query/pred_exp/geo_json_value" -require "aerospike/query/pred_exp/integer_value" -require "aerospike/query/pred_exp/op" -require "aerospike/query/pred_exp/regex" -require "aerospike/query/pred_exp/regex_flags" -require "aerospike/query/pred_exp/string_value" - module Aerospike extend Loggable end diff --git a/lib/aerospike/batch_attr.rb b/lib/aerospike/batch_attr.rb new file mode 100644 index 00000000..c6c8f449 --- /dev/null +++ b/lib/aerospike/batch_attr.rb @@ -0,0 +1,292 @@ +# frozen_string_literal: true + +# Copyright 2014-2020 Aerospike, Inc. +# +# Portions may be licensed to Aerospike, Inc. under one or more contributor +# license agreements. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at http:#www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + +module Aerospike + + class BatchAttr + + attr_reader :filter_exp, :read_attr, :write_attr, :info_attr, :expiration, :generation, :has_write, :send_key + + def initialize(ops = nil, opt = {}) + rp = create_policy(opt, BatchPolicy, nil) + wp = create_policy(opt, BatchWritePolicy, nil) + + read_all_bins = false + read_header = false + has_read = false + has_write_op = false + + ops&.each do |op| + case op.op_type + when Operation::BIT_READ, Operation::EXP_READ, Operation::HLL_READ, Operation::CDT_READ, Operation::READ # Read all bins if no bin is specified. + read_all_bins = op.bin_name.nil? + has_read = true + + when Operation::READ_HEADER + read_header = true + has_read = true + + else + has_write_op = true + end + end + + if has_write_op + set_batch_write(wp) + + if has_read + @read_attr |= Aerospike::INFO1_READ + + if read_all_bins + @read_attr |= Aerospike::INFO1_GET_ALL + elsif read_header + @read_attr |= Aerospike::INFO1_NOBINDATA + end + end + else + set_batch_read(rp) + + if read_all_bins + @read_attr |= Aerospike::INFO1_GET_ALL + elsif read_header + @read_attr |= Aerospike::INFO1_NOBINDATA + end + end + end + + def set_read(rp) + @filter_exp = nil + @read_attr = Aerospike::INFO1_READ + + @write_attr = 0 + @info_attr = 0 + + @expiration = 0 + @generation = 0 + @has_write = false + @send_key = false + end + + def set_batch_read(rp) + @filter_exp = rp.filter_exp + @read_attr = Aerospike::INFO1_READ + + @write_attr = 0 + @info_attr = 0 + + @expiration = 0 + @generation = 0 + @has_write = false + @send_key = false + end + + def adjust_read(ops) + read_all_bins = false + read_header = false + + ops.each do |op| + case op.op_type + when Operation::BIT_READ, Operation::EXP_READ, Operation::HLL_READ, Operation::CDT_READ, Operation::READ # Read all bins if no bin is specified. + read_all_bins = op.bin_name.nil? + when Operation::READ_HEADER + read_header = true + end + end + + if read_all_bins + @read_attr |= Aerospike::INFO1_GET_ALL + elsif read_header + @read_attr |= Aerospike::INFO1_NOBINDATA + end + end + + def adjust_read_all_bins(read_all_bins) + @read_attr |= read_all_bins ? Aerospike::INFO1_GET_ALL : Aerospike::INFO1_NOBINDATA + end + + def set_write(wp) + @filter_exp = nil + @read_attr = 0 + @write_attr = Aerospike::INFO2_WRITE | Aerospike::INFO2_RESPOND_ALL_OPS + @info_attr = 0 + @expiration = 0 + @generation = 0 + @has_write = true + @send_key = wp.send_key + end + + def set_batch_write(wp) + @filter_exp = wp.filter_exp + @read_attr = 0 + @write_attr = Aerospike::INFO2_WRITE | Aerospike::INFO2_RESPOND_ALL_OPS + @info_attr = 0 + @expiration = wp.expiration + @has_write = true + @send_key = wp.send_key + + case wp.generation_policy + when GenerationPolicy::NONE + @generation = 0 + when GenerationPolicy::EXPECT_GEN_EQUAL + @generation = wp.generation + @write_attr |= Aerospike::INFO2_GENERATION + when GenerationPolicy::EXPECT_GEN_GT + @generation = wp.generation + @write_attr |= Aerospike::INFO2_GENERATION_GT + else + @generation = 0 + end + + case wp.record_exists_action + when RecordExistsAction::UPDATE + # NOOP + when RecordExistsAction::UPDATE_ONLY + @info_attr |= Aerospike::INFO3_UPDATE_ONLY + when RecordExistsAction::REPLACE + @info_attr |= Aerospike::INFO3_CREATE_OR_REPLACE + when RecordExistsAction::REPLACE_ONLY + @info_attr |= Aerospike::INFO3_REPLACE_ONLY + when RecordExistsAction::CREATE_ONLY + @write_attr |= Aerospike::INFO2_CREATE_ONLY + end + + if wp.durable_delete + @write_attr |= Aerospike::INFO2_DURABLE_DELETE + end + + if wp.commit_level == CommitLevel::COMMIT_MASTER + @info_attr |= Aerospike::INFO3_COMMIT_MASTER + end + end + + def adjust_write(ops) + read_all_bins = false + read_header = false + has_read = false + + ops.each do |op| + case op.op_type + when Operation::BIT_READ, Operation::EXP_READ, Operation::HLL_READ, Operation::CDT_READ, Operation::READ # Read all bins if no bin is specified. + read_all_bins = op.bin_name.nil? + has_read = true + + when Operation::READ_HEADER + read_header = true + has_read = true + + end + end + + if has_read + @read_attr |= Aerospike::INFO1_READ + + if read_all_bins + @read_attr |= Aerospike::INFO1_GET_ALL + elsif read_header + @read_attr |= Aerospike::INFO1_NOBINDATA + end + end + end + + def set_udf(up) + @filter_exp = nil + @read_attr = 0 + @write_attr = Aerospike::INFO2_WRITE + @info_attr = 0 + @expiration = 0 + @generation = 0 + @has_write = true + @send_key = up.send_key + end + + def set_batch_udf(up) + @filter_exp = up.filter_exp + @read_attr = 0 + @write_attr = Aerospike::INFO2_WRITE + @info_attr = 0 + @expiration = up.expiration + @generation = 0 + @has_write = true + @send_key = up.send_key + + if up.durable_delete + @write_attr |= Aerospike::INFO2_DURABLE_DELETE + end + + if up.commit_level == CommitLevel::COMMIT_MASTER + @info_attr |= Aerospike::INFO3_COMMIT_MASTER + end + end + + def set_delete(dp) + @filter_exp = nil + @read_attr = 0 + @write_attr = Aerospike::INFO2_WRITE | Aerospike::INFO2_RESPOND_ALL_OPS | Aerospike::INFO2_DELETE + @info_attr = 0 + @expiration = 0 + @generation = 0 + @has_write = true + @send_key = dp.send_key + end + + def set_batch_delete(dp) + @filter_exp = dp.filter_exp + @read_attr = 0 + @write_attr = Aerospike::INFO2_WRITE | Aerospike::INFO2_RESPOND_ALL_OPS | Aerospike::INFO2_DELETE + @info_attr = 0 + @expiration = 0 + @has_write = true + @send_key = dp.send_key + + case dp.generation_policy + when GenerationPolicy::NONE + @generation = 0 + when GenerationPolicy::EXPECT_GEN_EQUAL + @generation = dp.generation + @write_attr |= Aerospike::INFO2_GENERATION + when GenerationPolicy::EXPECT_GEN_GT + @generation = dp.generation + @write_attr |= Aerospike::INFO2_GENERATION_GT + else + @generation = 0 + end + + if dp.durable_delete + @write_attr |= Aerospike::INFO2_DURABLE_DELETE + end + + if dp.commit_level == CommitLevel::COMMIT_MASTER + @info_attr |= Aerospike::INFO3_COMMIT_MASTER + end + end + + private + + def create_policy(policy, policy_klass, default_policy = nil) + case policy + when nil + default_policy || policy_klass.new + when policy_klass + policy + when Hash + policy_klass.new(policy) + else + raise TypeError, "policy should be a #{policy_klass.name} instance or a Hash" + end + end + end +end diff --git a/lib/aerospike/batch_delete.rb b/lib/aerospike/batch_delete.rb new file mode 100644 index 00000000..89af0403 --- /dev/null +++ b/lib/aerospike/batch_delete.rb @@ -0,0 +1,48 @@ +# encoding: utf-8 +# Copyright 2014-2024 Aerospike, Inc. +# +# Portions may be licensed to Aerospike, Inc. under one or more contributor +# license agreements. +# +# Licensed under the Apache License, Version 2.0 (the "License") you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + +module Aerospike + + # Batch delete operation. + class BatchDelete < BatchRecord + # Optional delete policy. + attr_accessor :policy + + # Initialize policy and key. + def initialize(key, opt = {}) + super(key, has_write: true) + @policy = BatchRecord.create_policy(opt, BatchDeletePolicy, DEFAULT_BATCH_DELETE_POLICY) + end + + def ==(other) # :nodoc: + other && other.instance_of?(self.class) && @policy == other.policy + end + + DEFAULT_BATCH_DELETE_POLICY = BatchDeletePolicy.new + + # Return wire protocol size. For internal use only. + def size # :nodoc: + size = 6 # gen(2) + exp(4) = 6 + + size += @policy&.filter_exp&.size if @policy&.filter_exp + if @policy&.send_key + size += @key.user_key.estimate_size + Aerospike::FIELD_HEADER_SIZE + 1 + end + + size + end + end +end \ No newline at end of file diff --git a/lib/aerospike/batch_read.rb b/lib/aerospike/batch_read.rb new file mode 100644 index 00000000..ef0f0934 --- /dev/null +++ b/lib/aerospike/batch_read.rb @@ -0,0 +1,97 @@ +# encoding: utf-8 +# Copyright 2014-2024 Aerospike, Inc. +# +# Portions may be licensed to Aerospike, Inc. under one or more contributor +# license agreements. +# +# Licensed under the Apache License, Version 2.0 (the "License") you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + +# Batch key and read only operations with default policy. +# Used in batch read commands where different bins are needed for each key. + +module Aerospike + + class BatchRead < BatchRecord + + # Optional read policy. + attr_accessor :policy + + # Bins to retrieve for this key. bin_names are mutually exclusive with + # {BatchRead#ops}. + attr_accessor :bin_names + + # Optional operations for this key. ops are mutually exclusive with + # {BatchRead#bin_names}. A bin_name can be emulated with + # {Operation#get(bin_name)} + attr_accessor :ops + + # If true, ignore bin_names and read all bins. + # If false and bin_names are set, read specified bin_names. + # If false and bin_names are not set, read record header (generation, expiration) only. + attr_accessor :read_all_bins + + # Initialize batch key and bins to retrieve. + def self.read_bins(key, bin_names, opt = {}) + br = BatchRead.new(key) + br.policy = BatchRecord.create_policy(opt, BatchReadPolicy, DEFAULT_BATCH_READ_POLICY) + br.bin_names = bin_names + br.read_all_bins = false + br + end + + # Initialize batch key and read_all_bins indicator. + def self.read_all_bins(key, opt = {}) + br = BatchRead.new(key) + br.policy = create_policy(opt, BatchReadPolicy, DEFAULT_BATCH_READ_POLICY) + br.read_all_bins = true + br + end + + # Initialize batch key and read operations. + def self.ops(key, ops, opt = {}) + br = BatchRead.new(key) + br.policy = create_policy(opt, BatchReadPolicy, DEFAULT_BATCH_READ_POLICY) + br.ops = ops + br.read_all_bins = false + br + end + + # Optimized reference equality check to determine batch wire protocol repeat flag. + # For internal use only. + def ==(other) # :nodoc: + other && other.instance_of?(self.class) && + @bin_names.sort == other.bin_names.sort && @ops.sort == other.ops.sort && + @policy == other.policy && @read_all_bins == other.read_all_bins + end + + DEFAULT_BATCH_READ_POLICY = BatchReadPolicy.new + + # Return wire protocol size. For internal use only. + def size # :nodoc: + size = 0 + size += @policy&.filter_exp&.size if @policy&.filter_exp + + @bin_names&.each do |bin_name| + size += bin_name.bytesize + Aerospike::OPERATION_HEADER_SIZE + end + + @ops&.each do |op| + if op.is_write? + raise AerospikeException.new(ResultCode::PARAMETER_ERROR, "Write operations not allowed in batch read") + end + size += op.bin_name.bytesize + Aerospike::OPERATION_HEADER_SIZE + size += op.value.estimate_size + end + + size + end + end +end diff --git a/lib/aerospike/batch_record.rb b/lib/aerospike/batch_record.rb new file mode 100644 index 00000000..273af83e --- /dev/null +++ b/lib/aerospike/batch_record.rb @@ -0,0 +1,83 @@ +# encoding: utf-8 +# Copyright 2014-2024 Aerospike, Inc. +# +# Portions may be licensed to Aerospike, Inc. under one or more contributor +# license agreements. +# +# Licensed under the Apache License, Version 2.0 (the "License") you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + +module Aerospike + + # Batch key and record result. + class BatchRecord + # Key. + attr_reader :key + + # Record result after batch command has completed. Will be null if record was not found + # or an error occurred. See {BatchRecord#result_code}. + attr_reader :record + + # Result code for this returned record. See {ResultCode}. + # If not {ResultCode#OK}, the record will be null. + attr_accessor :result_code + + # Is it possible that the write transaction may have completed even though an error + # occurred for this record. This may be the case when a client error occurs (like timeout) + # after the command was sent to the server. + attr_accessor :in_doubt + + # Does this command contain a write operation. For internal use only. + attr_reader :has_write + + # Constructor. + def initialize(key, result_code: ResultCode::NO_RESPONSE, in_doubt: false, has_write: false) + @key = key + @record = record + @result_code = result_code + @in_doubt = in_doubt + @has_write = has_write + end + + def self.create_policy(policy, policy_klass, default_policy = nil) # :nodoc: + case policy + when nil + default_policy || policy_klass.new + when policy_klass + policy + when Hash + policy_klass.new(policy) + else + raise TypeError, "policy should be a #{policy_klass.name} instance or a Hash" + end + end + + # Prepare for upcoming batch call. Reset result fields because this instance might be + # reused. For internal use only. + def prepare # :nodoc: + @record = nil + @result_code = ResultCode::NO_RESPONSE + @in_doubt = false + end + + # Set record result. For internal use only. + def record=(record) # :nodoc: + @record = record + @result_code = ResultCode::OK + end + + # Set error result. For internal use only. + def set_error(result_code, in_doubt) # :nodoc: + @result_code = result_code + @in_doubt = in_doubt + end + + end +end diff --git a/lib/aerospike/batch_results.rb b/lib/aerospike/batch_results.rb new file mode 100644 index 00000000..ae71041d --- /dev/null +++ b/lib/aerospike/batch_results.rb @@ -0,0 +1,38 @@ +# encoding: utf-8 +# Copyright 2014-2024 Aerospike, Inc. +# +# Portions may be licensed to Aerospike, Inc. under one or more contributor +# license agreements. +# +# Licensed under the Apache License, Version 2.0 (the "License") you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + +# Batch key and read only operations with default policy. +# Used in batch read commands where different bins are needed for each key. + +module Aerospike + + # Batch record results. + class BatchResults + + # Record results. + attr_accessor :records + + # Indicates if all records returned success. + attr_accessor :status + + # Constructor. + def intialize(records, status) + @records = records + @status = status + end + + end +end diff --git a/lib/aerospike/batch_udf.rb b/lib/aerospike/batch_udf.rb new file mode 100644 index 00000000..40c1c6e2 --- /dev/null +++ b/lib/aerospike/batch_udf.rb @@ -0,0 +1,76 @@ +# encoding: utf-8 +# Copyright 2014-2024 Aerospike, Inc. +# +# Portions may be licensed to Aerospike, Inc. under one or more contributor +# license agreements. +# +# Licensed under the Apache License, Version 2.0 (the "License") you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at http:#www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + +# Batch key and read only operations with default policy. +# Used in batch read commands where different bins are needed for each key. + +module Aerospike + + # Batch user defined functions. + class BatchUDF < BatchRecord + + # Optional UDF policy. + attr_accessor :policy + + # Package or lua module name. + attr_accessor :package_name + + # Lua function name. + attr_accessor :function_name + + # Optional arguments to lua function. + attr_accessor :function_args + + # Wire protocol bytes for function args. For internal use only. + attr_reader :arg_bytes + + # Constructor using default policy. + def initialize(key, package_name, function_name, function_args, opt = {}) + super(key, has_write: true) + @policy = BatchRecord.create_policy(opt, BatchUDFPolicy, DEFAULT_BATCH_UDF_POLICY) + @package_name = package_name + @function_name = function_name + @function_args = ListValue.new(function_args) + # Do not set arg_bytes here because may not be necessary if batch repeat flag is used. + end + + # Optimized reference equality check to determine batch wire protocol repeat flag. + # For internal use only. + def ==(other) # :nodoc: + other && other.instance_of?(self.class) && + @function_name == other.function_name && @function_args == other.function_args && + @package_name == other.package_name && @policy == other.policy + end + + DEFAULT_BATCH_UDF_POLICY = BatchUDFPolicy.new + + # Return wire protocol size. For internal use only. + def size # :nodoc: + size = 6 # gen(2) + exp(4) = 6 + + size += @policy&.filter_exp&.size if @policy&.filter_exp + + if @policy&.send_key + size += @key.user_key.estimate_size + Aerospike::FIELD_HEADER_SIZE + 1 + end + size += @package_name.bytesize + Aerospike::FIELD_HEADER_SIZE + size += @function_name.bytesize + Aerospike::FIELD_HEADER_SIZE + @arg_bytes = @function_args.to_bytes + size += @arg_bytes.bytesize + Aerospike::FIELD_HEADER_SIZE + size + end + end +end \ No newline at end of file diff --git a/lib/aerospike/batch_write.rb b/lib/aerospike/batch_write.rb new file mode 100644 index 00000000..5fa177a1 --- /dev/null +++ b/lib/aerospike/batch_write.rb @@ -0,0 +1,79 @@ +# encoding: utf-8 +# Copyright 2014-2024 Aerospike, Inc. +# +# Portions may be licensed to Aerospike, Inc. under one or more contributor +# license agreements. +# +# Licensed under the Apache License, Version 2.0 (the "License") you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at http:#www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + +# Batch key and read only operations with default policy. +# Used in batch read commands where different bins are needed for each key. + +module Aerospike + + private + + # Batch key and read/write operations with write policy. + class BatchWrite < BatchRecord + # Optional write policy. + attr_accessor :policy + + # Required operations for this key. + attr_accessor :ops + + # Initialize batch key and read/write operations. + # + # {Operation#get()} is not allowed because it returns a variable number of bins and + # makes it difficult (sometimes impossible) to lineup operations with results. Instead, + # use {Operation#get(bin_name)} for each bin name. + def initialize(key, ops, opt = {}) + super(key, has_write: true) + @policy = BatchRecord.create_policy(opt, BatchWritePolicy, DEFAULT_BATCH_WRITE_POLICY) + @ops = ops + end + + # Optimized reference equality check to determine batch wire protocol repeat flag. + # For internal use only. + def ==(other) # :nodoc: + other && other.instance_of?(self.class) && + @ops == other.ops && @policy == other.policy && (@policy.nil? || !@policy.send_key) + end + + DEFAULT_BATCH_WRITE_POLICY = BatchWritePolicy.new + + # Return wire protocol size. For internal use only. + def size # :nodoc: + size = 6 # gen(2) + exp(4) = 6 + + size += @policy&.filter_exp&.size if @policy&.filter_exp + + if @policy&.send_key + size += @key.user_key.estimate_size + Aerospike::FIELD_HEADER_SIZE + 1 + end + + has_write = false + @ops&.each do |op| + if op.is_write? + has_write = true + end + + size += op.bin_name.bytesize + Aerospike::OPERATION_HEADER_SIZE if op.bin_name + size += op.bin_value.estimate_size if op.bin_value + end + + unless has_write + raise AerospikeException.new(ResultCode::PARAMETER_ERROR, "Batch write operations do not contain a write") + end + + size + end + end +end \ No newline at end of file diff --git a/lib/aerospike/cdt/bit_operation.rb b/lib/aerospike/cdt/bit_operation.rb index d0a70857..cb64685a 100644 --- a/lib/aerospike/cdt/bit_operation.rb +++ b/lib/aerospike/cdt/bit_operation.rb @@ -65,9 +65,8 @@ def initialize(op_type, bit_op, bin_name, *arguments, ctx: nil, policy: nil) @arguments = arguments end - # BitResizeOp creates byte "resize" operation. - # Server resizes byte[] to byte_size according to resize_flags (See {@link BitResizeFlags}). + # Server resizes byte[] to byte_size according to resize_flags (See {BitResizeFlags}). # Server does not return a value. # Example: # bin = [0b00000001, 0b01000010] @@ -195,7 +194,7 @@ def self.rshift(bin_name, bit_offset, bit_size, shift, ctx: nil, policy: BitPoli # BitAddOp creates bit "add" operation. # Server adds value to byte[] bin starting at bit_offset for bit_size. Bit_size must be <= 64. # Signed indicates if bits should be treated as a signed number. - # If add overflows/underflows, {@link BitOverflowAction} is used. + # If add overflows/underflows, {BitOverflowAction} is used. # Server does not return a value. # Example: # bin = [0b00000001, 0b01000010, 0b00000011, 0b00000100, 0b00000101] @@ -223,7 +222,7 @@ def self.add( # BitSubtractOp creates bit "subtract" operation. # Server subtracts value from byte[] bin starting at bit_offset for bit_size. Bit_size must be <= 64. # Signed indicates if bits should be treated as a signed number. - # If add overflows/underflows, {@link BitOverflowAction} is used. + # If add overflows/underflows, {BitOverflowAction} is used. # Server does not return a value. # Example: # bin = [0b00000001, 0b01000010, 0b00000011, 0b00000100, 0b00000101] @@ -351,7 +350,7 @@ def pack_bin_value bytes = nil args = arguments.dup Packer.use do |packer| - if @ctx != nil && @ctx.length > 0 + if !@ctx.nil? && @ctx.length > 0 packer.write_array_header(3) Value.of(0xff).pack(packer) diff --git a/lib/aerospike/cdt/map_return_type.rb b/lib/aerospike/cdt/map_return_type.rb index b283a1fb..802d88f4 100644 --- a/lib/aerospike/cdt/map_return_type.rb +++ b/lib/aerospike/cdt/map_return_type.rb @@ -69,6 +69,14 @@ module MapReturnType # Return true if count > 0. EXISTS = 13 + ## + # Return an unordered map. + UNORDERED_MAP = 16 + + ## + # Return an ordered map. + ORDERED_MAP = 17 + ## # :private # diff --git a/lib/aerospike/client.rb b/lib/aerospike/client.rb index 1b56c9c3..b1a58c8a 100644 --- a/lib/aerospike/client.rb +++ b/lib/aerospike/client.rb @@ -36,15 +36,7 @@ module Aerospike # +:fail_if_not_connected+ set to true class Client - attr_accessor :default_admin_policy - attr_accessor :default_batch_policy - attr_accessor :default_info_policy - attr_accessor :default_query_policy - attr_accessor :default_read_policy - attr_accessor :default_scan_policy - attr_accessor :default_write_policy - attr_accessor :default_operate_policy - attr_accessor :cluster + attr_accessor :default_admin_policy, :default_batch_policy, :default_info_policy, :default_query_policy, :default_read_policy, :default_scan_policy, :default_write_policy, :default_operate_policy, :cluster def initialize(hosts = nil, policy: ClientPolicy.new, connect: true) hosts = ::Aerospike::Host::Parse.(hosts || ENV["AEROSPIKE_HOSTS"] || "localhost") @@ -230,11 +222,11 @@ def truncate(namespace, set_name = nil, before_last_update = nil, options = {}) str_cmd = "truncate:namespace=#{namespace}" str_cmd << ";set=#{set_name}" unless set_name.to_s.strip.empty? else - if node.supports_feature?(Aerospike::Features::TRUNCATE_NAMESPACE) - str_cmd = "truncate-namespace:namespace=#{namespace}" - else - str_cmd = "truncate:namespace=#{namespace}" - end + str_cmd = if node.supports_feature?(Aerospike::Features::TRUNCATE_NAMESPACE) + "truncate-namespace:namespace=#{namespace}" + else + "truncate:namespace=#{namespace}" + end end if before_last_update @@ -329,15 +321,8 @@ def batch_get(keys, bin_names = nil, options = nil) bin_names = nil end - if policy.use_batch_direct - key_map = BatchItem.generate_map(keys) - execute_batch_direct_commands(policy, keys) do |node, batch| - BatchDirectCommand.new(node, batch, policy, key_map, bin_names, results, info_flags) - end - else - execute_batch_index_commands(policy, keys) do |node, batch| - BatchIndexCommand.new(node, batch, policy, bin_names, results, info_flags) - end + execute_batch_index_commands(policy, keys) do |node, batch| + BatchIndexCommand.new(node, batch, policy, bin_names, results, info_flags) end results @@ -351,6 +336,21 @@ def batch_get_header(keys, options = nil) batch_get(keys, :none, options) end + # Operate on multiple records for specified batch keys in one batch call. + # This method allows different namespaces/bins for each key in the batch. + # The returned records are located in the same list. + # + # records can be BatchRead, BatchWrite, BatchDelete or BatchUDF. + # + # Requires server version 6.0+ + def batch_operate(records, options = nil) + policy = create_policy(options, BatchPolicy, default_batch_policy) + + execute_batch_operate_commands(policy, records) do |node, batch| + BatchOperateCommand.new(node, batch, policy, records) + end + end + # Check if multiple record keys exist in one batch call. # The returned boolean array is in positional order with the original key array order. # The policy can be used to specify timeouts and protocol type. @@ -358,15 +358,8 @@ def batch_exists(keys, options = nil) policy = create_policy(options, BatchPolicy, default_batch_policy) results = Array.new(keys.length) - if policy.use_batch_direct - key_map = BatchItem.generate_map(keys) - execute_batch_direct_commands(policy, keys) do |node, batch| - BatchDirectExistsCommand.new(node, batch, policy, key_map, results) - end - else - execute_batch_index_commands(policy, keys) do |node, batch| - BatchIndexExistsCommand.new(node, batch, policy, results) - end + execute_batch_index_commands(policy, keys) do |node, batch| + BatchIndexExistsCommand.new(node, batch, policy, results) end results @@ -430,7 +423,7 @@ def register_udf(udf_body, server_path, language, options = nil) end if res["error"] - raise Aerospike::Exceptions::CommandRejected.new("Registration failed: #{res["error"]}\nFile: #{res["file"]}\nLine: #{res["line"]}\nMessage: #{res["message"]}") + raise Aerospike::Exceptions::CommandRejected.new("Registration failed: #{res['error']}\nFile: #{res['file']}\nLine: #{res['line']}\nMessage: #{res['message']}") end UdfRegisterTask.new(@cluster, server_path) @@ -566,7 +559,8 @@ def execute_udf_on_query(statement, package_name, function_name, function_args = # ctx is an optional list of context. Supported on server v6.1+. def create_index(namespace, set_name, index_name, bin_name, index_type, collection_type = nil, options = nil, ctx: nil) if options.nil? && collection_type.is_a?(Hash) - options, collection_type = collection_type, nil + options = collection_type + collection_type = nil end policy = create_policy(options, Policy, default_info_policy) @@ -943,13 +937,11 @@ def create_policy(policy, policy_klass, default_policy = nil) when Hash policy_klass.new(policy) else - fail TypeError, "policy should be a #{policy_klass.name} instance or a Hash" + raise TypeError, "policy should be a #{policy_klass.name} instance or a Hash" end end - def cluster=(cluster) - @cluster = cluster - end + attr_writer :cluster def cluster_config_changed(cluster) Aerospike.logger.debug { "Cluster config change detected; active nodes: #{cluster.nodes.map(&:name)}" } @@ -999,24 +991,18 @@ def execute_batch_index_commands(policy, keys) threads.each(&:join) end - def execute_batch_direct_commands(policy, keys) + def execute_batch_operate_commands(policy, records) if @cluster.nodes.empty? - raise Aerospike::Exceptions::Aerospike.new(Aerospike::ResultCode::SERVER_NOT_AVAILABLE, "Executing Batch Direct command failed because cluster is empty.") + raise Aerospike::Exceptions::Aerospike.new(Aerospike::ResultCode::SERVER_NOT_AVAILABLE, "Executing Batch Index command failed because cluster is empty.") end - batch_nodes = BatchDirectNode.generate_list(@cluster, policy.replica, keys) + batch_nodes = BatchOperateNode.generate_list(@cluster, policy.replica, records) threads = [] - # Use a thread per namespace per node - batch_nodes.each do |batch_node| - # copy to avoid race condition - bn = batch_node - bn.batch_namespaces.each do |batch| - threads << Thread.new do - Thread.current.abort_on_exception = true - command = yield batch_node.node, batch - execute_command(command) - end + batch_nodes.each do |batch| + threads << Thread.new do + command = yield batch.node, batch + execute_command(command) end end diff --git a/lib/aerospike/cluster.rb b/lib/aerospike/cluster.rb index 643d3d04..f5f5b4c4 100644 --- a/lib/aerospike/cluster.rb +++ b/lib/aerospike/cluster.rb @@ -120,15 +120,15 @@ def connected? # Returns a node on the cluster for read operations def batch_read_node(partition, replica_policy) case replica_policy - when Aerospike::Replica::MASTER, Aerospike::Replica::SEQUENCE - return master_node(partition) - when Aerospike::Replica::MASTER_PROLES - return master_proles_node(partition) - when Aerospike::Replica::PREFER_RACK - return rack_node(partition, seq) - when Aerospike::Replica::RANDOM - return random_node - else + when Aerospike::Replica::MASTER, Aerospike::Replica::SEQUENCE + master_node(partition) + when Aerospike::Replica::MASTER_PROLES + master_proles_node(partition) + when Aerospike::Replica::PREFER_RACK + rack_node(partition, seq) + when Aerospike::Replica::RANDOM + random_node + else raise Aerospike::Exceptions::InvalidNode("invalid policy.replica value") end end @@ -136,17 +136,17 @@ def batch_read_node(partition, replica_policy) # Returns a node on the cluster for read operations def read_node(partition, replica_policy, seq) case replica_policy - when Aerospike::Replica::MASTER - return master_node(partition) - when Aerospike::Replica::MASTER_PROLES - return master_proles_node(partition) - when Aerospike::Replica::PREFER_RACK - return rack_node(partition, seq) - when Aerospike::Replica::SEQUENCE - return sequence_node(partition, seq) - when Aerospike::Replica::RANDOM - return random_node - else + when Aerospike::Replica::MASTER + master_node(partition) + when Aerospike::Replica::MASTER_PROLES + master_proles_node(partition) + when Aerospike::Replica::PREFER_RACK + rack_node(partition, seq) + when Aerospike::Replica::SEQUENCE + sequence_node(partition, seq) + when Aerospike::Replica::RANDOM + random_node + else raise Aerospike::Exceptions::InvalidNode("invalid policy.replica value") end end @@ -155,12 +155,12 @@ def read_node(partition, replica_policy, seq) def master_node(partition) partition_map = partitions replica_array = partition_map[partition.namespace] - raise Aerospike::Exceptions::InvalidNamespace("namespace not found in the partition map") if !replica_array + raise Aerospike::Exceptions::InvalidNamespace("namespace not found in the partition map") unless replica_array - node_array = (replica_array.get)[0] - raise Aerospike::Exceptions::InvalidNamespace("namespace not found in the partition map") if !node_array + node_array = replica_array.get[0] + raise Aerospike::Exceptions::InvalidNamespace("namespace not found in the partition map") unless node_array - node = (node_array.get)[partition.partition_id] + node = node_array.get[partition.partition_id] raise Aerospike::Exceptions::InvalidNode if !node || !node.active? node @@ -170,7 +170,7 @@ def master_node(partition) def rack_node(partition, seq) partition_map = partitions replica_array = partition_map[partition.namespace] - raise Aerospike::Exceptions::InvalidNamespace("namespace not found in the partition map") if !replica_array + raise Aerospike::Exceptions::InvalidNamespace("namespace not found in the partition map") unless replica_array replica_array = replica_array.get @@ -179,10 +179,10 @@ def rack_node(partition, seq) node = nil fallback = nil for i in 1..replica_array.length - idx = (seq.update{|v| v.succ} % replica_array.size).abs - node = (replica_array[idx].get)[partition.partition_id] + idx = (seq.update { |v| v.succ } % replica_array.size).abs + node = replica_array[idx].get[partition.partition_id] - next if !node + next unless node fallback = node @@ -202,14 +202,14 @@ def rack_node(partition, seq) def master_proles_node(partition) partition_map = partitions replica_array = partition_map[partition.namespace] - raise Aerospike::Exceptions::InvalidNamespace("namespace not found in the partition map") if !replica_array + raise Aerospike::Exceptions::InvalidNamespace("namespace not found in the partition map") unless replica_array replica_array = replica_array.get node = nil for replica in replica_array - idx = (@replica_index.update{|v| v.succ} % replica_array.size).abs - node = (replica_array[idx].get)[partition.partition_id] + idx = (@replica_index.update { |v| v.succ } % replica_array.size).abs + node = replica_array[idx].get[partition.partition_id] return node if node && node.active? end @@ -221,14 +221,14 @@ def master_proles_node(partition) def sequence_node(partition, seq) partition_map = partitions replica_array = partition_map[partition.namespace] - raise Aerospike::Exceptions::InvalidNamespace("namespace not found in the partition map") if !replica_array + raise Aerospike::Exceptions::InvalidNamespace("namespace not found in the partition map") unless replica_array replica_array = replica_array.get node = nil for replica in replica_array - idx = (seq.update{|v| v.succ} % replica_array.size).abs - node = (replica_array[idx].get)[partition.partition_id] + idx = (seq.update { |v| v.succ } % replica_array.size).abs + node = replica_array[idx].get[partition.partition_id] return node if node && node.active? end @@ -236,9 +236,13 @@ def sequence_node(partition, seq) raise Aerospike::Exceptions::InvalidNode end - def get_node_for_key(replica_policy, key) + def get_node_for_key(replica_policy, key, is_write: false) partition = Partition.new_by_key(key) - batch_read_node(partition, replica_policy) + if is_write + master_node(partition) + else + batch_read_node(partition, replica_policy) + end end # Returns partitions pertaining to a node @@ -247,10 +251,10 @@ def node_partitions(node, namespace) partition_map = partitions replica_array = partition_map[namespace] - raise Aerospike::Exceptions::InvalidNamespace("namespace not found in the partition map") if !replica_array + raise Aerospike::Exceptions::InvalidNamespace("namespace not found in the partition map") unless replica_array - node_array = (replica_array.get)[0] - raise Aerospike::Exceptions::InvalidNamespace("namespace not found in the partition map") if !node_array + node_array = replica_array.get[0] + raise Aerospike::Exceptions::InvalidNamespace("namespace not found in the partition map") unless node_array pid = 0 @@ -270,7 +274,7 @@ def random_node i = 0 while i < length # Must handle concurrency with other non-tending threads, so node_index is consistent. - idx = (@node_index.update{ |v| v.succ } % node_array.length).abs + idx = (@node_index.update { |v| v.succ } % node_array.length).abs node = node_array[idx] return node if node.active? @@ -366,13 +370,13 @@ def launch_tend_thread @tend_thread = Thread.new do Thread.current.abort_on_exception = false loop do - begin + tend sleep(@tend_interval / 1000.0) - rescue => e + rescue => e Aerospike.logger.error("Exception occured during tend: #{e}") Aerospike.logger.debug { e.backtrace.join("\n") } - end + end end end @@ -453,7 +457,7 @@ def refresh_nodes def log_tend_stats(nodes) diff = nodes.size - @old_node_count - action = "#{diff.abs} #{diff.abs == 1 ? "node has" : "nodes have"} #{diff > 0 ? "joined" : "left"} the cluster." + action = "#{diff.abs} #{diff.abs == 1 ? 'node has' : 'nodes have'} #{diff > 0 ? 'joined' : 'left'} the cluster." Aerospike.logger.info("Tend finished. #{action} Old node count: #{@old_node_count}, New node count: #{nodes.size}") @old_node_count = nodes.size end @@ -689,11 +693,11 @@ def remove_nodes_copy(nodes_to_remove) end def node_exists(search, node_list) - node_list.any? {|node| node == search } + node_list.any? { |node| node == search } end def find_node_by_name(node_name) - nodes.detect{|node| node.name == node_name } + nodes.detect { |node| node.name == node_name } end end end diff --git a/lib/aerospike/command/batch_direct_command.rb b/lib/aerospike/command/batch_direct_command.rb deleted file mode 100644 index 3c96d82e..00000000 --- a/lib/aerospike/command/batch_direct_command.rb +++ /dev/null @@ -1,105 +0,0 @@ -# Copyright 2014-2020 Aerospike, Inc. -# -# Portions may be licensed to Aerospike, Inc. under one or more contributor -# license agreements. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may not -# use this file except in compliance with the License. You may obtain a copy of -# the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations under -# the License. - -require 'aerospike/command/multi_command' - -module Aerospike - - class BatchDirectCommand < MultiCommand #:nodoc: - - attr_accessor :batch - attr_accessor :policy - attr_accessor :key_map - attr_accessor :bin_names - attr_accessor :results - attr_accessor :read_attr - - def initialize(node, batch, policy, key_map, bin_names, results, read_attr) - super(node) - - @batch = batch - @policy = policy - @key_map = key_map - @bin_names = bin_names - @results = results - @read_attr = read_attr - end - - def write_buffer - # Estimate buffer size - begin_cmd - byte_size = batch.keys.length * DIGEST_SIZE - - @data_offset += batch.namespace.bytesize + - FIELD_HEADER_SIZE + byte_size + FIELD_HEADER_SIZE - - if bin_names - bin_names.each do |bin_name| - estimate_operation_size_for_bin_name(bin_name) - end - end - - size_buffer - - operation_count = 0 - if bin_names - operation_count = bin_names.length - end - - write_header_read(policy, read_attr, 0, 2, operation_count) - write_field_string(batch.namespace, Aerospike::FieldType::NAMESPACE) - write_field_header(byte_size, Aerospike::FieldType::DIGEST_RIPE_ARRAY) - - batch.keys.each do |key| - @data_offset += @data_buffer.write_binary(key.digest, @data_offset) - end - - if bin_names - bin_names.each do |bin_name| - write_operation_for_bin_name(bin_name, Aerospike::Operation::READ) - end - end - - end_cmd - mark_compressed(@policy) - end - - # Parse all results in the batch. Add records to shared list. - # If the record was not found, the bins will be nil. - def parse_row(result_code) - generation = @data_buffer.read_int32(6) - expiration = @data_buffer.read_int32(10) - field_count = @data_buffer.read_int16(18) - op_count = @data_buffer.read_int16(20) - - key = parse_key(field_count) - - item = key_map[key.digest] - if item - if result_code == 0 - index = item.index - key = item.key - results[index] = parse_record(key, op_count, generation, expiration) - end - else - Aerospike.logger.warn("Unexpected batch key returned: #{key}") - end - end - - end # class - -end # module diff --git a/lib/aerospike/command/batch_direct_exists_command.rb b/lib/aerospike/command/batch_direct_exists_command.rb deleted file mode 100644 index 803af8c9..00000000 --- a/lib/aerospike/command/batch_direct_exists_command.rb +++ /dev/null @@ -1,51 +0,0 @@ -# Copyright 2014-2020 Aerospike, Inc. -# -# Portions may be licensed to Aerospike, Inc. under one or more contributor -# license agreements. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may not -# use this file except in compliance with the License. You may obtain a copy of -# the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations under -# the License. - -require 'aerospike/command/batch_direct_command' - -module Aerospike - - class BatchDirectExistsCommand < BatchDirectCommand #:nodoc: - - def initialize(node, batch, policy, key_map, results) - super(node, batch, policy, key_map, nil, results, INFO1_READ | INFO1_NOBINDATA) - end - - # Parse all results in the batch. Add records to shared list. - # If the record was not found, the bins will be nil. - def parse_row(result_code) - field_count = @data_buffer.read_int16(18) - op_count = @data_buffer.read_int16(20) - - if op_count > 0 - raise Aerospike::Exceptions::Parse.new('Received bins that were not requested!') - end - - key = parse_key(field_count) - item = key_map[key.digest] - - if item - index = item.index - results[index] = (result_code == 0) - else - Aerospike::logger.debug("Unexpected batch key returned: #{key.namespace}, #{key.digest}") - end - end - - end # class - -end # module diff --git a/lib/aerospike/command/batch_index_command.rb b/lib/aerospike/command/batch_index_command.rb index 65835279..495e52f5 100644 --- a/lib/aerospike/command/batch_index_command.rb +++ b/lib/aerospike/command/batch_index_command.rb @@ -21,11 +21,7 @@ module Aerospike class BatchIndexCommand < MultiCommand #:nodoc: - attr_accessor :batch - attr_accessor :policy - attr_accessor :bin_names - attr_accessor :results - attr_accessor :read_attr + attr_accessor :batch, :policy, :bin_names, :results, :read_attr def initialize(node, batch, policy, bin_names, results, read_attr) super(node) @@ -42,8 +38,8 @@ def write_buffer field_count_row = 1 field_count = 1 - predexp_size = estimate_predexp(@policy.predexp) - field_count += 1 if predexp_size > 0 + exp_size = estimate_expression_size(@policy.filter_exp) + field_count += 1 if exp_size > 0 if bin_names bin_names.each do |bin_name| @@ -58,7 +54,7 @@ def write_buffer batch.keys.each do |key| @data_offset += key.digest.length + 4 # 4 byte batch offset - if prev != nil && prev.namespace == key.namespace + if !prev.nil? && prev.namespace == key.namespace @data_offset += 1 else @data_offset += key.namespace.bytesize + FIELD_HEADER_SIZE + 1 + 1 + 2 + 2 # repeat/no-repeat flag + read_attr flags + field_count + operation_count @@ -68,7 +64,7 @@ def write_buffer size_buffer write_header_read(policy, read_attr | INFO1_BATCH, 0, field_count, 0) - write_predexp(@policy.predexp, predexp_size) + write_filter_exp(@policy.filter_exp, exp_size) write_field_header(0, Aerospike::FieldType::BATCH_INDEX) @data_offset += @data_buffer.write_int32(batch.keys.length, @data_offset) @@ -80,7 +76,7 @@ def write_buffer @data_offset += @data_buffer.write_int32(index, @data_offset) @data_offset += @data_buffer.write_binary(key.digest, @data_offset) - if (prev != nil && prev.namespace == key.namespace) + if !prev.nil? && prev.namespace == key.namespace @data_offset += @data_buffer.write_byte(1, @data_offset) else @data_offset += @data_buffer.write_byte(0, @data_offset) diff --git a/lib/aerospike/command/batch_index_node.rb b/lib/aerospike/command/batch_index_node.rb index 8cb93d28..33514076 100644 --- a/lib/aerospike/command/batch_index_node.rb +++ b/lib/aerospike/command/batch_index_node.rb @@ -19,13 +19,12 @@ module Aerospike class BatchIndexNode #:nodoc: - attr_accessor :node - attr_accessor :keys_by_idx + attr_accessor :node, :keys_by_idx def self.generate_list(cluster, replica_policy, keys) keys.each_with_index - .group_by { |key, _| cluster.get_node_for_key(replica_policy, key) } - .map { |node, keys_with_idx| BatchIndexNode.new(node, keys_with_idx) } + .group_by { |key, _| cluster.get_node_for_key(replica_policy, key) } + .map { |node, keys_with_idx| BatchIndexNode.new(node, keys_with_idx) } end def initialize(node, keys_with_idx) diff --git a/lib/aerospike/command/batch_operate_command.rb b/lib/aerospike/command/batch_operate_command.rb new file mode 100644 index 00000000..db816e83 --- /dev/null +++ b/lib/aerospike/command/batch_operate_command.rb @@ -0,0 +1,151 @@ +# Copyright 2018 Aerospike, Inc. +# +# Portions may be licensed to Aerospike, Inc. under one or more contributor +# license agreements. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + +require 'aerospike/command/multi_command' + +module Aerospike + + class BatchOperateCommand < MultiCommand #:nodoc: + + attr_accessor :batch, :policy, :attr, :records + + def initialize(node, batch, policy, records) + super(node) + @batch = batch + @policy = policy + @records = records + end + + def batch_flags + flags = 0 + # flags |= 0x1 if @policy.allow_inline + flags |= 0x2 if @policy.allow_inline_ssd + flags |= 0x4 if @policy.respond_all_keys + flags + end + + def write_buffer + field_count = 1 + + exp_size = estimate_expression_size(@policy.filter_exp) + @data_offset += exp_size + field_count += 1 if exp_size > 0 + + @data_buffer.reset + begin_cmd + @data_offset += FIELD_HEADER_SIZE + 4 + 1 # batch.keys.length + flags + + prev = nil + @records.each do |record| + key = record.key + @data_offset += key.digest.length + 4 # 4 byte batch offset + + if !@policy.send_key && !prev.nil? && prev.key.namespace == key.namespace && prev.key.set_name == key.set_name && record == prev + @data_offset += 1 + else + @data_offset += 12 + @data_offset += key.namespace.bytesize + FIELD_HEADER_SIZE + @data_offset += key.set_name.bytesize + FIELD_HEADER_SIZE + @data_offset += record.size + end + + prev = record + end + size_buffer + write_batch_header(policy, field_count) + + write_filter_exp(@policy.filter_exp, exp_size) + + field_size_offset = @data_offset + + write_field_header(0, Aerospike::FieldType::BATCH_INDEX) + @data_offset += @data_buffer.write_int32(batch.records.length, @data_offset) + @data_offset += @data_buffer.write_byte(batch_flags, @data_offset) + + prev = nil + attr = BatchAttr.new + batch.records.each_with_index do |record, index| + @data_offset += @data_buffer.write_int32(index, @data_offset) + key = record.key + @data_offset += @data_buffer.write_binary(key.digest, @data_offset) + + if !@policy.send_key && !prev.nil? && prev.key.namespace == key.namespace && prev.key.set_name == key.set_name && record == prev + @data_offset += @data_buffer.write_byte(BATCH_MSG_REPEAT, @data_offset) + else + case record + when BatchRead + attr.set_batch_read(record.policy) + if record.bin_names&.length&.> 0 + write_batch_bin_names(key, record.bin_names, attr, attr.filter_exp) + elsif record.ops&.length&.> 0 + attr.adjust_read(br.ops) + write_batch_operations(key, record.ops, attr, attr.filter_exp) + else + attr.adjust_read_all_bins(record.read_all_bins) + write_batch_read(key, attr, attr.filter_exp, 0) + end + + when BatchWrite + attr.set_batch_write(record.policy) + attr.adjust_write(record.ops) + write_batch_operations(key, record.ops, attr, attr.filter_exp) + + when BatchUDF + attr.set_batch_udf(record.policy) + write_batch_write(key, attr, attr.filter_exp, 3, 0) + write_field_string(record.package_name, Aerospike::FieldType::UDF_PACKAGE_NAME) + write_field_string(record.function_name, Aerospike::FieldType::UDF_FUNCTION) + write_field_bytes(record.arg_bytes, Aerospike::FieldType::UDF_ARGLIST) + + when BatchDelete + attr.set_batch_delete(record.policy) + write_batch_write(key, attr, attr.filter_exp, 0, 0) + end + + prev = record + end + end + + @data_buffer.write_uint32(@data_offset-MSG_TOTAL_HEADER_SIZE-4, field_size_offset) + + end_cmd + mark_compressed(@policy) + end + + # Parse all results in the batch. Add records to shared list. + # If the record was not found, the bins will be nil. + def parse_row(result_code) + generation = @data_buffer.read_int32(6) + expiration = @data_buffer.read_int32(10) + batch_index = @data_buffer.read_int32(14) + field_count = @data_buffer.read_int16(18) + op_count = @data_buffer.read_int16(20) + + skip_key(field_count) + req_key = records[batch_index].key + + records[batch_index].result_code = result_code + case result_code + when 0, ResultCode::UDF_BAD_RESPONSE + record = parse_record(req_key, op_count, generation, expiration) + records[batch_index].record = record + end + end + + end # class + +end # module diff --git a/lib/aerospike/command/batch_operate_node.rb b/lib/aerospike/command/batch_operate_node.rb new file mode 100644 index 00000000..1905ed6c --- /dev/null +++ b/lib/aerospike/command/batch_operate_node.rb @@ -0,0 +1,51 @@ +# Copyright 2018 Aerospike, Inc. +# +# Portions may be licensed to Aerospike, Inc. under one or more contributor +# license agreements. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + +module Aerospike + + class BatchOperateNode #:nodoc: + + attr_accessor :node, :records_by_idx + + def self.generate_list(cluster, replica_policy, records) + records.each_with_index + .group_by { |record, _| cluster.get_node_for_key(replica_policy, record.key, is_write: record.has_write) } + .map { |node, records_with_idx| BatchOperateNode.new(node, records_with_idx) } + end + + def initialize(node, records_with_idx) + @node = node + @records_by_idx = records_with_idx.map(&:reverse).to_h + end + + def records + records_by_idx.values + end + + def each_record_with_index + records_by_idx.each do |idx, rec| + yield rec, idx + end + end + + def record_for_index(idx) + @records_by_idx[idx] + end + + end + +end diff --git a/lib/aerospike/command/command.rb b/lib/aerospike/command/command.rb index 4bedc891..58a43348 100644 --- a/lib/aerospike/command/command.rb +++ b/lib/aerospike/command/command.rb @@ -76,6 +76,12 @@ module Aerospike # Completely replace existing record only. INFO3_REPLACE_ONLY = Integer(1 << 5) + BATCH_MSG_READ = 0x0 + BATCH_MSG_REPEAT = 0x1 + BATCH_MSG_INFO = 0x2 + BATCH_MSG_GEN = 0x4 + BATCH_MSG_TTL = 0x8 + MSG_TOTAL_HEADER_SIZE = 30 FIELD_HEADER_SIZE = 5 OPERATION_HEADER_SIZE = 8 @@ -112,9 +118,6 @@ def set_write(policy, operation, key, bins) begin_cmd field_count = estimate_key_size(key, policy) - predexp_size = estimate_predexp(policy.predexp) - field_count += 1 if predexp_size > 0 - exp_size = estimate_expression_size(@policy.filter_exp) field_count += 1 if exp_size > 0 @@ -126,7 +129,6 @@ def set_write(policy, operation, key, bins) write_header_write(policy, INFO2_WRITE, field_count, bins.length) write_key(key, policy) - write_predexp(policy.predexp, predexp_size) write_filter_exp(@policy.filter_exp, exp_size) bins.each do |bin| @@ -142,16 +144,12 @@ def set_delete(policy, key) begin_cmd field_count = estimate_key_size(key) - predexp_size = estimate_predexp(policy.predexp) - field_count += 1 if predexp_size > 0 - exp_size = estimate_expression_size(@policy.filter_exp) field_count += 1 if exp_size > 0 size_buffer - write_header_write(policy, INFO2_WRITE | INFO2_DELETE, field_count, 0) + write_header_write(policy, INFO2_WRITE | INFO2_DELETE, field_count, 0) write_key(key) - write_predexp(policy.predexp, predexp_size) write_filter_exp(@policy.filter_exp, exp_size) end_cmd end @@ -161,17 +159,13 @@ def set_touch(policy, key) begin_cmd field_count = estimate_key_size(key) - predexp_size = estimate_predexp(policy.predexp) - field_count += 1 if predexp_size > 0 - exp_size = estimate_expression_size(@policy.filter_exp) field_count += 1 if exp_size > 0 estimate_operation_size size_buffer - write_header_write(policy, INFO2_WRITE, field_count, 1) + write_header_write(policy, INFO2_WRITE, field_count, 1) write_key(key) - write_predexp(policy.predexp, predexp_size) write_filter_exp(@policy.filter_exp, exp_size) write_operation_for_operation_type(Aerospike::Operation::TOUCH) end_cmd @@ -182,16 +176,12 @@ def set_exists(policy, key) begin_cmd field_count = estimate_key_size(key) - predexp_size = estimate_predexp(policy.predexp) - field_count += 1 if predexp_size > 0 - exp_size = estimate_expression_size(@policy.filter_exp) field_count += 1 if exp_size > 0 size_buffer write_header_read_header(policy, INFO1_READ | INFO1_NOBINDATA, field_count, 0) write_key(key) - write_predexp(policy.predexp, predexp_size) write_filter_exp(@policy.filter_exp, exp_size) end_cmd end @@ -201,16 +191,12 @@ def set_read_for_key_only(policy, key) begin_cmd field_count = estimate_key_size(key) - predexp_size = estimate_predexp(policy.predexp) - field_count += 1 if predexp_size > 0 - exp_size = estimate_expression_size(@policy.filter_exp) field_count += 1 if exp_size > 0 size_buffer write_header_read(policy, INFO1_READ | INFO1_GET_ALL, 0, field_count, 0) write_key(key) - write_predexp(policy.predexp, predexp_size) write_filter_exp(@policy.filter_exp, exp_size) end_cmd end @@ -221,9 +207,6 @@ def set_read(policy, key, bin_names) begin_cmd field_count = estimate_key_size(key) - predexp_size = estimate_predexp(policy.predexp) - field_count += 1 if predexp_size > 0 - exp_size = estimate_expression_size(@policy.filter_exp) field_count += 1 if exp_size > 0 @@ -239,7 +222,6 @@ def set_read(policy, key, bin_names) write_header_read(policy, attr, 0, field_count, bin_names.length) write_key(key) - write_predexp(policy.predexp, predexp_size) write_filter_exp(@policy.filter_exp, exp_size) bin_names.each do |bin_name| @@ -258,9 +240,6 @@ def set_read_header(policy, key) begin_cmd field_count = estimate_key_size(key) - predexp_size = estimate_predexp(policy.predexp) - field_count += 1 if predexp_size > 0 - exp_size = estimate_expression_size(@policy.filter_exp) field_count += 1 if exp_size > 0 @@ -273,7 +252,6 @@ def set_read_header(policy, key) write_header_read_header(policy, INFO1_READ|INFO1_NOBINDATA, field_count, 0) write_key(key) - write_predexp(policy.predexp, predexp_size) write_filter_exp(@policy.filter_exp, exp_size) end_cmd mark_compressed(policy) @@ -284,9 +262,6 @@ def set_operate(policy, key, args) begin_cmd field_count = estimate_key_size(key, policy) - predexp_size = estimate_predexp(policy.predexp) - field_count += 1 if predexp_size > 0 - exp_size = estimate_expression_size(policy.filter_exp) field_count += 1 if exp_size > 0 @@ -296,7 +271,6 @@ def set_operate(policy, key, args) write_header_read_write(policy, args.read_attr, args.write_attr, field_count, args.operations.length) write_key(key, policy) - write_predexp(policy.predexp, predexp_size) write_filter_exp(policy.filter_exp, exp_size) args.operations.each do |operation| @@ -311,9 +285,6 @@ def set_udf(policy, key, package_name, function_name, args) begin_cmd field_count = estimate_key_size(key, policy) - predexp_size = estimate_predexp(policy.predexp) - field_count += 1 if predexp_size > 0 - exp_size = estimate_expression_size(@policy.filter_exp) field_count += 1 if exp_size > 0 @@ -324,7 +295,6 @@ def set_udf(policy, key, package_name, function_name, args) write_header_write(policy, INFO2_WRITE, field_count, 0) write_key(key, policy) - write_predexp(policy.predexp, predexp_size) write_filter_exp(@policy.filter_exp, exp_size) write_field_string(package_name, Aerospike::FieldType::UDF_PACKAGE_NAME) write_field_string(function_name, Aerospike::FieldType::UDF_FUNCTION) @@ -373,9 +343,6 @@ def set_scan(cluster, policy, namespace, set_name, bin_names, node_partitions) field_count += 1 end - predexp_size = estimate_predexp(policy.predexp) - field_count += 1 if predexp_size > 0 - exp_size = estimate_expression_size(@policy.filter_exp) field_count += 1 if exp_size > 0 @@ -446,7 +413,6 @@ def set_scan(cluster, policy, namespace, set_name, bin_names, node_partitions) write_field_int(policy.records_per_second, Aerospike::FieldType::RECORDS_PER_SECOND) end - write_predexp(policy.predexp, predexp_size) write_filter_exp(@policy.filter_exp, exp_size) # write_field_header(2, Aerospike::FieldType::SCAN_OPTIONS) @@ -475,7 +441,6 @@ def set_scan(cluster, policy, namespace, set_name, bin_names, node_partitions) end_cmd end - def set_query(cluster, policy, statement, background, node_partitions) function_arg_buffer = nil field_count = 0 @@ -525,7 +490,7 @@ def set_query(cluster, policy, statement, background, node_partitions) # Estimate INDEX_RANGE field. @data_offset += FIELD_HEADER_SIZE - filter_size += 1 # num filters + filter_size += 1 # num filters filter_size += filter.estimate_size @data_offset += filter_size @@ -539,14 +504,6 @@ def set_query(cluster, policy, statement, background, node_partitions) end statement.set_task_id - predexp = policy.predexp || statement.predexp - - if predexp - @data_offset += FIELD_HEADER_SIZE - pred_size = Aerospike::PredExp.estimate_size(predexp) - @data_offset += pred_size - field_count += 1 - end unless policy.filter_exp.nil? exp_size = estimate_expression_size(policy.filter_exp) @@ -656,13 +613,6 @@ def set_query(cluster, policy, statement, background, node_partitions) # Write task_id field write_field_int64(statement.task_id, FieldType::TRAN_ID) - unless predexp.nil? - write_field_header(pred_size, Aerospike::FieldType::PREDEXP) - @data_offset = Aerospike::PredExp.write( - predexp, @data_buffer, @data_offset - ) - end - if filter type = filter.collection_type @@ -683,7 +633,8 @@ def set_query(cluster, policy, statement, background, node_partitions) if statement.function_name write_field_header(1, FieldType::UDF_OP) - @data_offset += @data_buffer.write_byte(1, @data_offset) + ret_marker = statement.return_data ? 1 : 2 + @data_offset += @data_buffer.write_byte(ret_marker, @data_offset) write_field_string(statement.package_name, FieldType::UDF_PACKAGE_NAME) write_field_string(statement.function_name, FieldType::UDF_FUNCTION) write_field_string(function_arg_buffer, FieldType::UDF_ARGLIST) @@ -725,7 +676,6 @@ def set_query(cluster, policy, statement, background, node_partitions) end_cmd end - def execute iterations = 0 @@ -752,7 +702,7 @@ def execute # Socket connection error has occurred. Decrease health and retry. @node.decrease_health - Aerospike.logger.error("Node #{@node.to_s}: #{e}") + Aerospike.logger.error("Node #{@node}: #{e}") else Aerospike.logger.error("No node available for transaction: #{e}") end @@ -785,7 +735,7 @@ def execute # Close socket to flush out possible garbage. Do not put back in pool. @conn.close if @conn - Aerospike.logger.error("Node #{@node.to_s}: #{e}") + Aerospike.logger.error("Node #{@node}: #{e}") # IO error means connection to server @node is unhealthy. # Reflect cmd status. @node.decrease_health @@ -853,14 +803,14 @@ def estimate_key_size(key, policy = nil) field_count += 1 end - return field_count + field_count end def estimate_udf_size(package_name, function_name, bytes) @data_offset += package_name.bytesize + FIELD_HEADER_SIZE @data_offset += function_name.bytesize + FIELD_HEADER_SIZE @data_offset += bytes.bytesize + FIELD_HEADER_SIZE - return 3 + 3 end def estimate_operation_size_for_bin(bin) @@ -890,16 +840,6 @@ def estimate_operation_size @data_offset += OPERATION_HEADER_SIZE end - def estimate_predexp(predexp) - if predexp && !predexp.empty? - @data_offset += FIELD_HEADER_SIZE - sz = Aerospike::PredExp.estimate_size(predexp) - @data_offset += sz - return sz - end - return 0 - end - def estimate_expression_size(exp) unless exp.nil? @data_offset += FIELD_HEADER_SIZE @@ -1025,7 +965,7 @@ def write_header_read(policy, read_attr, info_attr, field_count, operation_count @data_buffer.write_byte(0, 10) @data_buffer.write_byte(info_attr, 11) - (12...22).each { |i| @data_buffer.write_byte(i, 0) } + (12...22).each { |i| @data_buffer.write_byte(0, i) } # Initialize timeout. It will be written later. @data_buffer.write_byte(0, 22) @@ -1048,7 +988,7 @@ def write_header_read_header(policy, read_attr, field_count, operation_count) @data_buffer.write_byte(0, 10) @data_buffer.write_byte(info_attr, 11) - (12...22).each { |i| @data_buffer.write_byte(i, 0) } + (12...22).each { |i| @data_buffer.write_byte(0, i) } # Initialize timeout. It will be written later. @data_buffer.write_byte(0, 22) @@ -1062,6 +1002,89 @@ def write_header_read_header(policy, read_attr, field_count, operation_count) @data_offset = MSG_TOTAL_HEADER_SIZE end + def write_batch_operations(key, ops, attr, filter_exp) + if attr.has_write + write_batch_write(key, attr, filter_exp, 0, ops.length) + else + write_batch_read(key, attr, filter_exp, ops.length) + end + + ops.each do |op| + write_operation_for_operation(op) + end + end + + def write_batch_fields(key, field_count, op_count) + field_count+=2 + @data_offset += @data_buffer.write_uint16(field_count, @data_offset) + @data_offset += @data_buffer.write_uint16(op_count, @data_offset) + write_field_string(key.namespace, Aerospike::FieldType::NAMESPACE) + write_field_string(key.set_name, Aerospike::FieldType::TABLE) + end + + def write_batch_fields_with_filter(key, filter_exp, field_count, op_count) + if filter_exp + field_count+=1 + write_batch_fields(key, field_count, op_count) + write_filter_exp(filter_exp, filter_exp.size) + else + write_batch_fields(key, field_count, op_count) + end + end + + def write_batch_read(key, attr, filter_exp, op_count) + @data_offset += @data_buffer.write_byte(BATCH_MSG_INFO | BATCH_MSG_TTL, @data_offset) + @data_offset += @data_buffer.write_byte(attr.read_attr, @data_offset) + @data_offset += @data_buffer.write_byte(attr.write_attr, @data_offset) + @data_offset += @data_buffer.write_byte(attr.info_attr, @data_offset) + @data_offset += @data_buffer.write_uint32(attr.expiration, @data_offset) + write_batch_fields_with_filter(key, filter_exp, 0, op_count) + end + + def write_batch_write(key, attr, filter_exp, field_count, op_count) + @data_offset += @data_buffer.write_byte(BATCH_MSG_INFO | BATCH_MSG_GEN | BATCH_MSG_TTL, @data_offset) + @data_offset += @data_buffer.write_byte(attr.read_attr, @data_offset) + @data_offset += @data_buffer.write_byte(attr.write_attr, @data_offset) + @data_offset += @data_buffer.write_byte(attr.info_attr, @data_offset) + @data_offset += @data_buffer.write_uint16(attr.generation, @data_offset) + @data_offset += @data_buffer.write_uint32(attr.expiration, @data_offset) + if attr.send_key + field_count+=1 + write_batch_fields_with_filter(key, filter_exp, field_count, op_count) + write_field_value(key.user_key, KEY) + else + write_batch_fields_with_filter(key, filter_exp, field_count, op_count) + end + end + + def write_batch_bin_names(key, bin_names, attr, filter_exp) + write_batch_read(key, attr, filter_exp, bin_names.length) + bin_names.each do |bin_name| + write_operation_for_bin_name(bin_name, Aerospike::Operation::READ) + end + end + + def write_batch_header(policy, field_count) + read_attr = INFO1_BATCH + read_attr |= INFO1_COMPRESS_RESPONSE if policy.use_compression + #TODO: Add SC Mode + + @data_buffer.write_byte(MSG_REMAINING_HEADER_SIZE, 8) # Message header.length, @data_offset. + @data_buffer.write_byte(read_attr, 9) + @data_buffer.write_byte(0, 10) + @data_buffer.write_byte(0, 11) + + (12...22).each { |i| @data_buffer.write_byte(0, i) } + + # Initialize timeout. It will be written later. + @data_buffer.write_uint32(0, 22) + + @data_buffer.write_uint16(field_count, 26) + @data_buffer.write_uint16(0, 28) + + @data_offset = MSG_TOTAL_HEADER_SIZE + end + def write_key(key, policy = nil) # Write key into buffer. if key.namespace @@ -1199,15 +1222,6 @@ def write_field_header(size, ftype) @data_offset += 1 end - def write_predexp(predexp, predexp_size) - if predexp && !predexp.empty? - write_field_header(predexp_size, Aerospike::FieldType::FILTER_EXP) - @data_offset = Aerospike::PredExp.write( - predexp, @data_buffer, @data_offset - ) - end - end - def write_filter_exp(exp, exp_size) unless exp.nil? write_field_header(exp_size, Aerospike::FieldType::FILTER_EXP) @@ -1238,7 +1252,7 @@ def use_compression? def compress_buffer if @data_offset > COMPRESS_THRESHOLD - compressed = Zlib::deflate(@data_buffer.buf, Zlib::DEFAULT_COMPRESSION) + compressed = Zlib.deflate(@data_buffer.buf, Zlib::DEFAULT_COMPRESSION) # write original size as header proto_s = format("%08d", 0) diff --git a/lib/aerospike/exp/exp.rb b/lib/aerospike/exp/exp.rb index b3f3a099..46fb90e1 100644 --- a/lib/aerospike/exp/exp.rb +++ b/lib/aerospike/exp/exp.rb @@ -96,7 +96,7 @@ def self.key(type) end # Create expression that returns if the primary key is stored in the record meta data - # as a boolean expression. This would occur when {@link Policy#send_key} + # as a boolean expression. This would occur when {Policy#send_key} # is true on record write. This expression usually evaluates quickly because record # meta data is cached in memory. # @@ -214,7 +214,7 @@ def self.bin_exists(name) end # Create expression that returns bin's integer particle type:: - # See {@link ParticleType}. + # See {ParticleType}. # # ==== Examples # # Bin "a" particle type is a list @@ -225,8 +225,8 @@ def self.bin_type(name) # Create expression that returns the record size. This expression usually evaluates # quickly because record meta data is cached in memory. - # Requires server version 7.0+. This expression replaces {@link #deviceSize()} and - # {@link #memorySize()} since those older expressions are equivalent on server version 7.0+. + # Requires server version 7.0+. This expression replaces {#deviceSize()} and + # {#memorySize()} since those older expressions are equivalent on server version 7.0+. # # {@code # // Record size >= 100 KB @@ -347,7 +347,7 @@ def self.digest_modulo(mod) # Exp.regex_compare("prefix.*suffix", RegexFlags.ICASE | RegexFlags.NEWLINE, Exp.str_bin("a")) # # @param regex regular expression string - # @param flags regular expression bit flags. See {@link Exp::RegexFlags} + # @param flags regular expression bit flags. See {Exp::RegexFlags} # @param bin string bin or string value expression def self.regex_compare(regex, flags, bin) Regex.new(bin, regex, flags) @@ -424,6 +424,16 @@ def self.nil_val Nil.new end + # Create Infinity value. + def self.infinity_val + Infinity.new + end + + # Create Wildcard value. + def self.wildcard_val + Wildcard.new + end + #-------------------------------------------------- # Boolean Operator #-------------------------------------------------- @@ -873,7 +883,7 @@ def self.cond(*exps) # # ==== Examples # Args Format: , , ..., - # def: {@link Exp#def(String, Exp)} + # def: {Exp#def(String, Exp)} # exp: Scoped expression # # ==== Examples @@ -887,7 +897,7 @@ def self.let(*exps) Let.new(exps) end - # Assign variable to a {@link Exp#let(Exp...)} expression that can be accessed later. + # Assign variable to a {Exp#let(Exp...)} expression that can be accessed later. # Requires server version 5.6.0+. # # ==== Examples @@ -920,8 +930,8 @@ def self.var(name) #-------------------------------------------------- # Create unknown value. Used to intentionally fail an expression. - # The failure can be ignored with {@link Exp::WriteFlags#EVAL_NO_FAIL} - # or {@link Exp::ReadFlags#EVAL_NO_FAIL}. + # The failure can be ignored with {Exp::WriteFlags#EVAL_NO_FAIL} + # or {Exp::ReadFlags#EVAL_NO_FAIL}. # Requires server version 5.6.0+. # # ==== Examples @@ -1066,10 +1076,7 @@ def self.pack_ctx(packer, ctx) # For internal use only. class Module < Exp - attr_reader :bin - attr_reader :bytes - attr_reader :ret_type - attr_reader :module + attr_reader :bin, :bytes, :ret_type, :module def initialize(bin, bytes, ret_type, modul) @bin = bin @@ -1090,8 +1097,7 @@ def pack(packer) end class Bin < Exp - attr_reader :name - attr_reader :type + attr_reader :name, :type def initialize(name, type) @name = name @@ -1107,9 +1113,7 @@ def pack(packer) end class Regex < Exp - attr_reader :bin - attr_reader :regex - attr_reader :flags + attr_reader :bin, :regex, :flags def initialize(bin, regex, flags) @bin = bin @@ -1135,7 +1139,7 @@ def initialize(exps) def pack(packer) # Let wire format: LET , , , , ..., - count = (@exps.length - 1) * 2 + 2 + count = ((@exps.length - 1) * 2) + 2 packer.write_array_header(count) packer.write(LET) @@ -1146,8 +1150,7 @@ def pack(packer) end class Def < Exp - attr_reader :name - attr_reader :exp + attr_reader :name, :exp def initialize(name, exp) @name = name @@ -1161,8 +1164,7 @@ def pack(packer) end class CmdExp < Exp - attr_reader :exps - attr_reader :cmd + attr_reader :exps, :cmd def initialize(cmd, *exps) @exps = exps @@ -1179,8 +1181,7 @@ def pack(packer) end class CmdInt < Exp - attr_reader :cmd - attr_reader :val + attr_reader :cmd, :val def initialize(cmd, val) @cmd = cmd @@ -1195,8 +1196,7 @@ def pack(packer) end class CmdStr < Exp - attr_reader :str - attr_reader :cmd + attr_reader :str, :cmd def initialize(cmd, str) @str = str @@ -1329,6 +1329,18 @@ def pack(packer) end end + class Infinity < Exp + def pack(packer) + InfinityValue.new.pack(packer) + end + end + + class Wildcard < Exp + def pack(packer) + WildcardValue.new.pack(packer) + end + end + class ExpBytes < Exp attr_reader :bytes diff --git a/lib/aerospike/exp/exp_bit.rb b/lib/aerospike/exp/exp_bit.rb index 2824d313..30a702a7 100644 --- a/lib/aerospike/exp/exp_bit.rb +++ b/lib/aerospike/exp/exp_bit.rb @@ -15,7 +15,7 @@ # the License. module Aerospike - # Bit expression generator. See {@link Exp}. + # Bit expression generator. See {Exp}. # # The bin expression argument in these methods can be a reference to a bin or the # result of another expression. Expressions that modify bin values are only used @@ -42,7 +42,7 @@ class Exp::Bit # Exp.val(2)) def self.resize(byte_size, resize_flags, bin, policy: CDT::BitPolicy::DEFAULT) bytes = Exp.pack(nil, RESIZE, byte_size, policy.flags, resize_flags) - self.add_write(bin, bytes) + add_write(bin, bytes) end # Create expression that inserts value bytes into byte[] bin at byte_offset and returns byte[]. @@ -60,7 +60,7 @@ def self.resize(byte_size, resize_flags, bin, policy: CDT::BitPolicy::DEFAULT) # Exp.val(2)) def self.insert(byte_offset, value, bin, policy: CDT::BitPolicy::DEFAULT) bytes = Exp.pack(nil, INSERT, byte_offset, value, policy.flags) - self.add_write(bin, bytes) + add_write(bin, bytes) end # Create expression that removes bytes from byte[] bin at byte_offset for byte_size and returns byte[]. @@ -78,7 +78,7 @@ def self.insert(byte_offset, value, bin, policy: CDT::BitPolicy::DEFAULT) # Exp.val(2)) def self.remove(byte_offset, byte_size, bin, policy: CDT::BitPolicy::DEFAULT) bytes = Exp.pack(nil, REMOVE, byte_offset, byte_size, policy.flags) - self.add_write(bin, bytes) + add_write(bin, bytes) end # Create expression that sets value on byte[] bin at bit_offset for bit_size and returns byte[]. @@ -97,7 +97,7 @@ def self.remove(byte_offset, byte_size, bin, policy: CDT::BitPolicy::DEFAULT) # Exp.val(2)) def self.set(bit_offset, bit_size, value, bin, policy: CDT::BitPolicy::DEFAULT) bytes = Exp.pack(nil, SET, bit_offset, bit_size, value, policy.flags) - self.add_write(bin, bytes) + add_write(bin, bytes) end # Create expression that performs bitwise "or" on value and byte[] bin at bit_offset for bit_size @@ -111,7 +111,7 @@ def self.set(bit_offset, bit_size, value, bin, policy: CDT::BitPolicy::DEFAULT) # def self.or(bit_offset, bit_size, value, bin, policy: CDT::BitPolicy::DEFAULT) bytes = Exp.pack(nil, OR, bit_offset, bit_size, value, policy.flags) - self.add_write(bin, bytes) + add_write(bin, bytes) end # Create expression that performs bitwise "xor" on value and byte[] bin at bit_offset for bit_size @@ -125,7 +125,7 @@ def self.or(bit_offset, bit_size, value, bin, policy: CDT::BitPolicy::DEFAULT) # def self.xor(bit_offset, bit_size, value, bin, policy: CDT::BitPolicy::DEFAULT) bytes = Exp.pack(nil, XOR, bit_offset, bit_size, value, policy.flags) - self.add_write(bin, bytes) + add_write(bin, bytes) end # Create expression that performs bitwise "and" on value and byte[] bin at bit_offset for bit_size @@ -139,7 +139,7 @@ def self.xor(bit_offset, bit_size, value, bin, policy: CDT::BitPolicy::DEFAULT) # def self.and(bit_offset, bit_size, value, bin, policy: CDT::BitPolicy::DEFAULT) bytes = Exp.pack(nil, AND, bit_offset, bit_size, value, policy.flags) - self.add_write(bin, bytes) + add_write(bin, bytes) end # Create expression that negates byte[] bin starting at bit_offset for bit_size and returns byte[]. @@ -151,7 +151,7 @@ def self.and(bit_offset, bit_size, value, bin, policy: CDT::BitPolicy::DEFAULT) # def self.not(bit_offset, bit_size, bin, policy: CDT::BitPolicy::DEFAULT) bytes = Exp.pack(nil, NOT, bit_offset, bit_size, policy.flags) - self.add_write(bin, bytes) + add_write(bin, bytes) end # Create expression that shifts left byte[] bin starting at bit_offset for bit_size and returns byte[]. @@ -164,7 +164,7 @@ def self.not(bit_offset, bit_size, bin, policy: CDT::BitPolicy::DEFAULT) # def self.lshift(bit_offset, bit_size, shift, bin, policy: CDT::BitPolicy::DEFAULT) bytes = Exp.pack(nil, LSHIFT, bit_offset, bit_size, shift, policy.flags) - self.add_write(bin, bytes) + add_write(bin, bytes) end # Create expression that shifts right byte[] bin starting at bit_offset for bit_size and returns byte[]. @@ -177,12 +177,12 @@ def self.lshift(bit_offset, bit_size, shift, bin, policy: CDT::BitPolicy::DEFAUL # def self.rshift(bit_offset, bit_size, shift, bin, policy: CDT::BitPolicy::DEFAULT) bytes = Exp.pack(nil, RSHIFT, bit_offset, bit_size, shift, policy.flags) - self.add_write(bin, bytes) + add_write(bin, bytes) end # Create expression that adds value to byte[] bin starting at bit_offset for bit_size and returns byte[]. # BitSize must be <= 64. Signed indicates if bits should be treated as a signed number. - # If add overflows/underflows, {@link BitOverflowAction} is used. + # If add overflows/underflows, {BitOverflowAction} is used. # # bin = [0b00000001, 0b01000010, 0b00000011, 0b00000100, 0b00000101] # bit_offset = 24 @@ -192,13 +192,13 @@ def self.rshift(bit_offset, bit_size, shift, bin, policy: CDT::BitPolicy::DEFAUL # bin result = [0b00000001, 0b01000010, 0b00000011, 0b00000100, 0b10000101] # def self.add(bit_offset, bit_size, value, signed, bit_overflow_action, bin, policy: CDT::BitPolicy::DEFAULT) - bytes = self.pack_math(ADD, policy, bit_offset, bit_size, value, signed, bit_overflow_action) - self.add_write(bin, bytes) + bytes = pack_math(ADD, policy, bit_offset, bit_size, value, signed, bit_overflow_action) + add_write(bin, bytes) end # Create expression that subtracts value from byte[] bin starting at bit_offset for bit_size and returns byte[]. # BitSize must be <= 64. Signed indicates if bits should be treated as a signed number. - # If add overflows/underflows, {@link BitOverflowAction} is used. + # If add overflows/underflows, {BitOverflowAction} is used. # # bin = [0b00000001, 0b01000010, 0b00000011, 0b00000100, 0b00000101] # bit_offset = 24 @@ -208,8 +208,8 @@ def self.add(bit_offset, bit_size, value, signed, bit_overflow_action, bin, poli # bin result = [0b00000001, 0b01000010, 0b00000011, 0b0000011, 0b10000101] # def self.subtract(bit_offset, bit_size, value, signed, bit_overflow_action, bin, policy: CDT::BitPolicy::DEFAULT) - bytes = self.pack_math(SUBTRACT, policy, bit_offset, bit_size, value, signed, bit_overflow_action) - self.add_write(bin, bytes) + bytes = pack_math(SUBTRACT, policy, bit_offset, bit_size, value, signed, bit_overflow_action) + add_write(bin, bytes) end # Create expression that sets value to byte[] bin starting at bit_offset for bit_size and returns byte[]. @@ -223,7 +223,7 @@ def self.subtract(bit_offset, bit_size, value, signed, bit_overflow_action, bin, # def self.set_int(bit_offset, bit_size, value, bin, policy: CDT::BitPolicy::DEFAULT) bytes = Exp.pack(nil, SET_INT, bit_offset, bit_size, value, policy.flags) - self.add_write(bin, bytes) + add_write(bin, bytes) end # Create expression that returns bits from byte[] bin starting at bit_offset for bit_size. @@ -240,7 +240,7 @@ def self.set_int(bit_offset, bit_size, value, bin, policy: CDT::BitPolicy::DEFAU # Exp.val(new byte[] {(byte)0b10000000})) def self.get(bit_offset, bit_size, bin) bytes = Exp.pack(nil, GET, bit_offset, bit_size) - self.add_read(bin, bytes, Exp::Type::BLOB) + add_read(bin, bytes, Exp::Type::BLOB) end # Create expression that returns integer count of set bits from byte[] bin starting at @@ -256,7 +256,7 @@ def self.get(bit_offset, bit_size, bin) # Exp.le(BitExp.count(Exp.val(0), Exp.val(5), Exp.blobBin("a")), Exp.val(2)) def self.count(bit_offset, bit_size, bin) bytes = Exp.pack(nil, COUNT, bit_offset, bit_size) - self.add_read(bin, bytes, Exp::Type::INT) + add_read(bin, bytes, Exp::Type::INT) end # Create expression that returns integer bit offset of the first specified value bit in byte[] bin @@ -278,7 +278,7 @@ def self.count(bit_offset, bit_size, bin) # @param bin bin or blob value expression def self.lscan(bit_offset, bit_size, value, bin) bytes = Exp.pack(nil, LSCAN, bit_offset, bit_size, value) - self.add_read(bin, bytes, Exp::Type::INT) + add_read(bin, bytes, Exp::Type::INT) end # Create expression that returns integer bit offset of the last specified value bit in byte[] bin @@ -301,7 +301,7 @@ def self.lscan(bit_offset, bit_size, value, bin) # @param bin bin or blob value expression def self.rscan(bit_offset, bit_size, value, bin) bytes = Exp.pack(nil, RSCAN, bit_offset, bit_size, value) - self.add_read(bin, bytes, Exp::Type::INT) + add_read(bin, bytes, Exp::Type::INT) end # Create expression that returns integer from byte[] bin starting at bit_offset for bit_size. @@ -317,8 +317,8 @@ def self.rscan(bit_offset, bit_size, value, bin) # # getInt(a) == 16899 # Exp.eq(BitExp.getInt(Exp.val(8), Exp.val(16), false, Exp.blobBin("a")), Exp.val(16899)) def self.get_int(bit_offset, bit_size, signed, bin) - bytes = self.pack_get_int(bit_offset, bit_size, signed) - self.add_read(bin, bytes, Exp::Type::INT) + bytes = pack_get_int(bit_offset, bit_size, signed) + add_read(bin, bytes, Exp::Type::INT) end private diff --git a/lib/aerospike/exp/exp_hll.rb b/lib/aerospike/exp/exp_hll.rb index 16b4c53e..156c4414 100644 --- a/lib/aerospike/exp/exp_hll.rb +++ b/lib/aerospike/exp/exp_hll.rb @@ -15,7 +15,7 @@ # the License. module Aerospike - # HyperLogLog (HLL) expression generator. See {@link Exp}. + # HyperLogLog (HLL) expression generator. See {Exp}. # # The bin expression argument in these methods can be a reference to a bin or the # result of another expression. Expressions that modify bin values are only used @@ -25,14 +25,14 @@ class Exp::HLL # Create expression that creates a new HLL or resets an existing HLL with minhash bits. # - # @param policy write policy, use {@link HLLPolicy#Default} for default + # @param policy write policy, use {HLLPolicy#Default} for default # @param index_bit_count number of index bits. Must be between 4 and 16 inclusive. # @param min_hash_bit_count number of min hash bits. Must be between 4 and 51 inclusive. # Also, index_bit_count + min_hash_bit_count must be <= 64. Optional. # @param bin HLL bin or value expression def self.init(index_bit_count, bin, min_hash_bit_count: Exp.int_val(-1), policy: CDT::HLLPolicy::DEFAULT) bytes = Exp.pack(nil, INIT, index_bit_count, min_hash_bit_count, policy.flags) - self.add_write(bin, bytes) + add_write(bin, bytes) end # Create expression that adds values to a HLL set and returns HLL set. If HLL bin does not @@ -45,7 +45,7 @@ def self.init(index_bit_count, bin, min_hash_bit_count: Exp.int_val(-1), policy: # HLLExp.add(HLLPolicy.Default, Exp.val(list), Exp.val(10), Exp.val(20), Exp.hllBin("a"))), # Exp.val(7)) # - # @param policy write policy, use {@link HLLPolicy#Default} for default + # @param policy write policy, use {HLLPolicy#Default} for default # @param list list bin or value expression of values to be added # @param index_bit_count number of index bits expression. Must be between 4 and 16 inclusive. # @param min_hash_bit_count number of min hash bits expression. Must be between 4 and 51 inclusive. @@ -53,7 +53,7 @@ def self.init(index_bit_count, bin, min_hash_bit_count: Exp.int_val(-1), policy: # @param bin HLL bin or value expression def self.add(list, bin, policy: CDT::HLLPolicy::DEFAULT, index_bit_count: Exp.val(-1), min_hash_bit_count: Exp.val(-1)) bytes = Exp.pack(nil, ADD, list, index_bit_count, min_hash_bit_count, policy.flags) - self.add_write(bin, bytes) + add_write(bin, bytes) end # Create expression that returns estimated number of elements in the HLL bin. @@ -63,7 +63,7 @@ def self.add(list, bin, policy: CDT::HLLPolicy::DEFAULT, index_bit_count: Exp.va # Exp.gt(HLLExp.getCount(Exp.hllBin("a")), Exp.val(7)) def self.get_count(bin) bytes = Exp.pack(nil, COUNT) - self.add_read(bin, bytes, Exp::Type::INT) + add_read(bin, bytes, Exp::Type::INT) end # Create expression that returns a HLL object that is the union of all specified HLL objects @@ -77,7 +77,7 @@ def self.get_count(bin) # HLLExp.getUnion(Exp.val(list), Exp.hllBin("b")) def self.get_union(list, bin) bytes = Exp.pack(nil, UNION, list) - self.add_read(bin, bytes, Exp::Type::HLL) + add_read(bin, bytes, Exp::Type::HLL) end # Create expression that returns estimated number of elements that would be contained by @@ -91,7 +91,7 @@ def self.get_union(list, bin) # HLLExp.getUnionCount(Exp.val(list), Exp.hllBin("b")) def self.get_union_count(list, bin) bytes = Exp.pack(nil, UNION_COUNT, list) - self.add_read(bin, bytes, Exp::Type::INT) + add_read(bin, bytes, Exp::Type::INT) end # Create expression that returns estimated number of elements that would be contained by @@ -105,7 +105,7 @@ def self.get_union_count(list, bin) # HLLExp.getIntersectCount(Exp.val(list), Exp.hllBin("b")) def self.get_intersect_count(list, bin) bytes = Exp.pack(nil, INTERSECT_COUNT, list) - self.add_read(bin, bytes, Exp::Type::INT) + add_read(bin, bytes, Exp::Type::INT) end # Create expression that returns estimated similarity of these HLL objects as a @@ -116,7 +116,7 @@ def self.get_intersect_count(list, bin) # Exp.ge(HLLExp.getSimilarity(Exp.hllBin("a"), Exp.hllBin("b")), Exp.val(0.75)) def self.get_similarity(list, bin) bytes = Exp.pack(nil, SIMILARITY, list) - self.add_read(bin, bytes, Exp::Type::FLOAT) + add_read(bin, bytes, Exp::Type::FLOAT) end # Create expression that returns index_bit_count and min_hash_bit_count used to create HLL bin @@ -130,7 +130,7 @@ def self.get_similarity(list, bin) # Exp.val(10)) def self.describe(bin) bytes = Exp.pack(nil, DESCRIBE) - self.add_read(bin, bytes, Exp::Type::LIST) + add_read(bin, bytes, Exp::Type::LIST) end # Create expression that returns one if HLL bin may contain all items in the list. @@ -142,7 +142,7 @@ def self.describe(bin) # Exp.eq(HLLExp.mayContain(Exp.val(list), Exp.hllBin("a")), Exp.val(1)) def self.may_contain(list, bin) bytes = Exp.pack(nil, MAY_CONTAIN, list) - self.add_read(bin, bytes, Exp::Type::INT) + add_read(bin, bytes, Exp::Type::INT) end private diff --git a/lib/aerospike/exp/exp_list.rb b/lib/aerospike/exp/exp_list.rb index 70a03007..0235ae21 100644 --- a/lib/aerospike/exp/exp_list.rb +++ b/lib/aerospike/exp/exp_list.rb @@ -16,7 +16,7 @@ module Aerospike - # List expression generator. See {@link Exp}. + # List expression generator. See {Exp}. # # The bin expression argument in these methods can be a reference to a bin or the # result of another expression. Expressions that modify bin values are only used @@ -50,74 +50,74 @@ class Exp::List # Create expression that appends value to end of list. def self.append(value, bin, ctx: nil, policy: CDT::ListPolicy::DEFAULT) bytes = Exp.pack(ctx, APPEND, value, policy.order, policy.flags) - self.add_write(bin, bytes, ctx) + add_write(bin, bytes, ctx) end # Create expression that appends list items to end of list. def self.append_items(list, bin, ctx: nil, policy: CDT::ListPolicy::DEFAULT) bytes = Exp.pack(ctx, APPEND_ITEMS, list, policy.order, policy.flags) - self.add_write(bin, bytes, ctx) + add_write(bin, bytes, ctx) end # Create expression that inserts value to specified index of list. def self.insert(index, value, bin, ctx: nil, policy: CDT::ListPolicy::DEFAULT) bytes = Exp.pack(ctx, INSERT, index, value, policy.flags) - self.add_write(bin, bytes, ctx) + add_write(bin, bytes, ctx) end # Create expression that inserts each input list item starting at specified index of list. def self.insert_items(index, list, bin, ctx: nil, policy: CDT::ListPolicy::DEFAULT) bytes = Exp.pack(ctx, INSERT_ITEMS, index, list, policy.flags) - self.add_write(bin, bytes, ctx) + add_write(bin, bytes, ctx) end # Create expression that increments list[index] by value. # Value expression should resolve to a number. def self.increment(index, value, bin, ctx: nil, policy: CDT::ListPolicy::DEFAULT) bytes = Exp.pack(ctx, INCREMENT, index, value, policy.order, policy.flags) - self.add_write(bin, bytes, ctx) + add_write(bin, bytes, ctx) end # Create expression that sets item value at specified index in list. def self.set(index, value, bin, ctx: nil, policy: CDT::ListPolicy::DEFAULT) bytes = Exp.pack(ctx, SET, index, value, policy.flags) - self.add_write(bin, bytes, ctx) + add_write(bin, bytes, ctx) end # Create expression that removes all items in list. def self.clear(bin, ctx: nil) bytes = Exp.pack(ctx, CLEAR) - self.add_write(bin, bytes, ctx) + add_write(bin, bytes, ctx) end # Create expression that sorts list according to sort_flags. # - # @param sort_flags sort flags. See {@link ListSortFlagsend. + # @param sort_flags sort flags. See {ListSortFlagsend. # @param bin bin or list value expression # @param ctx optional context path for nested CDT def self.sort(sort_flags, bin, ctx: nil) bytes = Exp.pack(ctx, SORT, sort_flags) - self.add_write(bin, bytes, ctx) + add_write(bin, bytes, ctx) end # Create expression that removes list items identified by value. def self.remove_by_value(value, bin, ctx: nil) bytes = Exp.pack(ctx, REMOVE_BY_VALUE, CDT::ListReturnType::NONE, value) - self.add_write(bin, bytes, ctx) + add_write(bin, bytes, ctx) end # Create expression that removes list items identified by values. def self.remove_by_value_list(values, bin, ctx: nil) bytes = Exp.pack(ctx, REMOVE_BY_VALUE_LIST, CDT::ListReturnType::NONE, values) - self.add_write(bin, bytes, ctx) + add_write(bin, bytes, ctx) end # Create expression that removes list items identified by value range (value_begin inclusive, value_end exclusive). # If value_begin is nil, the range is less than value_end. If value_end is nil, the range is # greater than equal to value_begin. def self.remove_by_value_range(value_begin, value_end, bin, ctx: nil) - bytes = self.pack_range_operation(REMOVE_BY_VALUE_INTERVAL, CDT::ListReturnType::NONE, value_begin, value_end, ctx) - self.add_write(bin, bytes, ctx) + bytes = pack_range_operation(REMOVE_BY_VALUE_INTERVAL, CDT::ListReturnType::NONE, value_begin, value_end, ctx) + add_write(bin, bytes, ctx) end # Create expression that removes list items nearest to value and greater by relative rank with a count limit if provided. @@ -132,44 +132,44 @@ def self.remove_by_value_range(value_begin, value_end, bin, ctx: nil) # (3,3,7) = [11,15] # (3,-3,2) = [] def self.remove_by_value_relative_rank_range(value, rank, bin, ctx: nil, count: nil) - unless count.nil? - bytes = Exp.pack(ctx, REMOVE_BY_VALUE_REL_RANK_RANGE, CDT::ListReturnType::NONE, value, rank, count) - else - bytes = Exp.pack(ctx, REMOVE_BY_VALUE_REL_RANK_RANGE, CDT::ListReturnType::NONE, value, rank) - end - self.add_write(bin, bytes, ctx) + bytes = if count.nil? + Exp.pack(ctx, REMOVE_BY_VALUE_REL_RANK_RANGE, CDT::ListReturnType::NONE, value, rank) + else + Exp.pack(ctx, REMOVE_BY_VALUE_REL_RANK_RANGE, CDT::ListReturnType::NONE, value, rank, count) + end + add_write(bin, bytes, ctx) end # Create expression that removes list item identified by index. def self.remove_by_index(index, bin, ctx: nil) bytes = Exp.pack(ctx, REMOVE_BY_INDEX, CDT::ListReturnType::NONE, index) - self.add_write(bin, bytes, ctx) + add_write(bin, bytes, ctx) end # Create expression that removes "count" list items starting at specified index. def self.remove_by_index_range(index, bin, ctx: nil, count: nil) - unless count.nil? - bytes = Exp.pack(ctx, REMOVE_BY_INDEX_RANGE, CDT::ListReturnType::NONE, index, count) - else - bytes = Exp.pack(ctx, REMOVE_BY_INDEX_RANGE, CDT::ListReturnType::NONE, index) - end - self.add_write(bin, bytes, ctx) + bytes = if count.nil? + Exp.pack(ctx, REMOVE_BY_INDEX_RANGE, CDT::ListReturnType::NONE, index) + else + Exp.pack(ctx, REMOVE_BY_INDEX_RANGE, CDT::ListReturnType::NONE, index, count) + end + add_write(bin, bytes, ctx) end # Create expression that removes list item identified by rank. def self.remove_by_rank(rank, bin, ctx: nil) bytes = Exp.pack(ctx, REMOVE_BY_RANK, CDT::ListReturnType::NONE, rank) - self.add_write(bin, bytes, ctx) + add_write(bin, bytes, ctx) end # Create expression that removes "count" list items starting at specified rank. def self.remove_by_rank_range(rank, bin, ctx: nil, count: nil) - unless count.nil? - bytes = Exp.pack(ctx, REMOVE_BY_RANK_RANGE, CDT::ListReturnType::NONE, rank, count) - else - bytes = Exp.pack(ctx, REMOVE_BY_RANK_RANGE, CDT::ListReturnType::NONE, rank) - end - self.add_write(bin, bytes, ctx) + bytes = if count.nil? + Exp.pack(ctx, REMOVE_BY_RANK_RANGE, CDT::ListReturnType::NONE, rank) + else + Exp.pack(ctx, REMOVE_BY_RANK_RANGE, CDT::ListReturnType::NONE, rank, count) + end + add_write(bin, bytes, ctx) end # Create expression that returns list size. @@ -180,7 +180,7 @@ def self.remove_by_rank_range(rank, bin, ctx: nil, count: nil) # end def self.size(bin, ctx: nil) bytes = Exp.pack(ctx, SIZE) - self.add_read(bin, bytes, Exp::Type::INT) + add_read(bin, bytes, Exp::Type::INT) end # Create expression that selects list items identified by value and returns selected @@ -193,13 +193,13 @@ def self.size(bin, ctx: nil) # Exp.val(0)) # end # - # @param return_type metadata attributes to return. See {@link CDT::ListReturnTypeend + # @param return_type metadata attributes to return. See {CDT::ListReturnType} # @param value search expression # @param bin list bin or list value expression # @param ctx optional context path for nested CDT def self.get_by_value(return_type, value, bin, ctx: nil) bytes = Exp.pack(ctx, GET_BY_VALUE, return_type, value) - self.add_read(bin, bytes, get_value_type(return_type)) + add_read(bin, bytes, get_value_type(return_type)) end # Create expression that selects list items identified by value range and returns selected data @@ -210,25 +210,25 @@ def self.get_by_value(return_type, value, bin, ctx: nil) # ListExp.getByValueRange(CDT::ListReturnType::VALUE, Exp.val(10), Exp.val(20), Exp.listBin("a")) # end # - # @param return_type metadata attributes to return. See {@link CDT::ListReturnTypeend + # @param return_type metadata attributes to return. See {CDT::ListReturnType} # @param value_begin begin expression inclusive. If nil, range is less than value_end. # @param value_end end expression exclusive. If nil, range is greater than equal to value_begin. # @param bin bin or list value expression # @param ctx optional context path for nested CDT def self.get_by_value_range(return_type, value_begin, value_end, bin, ctx: nil) - bytes = self.pack_range_operation(GET_BY_VALUE_INTERVAL, return_type, value_begin, value_end, ctx) - self.add_read(bin, bytes, get_value_type(return_type)) + bytes = pack_range_operation(GET_BY_VALUE_INTERVAL, return_type, value_begin, value_end, ctx) + add_read(bin, bytes, get_value_type(return_type)) end # Create expression that selects list items identified by values and returns selected data # specified by return_type. def self.get_by_value_list(return_type, values, bin, ctx: nil) bytes = Exp.pack(ctx, GET_BY_VALUE_LIST, return_type, values) - self.add_read(bin, bytes, get_value_type(return_type)) + add_read(bin, bytes, get_value_type(return_type)) end # Create expression that selects list items nearest to value and greater by relative rank with a count limit - # and returns selected data specified by return_type (See {@link CDT::ListReturnTypeend). + # and returns selected data specified by return_type (See {CDT::ListReturnType}). # # Examples for ordered list [0,4,5,9,11,15]: # @@ -240,12 +240,12 @@ def self.get_by_value_list(return_type, values, bin, ctx: nil) # (3,3,7) = [11,15] # (3,-3,2) = [] def self.get_by_value_relative_rank_range(return_type, value, rank, bin, ctx: nil, count: nil) - unless count.nil? - bytes = Exp.pack(ctx, GET_BY_VALUE_REL_RANK_RANGE, return_type, value, rank, count) - else - bytes = Exp.pack(ctx, GET_BY_VALUE_REL_RANK_RANGE, return_type, value, rank) - end - self.add_read(bin, bytes, get_value_type(return_type)) + bytes = if count.nil? + Exp.pack(ctx, GET_BY_VALUE_REL_RANK_RANGE, return_type, value, rank) + else + Exp.pack(ctx, GET_BY_VALUE_REL_RANK_RANGE, return_type, value, rank, count) + end + add_read(bin, bytes, get_value_type(return_type)) end # Create expression that selects list item identified by index and returns @@ -258,32 +258,32 @@ def self.get_by_value_relative_rank_range(return_type, value, rank, bin, ctx: ni # Exp.val(5)) # end # - # @param return_type metadata attributes to return. See {@link CDT::ListReturnTypeend + # @param return_type metadata attributes to return. See {CDT::ListReturnType} # @param value_type expected type of value # @param index list index expression # @param bin list bin or list value expression # @param ctx optional context path for nested CDT def self.get_by_index(return_type, value_type, index, bin, ctx: nil) bytes = Exp.pack(ctx, GET_BY_INDEX, return_type, index) - self.add_read(bin, bytes, value_type) + add_read(bin, bytes, value_type) end # Create expression that selects list items starting at specified index to the end of list - # and returns selected data specified by return_type (See {@link CDT::ListReturnTypeend). + # and returns selected data specified by return_type (See {CDT::ListReturnType}). def self.get_by_index_range(return_type, index, bin, ctx: nil) bytes = Exp.pack(ctx, GET_BY_INDEX_RANGE, return_type, index) - self.add_read(bin, bytes, get_value_type(return_type)) + add_read(bin, bytes, get_value_type(return_type)) end # Create expression that selects "count" list items starting at specified index - # and returns selected data specified by return_type (See {@link CDT::ListReturnTypeend). + # and returns selected data specified by return_type (See {CDT::ListReturnType}). def self.get_by_index_range(return_type, index, bin, ctx: nil, count: nil) - unless count.nil? - bytes = Exp.pack(ctx, GET_BY_INDEX_RANGE, return_type, index, count) - else - bytes = Exp.pack(ctx, GET_BY_INDEX_RANGE, return_type, index) - end - self.add_read(bin, bytes, get_value_type(return_type)) + bytes = if count.nil? + Exp.pack(ctx, GET_BY_INDEX_RANGE, return_type, index) + else + Exp.pack(ctx, GET_BY_INDEX_RANGE, return_type, index, count) + end + add_read(bin, bytes, get_value_type(return_type)) end # Create expression that selects list item identified by rank and returns selected @@ -294,32 +294,32 @@ def self.get_by_index_range(return_type, index, bin, ctx: nil, count: nil) # ListExp.getByRank(CDT::ListReturnType::VALUE, Type.STRING, Exp.val(0), Exp.listBin("a")) # end # - # @param return_type metadata attributes to return. See {@link CDT::ListReturnTypeend + # @param return_type metadata attributes to return. See {CDT::ListReturnType} # @param value_type expected type of value # @param rank rank expression # @param bin list bin or list value expression # @param ctx optional context path for nested CDT def self.get_by_rank(return_type, value_type, rank, bin, ctx: nil) bytes = Exp.pack(ctx, GET_BY_RANK, return_type, rank) - self.add_read(bin, bytes, value_type) + add_read(bin, bytes, value_type) end # Create expression that selects list items starting at specified rank to the last ranked item - # and returns selected data specified by return_type (See {@link CDT::ListReturnTypeend). + # and returns selected data specified by return_type (See {CDT::ListReturnType}). def self.get_by_rank_range(return_type, rank, bin, ctx: nil) bytes = Exp.pack(ctx, GET_BY_RANK_RANGE, return_type, rank) - self.add_read(bin, bytes, get_value_type(return_type)) + add_read(bin, bytes, get_value_type(return_type)) end # Create expression that selects "count" list items starting at specified rank and returns - # selected data specified by return_type (See {@link CDT::ListReturnTypeend). + # selected data specified by return_type (See {CDT::ListReturnType}). def self.get_by_rank_range(return_type, rank, bin, ctx: nil, count: nil) - unless count.nil? - bytes = Exp.pack(ctx, GET_BY_RANK_RANGE, return_type, rank, count) - else - bytes = Exp.pack(ctx, GET_BY_RANK_RANGE, return_type, rank) - end - self.add_read(bin, bytes, get_value_type(return_type)) + bytes = if count.nil? + Exp.pack(ctx, GET_BY_RANK_RANGE, return_type, rank) + else + Exp.pack(ctx, GET_BY_RANK_RANGE, return_type, rank, count) + end + add_read(bin, bytes, get_value_type(return_type)) end private @@ -336,7 +336,7 @@ def self.get_by_rank_range(return_type, rank, bin, ctx: nil, count: nil) SIZE = 16 GET_BY_INDEX = 19 GET_BY_RANK = 21 - GET_BY_VALUE = 22 # GET_ALL_BY_VALUE on server + GET_BY_VALUE = 22 # GET_ALL_BY_VALUE on server GET_BY_VALUE_LIST = 23 GET_BY_INDEX_RANGE = 24 GET_BY_VALUE_INTERVAL = 25 @@ -352,11 +352,11 @@ def self.get_by_rank_range(return_type, rank, bin, ctx: nil, count: nil) REMOVE_BY_VALUE_REL_RANK_RANGE = 40 def self.add_write(bin, bytes, ctx) - if ctx.to_a.empty? - ret_type = Exp::Type::LIST - else - ret_type = ((ctx[0].id & 0x10) == 0) ? Exp::Type::MAP : Exp::Type::LIST - end + ret_type = if ctx.to_a.empty? + Exp::Type::LIST + else + (ctx[0].id & 0x10) == 0 ? Exp::Type::MAP : Exp::Type::LIST + end Exp::Module.new(bin, bytes, ret_type, MODULE | Exp::MODIFY) end @@ -365,10 +365,27 @@ def self.add_read(bin, bytes, ret_type) end def self.get_value_type(return_type) - if (return_type & ~CDT::ListReturnType::INVERTED) == CDT::ListReturnType::VALUE - Exp::Type::LIST - else + t = return_type & ~CDT::ListReturnType::INVERTED + case t + when ListReturnType::INDEX, + ListReturnType::REVERSE_INDEX, + ListReturnType::RANK, + ListReturnType::REVERSE_RANK + # This method only called from expressions that can return multiple integers (ie list). + Exp::Type::LIST + + when ListReturnType::COUNT Exp::Type::INT + + when ListReturnType::VALUE + # This method only called from expressions that can return multiple objects (ie list):: + Exp::Type::LIST + + when ListReturnType::EXISTS + Exp::Type::BOOL + + else + raise Exceptions::Aerospike.new(Aerospike::ResultCode::PARAMETER_ERROR, "Invalid ListReturnType: #{return_type}") end end @@ -379,14 +396,12 @@ def self.pack_range_operation(command, return_type, value_begin, value_end, ctx) packer.write(command) packer.write(return_type) - unless value_begin.nil? - if value_begin.is_a?(Exp) - value_begin.pack(packer) - else - Value.of(value_begin).pack(packer) - end - else + if value_begin.nil? packer.write(nil) + elsif value_begin.is_a?(Exp) + value_begin.pack(packer) + else + Value.of(value_begin).pack(packer) end unless value_end.nil? diff --git a/lib/aerospike/exp/exp_map.rb b/lib/aerospike/exp/exp_map.rb index 0cae9958..6dcc686d 100644 --- a/lib/aerospike/exp/exp_map.rb +++ b/lib/aerospike/exp/exp_map.rb @@ -4,7 +4,7 @@ # Portions may be licensed to Aerospike, Inc. under one or more contributor # license agreements. # -# Licensed under the Apache License, Version 2.0 (the "License"); you may no +# Licensed under the Apache License, Version 2.0 (the "License") you may no # use this file except in compliance with the License. You may obtain a copy of # the License at http:#www.apache.org/licenses/LICENSE-2.0 # @@ -15,7 +15,7 @@ # the License. module Aerospike - # Map expression generator. See {@link Exp}. + # Map expression generator. See {Exp}. # # The bin expression argument in these methods can be a reference to a bin or the # result of another expression. Expressions that modify bin values are only used @@ -80,24 +80,22 @@ def self.put(key, value, bin, ctx: nil, policy: CDT::MapPolicy::DEFAULT) value.pack(packer) packer.write(policy.attributes) packer.write(policy.flags) + elsif policy.item_command == REPLACE + Exp.pack_ctx(packer, ctx) + packer.write_array_header(3) + packer.write(policy.item_command) + key.pack(packer) + value.pack(packer) + # Replace doesn't allow map attributes because it does not create on non-existing key. else - if policy.item_command == REPLACE - # Replace doesn't allow map attributes because it does not create on non-existing key. - Exp.pack_ctx(packer, ctx) - packer.write_array_header(3) - packer.write(policy.item_command) - key.pack(packer) - value.pack(packer) - else Exp.pack_ctx(packer, ctx) packer.write_array_header(4) packer.write(policy.item_command) key.pack(packer) value.pack(packer) packer.write(policy.attributes) - end end - self.add_write(bin, packer.bytes, ctx) + add_write(bin, packer.bytes, ctx) end end @@ -111,22 +109,20 @@ def self.put_items(map, bin, ctx: nil, policy: CDT::MapPolicy::DEFAULT) map.pack(packer) packer.write(policy.attributes) packer.write(policy.flags) + elsif policy.items_command == REPLACE_ITEMS + Exp.pack_ctx(packer, ctx) + packer.write_array_header(2) + packer.write(policy.items_command) + map.pack(packer) + # Replace doesn't allow map attributes because it does not create on non-existing key. else - if policy.items_command == REPLACE_ITEMS - # Replace doesn't allow map attributes because it does not create on non-existing key. - Exp.pack_ctx(packer, ctx) - packer.write_array_header(2) - packer.write(policy.items_command) - map.pack(packer) - else Exp.pack_ctx(packer, ctx) packer.write_array_header(3) packer.write(policy.items_command) map.pack(packer) packer.write(policy.attributes) - end end - self.add_write(bin, packer.bytes, ctx) + add_write(bin, packer.bytes, ctx) end end @@ -134,25 +130,25 @@ def self.put_items(map, bin, ctx: nil, policy: CDT::MapPolicy::DEFAULT) # Valid only for numbers. def self.increment(key, incr, bin, ctx: nil, policy: CDT::MapPolicy::DEFAULT) bytes = Exp.pack(ctx, INCREMENT, key, incr, policy.attributes) - return self.add_write(bin, bytes, ctx) + add_write(bin, bytes, ctx) end # Create expression that removes all items in map. def self.clear(bin, ctx: nil) bytes = Exp.pack(ctx, CLEAR) - return self.add_write(bin, bytes, ctx) + add_write(bin, bytes, ctx) end # Create expression that removes map item identified by key. def self.remove_by_key(key, bin, ctx: nil) bytes = Exp.pack(ctx, REMOVE_BY_KEY, CDT::MapReturnType::NONE, key) - return self.add_write(bin, bytes, ctx) + add_write(bin, bytes, ctx) end # Create expression that removes map items identified by keys. def self.remove_by_key_list(keys, bin, ctx: nil) bytes = Exp.pack(ctx, REMOVE_BY_KEY_LIST, CDT::MapReturnType::NONE, keys) - return self.add_write(bin, bytes, ctx) + add_write(bin, bytes, ctx) end # Create expression that removes map items identified by key range (key_begin inclusive, key_end exclusive). @@ -160,7 +156,7 @@ def self.remove_by_key_list(keys, bin, ctx: nil) # If key_end is nil, the range is greater than equal to key_begin. def self.remove_by_key_range(key_begin, key_end, bin, ctx: nil) bytes = Exp::List.pack_range_operation(REMOVE_BY_KEY_INTERVAL, CDT::MapReturnType::NONE, key_begin, key_end, ctx) - return self.add_write(bin, bytes, ctx) + add_write(bin, bytes, ctx) end # Create expression that removes map items nearest to key and greater by index with a count limit if provided. @@ -174,24 +170,24 @@ def self.remove_by_key_range(key_begin, key_end, bin, ctx: nil) # (3,2,1) = [{9=10}] # (3,-2,2) = [{0=17}] def self.remove_by_key_relative_index_range(key, index, bin, ctx: nil, count: nil) - unless count.nil? - bytes = Exp.pack(ctx, REMOVE_BY_KEY_REL_INDEX_RANGE, CDT::MapReturnType::NONE, key, index, count) - else - bytes = Exp.pack(ctx, REMOVE_BY_KEY_REL_INDEX_RANGE, CDT::MapReturnType::NONE, key, index) - end - return self.add_write(bin, bytes, ctx) + bytes = if count.nil? + Exp.pack(ctx, REMOVE_BY_KEY_REL_INDEX_RANGE, CDT::MapReturnType::NONE, key, index) + else + Exp.pack(ctx, REMOVE_BY_KEY_REL_INDEX_RANGE, CDT::MapReturnType::NONE, key, index, count) + end + add_write(bin, bytes, ctx) end # Create expression that removes map items identified by value. def self.remove_by_value(value, bin, ctx: nil) bytes = Exp.pack(ctx, REMOVE_BY_VALUE, CDT::MapReturnType::NONE, value) - return self.add_write(bin, bytes, ctx) + add_write(bin, bytes, ctx) end # Create expression that removes map items identified by values. def self.remove_by_value_list(values, bin, ctx: nil) bytes = Exp.pack(ctx, REMOVE_BY_VALUE_LIST, CDT::MapReturnType::NONE, values) - return self.add_write(bin, bytes, ctx) + add_write(bin, bytes, ctx) end # Create expression that removes map items identified by value range (valueBegin inclusive, valueEnd exclusive). @@ -199,7 +195,7 @@ def self.remove_by_value_list(values, bin, ctx: nil) # If valueEnd is nil, the range is greater than equal to valueBegin. def self.remove_by_value_range(valueBegin, valueEnd, bin, ctx: nil) bytes = Exp::List.pack_range_operation(REMOVE_BY_VALUE_INTERVAL, CDT::MapReturnType::NONE, valueBegin, valueEnd, ctx) - return self.add_write(bin, bytes, ctx) + add_write(bin, bytes, ctx) end # Create expression that removes map items nearest to value and greater by relative rank. @@ -211,7 +207,7 @@ def self.remove_by_value_range(valueBegin, valueEnd, bin, ctx: nil) # (11,-1) = [{9=10},{5=15},{0=17}] def self.remove_by_value_relative_rank_range(value, rank, bin, ctx: nil) bytes = Exp.pack(ctx, REMOVE_BY_VALUE_REL_RANK_RANGE, CDT::MapReturnType::NONE, value, rank) - return self.add_write(bin, bytes, ctx) + add_write(bin, bytes, ctx) end # Create expression that removes map items nearest to value and greater by relative rank with a count limit. @@ -223,40 +219,40 @@ def self.remove_by_value_relative_rank_range(value, rank, bin, ctx: nil) # (11,-1,1) = [{9=10}] def self.remove_by_value_relative_rank_range(value, rank, count, bin, ctx: nil) bytes = Exp.pack(ctx, REMOVE_BY_VALUE_REL_RANK_RANGE, CDT::MapReturnType::NONE, value, rank, count) - return self.add_write(bin, bytes, ctx) + add_write(bin, bytes, ctx) end # Create expression that removes map item identified by index. def self.remove_by_index(index, bin, ctx: nil) bytes = Exp.pack(ctx, REMOVE_BY_INDEX, CDT::MapReturnType::NONE, index) - return self.add_write(bin, bytes, ctx) + add_write(bin, bytes, ctx) end # Create expression that removes "count" map items starting at specified index limited by count if provided. def self.remove_by_index_range(index, bin, ctx: nil, count: nil) - unless count.nil? - bytes = Exp.pack(ctx, REMOVE_BY_INDEX_RANGE, CDT::MapReturnType::NONE, index, count) - else - bytes = Exp.pack(ctx, REMOVE_BY_INDEX_RANGE, CDT::MapReturnType::NONE, index) - end - return self.add_write(bin, bytes, ctx) + bytes = if count.nil? + Exp.pack(ctx, REMOVE_BY_INDEX_RANGE, CDT::MapReturnType::NONE, index) + else + Exp.pack(ctx, REMOVE_BY_INDEX_RANGE, CDT::MapReturnType::NONE, index, count) + end + add_write(bin, bytes, ctx) end # Create expression that removes map item identified by rank. def self.remove_by_rank(rank, bin, ctx: nil) bytes = Exp.pack(ctx, REMOVE_BY_RANK, CDT::MapReturnType::NONE, rank) - return self.add_write(bin, bytes, ctx) + add_write(bin, bytes, ctx) end # Create expression that removes "count" map items starting at specified rank. If count is not provided, # all items until the last ranked item will be removed def self.remove_by_rank_range(rank, bin, ctx: nil, count: nil) - unless count.nil? - bytes = Exp.pack(ctx, REMOVE_BY_RANK_RANGE, CDT::MapReturnType::NONE, rank, count) - else - bytes = Exp.pack(ctx, REMOVE_BY_RANK_RANGE, CDT::MapReturnType::NONE, rank) - end - return self.add_write(bin, bytes, ctx) + bytes = if count.nil? + Exp.pack(ctx, REMOVE_BY_RANK_RANGE, CDT::MapReturnType::NONE, rank) + else + Exp.pack(ctx, REMOVE_BY_RANK_RANGE, CDT::MapReturnType::NONE, rank, count) + end + add_write(bin, bytes, ctx) end # Create expression that returns list size. @@ -266,7 +262,7 @@ def self.remove_by_rank_range(rank, bin, ctx: nil, count: nil) # Exp.gt(MapExp.size(mapBin("a")), Exp.val(7)) def self.size(bin, ctx: nil) bytes = Exp.pack(ctx, SIZE) - return self.add_read(bin, bytes, Exp::Type::INT) + add_read(bin, bytes, Exp::Type::INT) end # Create expression that selects map item identified by key and returns selected data @@ -278,35 +274,35 @@ def self.size(bin, ctx: nil) # MapExp.getByKey(CDT::MapReturnType::COUNT, Exp::Type::INT, Exp.val("B"), Exp.mapBin("a")), # Exp.val(0)) # - # @param return_type metadata attributes to return. See {@link MapReturnType} + # @param return_type metadata attributes to return. See {MapReturnType} # @param value_type expected type of return value # @param key map key expression # @param bin bin or map value expression # @param ctx optional context path for nested CDT def self.get_by_key(return_type, value_type, key, bin, ctx: nil) bytes = Exp.pack(ctx, GET_BY_KEY, return_type, key) - return self.add_read(bin, bytes, value_type) + add_read(bin, bytes, value_type) end # Create expression that selects map items identified by key range (key_begin inclusive, key_end exclusive). # If key_begin is nil, the range is less than key_end. # If key_end is nil, the range is greater than equal to key_begin. # - # Expression returns selected data specified by return_type (See {@link MapReturnType}). + # Expression returns selected data specified by return_type (See {MapReturnType}). def self.get_by_key_range(return_type, key_begin, key_end, bin, ctx: nil) bytes = Exp::List.pack_range_operation(GET_BY_KEY_INTERVAL, return_type, key_begin, key_end, ctx) - return self.add_read(bin, bytes, get_value_type(return_type)) + add_read(bin, bytes, get_value_type(return_type)) end # Create expression that selects map items identified by keys and returns selected data specified by - # return_type (See {@link MapReturnType}). + # return_type (See {MapReturnType}). def self.get_by_key_list(return_type, keys, bin, ctx: nil) bytes = Exp.pack(ctx, GET_BY_KEY_LIST, return_type, keys) - return self.add_read(bin, bytes, get_value_type(return_type)) + add_read(bin, bytes, get_value_type(return_type)) end # Create expression that selects map items nearest to key and greater by index with a coun. - # Expression returns selected data specified by return_type (See {@link MapReturnType}). + # Expression returns selected data specified by return_type (See {MapReturnType}). # # Examples for ordered map [{0=17},{4=2},{5=15},{9=10}]: # @@ -318,11 +314,11 @@ def self.get_by_key_list(return_type, keys, bin, ctx: nil) # (3,-2) = [{0=17},{4=2},{5=15},{9=10}] def self.get_by_key_relative_index_range(return_type, key, index, bin, ctx: nil) bytes = Exp.pack(ctx, GET_BY_KEY_REL_INDEX_RANGE, return_type, key, index) - return self.add_read(bin, bytes, get_value_type(return_type)) + add_read(bin, bytes, get_value_type(return_type)) end # Create expression that selects map items nearest to key and greater by index with a count limit if provided. - # Expression returns selected data specified by return_type (See {@link MapReturnType}). + # Expression returns selected data specified by return_type (See {MapReturnType}). # # Examples for ordered map [{0=17},{4=2},{5=15},{9=10}]: # @@ -333,12 +329,12 @@ def self.get_by_key_relative_index_range(return_type, key, index, bin, ctx: nil) # (3,2,1) = [{9=10}] # (3,-2,2) = [{0=17}] def self.get_by_key_relative_index_range(return_type, key, index, bin, ctx: nil, count: nil) - unless count.nil? - bytes = Exp.pack(ctx, GET_BY_KEY_REL_INDEX_RANGE, return_type, key, index, count) - else - bytes = Exp.pack(ctx, GET_BY_KEY_REL_INDEX_RANGE, return_type, key, index) - end - return self.add_read(bin, bytes, get_value_type(return_type)) + bytes = if count.nil? + Exp.pack(ctx, GET_BY_KEY_REL_INDEX_RANGE, return_type, key, index) + else + Exp.pack(ctx, GET_BY_KEY_REL_INDEX_RANGE, return_type, key, index, count) + end + add_read(bin, bytes, get_value_type(return_type)) end # Create expression that selects map items identified by value and returns selected data @@ -350,34 +346,34 @@ def self.get_by_key_relative_index_range(return_type, key, index, bin, ctx: nil, # MapExp.getByValue(CDT::MapReturnType::COUNT, Exp.val("BBB"), Exp.mapBin("a")), # Exp.val(0)) # - # @param return_type metadata attributes to return. See {@link MapReturnType} + # @param return_type metadata attributes to return. See {MapReturnType} # @param value value expression # @param bin bin or map value expression # @param ctx optional context path for nested CDT def self.get_by_value(return_type, value, bin, ctx: nil) bytes = Exp.pack(ctx, GET_BY_VALUE, return_type, value) - return self.add_read(bin, bytes, get_value_type(return_type)) + add_read(bin, bytes, get_value_type(return_type)) end # Create expression that selects map items identified by value range (valueBegin inclusive, valueEnd exclusive) # If valueBegin is nil, the range is less than valueEnd. # If valueEnd is nil, the range is greater than equal to valueBegin. # - # Expression returns selected data specified by return_type (See {@link MapReturnType}). + # Expression returns selected data specified by return_type (See {MapReturnType}). def self.get_by_value_range(return_type, valueBegin, valueEnd, bin, ctx: nil) bytes = Exp::List.pack_range_operation(GET_BY_VALUE_INTERVAL, return_type, valueBegin, valueEnd, ctx) - return self.add_read(bin, bytes, get_value_type(return_type)) + add_read(bin, bytes, get_value_type(return_type)) end # Create expression that selects map items identified by values and returns selected data specified by - # return_type (See {@link MapReturnType}). + # return_type (See {MapReturnType}). def self.get_by_value_list(return_type, values, bin, ctx: nil) bytes = Exp.pack(ctx, GET_BY_VALUE_LIST, return_type, values) - return self.add_read(bin, bytes, get_value_type(return_type)) + add_read(bin, bytes, get_value_type(return_type)) end # Create expression that selects map items nearest to value and greater by relative rank (with a count limit if passed). - # Expression returns selected data specified by return_type (See {@link MapReturnType}). + # Expression returns selected data specified by return_type (See {MapReturnType}). # # Examples for map [{4=2},{9=10},{5=15},{0=17}]: # @@ -385,48 +381,48 @@ def self.get_by_value_list(return_type, values, bin, ctx: nil) # (11,1) = [{0=17}] # (11,-1) = [{9=10},{5=15},{0=17}] def self.get_by_value_relative_rank_range(return_type, value, rank, bin, ctx: nil, count: nil) - unless count.nil? - bytes = Exp.pack(ctx, GET_BY_VALUE_REL_RANK_RANGE, return_type, value, rank, count) - else - bytes = Exp.pack(ctx, GET_BY_VALUE_REL_RANK_RANGE, return_type, value, rank) - end - return self.add_read(bin, bytes, get_value_type(return_type)) + bytes = if count.nil? + Exp.pack(ctx, GET_BY_VALUE_REL_RANK_RANGE, return_type, value, rank) + else + Exp.pack(ctx, GET_BY_VALUE_REL_RANK_RANGE, return_type, value, rank, count) + end + add_read(bin, bytes, get_value_type(return_type)) end # Create expression that selects map item identified by index and returns selected data specified by - # return_type (See {@link MapReturnType}). + # return_type (See {MapReturnType}). def self.get_by_index(return_type, value_type, index, bin, ctx: nil) bytes = Exp.pack(ctx, GET_BY_INDEX, return_type, index) - return self.add_read(bin, bytes, value_type) + add_read(bin, bytes, value_type) end # Create expression that selects map items starting at specified index to the end of map and returns selected - # data specified by return_type (See {@link MapReturnType}) limited by count if provided. + # data specified by return_type (See {MapReturnType}) limited by count if provided. def self.get_by_index_range(return_type, index, bin, ctx: nil, count: nil) - unless count.nil? - bytes = Exp.pack(ctx, GET_BY_INDEX_RANGE, return_type, index, count) - else - bytes = Exp.pack(ctx, GET_BY_INDEX_RANGE, return_type, index) - end - return self.add_read(bin, bytes, get_value_type(return_type)) + bytes = if count.nil? + Exp.pack(ctx, GET_BY_INDEX_RANGE, return_type, index) + else + Exp.pack(ctx, GET_BY_INDEX_RANGE, return_type, index, count) + end + add_read(bin, bytes, get_value_type(return_type)) end # Create expression that selects map item identified by rank and returns selected data specified by - # return_type (See {@link MapReturnType}). + # return_type (See {MapReturnType}). def self.get_by_rank(return_type, value_type, rank, bin, ctx: nil) bytes = Exp.pack(ctx, GET_BY_RANK, return_type, rank) - return self.add_read(bin, bytes, value_type) + add_read(bin, bytes, value_type) end # Create expression that selects map items starting at specified rank to the last ranked item and - # returns selected data specified by return_type (See {@link MapReturnType}). + # returns selected data specified by return_type (See {MapReturnType}). def self.get_by_rank_range(return_type, rank, bin, ctx: nil, count: nil) - unless count.nil? - bytes = Exp.pack(ctx, GET_BY_RANK_RANGE, return_type, rank, count) - else - bytes = Exp.pack(ctx, GET_BY_RANK_RANGE, return_type, rank) - end - return self.add_read(bin, bytes, get_value_type(return_type)) + bytes = if count.nil? + Exp.pack(ctx, GET_BY_RANK_RANGE, return_type, rank) + else + Exp.pack(ctx, GET_BY_RANK_RANGE, return_type, rank, count) + end + add_read(bin, bytes, get_value_type(return_type)) end private @@ -454,7 +450,7 @@ def self.get_by_rank_range(return_type, rank, bin, ctx: nil, count: nil) GET_BY_KEY = 97 GET_BY_INDEX = 98 GET_BY_RANK = 100 - GET_BY_VALUE = 102; # GET_ALL_BY_VALUE on server + GET_BY_VALUE = 102 # GET_ALL_BY_VALUE on server GET_BY_KEY_INTERVAL = 103 GET_BY_INDEX_RANGE = 104 GET_BY_VALUE_INTERVAL = 105 @@ -465,11 +461,11 @@ def self.get_by_rank_range(return_type, rank, bin, ctx: nil, count: nil) GET_BY_VALUE_REL_RANK_RANGE = 110 def self.add_write(bin, bytes, ctx) - if ctx.to_a.empty? - ret_type = Exp::Type::MAP - else - ret_type = ((ctx[0].id & 0x10) == 0) ? Exp::Type::MAP : Exp::Type::LIST - end + ret_type = if ctx.to_a.empty? + Exp::Type::MAP + else + (ctx[0].id & 0x10) == 0 ? Exp::Type::MAP : Exp::Type::LIST + end Exp::Module.new(bin, bytes, ret_type, MODULE | Exp::MODIFY) end @@ -479,15 +475,27 @@ def self.add_read(bin, bytes, ret_type) def self.get_value_type(return_type) t = return_type & ~CDT::MapReturnType::INVERTED + case t + when MapReturnType::INDEX, MapReturnType::REVERSE_INDEX, MapReturnType::RANK, MapReturnType::REVERSE_RANK + # This method only called from expressions that can return multiple integers (ie list). + Exp::Type::LIST - if t <= CDT::MapReturnType::COUNT - return Exp::Type::INT - end + when MapReturnType::COUNT + Exp::Type::INT - if t == CDT::MapReturnType::KEY_VALUE - return Exp::Type::MAP + when MapReturnType::KEY, MapReturnType::VALUE + # This method only called from expressions that can return multiple objects (ie list). + Exp::Type::LIST + + when MapReturnType::KEY_VALUE, MapReturnType::ORDERED_MAP, MapReturnType::UNORDERED_MAP + Exp::Type::MAP + + when MapReturnType::EXISTS + Exp::Type::BOOL + + else + raise Exceptions::Aerospike.new(Aerospike::ResultCode::PARAMETER_ERROR, "Invalid MapReturnType: #{return_type}") end - return Exp::Type::LIST end end # class MapExp end # module Aerospike diff --git a/lib/aerospike/exp/operation.rb b/lib/aerospike/exp/operation.rb index 7cfc6104..3c6e70dd 100644 --- a/lib/aerospike/exp/operation.rb +++ b/lib/aerospike/exp/operation.rb @@ -24,7 +24,7 @@ class Exp::Operation # # @param bin_name name of bin to store expression result # @param exp expression to evaluate - # @param flags expression write flags. See {@link Exp::WriteFlags} + # @param flags expression write flags. See {Exp::WriteFlags} def self.write(bin_name, exp, flags = Aerospike::Exp::WriteFlags::DEFAULT) create_operation(Aerospike::Operation::EXP_MODIFY, bin_name, exp, flags) end @@ -36,7 +36,7 @@ def self.write(bin_name, exp, flags = Aerospike::Exp::WriteFlags::DEFAULT) # @param name variable name of read expression result. This name can be used as the # bin name when retrieving bin results from the record. # @param exp expression to evaluate - # @param flags expression read flags. See {@link Exp::ExpReadFlags} + # @param flags expression read flags. See {Exp::ExpReadFlags} def self.read(name, exp, flags = Aerospike::Exp::ReadFlags::DEFAULT) create_operation(Aerospike::Operation::EXP_READ, name, exp, flags) end diff --git a/lib/aerospike/info.rb b/lib/aerospike/info.rb index 30217e3b..b8e231e4 100644 --- a/lib/aerospike/info.rb +++ b/lib/aerospike/info.rb @@ -66,7 +66,6 @@ def self.parse_multiple_response(buf_length, buffer) private def self.send_command(conn, offset, buffer) - begin # Write size field. size = (offset - 8) | (2 << 56) | (1 << 48) @@ -82,12 +81,11 @@ def self.send_command(conn, offset, buffer) buffer.resize(length) conn.read(buffer, length) - return length - rescue => e + length + rescue => e Aerospike.logger.error(e) conn.close if conn raise e - end end end diff --git a/lib/aerospike/node.rb b/lib/aerospike/node.rb index f95b9bb1..b2799e7b 100644 --- a/lib/aerospike/node.rb +++ b/lib/aerospike/node.rb @@ -71,6 +71,10 @@ def query_show? (@features & HAS_QUERY_SHOW) != 0 end + def batch_any? + (@features & HAS_BATCH_ANY) != 0 + end + def update_racks(parser) new_racks = parser.update_racks @racks.value = new_racks if new_racks @@ -78,7 +82,7 @@ def update_racks(parser) def has_rack(ns, rack_id) racks = @racks.value - return false if !racks + return false unless racks racks[ns] == rack_id end @@ -108,7 +112,7 @@ def get_connection(timeout) # Put back a connection to the cache. If cache is full, the connection will be # closed and discarded def put_connection(conn) - conn.close if !active? + conn.close unless active? @connections.offer(conn) end @@ -236,7 +240,7 @@ def refresh_partitions(peers) Node::Refresh::Partitions.(self, peers) end - def refresh_racks() + def refresh_racks Node::Refresh::Racks.(self) end diff --git a/lib/aerospike/operation.rb b/lib/aerospike/operation.rb index 974110c6..944bc5c5 100644 --- a/lib/aerospike/operation.rb +++ b/lib/aerospike/operation.rb @@ -80,5 +80,43 @@ def self.touch def self.delete Operation.new(DELETE) end + + # :nodoc: + def is_write? + case @op_type + when READ + false + when READ_HEADER + false + when WRITE + true + when CDT_READ + false + when CDT_MODIFY + true + when ADD + true + when EXP_READ + false + when EXP_MODIFY + true + when APPEND + true + when PREPEND + true + when TOUCH + true + when BIT_READ + false + when BIT_MODIFY + true + when DELETE + true + when HLL_READ + false + when HLL_MODIFY + true + end + end end end # module diff --git a/lib/aerospike/policy/batch_delete_policy.rb b/lib/aerospike/policy/batch_delete_policy.rb new file mode 100644 index 00000000..2978d1e6 --- /dev/null +++ b/lib/aerospike/policy/batch_delete_policy.rb @@ -0,0 +1,71 @@ +# encoding: utf-8 +# Copyright 2014-2024 Aerospike, Inc. +# +# Portions may be licensed to Aerospike, Inc. under one or more contributor +# license agreements. +# +# Licensed under the Apache License, Version 2.0 (the "License") you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + +module Aerospike + + # Policy attributes used in batch delete commands. + class BatchDeletePolicy + attr_accessor :filter_exp, :commit_level, :generation_policy, :generation, :durable_delete, :send_key + + def initialize(opt = {}) + # Optional expression filter. If filter_exp exists and evaluates to false, the specific batch key + # request is not performed and {BatchRecord#result_code} is set to + # {ResultCode#FILTERED_OUT}. + # + # If exists, this filter overrides the batch parent filter {Policy#filter_exp} + # for the specific key in batch commands that allow a different policy per key. + # Otherwise, this filter is ignored. + # + # Default: nil + @filter_exp = opt[:filter_exp] + + # Desired consistency guarantee when committing a transaction on the server. The default + # (COMMIT_ALL) indicates that the server should wait for master and all replica commits to + # be successful before returning success to the client. + # + # Default: CommitLevel.COMMIT_ALL + @commit_level = opt[:commit_level] || CommitLevel::COMMIT_ALL + + # Qualify how to handle record deletes based on record generation. The default (NONE) + # indicates that the generation is not used to restrict deletes. + # + # Default: GenerationPolicy.NONE + @generation_policy = opt[:generation_policy] || GenerationPolicy::NONE + + # Expected generation. Generation is the number of times a record has been modified + # (including creation) on the server. This field is only relevant when generationPolicy + # is not NONE. + # + # Default: 0 + @generation = opt[:generation] || 0 + + # If the transaction results in a record deletion, leave a tombstone for the record. + # This prevents deleted records from reappearing after node failures. + # Valid for Aerospike Server Enterprise Edition only. + # + # Default: false (do not tombstone deleted records). + @durable_delete = opt[:durable_delete] || false + + # Send user defined key in addition to hash digest. + # If true, the key will be stored with the tombstone record on the server. + # + # Default: false (do not send the user defined key) + @send_key = opt[:send_key] || false + + self + end + end +end \ No newline at end of file diff --git a/lib/aerospike/policy/batch_policy.rb b/lib/aerospike/policy/batch_policy.rb index a5800b83..4c9d9d96 100644 --- a/lib/aerospike/policy/batch_policy.rb +++ b/lib/aerospike/policy/batch_policy.rb @@ -21,12 +21,14 @@ module Aerospike # Container object for batch policy command. class BatchPolicy < Policy - - attr_accessor :use_batch_direct + attr_accessor :allow_inline_ssd, :respond_all_keys, :send_key def initialize(opt={}) - super(opt) + super + # [:nodoc:] + # DEPRECATED + # This setting does not have any effect anymore. # Use old batch direct protocol where batch reads are handled by direct # low-level batch server database routines. The batch direct protocol can # be faster when there is a single namespace. But there is one important @@ -38,11 +40,58 @@ def initialize(opt={}) # index protocol will perform this record proxy when necessary. # # Default: false (use new batch index protocol if server supports it) - @use_batch_direct = opt.fetch(:use_batch_direct) { false } + @use_batch_direct = opt.fetch(:use_batch_direct, false) + + + # Allow batch to be processed immediately in the server's receiving thread for SSD + # namespaces. If false, the batch will always be processed in separate service threads. + # Server versions < 6.0 ignore this field. + # + # Inline processing can introduce the possibility of unfairness because the server + # can process the entire batch before moving onto the next command. + # + # Default: false + @allow_inline_ssd = opt.fetch(:allow_inline_ssd, false) + + + # Should all batch keys be attempted regardless of errors. This field is used on both + # the client and server. The client handles node specific errors and the server handles + # key specific errors. + # + # If true, every batch key is attempted regardless of previous key specific errors. + # Node specific errors such as timeouts stop keys to that node, but keys directed at + # other nodes will continue to be processed. + # + # If false, the server will stop the batch to its node on most key specific errors. + # The exceptions are {ResultCode#KEY_NOT_FOUND_ERROR} and + # {ResultCode#FILTERED_OUT} which never stop the batch. + # The client will stop the entire batch on node specific errors. The client will + # not stop the entire batch commands run in parallel. + # + # Server versions < 6.0 do not support this field and treat this value as false + # for key specific errors. + # + # Default: true + @respond_all_keys = opt.fetch(:respond_all_keys, true) + + + # Send user defined key in addition to hash digest on a record put. + # The default is to _not_ send the user defined key. + @send_key = opt.fetch(:send_key, false) self end + def self.read_default + BatchPolicy.new + end + + def self.write_default + bp = BatchPolicy.new + bp.max_retries = 0 + bp + end + end # class end # module diff --git a/lib/aerospike/command/batch_direct_node.rb b/lib/aerospike/policy/batch_read_policy.rb similarity index 53% rename from lib/aerospike/command/batch_direct_node.rb rename to lib/aerospike/policy/batch_read_policy.rb index dc1aab56..c61160e0 100644 --- a/lib/aerospike/command/batch_direct_node.rb +++ b/lib/aerospike/policy/batch_read_policy.rb @@ -17,24 +17,22 @@ module Aerospike - BatchNamespace = Struct.new :namespace, :keys - - class BatchDirectNode #:nodoc: - - attr_accessor :node - attr_accessor :batch_namespaces - - def self.generate_list(cluster, replica_policy, keys) - keys.group_by { |key| cluster.get_node_for_key(replica_policy, key) } - .map { |node, keys_for_node| BatchDirectNode.new(node, keys_for_node) } + # Policy attributes used in batch read commands. + class BatchReadPolicy + + attr_accessor :filter_exp + + def initialize(opt={}) + # Optional expression filter. If filter_exp exists and evaluates to false, the specific batch key + # request is not performed and {BatchRecord#result_code} is set to + # {ResultCode#FILTERED_OUT}. + # + # If exists, this filter overrides the batch parent filter {Policy#filter_exp} + # for the specific key in batch commands that allow a different policy per key. + # Otherwise, this filter is ignored. + # + # Default: nil + @filter_exp = opt[:filter_exp] end - - def initialize(node, keys) - @node = node - @batch_namespaces = keys.group_by(&:namespace) - .map { |ns, keys_for_ns| BatchNamespace.new(ns, keys_for_ns) } - end - end - -end +end \ No newline at end of file diff --git a/lib/aerospike/policy/batch_udf_policy.rb b/lib/aerospike/policy/batch_udf_policy.rb new file mode 100644 index 00000000..42c54ed7 --- /dev/null +++ b/lib/aerospike/policy/batch_udf_policy.rb @@ -0,0 +1,75 @@ +# Copyright 2014-2023 Aerospike, Inc. +# +# Portions may be licensed to Aerospike, Inc. under one or more contributor +# license agreements. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + +module Aerospike + + # Policy attributes used in batch UDF execute commands. + class BatchUDFPolicy + + attr_accessor :filter_exp, :commit_level, :ttl, :durable_delete, :send_key + + alias expiration ttl + alias expiration= ttl= + + def initialize(opt={}) + # Optional expression filter. If filter_exp exists and evaluates to false, the specific batch key + # request is not performed and {BatchRecord#resultCode} is set to + # {ResultCode#FILTERED_OUT}. + # + # If exists, this filter overrides the batch parent filter {Policy#filter_exp} + # for the specific key in batch commands that allow a different policy per key. + # Otherwise, this filter is ignored. + # + # Default: nil + @filter_exp = opt[:filter_exp] + + # Desired consistency guarantee when committing a transaction on the server. The default + # (COMMIT_ALL) indicates that the server should wait for master and all replica commits to + # be successful before returning success to the client. + # + # Default: CommitLevel::COMMIT_ALL + @commit_level = opt.fetch(:commit_level, CommitLevel::COMMIT_ALL) + + # Record expiration; also known as time-to-live (TTL). + # Seconds record will live before being removed by the server. + # + # Supported values: + # - `Aerospike::TTL::NEVER_EXPIRE`: Never expire record; requires Aerospike 2 + # server versions >= 2.7.2 or Aerospike 3 server versions >= 3.1.4. Do + # not use for older servers. + # - `Aerospike::TTL::NAMESPACE_DEFAULT`: Default to namespace configuration + # variable "default-ttl" on the server. + # - `Aerospike::TTL::DONT_UPDATE`: Do not change a record's expiration date + # when updating the record. Requires Aerospike server v3.10.1 or later. + # - Any value > 0: Actual time-to-live in seconds. + @ttl = opt[:ttl] || opt[:expiration] || 0 + + # If the transaction results in a record deletion, leave a tombstone for the record. + # This prevents deleted records from reappearing after node failures. + # Valid for Aerospike Server Enterprise Edition only. + # + # Default: false (do not tombstone deleted records). + @durable_delete = opt.fetch(:durable_delete, false) + + # Send user defined key in addition to hash digest. + # If true, the key will be stored with the record on the server. + # + # Default: false (do not send the user defined key) + @send_key = opt.fetch(:send_key, false) + end + end +end \ No newline at end of file diff --git a/lib/aerospike/policy/batch_write_policy.rb b/lib/aerospike/policy/batch_write_policy.rb new file mode 100644 index 00000000..dfcbb1b0 --- /dev/null +++ b/lib/aerospike/policy/batch_write_policy.rb @@ -0,0 +1,105 @@ +# Copyright 2014-2023 Aerospike, Inc. +# +# Portions may be licensed to Aerospike, Inc. under one or more contributor +# license agreements. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + +module Aerospike + + + # Policy attributes used in batch write commands. + class BatchWritePolicy + + attr_accessor :filter_exp, :record_exists_action, :commit_level, + :generation_policy, :generation, :ttl, :durable_delete, + :send_key + + alias expiration ttl + alias expiration= ttl= + + def initialize(opt={}) + # Optional expression filter. If filter_exp exists and evaluates to false, the specific batch key + # request is not performed and {BatchRecord#result_code} is set to + # {ResultCode#FILTERED_OUT}. + # + # If exists, this filter overrides the batch parent filter {Policy#filter_exp} + # for the specific key in batch commands that allow a different policy per key. + # Otherwise, this filter is ignored. + # + # Default: nil + @filter_exp = opt[:filter_exp] + + # Qualify how to handle writes where the record already exists. + # + # Default: RecordExistsAction::UPDATE + @record_exists_action = opt.fetch(:record_exists_action, RecordExistsAction::UPDATE) + + # Desired consistency guarantee when committing a transaction on the server. The default + # (COMMIT_ALL) indicates that the server should wait for master and all replica commits to + # be successful before returning success to the client. + # + # Default: CommitLevel::COMMIT_ALL + @commit_level = opt.fetch(:commit_level, CommitLevel::COMMIT_ALL) + + # Qualify how to handle record writes based on record generation. The default (NONE) + # indicates that the generation is not used to restrict writes. + # + # The server does not support this field for UDF execute() calls. The read-modify-write + # usage model can still be enforced inside the UDF code itself. + # + # Default: GenerationPolicy::NONE + @generation_policy = opt.fetch(:generation_policy, GenerationPolicy::NONE) + + # Expected generation. Generation is the number of times a record has been modified + # (including creation) on the server. If a write operation is creating a record, + # the expected generation would be 0. This field is only relevant when + # generationPolicy is not NONE. + # + # The server does not support this field for UDF execute() calls. The read-modify-write + # usage model can still be enforced inside the UDF code itself. + # + # Default: 0 + @generation = opt.fetch(:generation, 0) + + # Record expiration; also known as time-to-live (TTL). + # Seconds record will live before being removed by the server. + # + # Supported values: + # - `Aerospike::TTL::NEVER_EXPIRE`: Never expire record; requires Aerospike 2 + # server versions >= 2.7.2 or Aerospike 3 server versions >= 3.1.4. Do + # not use for older servers. + # - `Aerospike::TTL::NAMESPACE_DEFAULT`: Default to namespace configuration + # variable "default-ttl" on the server. + # - `Aerospike::TTL::DONT_UPDATE`: Do not change a record's expiration date + # when updating the record. Requires Aerospike server v3.10.1 or later. + # - Any value > 0: Actual time-to-live in seconds. + @ttl = opt[:ttl] || opt[:expiration] || 0 + + # If the transaction results in a record deletion, leave a tombstone for the record. + # This prevents deleted records from reappearing after node failures. + # Valid for Aerospike Server Enterprise Edition only. + # + # Default: false (do not tombstone deleted records). + @durable_delete = opt.fetch(:durable_delete, false) + + # Send user defined key in addition to hash digest. + # If true, the key will be stored with the record on the server. + # + # Default: false (do not send the user defined key) + @send_key = opt.fetch(:send_key, false) + + self + end + end +end \ No newline at end of file diff --git a/lib/aerospike/policy/policy.rb b/lib/aerospike/policy/policy.rb index 054f51f1..8e4c9c54 100644 --- a/lib/aerospike/policy/policy.rb +++ b/lib/aerospike/policy/policy.rb @@ -22,7 +22,7 @@ module Aerospike # Container object for client policy command. class Policy attr_accessor :filter_exp, :priority, :timeout, :max_retries, :sleep_between_retries, :consistency_level, - :predexp, :fail_on_filtered_out, :replica, :use_compression, :socket_timeout + :fail_on_filtered_out, :replica, :use_compression, :socket_timeout alias total_timeout timeout alias total_timeout= timeout= @@ -31,7 +31,7 @@ def initialize(opt = {}) # Container object for transaction policy attributes used in all database # operation calls. - # Optional expression filter. If filterExp exists and evaluates to false, the + # Optional expression filter. If filter_exp exists and evaluates to false, the # transaction is ignored. # # Default: nil @@ -57,44 +57,7 @@ def initialize(opt = {}) # TODO: Remove for next major release @priority = opt[:priority] || Priority::DEFAULT - # Set optional predicate expression filters in postfix notation. - # Predicate expression filters are applied on the query results on the server. - # Predicate expression filters may occur on any bin in the record. - # Requires Aerospike Server versions >= 3.12 - # - # Postfix notation is described here: http://wiki.c2.com/?PostfixNotation - # - # Example: - # - # (c >= 11 and c <= 20) or (d > 3 and (d < 5) - # policy.predexp = [ - # PredExp.integer_bin("c"), - # PredExp.integer_value(11), - # PredExp.integer_greater_eq(), - # PredExp.integer_bin("c"), - # PredExp.integer_value(20), - # PredExp.integer_less_eq(), - # PredExp.and(2), - # PredExp.integer_bin("d"), - # PredExp.integer_value(3), - # PredExp.integer_greater(), - # PredExp.integer_bin("d"), - # PredExp.integer_value(5), - # PredExp.integer_less(), - # PredExp.and(2), - # PredExp.or(2) - # ] - # - # # Record last update time > 2017-01-15 - # policy.predexp = [ - # PredExp.rec_last_update(), - # PredExp.integer_value(Time.new(2017, 1, 15).to_i), - # PredExp.integer_greater(), - # PredExp.integer_greater() - # ] - @predexp = opt[:predexp] || nil - - # Throw exception if @predexp is defined and that filter evaluates + # Throw exception if @filter_exp is defined and that filter evaluates # to false (transaction ignored). The Aerospike::Exceptions::Aerospike # will contain result code Aerospike::ResultCode::FILTERED_OUT. # This field is not applicable to batch, scan or query commands. diff --git a/lib/aerospike/query/pred_exp.rb b/lib/aerospike/query/pred_exp.rb deleted file mode 100644 index 761615cc..00000000 --- a/lib/aerospike/query/pred_exp.rb +++ /dev/null @@ -1,192 +0,0 @@ -# frozen_string_literal: true - -module Aerospike - class PredExp - AND = 1 - OR = 2 - NOT = 3 - INTEGER_VALUE = 10 - STRING_VALUE = 11 - GEOJSON_VALUE = 12 - INTEGER_BIN = 100 - STRING_BIN = 101 - GEOJSON_BIN = 102 - LIST_BIN = 103 - MAP_BIN = 104 - INTEGER_VAR = 120 - STRING_VAR = 121 - GEOJSON_VAR = 122 - RECSIZE = 150 - LAST_UPDATE = 151 - VOID_TIME = 152 - INTEGER_EQUAL = 200 - INTEGER_UNEQUAL = 201 - INTEGER_GREATER = 202 - INTEGER_GREATEREQ = 203 - INTEGER_LESS = 204 - INTEGER_LESSEQ = 205 - STRING_EQUAL = 210 - STRING_UNEQUAL = 211 - STRING_REGEX = 212 - GEOJSON_WITHIN = 220 - GEOJSON_CONTAINS = 221 - LIST_ITERATE_OR = 250 - MAPKEY_ITERATE_OR = 251 - MAPVAL_ITERATE_OR = 252 - LIST_ITERATE_AND = 253 - MAPKEY_ITERATE_AND = 254 - MAPVAL_ITERATE_AND = 255 - - def self.and(nexp) - AndOr.new(AND, nexp) - end - - def self.or(nexp) - AndOr.new(OR, nexp) - end - - def self.not - Op.new(NOT) - end - - def self.integer_value(value) - IntegerValue.new(value, INTEGER_VALUE) - end - - def self.string_value(value) - StringValue.new(value, STRING_VALUE) - end - - def self.geojson_value(value) - raise(ArgumentError, "value must be a GeoJSON object!") unless value.is_a?(Aerospike::GeoJSON) - GeoJsonValue.new(value.to_s, GEOJSON_VALUE) - end - - def self.integer_bin(name) - StringValue.new(name, INTEGER_BIN) - end - - def self.string_bin(name) - StringValue.new(name, STRING_BIN) - end - - def self.geojson_bin(name) - StringValue.new(name, GEOJSON_BIN) - end - - def self.list_bin(name) - StringValue.new(name, LIST_BIN) - end - - def self.map_bin(name) - StringValue.new(name, MAP_BIN) - end - - def self.integer_var(name) - StringValue.new(name, INTEGER_VAR) - end - - def self.string_var(name) - StringValue.new(name, STRING_VAR) - end - - def self.geojson_var(name) - StringValue.new(name, GEOJSON_VAR) - end - - def self.record_size - Op.new(RECSIZE) - end - - def self.last_update - Op.new(LAST_UPDATE) - end - - def self.void_time - Op.new(VOID_TIME) - end - - def self.integer_equal - Op.new(INTEGER_EQUAL) - end - - def self.integer_unequal - Op.new(INTEGER_UNEQUAL) - end - - def self.integer_greater - Op.new(INTEGER_GREATER) - end - - def self.integer_greater_eq - Op.new(INTEGER_GREATEREQ) - end - - def self.integer_less - Op.new(INTEGER_LESS) - end - - def self.integer_less_eq - Op.new(INTEGER_LESSEQ) - end - - def self.string_equal - Op.new(STRING_EQUAL) - end - - def self.string_unequal - Op.new(STRING_UNEQUAL) - end - - def self.string_regex(flags) - Regex.new(STRING_REGEX, flags) - end - - def self.geojson_within - Op.new(GEOJSON_WITHIN) - end - - def self.geojson_contains - Op.new(GEOJSON_CONTAINS) - end - - def self.list_iterate_or(var_name) - StringValue.new(var_name, LIST_ITERATE_OR) - end - - def self.list_iterate_and(var_name) - StringValue.new(var_name, LIST_ITERATE_AND) - end - - def self.mapkey_iterate_or(var_name) - StringValue.new(var_name, MAPKEY_ITERATE_OR) - end - - def self.mapkey_iterate_and(var_name) - StringValue.new(var_name, MAPKEY_ITERATE_AND) - end - - def self.mapval_iterate_or(var_name) - StringValue.new(var_name, MAPVAL_ITERATE_OR) - end - - def self.mapval_iterate_and(var_name) - StringValue.new(var_name, MAPVAL_ITERATE_AND) - end - - - - def self.estimate_size(predexp) - return 0 unless predexp - predexp.map(&:estimate_size).inject { |sum, size| sum + size } - end - - def self.write(predexp, buffer, offset) - predexp.each do |p| - offset = p.write(buffer, offset) - end - - offset - end - end -end diff --git a/lib/aerospike/query/pred_exp/and_or.rb b/lib/aerospike/query/pred_exp/and_or.rb deleted file mode 100644 index 4da893fd..00000000 --- a/lib/aerospike/query/pred_exp/and_or.rb +++ /dev/null @@ -1,32 +0,0 @@ -# frozen_string_literal: true - -module Aerospike - class PredExp - class AndOr < PredExp - def initialize(op, nexp) - @op = op - @nexp = nexp - end - - def estimate_size - 8 - end - - def write(buffer, offset) - # write type - buffer.write_int16(@op, offset) - offset += 2 - - # write length - buffer.write_int32(2, offset) - offset += 4 - - # write predicate count - buffer.write_int16(@nexp, offset) - offset += 2 - - offset - end - end - end -end diff --git a/lib/aerospike/query/pred_exp/geo_json_value.rb b/lib/aerospike/query/pred_exp/geo_json_value.rb deleted file mode 100644 index da4a8dd8..00000000 --- a/lib/aerospike/query/pred_exp/geo_json_value.rb +++ /dev/null @@ -1,41 +0,0 @@ -# frozen_string_literal: true - -module Aerospike - class PredExp - class GeoJsonValue < PredExp - def initialize(value, type) - @value = value - @type = type - end - - def estimate_size - @value.bytesize + 9 - end - - def write(buffer, offset) - # tag - buffer.write_uint16(@type, offset) - offset += 2 - - # len - buffer.write_uint32(@value.bytesize + 3, offset) - offset += 4 - - # flags - - buffer.write_byte(0, offset) - offset += 1 - - # ncells - buffer.write_uint16(0, offset) - offset += 2 - - # value - len = buffer.write_binary(@value, offset) - offset += len - - offset - end - end - end -end diff --git a/lib/aerospike/query/pred_exp/integer_value.rb b/lib/aerospike/query/pred_exp/integer_value.rb deleted file mode 100644 index 42d71b24..00000000 --- a/lib/aerospike/query/pred_exp/integer_value.rb +++ /dev/null @@ -1,32 +0,0 @@ -# frozen_string_literal: true - -module Aerospike - class PredExp - class IntegerValue < PredExp - def initialize(value, type) - @value = value - @type = type - end - - def estimate_size - 14 - end - - def write(buffer, offset) - # Write type - buffer.write_int16(@type, offset) - offset += 2 - - # Write length - buffer.write_int32(8, offset) - offset += 4 - - # Write value. - buffer.write_int64(@value, offset) - offset += 8 - - offset - end - end - end -end diff --git a/lib/aerospike/query/pred_exp/op.rb b/lib/aerospike/query/pred_exp/op.rb deleted file mode 100644 index 53ca25f9..00000000 --- a/lib/aerospike/query/pred_exp/op.rb +++ /dev/null @@ -1,27 +0,0 @@ -# frozen_string_literal: true - -module Aerospike - class PredExp - class Op < PredExp - def initialize(op) - @op = op - end - - def estimate_size - 6 - end - - def write(buffer, offset) - # write type - buffer.write_int16(@op, offset) - offset += 2 - - # write zero length - buffer.write_int32(0, offset) - offset += 4 - - offset - end - end - end -end diff --git a/lib/aerospike/query/pred_exp/regex.rb b/lib/aerospike/query/pred_exp/regex.rb deleted file mode 100644 index f74cd744..00000000 --- a/lib/aerospike/query/pred_exp/regex.rb +++ /dev/null @@ -1,32 +0,0 @@ -# frozen_string_literal: true - -module Aerospike - class PredExp - class Regex < PredExp - def initialize(op, flag = Flags::NONE) - @op = op - @flag = flag - end - - def estimate_size - 10 - end - - def write(buffer, offset) - # write op type - buffer.write_int16(@op, offset) - offset += 2 - - # write length - buffer.write_int32(4, offset) - offset += 4 - - # write predicate count - buffer.write_int32(@flag, offset) - offset += 4 - - offset - end - end - end -end diff --git a/lib/aerospike/query/pred_exp/regex_flags.rb b/lib/aerospike/query/pred_exp/regex_flags.rb deleted file mode 100644 index 354d4131..00000000 --- a/lib/aerospike/query/pred_exp/regex_flags.rb +++ /dev/null @@ -1,23 +0,0 @@ -# fr# frozen_string_literal: true - -module Aerospike - class PredExp - # Regex bit flags - module RegexFlags - # Regex defaults - NONE = 0 - - # Use POSIX Extended Regular Expression syntax when interpreting regex. - EXTENDED = 1 - - # Do not differentiate case. - ICASE = 2 - - # Do not report position of matches. - NOSUB = 4 - - # Match-any-character operators don't match a newline. - NEWLINE = 8 - end - end -end diff --git a/lib/aerospike/query/pred_exp/string_value.rb b/lib/aerospike/query/pred_exp/string_value.rb deleted file mode 100644 index 723a6e3c..00000000 --- a/lib/aerospike/query/pred_exp/string_value.rb +++ /dev/null @@ -1,29 +0,0 @@ -# frozen_string_literal: true - -module Aerospike - class PredExp - class StringValue < PredExp - def initialize(value, type) - @value = value - @type = type - end - - def estimate_size - @value.bytesize + 6 - end - - def write(buffer, offset) - buffer.write_int16(@type, offset) - offset += 2 - - buffer.write_int32(@value.bytesize, offset) - offset += 4 - - len = buffer.write_binary(@value, offset) - offset += len - - offset - end - end - end -end diff --git a/lib/aerospike/query/statement.rb b/lib/aerospike/query/statement.rb index 56d4a560..c5f49895 100644 --- a/lib/aerospike/query/statement.rb +++ b/lib/aerospike/query/statement.rb @@ -21,9 +21,7 @@ module Aerospike # index name, filters, and operations. class Statement - attr_accessor :namespace, :set_name, :index_name, :bin_names, :task_id - attr_accessor :filters, :package_name, :function_name, :function_args, :operations - attr_accessor :predexp, :return_data, :records_per_second + attr_accessor :namespace, :set_name, :index_name, :bin_names, :task_id, :filters, :package_name, :function_name, :function_args, :operations, :return_data, :records_per_second def initialize(namespace, set_name, bin_names=[]) # Namespace determines query Namespace @@ -46,16 +44,6 @@ def initialize(namespace, set_name, bin_names=[]) # aggregation function. @filters = [] - # Predicate expressions in postfix notation. If the expression is evaluated to false, - # the record will be ommited in the results. - # - # This method is redundant because PredExp can now be set in the base Policy for - # any transaction (including queries). - # - # NOTE : Policy.predexp takes precedence to this value. This value will be - # deprecated in the future. - @predexp = nil - @package_name = nil @function_name = nil @function_args = nil @@ -83,27 +71,23 @@ def set_aggregate_function(package_name, function_name, function_args=[], return end def is_scan? - return (filters.nil? || (filters.empty?)) + filters.nil? || filters.empty? end def set_task_id - while @task_id == 0 - @task_id = rand(RAND_MAX) - end + @task_id = rand(RAND_MAX) while @task_id == 0 end def reset_task_id @task_id = rand(RAND_MAX) - while @task_id == 0 - @task_id = rand(RAND_MAX) - end + @task_id = rand(RAND_MAX) while @task_id == 0 end private - RAND_MAX = 2**63 - 1 + RAND_MAX = (2**63) - 1 end # class end diff --git a/lib/aerospike/utils/buffer.rb b/lib/aerospike/utils/buffer.rb index d14c757b..0ed7d62f 100644 --- a/lib/aerospike/utils/buffer.rb +++ b/lib/aerospike/utils/buffer.rb @@ -25,7 +25,7 @@ module Aerospike # Buffer class to ease the work around class Buffer #:nodoc: @@buf_pool = Pool.new - @@buf_pool.create_proc = Proc.new { Buffer.new } + @@buf_pool.create_proc = proc { Buffer.new } attr_accessor :buf @@ -43,7 +43,7 @@ class Buffer #:nodoc: MAX_BUFFER_SIZE = 10 * 1024 * 1024 def initialize(size = DEFAULT_BUFFER_SIZE, buf = nil) - @buf = (buf ? buf : ("%0#{size}d" % 0)) + @buf = buf || format("%0#{size}d", 0) @buf.force_encoding("binary") @slice_end = @buf.bytesize end @@ -60,7 +60,7 @@ def size @buf.bytesize end - alias_method :length, :size + alias length size def eat!(n) @buf.replace(@buf[n..-1]) @@ -74,7 +74,7 @@ def resize(length) end if @buf.bytesize < length - @buf.concat("%0#{length - @buf.bytesize}d" % 0) + @buf.concat(format("%0#{length - @buf.bytesize}d", 0)) end @slice_end = length end @@ -144,37 +144,37 @@ def read(offset, len = nil) def read_int16(offset) vals = @buf[offset..offset + 1] - vals.unpack(INT16)[0] + vals.unpack1(INT16) end def read_uint16(offset) vals = @buf[offset..offset + 1] - vals.unpack(UINT16)[0] + vals.unpack1(UINT16) end def read_int32(offset) vals = @buf[offset..offset + 3] - vals.unpack(INT32)[0] + vals.unpack1(INT32) end def read_uint32(offset) vals = @buf[offset..offset + 3] - vals.unpack(UINT32)[0] + vals.unpack1(UINT32) end def read_int64(offset) vals = @buf[offset..offset + 7] - vals.unpack(INT64)[0] + vals.unpack1(INT64) end def read_uint64_little_endian(offset) vals = @buf[offset..offset + 7] - vals.unpack(UINT64LE)[0] + vals.unpack1(UINT64LE) end def read_uint64(offset) vals = @buf[offset..offset + 7] - vals.unpack(UINT64)[0] + vals.unpack1(UINT64) end def read_var_int64(offset, len) @@ -190,7 +190,7 @@ def read_var_int64(offset, len) def read_double(offset) vals = @buf[offset..offset + 7] - vals.unpack(DOUBLE)[0] + vals.unpack1(DOUBLE) end def read_bool(offset, length) @@ -219,13 +219,13 @@ def dump(start = 0, finish = nil) @buf.bytes[start...finish].each do |c| if counter >= start print "%02x " % c - ascii << (c.between?(32, 126) ? c : ?.) - print " " if ascii.length == (width / 2 + 1) + ascii << (c.between?(32, 126) ? c : '.') + print " " if ascii.length == ((width / 2) + 1) if ascii.length > width ascii << "|" puts ascii ascii = "|" - print "%08x " % (counter + 1) + print format("%08x ", (counter + 1)) end end counter += 1 diff --git a/lib/aerospike/version.rb b/lib/aerospike/version.rb index e135e52b..fe400355 100644 --- a/lib/aerospike/version.rb +++ b/lib/aerospike/version.rb @@ -1,4 +1,4 @@ # encoding: utf-8 module Aerospike - VERSION = "3.0.0" + VERSION = "4.0.0" end diff --git a/spec/aerospike/batch_operate_spec.rb b/spec/aerospike/batch_operate_spec.rb new file mode 100644 index 00000000..a165f8e0 --- /dev/null +++ b/spec/aerospike/batch_operate_spec.rb @@ -0,0 +1,185 @@ +# encoding: utf-8 +# Copyright 2014 Aerospike, Inc. +# +# Portions may be licensed to Aerospike, Inc. under one or more contributor +# license agreements. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at http:#www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + +require 'aerospike' +require 'aerospike/batch_read' + +describe Aerospike::Client do + + let(:client) { Support.client } + + describe "#batch_operate" do + let(:batch_policy) do + Aerospike::BatchPolicy.new + end + + let(:existing_keys) { Array.new(3) { Support.gen_random_key } } + # let(:existing_keys) { [Aerospike::Key.new("test", "test", 1)] } + let(:keys) { existing_keys } + let(:no_such_key) { Support.gen_random_key } + let(:keys) { existing_keys } + let(:opts) { { filter_exp: Aerospike::Exp.eq(Aerospike::Exp.int_bin("strval"), Aerospike::Exp.int_val(0)) } } + + before do + existing_keys.each_with_index do |key, idx| + client.put(key, { + 'idx' => idx, + 'key' => key.user_key, + 'rnd' => 99 #rand + }, {}) + end + end + + context '#BatchRead' do + it 'returns specified bins' do + bin_names = %w[idx rnd] + records = [Aerospike::BatchRead.read_bins(keys.first, bin_names)] + client.batch_operate(records, batch_policy) + + expect(records[0].result_code).to eql 0 + expect(records[0].record.bins.length).to eql 2 + end + + it 'returns all records' do + records = [Aerospike::BatchRead.read_all_bins(keys.first)] + client.batch_operate(records, batch_policy) + + expect(records[0].result_code).to eql 0 + expect(records[0].record.bins.length).to eql 3 + end + + it 'filter out' do + records = [Aerospike::BatchRead.read_all_bins(keys.first)] + client.batch_operate(records, opts) + + expect(records[0].result_code).to eql Aerospike::ResultCode::FILTERED_OUT + expect(records[0].record).to eql nil + end + end + + context '#BatchWrite' do + it 'updates specified bins' do + ops = [ + Aerospike::Operation.put(Aerospike::Bin.new("new_bin_str", "value")), + Aerospike::Operation.put(Aerospike::Bin.new("new_bin_int", 999)), + Aerospike::Operation.add(Aerospike::Bin.new("new_bin_int", 1)) + ] + records = [Aerospike::BatchWrite.new(keys.first, ops)] + client.batch_operate(records, batch_policy) + + expect(records[0].result_code).to eql 0 + expect(records[0].record.bins).to eql({ "new_bin_int"=>nil, "new_bin_str"=>nil }) + + records = [Aerospike::BatchRead.read_all_bins(keys.first)] + client.batch_operate(records, batch_policy) + + expect(records[0].record.bins).to eql({ + 'idx' => 0, + 'key' => keys.first.user_key, + 'rnd' => 99, #rand + "new_bin_str" => "value", + "new_bin_int" => 1000 + }) + end + + it 'filter out' do + ops = [ + Aerospike::Operation.put(Aerospike::Bin.new("new_bin_str", "value")), + Aerospike::Operation.put(Aerospike::Bin.new("new_bin_int", 999)), + Aerospike::Operation.add(Aerospike::Bin.new("new_bin_int", 1)) + ] + records = [Aerospike::BatchWrite.new(keys.first, ops)] + client.batch_operate(records, opts) + + expect(records[0].result_code).to eql Aerospike::ResultCode::FILTERED_OUT + expect(records[0].record).to eql nil + end + + it 'removes specific records' do + ops = [ + Aerospike::Operation.delete + ] + records = [Aerospike::BatchWrite.new(keys.first, ops)] + client.batch_operate(records, batch_policy) + + exists = client.exists(keys.first) + expect(exists).to eql false + + end + end + + context '#BatchDelete' do + it 'removes specific records' do + ops = [ + Aerospike::Operation.delete + ] + records = [Aerospike::BatchDelete.new(keys.first)] + client.batch_operate(records, batch_policy) + + expect(records[0].result_code).to eql 0 + expect(records[0].record.bins).to eql nil + + exists = client.exists(keys.first) + expect(exists).to eql false + end + + it 'filter out' do + ops = [ + Aerospike::Operation.delete + ] + records = [Aerospike::BatchDelete.new(keys.first)] + client.batch_operate(records, opts) + + expect(records[0].result_code).to eql Aerospike::ResultCode::FILTERED_OUT + expect(records[0].record).to eql nil + end + + end # context + + context '#BatchUDF' do + let(:udf_body_string) do + "function testStr(rec, str) + return str -- Return the Return value and/or status + end" + end + + before do + register_task = client.register_udf(udf_body_string, "test-udf-batch.lua", Aerospike::Language::LUA) + expect(register_task.wait_till_completed).to be true + expect(register_task.completed?).to be true + end + + it 'calls specific UDF' do + records = [Aerospike::BatchUDF.new(keys.first, "test-udf-batch", "testStr", ["ping_str"])] + client.batch_operate(records, batch_policy) + + expect(records[0].result_code).to eql 0 + expect(records[0].record.bins).to eql({ "SUCCESS"=>"ping_str" }) + end + + it 'filter out' do + records = [Aerospike::BatchUDF.new(keys.first, "test-udf-batch", "testStr", ["ping_str"])] + client.batch_operate(records, opts) + + expect(records[0].result_code).to eql Aerospike::ResultCode::FILTERED_OUT + expect(records[0].record).to eql nil + end + + end # context + + end # describe + +end # describe diff --git a/spec/aerospike/batch_spec.rb b/spec/aerospike/batch_spec.rb index 964d2d73..bffd3283 100644 --- a/spec/aerospike/batch_spec.rb +++ b/spec/aerospike/batch_spec.rb @@ -23,9 +23,9 @@ describe "#batch_exists" do shared_examples_for 'a batch_exists request' do - let(:batch_policy) { - Aerospike::BatchPolicy.new(use_batch_direct: use_batch_direct) - } + let(:batch_policy) do + Aerospike::BatchPolicy.new + end let(:existing_keys) { Array.new(3) { Support.gen_random_key } } let(:no_such_key) { Support.gen_random_key } let(:keys) { existing_keys } @@ -34,10 +34,10 @@ before do existing_keys.each_with_index do |key, idx| client.put(key, { - 'idx' => idx, - 'key' => key.user_key, - 'rnd' => rand - }) + 'idx' => idx, + 'key' => key.user_key, + 'rnd' => rand + }) end end @@ -65,23 +65,15 @@ end context 'using batch index protocol' do - let(:use_batch_direct) { false } - - it_behaves_like 'a batch_exists request' - end - - context 'using batch direct protocol', skip: Support.min_version?('4.4.0') do - let(:use_batch_direct) { true } - it_behaves_like 'a batch_exists request' end end describe "#batch_get" do shared_examples_for 'a batch_get request' do - let(:batch_policy) { - Aerospike::BatchPolicy.new(use_batch_direct: use_batch_direct) - } + let(:batch_policy) do + Aerospike::BatchPolicy.new + end let(:existing_keys) { Array.new(3) { Support.gen_random_key } } let(:no_such_key) { Support.gen_random_key } let(:keys) { existing_keys } @@ -91,10 +83,10 @@ before do existing_keys.each_with_index do |key, idx| client.put(key, { - 'idx' => idx, - 'key' => key.user_key, - 'rnd' => rand - }) + 'idx' => idx, + 'key' => key.user_key, + 'rnd' => rand + }) end end @@ -133,7 +125,7 @@ end context 'when given a list of bin names' do - let(:bins) { %w[ idx rnd ] } + let(:bins) { %w[idx rnd] } it 'returns only the specified bins' do expect(result.first.bins.keys).to eql %w[idx rnd] @@ -158,23 +150,16 @@ end context 'using batch index protocol' do - let(:use_batch_direct) { false } - it_behaves_like 'a batch_get request' end - context 'using batch direct protocol', skip: Support.min_version?('4.4.0') do - let(:use_batch_direct) { true } - - it_behaves_like 'a batch_get request' - end end describe "#batch_get_header" do shared_examples_for 'a batch_get_header request' do - let(:batch_policy) { - Aerospike::BatchPolicy.new(use_batch_direct: use_batch_direct) - } + let(:batch_policy) do + Aerospike::BatchPolicy.new + end let(:existing_keys) { Array.new(3) { Support.gen_random_key } } let(:no_such_key) { Support.gen_random_key } let(:keys) { existing_keys } @@ -183,12 +168,10 @@ before do existing_keys.each_with_index do |key, idx| client.put(key, { - 'idx' => idx, - 'key' => key.user_key, - 'rnd' => rand - }, { - ttl: 1000 - }) + 'idx' => idx, + 'key' => key.user_key, + 'rnd' => rand + }, {}) end end @@ -201,7 +184,7 @@ expect(result.map(&:key)).to eql keys end - it 'returns the meta-data for each record' do + it 'returns the meta-data for each record', skip: !Support.ttl_supported? do expect(result.first.generation).to eq 1 expect(result.first.ttl).to be_within(100).of(1000) end @@ -225,15 +208,8 @@ end context 'using batch index protocol' do - let(:use_batch_direct) { false } - it_behaves_like 'a batch_get_header request' end - context 'using batch direct protocol', skip: Support.min_version?('4.4.0') do - let(:use_batch_direct) { true } - - it_behaves_like 'a batch_get_header request' - end end end diff --git a/spec/aerospike/cdt/cdt_list_spec.rb b/spec/aerospike/cdt/cdt_list_spec.rb index ac491edb..618b2368 100644 --- a/spec/aerospike/cdt/cdt_list_spec.rb +++ b/spec/aerospike/cdt/cdt_list_spec.rb @@ -275,7 +275,7 @@ def list_post_op it "returns the value at the specified index" do operation = ListOperation.get_by_index(list_bin, 2) - .and_return(return_type) + .and_return(return_type) result = client.operate(key, [operation]) expect(result.bins[list_bin]).to be 3 @@ -293,7 +293,7 @@ def list_post_op it "returns the value at the specified index range" do operation = ListOperation.get_by_index_range(list_bin, 1, 3) - .and_return(return_type) + .and_return(return_type) result = client.operate(key, [operation]) expect(result.bins[list_bin]).to eql([2, 3, 4]) @@ -301,7 +301,7 @@ def list_post_op it "returns all values starting at the specified index if count is not specified" do operation = ListOperation.get_by_index_range(list_bin, 1) - .and_return(return_type) + .and_return(return_type) result = client.operate(key, [operation]) expect(result.bins[list_bin]).to eql([2, 3, 4, 5]) @@ -313,7 +313,7 @@ def list_post_op it "returns the value at the specified rank" do operation = ListOperation.get_by_rank(list_bin, 0) - .and_return(return_type) + .and_return(return_type) result = client.operate(key, [operation]) expect(result.bins[list_bin]).to be 1 @@ -325,7 +325,7 @@ def list_post_op it "returns the value at the specified rank range" do operation = ListOperation.get_by_rank_range(list_bin, 1, 3) - .and_return(return_type) + .and_return(return_type) result = client.operate(key, [operation]) expect(result.bins[list_bin]).to contain_exactly(2, 3, 4) @@ -333,7 +333,7 @@ def list_post_op it "returns all values starting at the specified index if count is not specified" do operation = ListOperation.get_by_rank_range(list_bin, 3) - .and_return(return_type) + .and_return(return_type) result = client.operate(key, [operation]) expect(result.bins[list_bin]).to contain_exactly(4, 5) @@ -346,7 +346,7 @@ def list_post_op it "returns the index of the specified value" do operation = ListOperation.get_by_value(list_bin, 2) - .and_return(return_type) + .and_return(return_type) result = client.operate(key, [operation]) expect(result.bins[list_bin]).to contain_exactly(2, 6) @@ -359,7 +359,7 @@ def list_post_op it "returns the indeces of the items in the specified value range" do operation = ListOperation.get_by_value_range(list_bin, 2, 4) - .and_return(return_type) + .and_return(return_type) result = client.operate(key, [operation]) expect(result.bins[list_bin]).to contain_exactly(2, 3, 6) @@ -367,7 +367,7 @@ def list_post_op it "returns the indeces of the items starting with the specified value" do operation = ListOperation.get_by_value_range(list_bin, 2) - .and_return(return_type) + .and_return(return_type) result = client.operate(key, [operation]) expect(result.bins[list_bin]).to contain_exactly(1, 2, 3, 4, 6) @@ -380,7 +380,7 @@ def list_post_op it "returns the indeces of the items in the specified list" do operation = ListOperation.get_by_value_list(list_bin, [2, 4]) - .and_return(return_type) + .and_return(return_type) result = client.operate(key, [operation]) expect(result.bins[list_bin]).to contain_exactly(1, 2, 6) @@ -392,7 +392,7 @@ def list_post_op it "returns the values of the items nearest to and greater than the specified value, by relative rank range" do operation = ListOperation.get_by_value_rel_rank_range(list_bin, 5, 0, 2) - .and_return(return_type) + .and_return(return_type) result = client.operate(key, [operation]) expect(result.bins[list_bin]).to contain_exactly(5, 9) @@ -400,7 +400,7 @@ def list_post_op it "returns the values of the items nearest to and greater than the specified value, starting with the specified relative rank" do operation = ListOperation.get_by_value_rel_rank_range(list_bin, 5, 0) - .and_return(return_type) + .and_return(return_type) result = client.operate(key, [operation]) expect(result.bins[list_bin]).to contain_exactly(5, 9, 11, 15) @@ -412,7 +412,7 @@ def list_post_op it "removes the value at the specified index" do operation = ListOperation.remove_by_index(list_bin, 2) - .and_return(return_type) + .and_return(return_type) result = client.operate(key, [operation]) expect(result.bins[list_bin]).to be 3 @@ -431,7 +431,7 @@ def list_post_op it "removes the values at the specified index range" do operation = ListOperation.remove_by_index_range(list_bin, 1, 3) - .and_return(return_type) + .and_return(return_type) result = client.operate(key, [operation]) expect(result.bins[list_bin]).to eql([2, 3, 4]) @@ -440,7 +440,7 @@ def list_post_op it "returns all values starting at the specified index if count is not specified" do operation = ListOperation.remove_by_index_range(list_bin, 1) - .and_return(return_type) + .and_return(return_type) result = client.operate(key, [operation]) expect(result.bins[list_bin]).to eql([2, 3, 4, 5]) @@ -453,7 +453,7 @@ def list_post_op it "removes the value at the specified rank" do operation = ListOperation.remove_by_rank(list_bin, 0) - .and_return(return_type) + .and_return(return_type) result = client.operate(key, [operation]) expect(result.bins[list_bin]).to be 1 @@ -466,7 +466,7 @@ def list_post_op it "removes the value at the specified rank range" do operation = ListOperation.remove_by_rank_range(list_bin, 1, 3) - .and_return(return_type) + .and_return(return_type) result = client.operate(key, [operation]) expect(result.bins[list_bin]).to contain_exactly(2, 3, 4) @@ -475,7 +475,7 @@ def list_post_op it "returns all values starting at the specified index if count is not specified" do operation = ListOperation.remove_by_rank_range(list_bin, 3) - .and_return(return_type) + .and_return(return_type) result = client.operate(key, [operation]) expect(result.bins[list_bin]).to contain_exactly(4, 5) @@ -489,7 +489,7 @@ def list_post_op it "removes the index of the specified value" do operation = ListOperation.remove_by_value(list_bin, 2) - .and_return(return_type) + .and_return(return_type) result = client.operate(key, [operation]) expect(result.bins[list_bin]).to contain_exactly(2, 6) @@ -503,7 +503,7 @@ def list_post_op it "removes the indeces of the items in the specified value range" do operation = ListOperation.remove_by_value_range(list_bin, 2, 4) - .and_return(return_type) + .and_return(return_type) result = client.operate(key, [operation]) expect(result.bins[list_bin]).to contain_exactly(2, 3, 6) @@ -512,7 +512,7 @@ def list_post_op it "removes the indeces of the items starting with the specified value" do operation = ListOperation.remove_by_value_range(list_bin, 2) - .and_return(return_type) + .and_return(return_type) result = client.operate(key, [operation]) expect(result.bins[list_bin]).to contain_exactly(1, 2, 3, 4, 6) @@ -526,7 +526,7 @@ def list_post_op it "removes the indeces of the items in the specified list" do operation = ListOperation.remove_by_value_list(list_bin, [2, 4]) - .and_return(return_type) + .and_return(return_type) result = client.operate(key, [operation]) expect(result.bins[list_bin]).to contain_exactly(1, 2, 6) @@ -539,7 +539,7 @@ def list_post_op it "removes the values of the items nearest to and greater than the specified value, by relative rank range" do operation = ListOperation.remove_by_value_rel_rank_range(list_bin, 5, 0, 2) - .and_return(return_type) + .and_return(return_type) result = client.operate(key, [operation]) expect(result.bins[list_bin]).to contain_exactly(5, 9) @@ -548,7 +548,7 @@ def list_post_op it "removes the values of the items nearest to and greater than the specified value, starting with the specified relative rank" do operation = ListOperation.remove_by_value_rel_rank_range(list_bin, 5, 0) - .and_return(return_type) + .and_return(return_type) result = client.operate(key, [operation]) expect(result.bins[list_bin]).to contain_exactly(5, 9, 11, 15) @@ -561,7 +561,7 @@ def list_post_op it "returns nothing by default" do operation = ListOperation.remove_by_index_range(list_bin, 2, 4) - .and_return(ListReturnType::DEFAULT) + .and_return(ListReturnType::DEFAULT) result = client.operate(key, [operation]) expected = { list_bin => nil } @@ -570,7 +570,7 @@ def list_post_op it "returns the list index" do operation = ListOperation.remove_by_index_range(list_bin, 2, 4) - .and_return(ListReturnType::INDEX) + .and_return(ListReturnType::INDEX) result = client.operate(key, [operation]) expect(result.bins[list_bin]).to eql([2, 3, 4, 5]) @@ -578,7 +578,7 @@ def list_post_op it "returns the reverse list index" do operation = ListOperation.remove_by_index_range(list_bin, 2, 4) - .and_return(ListReturnType::REVERSE_INDEX) + .and_return(ListReturnType::REVERSE_INDEX) result = client.operate(key, [operation]) expect(result.bins[list_bin]).to eql([1, 2, 3, 4]) @@ -586,7 +586,7 @@ def list_post_op it "returns the list rank" do operation = ListOperation.remove_by_index_range(list_bin, 2, 4) - .and_return(ListReturnType::RANK) + .and_return(ListReturnType::RANK) result = client.operate(key, [operation]) expect(result.bins[list_bin]).to eql([2, 4, 6, 0]) @@ -594,7 +594,7 @@ def list_post_op it "returns the reverse list rank" do operation = ListOperation.remove_by_index_range(list_bin, 2, 4) - .and_return(ListReturnType::REVERSE_RANK) + .and_return(ListReturnType::REVERSE_RANK) result = client.operate(key, [operation]) expect(result.bins[list_bin]).to eql([4, 2, 0, 6]) @@ -602,7 +602,7 @@ def list_post_op it "returns the number of items" do operation = ListOperation.remove_by_index_range(list_bin, 2, 4) - .and_return(ListReturnType::COUNT) + .and_return(ListReturnType::COUNT) result = client.operate(key, [operation]) expect(result.bins[list_bin]).to be 4 @@ -610,7 +610,7 @@ def list_post_op it "returns the value of the items" do operation = ListOperation.remove_by_index_range(list_bin, 2, 4) - .and_return(ListReturnType::VALUE) + .and_return(ListReturnType::VALUE) result = client.operate(key, [operation]) expect(result.bins[list_bin]).to eql([2, 3, 5, 1]) @@ -622,8 +622,8 @@ def list_post_op it "inverts the selection of items affected by the operation" do operation = ListOperation.remove_by_index_range(list_bin, 2, 4) - .and_return(return_type) - .invert_selection + .and_return(return_type) + .invert_selection result = client.operate(key, [operation]) expect(result.bins[list_bin]).to eql([1, 4, 2]) @@ -638,7 +638,7 @@ def list_post_op list = [ [7, 9, 5], [1, 2, 3], - [6, 5, 4, 1], + [6, 5, 4, 1] ] client.put(key, Aerospike::Bin.new(list_bin, list)) @@ -646,7 +646,7 @@ def list_post_op # 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), + Aerospike::Operation.get(list_bin) ] record = client.operate(key, operation) @@ -660,7 +660,7 @@ def list_post_op list = [ [7, 9, 5], [1, 2, 3], - [6, 5, 4, 1], + [6, 5, 4, 1] ] client.put(key, Aerospike::Bin.new(list_bin, list)) @@ -670,10 +670,10 @@ def list_post_op record = client.operate(key, [ListOperation.append(list_bin, 11, ctx: [Context.list_index(-1)]), Aerospike::Operation.get(list_bin)]) expect(record.bins[list_bin]).to eq([ - [7, 9, 5], - [1, 2, 3], - [6, 5, 4, 1, 11], - ]) + [7, 9, 5], + [1, 2, 3], + [6, 5, 4, 1, 11] + ]) end it "is used to change a map in nested list" do @@ -682,13 +682,13 @@ def list_post_op m = { "key1" => [ [7, 9, 5], - [13], + [13] ], "key2" => [ [9], [2, 4], - [6, 1, 9], - ], + [6, 1, 9] + ] } client.put(key, Aerospike::Bin.new(list_bin, m)) @@ -701,13 +701,13 @@ def list_post_op { "key1" => [ [7, 9, 5], - [13], + [13] ], "key2" => [ [9], [2, 4, 11], - [6, 1, 9], - ], + [6, 1, 9] + ] } ) end @@ -789,7 +789,7 @@ def list_post_op it "returns all list elements from 10 to Infinity" do operation = ListOperation.get_by_value_range(list_bin, 10, Aerospike::Value::INFINITY) - .and_return(return_type) + .and_return(return_type) result = client.operate(key, [operation]) @@ -802,7 +802,7 @@ def list_post_op it "returns all list elements that match a wildcard" do operation = ListOperation.get_by_value(list_bin, ["Jim", Aerospike::Value::WILDCARD]) - .and_return(return_type) + .and_return(return_type) result = client.operate(key, [operation]) diff --git a/spec/aerospike/cdt/cdt_map_spec.rb b/spec/aerospike/cdt/cdt_map_spec.rb index 3e1088e5..2a31a3ac 100644 --- a/spec/aerospike/cdt/cdt_map_spec.rb +++ b/spec/aerospike/cdt/cdt_map_spec.rb @@ -39,7 +39,7 @@ def map_post_op client.delete(key) m = { - "key1" => [7, 9, 5], + "key1" => [7, 9, 5] } client.put(key, Aerospike::Bin.new(map_bin, m)) @@ -48,16 +48,16 @@ def map_post_op 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), - ]) + 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], - }) + "key1" => [7, 9, 5], + "key2" => [1, 2] + }) end it "should support Nested Map ops with Lists" do @@ -65,26 +65,26 @@ def map_post_op m = { "key1" => { - "key11" => 9, "key12" => 4, + "key11" => 9, "key12" => 4 }, "key2" => { - "key21" => 3, "key22" => 5, - }, + "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)]) + 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, - }, - }) + "key1" => { + "key11" => 9, "key12" => 4 + }, + "key2" => { + "key21" => 11, "key22" => 5 + } + }) end it "should support Nested Map ops" do @@ -92,26 +92,26 @@ def map_post_op m = { "key1" => { - "key11" => 9, "key12" => 4, + "key11" => 9, "key12" => 4 }, "key2" => { - "key21" => 3, "key22" => 5, - }, + "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)]) + 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, - }, - }) + "key1" => { + "key11" => 9, "key12" => 4 + }, + "key2" => { + "key21" => 11, "key22" => 5 + } + }) end it "should support Double Nested Map ops" do @@ -119,11 +119,11 @@ def map_post_op m = { "key1" => { - "key11" => { "key111" => 1 }, "key12" => { "key121" => 5 }, + "key11" => { "key111" => 1 }, "key12" => { "key121" => 5 } }, "key2" => { - "key21" => { "key211" => 7 }, - }, + "key21" => { "key211" => 7 } + } } client.put(key, Aerospike::Bin.new(map_bin, m)) @@ -131,16 +131,16 @@ def map_post_op 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, "key121", 11, ctx: [Context::map_key("key1"), Context.map_rank(-1)]), Aerospike::Operation.get(map_bin)]) + record = client.operate(key, [MapOperation.put(map_bin, "key121", 11, ctx: [Context.map_key("key1"), Context.map_rank(-1)]), Aerospike::Operation.get(map_bin)]) expect(record.bins[map_bin]).to eq({ - "key1" => { - "key11" => { "key111" => 1 }, "key12" => { "key121" => 11 }, - }, - "key2" => { - "key21" => { "key211" => 7 }, - }, - }) + "key1" => { + "key11" => { "key111" => 1 }, "key12" => { "key121" => 11 } + }, + "key2" => { + "key21" => { "key211" => 7 } + } + }) end end @@ -221,7 +221,7 @@ def map_post_op it "removes a single key from the map" do operation = MapOperation.remove_by_key(map_bin, "b") - .and_return(MapReturnType::VALUE) + .and_return(MapReturnType::VALUE) result = client.operate(key, [operation]) expect(result.bins[map_bin]).to be(2) @@ -233,8 +233,8 @@ def map_post_op let(:map_value) { { "a" => 1, "b" => 2, "c" => 3 } } it "removes a list of keys from the map" do - operation = MapOperation.remove_by_key_list(map_bin, ["a", "b"]) - .and_return(MapReturnType::VALUE) + operation = MapOperation.remove_by_key_list(map_bin, %w[a b]) + .and_return(MapReturnType::VALUE) result = client.operate(key, [operation]) expect(result.bins[map_bin]).to contain_exactly(1, 2) @@ -278,7 +278,7 @@ def map_post_op it "removes specified number of elements" do operation = MapOperation.remove_by_key_rel_index_range(map_bin, "f", 1, 2) - .and_return(MapReturnType::KEY) + .and_return(MapReturnType::KEY) result = client.operate(key, [operation]) expect(result.bins[map_bin]).to contain_exactly("j") @@ -287,7 +287,7 @@ def map_post_op it "removes elements from specified key until the end" do operation = MapOperation.remove_by_key_rel_index_range(map_bin, "f", 1) - .and_return(MapReturnType::KEY) + .and_return(MapReturnType::KEY) result = client.operate(key, [operation]) expect(result.bins[map_bin]).to contain_exactly("j") @@ -300,10 +300,10 @@ def map_post_op it "removes the items identified by a single value" do operation = MapOperation.remove_by_value(map_bin, 2) - .and_return(MapReturnType::KEY) + .and_return(MapReturnType::KEY) result = client.operate(key, [operation]) - expect(result.bins[map_bin]).to eql(["b", "d"]) + expect(result.bins[map_bin]).to eql(%w[b d]) expect(map_post_op).to eql({ "a" => 1, "c" => 3 }) end end @@ -313,7 +313,7 @@ def map_post_op it "removes the items identified by a list of values" do operation = MapOperation.remove_by_value_list(map_bin, [2, 3]) - .and_return(MapReturnType::KEY) + .and_return(MapReturnType::KEY) result = client.operate(key, [operation]) expect(result.bins[map_bin]).to contain_exactly("b", "c", "d") @@ -357,7 +357,7 @@ def map_post_op it "removes specified number of elements" do operation = MapOperation.remove_by_value_rel_rank_range(map_bin, 11, -1, 1) - .and_return(MapReturnType::KEY_VALUE) + .and_return(MapReturnType::KEY_VALUE) result = client.operate(key, [operation]) expect(result.bins[map_bin]).to eql({ 9 => 10 }) @@ -366,7 +366,7 @@ def map_post_op it "removes elements from specified key until the end" do operation = MapOperation.remove_by_value_rel_rank_range(map_bin, 11, -1) - .and_return(MapReturnType::KEY_VALUE) + .and_return(MapReturnType::KEY_VALUE) result = client.operate(key, [operation]) expect(result.bins[map_bin]).to eql({ 9 => 10, 5 => 15, 0 => 17 }) @@ -460,7 +460,7 @@ def map_post_op it "gets a single key from the map" do operation = MapOperation.get_by_key(map_bin, "b") - .and_return(MapReturnType::KEY_VALUE) + .and_return(MapReturnType::KEY_VALUE) result = client.operate(key, [operation]) expect(result.bins[map_bin]).to eql({ "b" => 2 }) @@ -471,8 +471,8 @@ def map_post_op let(:map_value) { { "a" => 1, "b" => 2, "c" => 3 } } it "gets a list of keys from the map" do - operation = MapOperation.get_by_key_list(map_bin, ["b", "c"]) - .and_return(MapReturnType::VALUE) + operation = MapOperation.get_by_key_list(map_bin, %w[b c]) + .and_return(MapReturnType::VALUE) result = client.operate(key, [operation]) expect(result.bins[map_bin]).to contain_exactly(2, 3) @@ -484,7 +484,7 @@ def map_post_op it "gets the specified key range from the map" do operation = MapOperation.get_by_key_range(map_bin, "b", "c") - .and_return(MapReturnType::KEY_VALUE) + .and_return(MapReturnType::KEY_VALUE) result = client.operate(key, [operation]) expect(result.bins[map_bin]).to eql({ "b" => 2 }) @@ -492,7 +492,7 @@ def map_post_op it "gets all keys from the specified start key until the end" do operation = MapOperation.get_by_key_range(map_bin, "b") - .and_return(MapReturnType::KEY_VALUE) + .and_return(MapReturnType::KEY_VALUE) result = client.operate(key, [operation]) expect(result.bins[map_bin]).to eql({ "b" => 2, "c" => 3 }) @@ -500,39 +500,59 @@ def map_post_op it "gets all keys from the start to the specified end key" do operation = MapOperation.get_by_key_range(map_bin, nil, "b") - .and_return(MapReturnType::KEY_VALUE) + .and_return(MapReturnType::KEY_VALUE) result = client.operate(key, [operation]) expect(result.bins[map_bin]).to eql({ "a" => 1 }) end end - describe "MapOperation.get_by_key_rel_index_range", skip: !Support.min_version?("4.3") do - let(:map_value) { { "a" => 17, "e" => 2, "f" => 15, "j" => 10 } } + describe "MapOperation.get_by_key_range" do + let(:map_value) { { "b" => 2, "a" => 1, "c" => 3 } } - it "gets specified number of elements" do - operation = MapOperation.get_by_key_rel_index_range(map_bin, "f", 1, 2) - .and_return(MapReturnType::KEY) + it "gets all keys from the start to the specified end key, return ordered map" do + operation = MapOperation.get_by_key_range(map_bin, nil, "c") + .and_return(MapReturnType::ORDERED_MAP) result = client.operate(key, [operation]) - expect(result.bins[map_bin]).to contain_exactly("j") + expect(result.bins[map_bin]).to eql({ "a" => 1, "b" => 2 }) end - it "get elements from specified key until the end" do - operation = MapOperation.get_by_key_rel_index_range(map_bin, "f", 1) - .and_return(MapReturnType::KEY) + it "gets all keys from the start to the specified end key, return unordered map" do + operation = MapOperation.get_by_key_range(map_bin, nil, "c") + .and_return(MapReturnType::UNORDERED_MAP) result = client.operate(key, [operation]) - expect(result.bins[map_bin]).to contain_exactly("j") + expect(result.bins[map_bin]).to eql({ "b" => 2, "a" => 1 }) end end + describe "MapOperation.get_by_key_rel_index_range", skip: !Support.min_version?("4.3") do + let(:map_value) { { "a" => 17, "e" => 2, "f" => 15, "j" => 10 } } + + it "gets specified number of elements" do + operation = MapOperation.get_by_key_rel_index_range(map_bin, "f", 1, 2) + .and_return(MapReturnType::KEY) + result = client.operate(key, [operation]) + + expect(result.bins[map_bin]).to contain_exactly("j") + end + + it "get elements from specified key until the end" do + operation = MapOperation.get_by_key_rel_index_range(map_bin, "f", 1) + .and_return(MapReturnType::KEY) + result = client.operate(key, [operation]) + + expect(result.bins[map_bin]).to contain_exactly("j") + end + end + describe "MapOperation.get_by_value" do let(:map_value) { { "a" => 1, "b" => 2, "c" => 3, "d" => 2 } } it "gets the item identified by a single value" do operation = MapOperation.get_by_value(map_bin, 2) - .and_return(MapReturnType::KEY_VALUE) + .and_return(MapReturnType::KEY_VALUE) result = client.operate(key, [operation]) expect(result.bins[map_bin]).to eql({ "b" => 2, "d" => 2 }) @@ -544,7 +564,7 @@ def map_post_op it "gets the items identified by a list of values" do operation = MapOperation.get_by_value_list(map_bin, [2, 3]) - .and_return(MapReturnType::KEY_VALUE) + .and_return(MapReturnType::KEY_VALUE) result = client.operate(key, [operation]) expect(result.bins[map_bin]).to eql({ "b" => 2, "c" => 3, "d" => 2 }) @@ -556,7 +576,7 @@ def map_post_op it "gets the specified key range from the map" do operation = MapOperation.get_by_value_range(map_bin, 2, 3) - .and_return(MapReturnType::KEY_VALUE) + .and_return(MapReturnType::KEY_VALUE) result = client.operate(key, [operation]) expect(result.bins[map_bin]).to eql({ "b" => 2, "d" => 2 }) @@ -564,7 +584,7 @@ def map_post_op it "gets all values from the specified start value until the end" do operation = MapOperation.get_by_value_range(map_bin, 2) - .and_return(MapReturnType::KEY_VALUE) + .and_return(MapReturnType::KEY_VALUE) result = client.operate(key, [operation]) expect(result.bins[map_bin]).to eql({ "b" => 2, "d" => 2, "c" => 3 }) @@ -572,7 +592,7 @@ def map_post_op it "gets all values from the start of the map until the specified end value" do operation = MapOperation.get_by_value_range(map_bin, nil, 3) - .and_return(MapReturnType::KEY_VALUE) + .and_return(MapReturnType::KEY_VALUE) result = client.operate(key, [operation]) expect(result.bins[map_bin]).to eql({ "a" => 1, "b" => 2, "d" => 2 }) @@ -584,7 +604,7 @@ def map_post_op it "gets specified number of elements" do operation = MapOperation.get_by_value_rel_rank_range(map_bin, 11, -1, 1) - .and_return(MapReturnType::KEY_VALUE) + .and_return(MapReturnType::KEY_VALUE) result = client.operate(key, [operation]) expect(result.bins[map_bin]).to eql({ 9 => 10 }) @@ -592,7 +612,7 @@ def map_post_op it "gets elements from specified key until the end" do operation = MapOperation.get_by_value_rel_rank_range(map_bin, 11, -1) - .and_return(MapReturnType::KEY_VALUE) + .and_return(MapReturnType::KEY_VALUE) result = client.operate(key, [operation]) expect(result.bins[map_bin]).to eql({ 9 => 10, 5 => 15, 0 => 17 }) @@ -604,7 +624,7 @@ def map_post_op it "gets a map item identified by index from the map" do operation = MapOperation.get_by_index(map_bin, 1) - .and_return(MapReturnType::KEY_VALUE) + .and_return(MapReturnType::KEY_VALUE) result = client.operate(key, [operation]) expect(result.bins[map_bin]).to eql({ "b" => 2 }) @@ -616,18 +636,18 @@ def map_post_op it "gets 'count' map items starting at the specified index from the map" do operation = MapOperation.get_by_index_range(map_bin, 1, 2) - .and_return(MapReturnType::KEY) + .and_return(MapReturnType::KEY) result = client.operate(key, [operation]) - expect(result.bins[map_bin]).to eql(["b", "c"]) + expect(result.bins[map_bin]).to eql(%w[b c]) end it "gets all items starting at the specified index to the end of the map" do operation = MapOperation.get_by_index_range(map_bin, 1) - .and_return(MapReturnType::KEY) + .and_return(MapReturnType::KEY) result = client.operate(key, [operation]) - expect(result.bins[map_bin]).to eql(["b", "c"]) + expect(result.bins[map_bin]).to eql(%w[b c]) end end @@ -636,7 +656,7 @@ def map_post_op it "gets a map item identified by rank from the map" do operation = MapOperation.get_by_rank(map_bin, 1) - .and_return(MapReturnType::KEY_VALUE) + .and_return(MapReturnType::KEY_VALUE) result = client.operate(key, [operation]) expect(result.bins[map_bin]).to eql({ "b" => 2 }) @@ -648,18 +668,18 @@ def map_post_op it "gets 'count' map items starting at the specified rank from the map" do operation = MapOperation.get_by_rank_range(map_bin, 1, 2) - .and_return(MapReturnType::KEY) + .and_return(MapReturnType::KEY) result = client.operate(key, [operation]) - expect(result.bins[map_bin]).to eql(["b", "a"]) + expect(result.bins[map_bin]).to eql(%w[b a]) end it "gets all items starting at the specified rank to the end of the map" do operation = MapOperation.get_by_rank_range(map_bin, 1) - .and_return(MapReturnType::KEY) + .and_return(MapReturnType::KEY) result = client.operate(key, [operation]) - expect(result.bins[map_bin]).to eql(["b", "a"]) + expect(result.bins[map_bin]).to eql(%w[b a]) end end @@ -669,7 +689,7 @@ def map_post_op it "removes a single key from the map" do operation = MapOperation.remove_keys(map_bin, "b") - .and_return(MapReturnType::VALUE) + .and_return(MapReturnType::VALUE) result = client.operate(key, [operation]) expect(result.bins[map_bin]).to be(2) @@ -678,7 +698,7 @@ def map_post_op it "removes multiple keys from the map" do operation = MapOperation.remove_keys(map_bin, "b", "c") - .and_return(MapReturnType::VALUE) + .and_return(MapReturnType::VALUE) result = client.operate(key, [operation]) expect(result.bins[map_bin]).to contain_exactly(2, 3) @@ -691,7 +711,7 @@ def map_post_op it "removes the items identified by a single value" do operation = MapOperation.remove_values(map_bin, 2) - .and_return(MapReturnType::KEY) + .and_return(MapReturnType::KEY) result = client.operate(key, [operation]) expect(result.bins[map_bin]).to contain_exactly("b", "d") @@ -700,7 +720,7 @@ def map_post_op it "removes the items identified by multiple values" do operation = MapOperation.remove_values(map_bin, 2, 3) - .and_return(MapReturnType::KEY) + .and_return(MapReturnType::KEY) result = client.operate(key, [operation]) expect(result.bins[map_bin]).to contain_exactly("b", "c", "d") @@ -715,7 +735,7 @@ def map_post_op context "NONE" do it "returns nothing" do operation = MapOperation.get_by_key(map_bin, "a") - .and_return(MapReturnType::NONE) + .and_return(MapReturnType::NONE) result = client.operate(key, [operation]) expected = { "map_bin" => nil } @@ -726,7 +746,7 @@ def map_post_op context "INDEX" do it "returns returns the elements index" do operation = MapOperation.get_by_key(map_bin, "a") - .and_return(MapReturnType::INDEX) + .and_return(MapReturnType::INDEX) result = client.operate(key, [operation]) expect(result.bins[map_bin]).to eql(0) @@ -736,7 +756,7 @@ def map_post_op context "REVERSE_INDEX" do it "returns the elements reverse index" do operation = MapOperation.get_by_key(map_bin, "a") - .and_return(MapReturnType::REVERSE_INDEX) + .and_return(MapReturnType::REVERSE_INDEX) result = client.operate(key, [operation]) expect(result.bins[map_bin]).to eql(2) @@ -746,7 +766,7 @@ def map_post_op context "RANK" do it "returns the elements rank" do operation = MapOperation.get_by_key(map_bin, "a") - .and_return(MapReturnType::RANK) + .and_return(MapReturnType::RANK) result = client.operate(key, [operation]) expect(result.bins[map_bin]).to eql(2) @@ -756,7 +776,7 @@ def map_post_op context "REVERSE_RANK" do it "returns the elements reverse rank" do operation = MapOperation.get_by_key(map_bin, "a") - .and_return(MapReturnType::REVERSE_RANK) + .and_return(MapReturnType::REVERSE_RANK) result = client.operate(key, [operation]) expect(result.bins[map_bin]).to eql(0) @@ -766,7 +786,7 @@ def map_post_op context "COUNT" do it "returns the count of items selected" do operation = MapOperation.get_by_key_range(map_bin, "a", "c") - .and_return(MapReturnType::COUNT) + .and_return(MapReturnType::COUNT) result = client.operate(key, [operation]) expect(result.bins[map_bin]).to eql(2) @@ -776,7 +796,7 @@ def map_post_op context "KEY" do it "returns the map key" do operation = MapOperation.get_by_index(map_bin, 0) - .and_return(MapReturnType::KEY) + .and_return(MapReturnType::KEY) result = client.operate(key, [operation]) expect(result.bins[map_bin]).to eql("a") @@ -786,7 +806,7 @@ def map_post_op context "VALUE" do it "returns the map value" do operation = MapOperation.get_by_index(map_bin, 0) - .and_return(MapReturnType::VALUE) + .and_return(MapReturnType::VALUE) result = client.operate(key, [operation]) expect(result.bins[map_bin]).to eql(3) @@ -796,7 +816,7 @@ def map_post_op context "KEY_VALUE" do it "returns the map key & value" do operation = MapOperation.get_by_index(map_bin, 0) - .and_return(MapReturnType::KEY_VALUE) + .and_return(MapReturnType::KEY_VALUE) result = client.operate(key, [operation]) expect(result.bins[map_bin]).to eql({ "a" => 3 }) @@ -1065,7 +1085,7 @@ def map_post_op it "returns all keys from 5 to Infinity" do operation = MapOperation.get_by_key_range(map_bin, 5, Aerospike::Value::INFINITY) - .and_return(MapReturnType::KEY) + .and_return(MapReturnType::KEY) result = client.operate(key, [operation]) @@ -1074,18 +1094,18 @@ def map_post_op end context "Wildcard value", skip: !Support.min_version?("4.3.1") do - let(:map_value) { + let(:map_value) do { 4 => ["John", 55], 5 => ["Jim", 95], 9 => ["Joe", 80], - 12 => ["Jim", 46], + 12 => ["Jim", 46] } - } + end it "returns all values that match a wildcard" do operation = MapOperation.get_by_value(map_bin, ["Jim", Aerospike::Value::WILDCARD]) - .and_return(MapReturnType::KEY) + .and_return(MapReturnType::KEY) result = client.operate(key, [operation]) diff --git a/spec/aerospike/exp/exp_bit_spec.rb b/spec/aerospike/exp/exp_bit_spec.rb index 7dc72374..18ecc926 100644 --- a/spec/aerospike/exp/exp_bit_spec.rb +++ b/spec/aerospike/exp/exp_bit_spec.rb @@ -27,7 +27,7 @@ @set = "query1000" Support.client.truncate(@namespace, @set) - opts = { expiration: 24 * 60 * 60 } + opts = { } @key_count.times do |ii| key = Aerospike::Key.new(@namespace, @set, ii) bytes = bytes_to_str([0b00000001, 0b01000010]) diff --git a/spec/aerospike/exp/exp_hll_spec.rb b/spec/aerospike/exp/exp_hll_spec.rb index 2f14cc63..ebde871e 100644 --- a/spec/aerospike/exp/exp_hll_spec.rb +++ b/spec/aerospike/exp/exp_hll_spec.rb @@ -28,7 +28,7 @@ Support.client.truncate(@namespace, @set) - opts = { expiration: 24 * 60 * 60 } + opts = { } @key_count.times do |ii| key = Aerospike::Key.new(@namespace, @set, ii) bin = { "bin" => ii, "lbin" => [ii, "a"] } diff --git a/spec/aerospike/exp/exp_list_spec.rb b/spec/aerospike/exp/exp_list_spec.rb index cf8eadd6..00d7c702 100644 --- a/spec/aerospike/exp/exp_list_spec.rb +++ b/spec/aerospike/exp/exp_list_spec.rb @@ -26,7 +26,7 @@ @namespace = "test" @set = "query1000" - opts = { expiration: 24 * 60 * 60 } + opts = { } @key_count.times do |ii| key = Aerospike::Key.new(@namespace, @set, ii) ibin = { "bin" => [1, 2, 3, ii] } diff --git a/spec/aerospike/exp/exp_map_spec.rb b/spec/aerospike/exp/exp_map_spec.rb index 3e0a4eee..4ee12468 100644 --- a/spec/aerospike/exp/exp_map_spec.rb +++ b/spec/aerospike/exp/exp_map_spec.rb @@ -26,7 +26,7 @@ @namespace = "test" @set = "query1000" - opts = { expiration: 24 * 60 * 60 } + opts = { } @key_count.times do |ii| key = Aerospike::Key.new(@namespace, @set, ii) ibin = { "bin" => { "test" => ii, "test2" => "a" } } diff --git a/spec/aerospike/exp/expression_spec.rb b/spec/aerospike/exp/expression_spec.rb index f57f5143..f044b4e6 100644 --- a/spec/aerospike/exp/expression_spec.rb +++ b/spec/aerospike/exp/expression_spec.rb @@ -31,7 +31,7 @@ "bin1" => "value#{i}", "bin2" => i, "bin3" => [i, i + 1_000, i + 1_000_000], - "bin4" => { "key#{i}" => i }, + "bin4" => { "key#{i}" => i } } Support.client.put(key, bin_map) end @@ -39,7 +39,7 @@ Support.client.drop_index(@namespace, @set, "index_intval") Support.client.drop_index(@namespace, @set, "index_strval") - wpolicy = { generation: 0, expiration: 24 * 60 * 60 } + wpolicy = { generation: 0 } starbucks = [ [-122.1708441, 37.4241193], @@ -56,7 +56,7 @@ [-122.0303178, 37.3882739], [-122.0464861, 37.3786236], [-122.0582128, 37.3726980], - [-122.0365083, 37.3676930], + [-122.0365083, 37.3676930] ] @record_count.times do |ii| @@ -74,12 +74,12 @@ lat = 37.5 + (0.01 * ii) point = Aerospike::GeoJSON.point(lat, lng) - if ii < starbucks.length - region = Aerospike::GeoJSON.circle(starbucks[ii][0], starbucks[ii][1], 3000.0) - else + region = if ii < starbucks.length + Aerospike::GeoJSON.circle(starbucks[ii][0], starbucks[ii][1], 3000.0) + else # Somewhere off Africa ... - region = Aerospike::GeoJSON.circle(0.0, 0.0, 3000.0) - end + Aerospike::GeoJSON.circle(0.0, 0.0, 3000.0) + end # Accumulate prime factors of the index into a list and map. listval = [] @@ -87,7 +87,7 @@ [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31].each do |ff| if ii >= ff && ii % ff == 0 listval << ff - mapval[ff] = sprintf("0x%04x", ff) + mapval[ff] = format("0x%04x", ff) end end @@ -95,13 +95,13 @@ bins = { "intval" => ii, - "strval" => sprintf("0x%04x", ii), + "strval" => format("0x%04x", ii), "modval" => ii % 10, # "locval" => point, # "rgnval" => region, "lstval" => listval, "mapval" => mapval, - "ballast" => ballast, + "ballast" => ballast } Support.client.put(key, bins, wpolicy) @@ -119,10 +119,12 @@ stmt = Aerospike::Statement.new(@namespace, @set) stmt.filters << Aerospike::Filter.Range("intval", 0, 400) opts = { filter_exp: Aerospike::Exp.int_val(100) } - expect { + expect do rs = client.query(stmt, opts) - rs.each do end - }.to raise_error (Aerospike::Exceptions::Aerospike) { |error| + rs.each do + + end + end.to raise_error(Aerospike::Exceptions::Aerospike) { |error| error.result_code == Aerospike::ResultCode::PARAMETER_ERROR } end @@ -134,7 +136,7 @@ stmt.filters << Aerospike::Filter.Range("intval", 0, 400) opts = { filter_exp: Aerospike::Exp.ge(Aerospike::Exp.int_bin("modval"), Aerospike::Exp.int_val(8)) } - # The query clause selects [0, 1, ... 400, 401] The predexp + # The query clause selects [0, 1, ... 400, 401] The filter_exp # only takes mod 8 and 9, should be 2 pre decade or 80 total. rs = client.query(stmt, opts) @@ -162,11 +164,11 @@ opts = { filter_exp: Aerospike::Exp.or( Aerospike::Exp.and( Aerospike::Exp.not(Aerospike::Exp.eq(Aerospike::Exp.str_bin("strval"), Aerospike::Exp.str_val("0x0001"))), - Aerospike::Exp.ge(Aerospike::Exp.int_bin("modval"), Aerospike::Exp.int_val(8)), + Aerospike::Exp.ge(Aerospike::Exp.int_bin("modval"), Aerospike::Exp.int_val(8)) ), Aerospike::Exp.eq(Aerospike::Exp.str_bin("strval"), Aerospike::Exp.str_val("0x0104")), Aerospike::Exp.eq(Aerospike::Exp.str_bin("strval"), Aerospike::Exp.str_val("0x0105")), - Aerospike::Exp.eq(Aerospike::Exp.str_bin("strval"), Aerospike::Exp.str_val("0x0106")), + Aerospike::Exp.eq(Aerospike::Exp.str_bin("strval"), Aerospike::Exp.str_val("0x0106")) ) } rs = client.query(stmt, opts) @@ -219,7 +221,7 @@ def query_method(exp, ops = {}) "bin3" => ii.to_f / 3, "bin4" => BytesValue.new("blob#{ii}"), "bin5" => ["a", "b", ii], - "bin6" => { "a": "test", "b": ii }, + "bin6" => { a: "test", b: ii } } Support.client.put(key, bins) end @@ -255,7 +257,7 @@ def query_method(exp, ops = {}) ["key must work", 0, Exp.eq(Exp.key(Exp::Type::INT), Exp.int_val(50))], ["key_exists must work", 0, Exp.key_exists], ["nil must work", 100, Exp.eq(Exp.nil_val, Exp.nil_val)], - ["regex_compare must work", 75, Exp.regex_compare("[1-5]", Exp::RegexFlags::ICASE, Exp.str_bin("bin2"))], + ["regex_compare must work", 75, Exp.regex_compare("[1-5]", Exp::RegexFlags::ICASE, Exp.str_bin("bin2"))] ] matrix.each do |title, result, exp| @@ -281,19 +283,19 @@ def query_method(exp, ops = {}) fail_on_filtered_out: true, filter_exp: Exp.eq( Exp.int_bin("bin"), - Exp.int_val(16), - ), + Exp.int_val(16) + ) } - expect { + expect do client.delete(key, opts) - }.to raise_aerospike_error(Aerospike::ResultCode::FILTERED_OUT) + end.to raise_aerospike_error(Aerospike::ResultCode::FILTERED_OUT) opts = { fail_on_filtered_out: true, filter_exp: Exp.eq( Exp.int_bin("bin"), - Exp.int_val(15), - ), + Exp.int_val(15) + ) } client.delete(key, opts) end @@ -304,19 +306,19 @@ def query_method(exp, ops = {}) fail_on_filtered_out: true, filter_exp: Exp.eq( Exp.int_bin("bin"), - Exp.int_val(15), - ), + Exp.int_val(15) + ) } - expect { + expect do client.put(key, { "bin" => 26 }, opts) - }.to raise_aerospike_error(Aerospike::ResultCode::FILTERED_OUT) + end.to raise_aerospike_error(Aerospike::ResultCode::FILTERED_OUT) opts = { fail_on_filtered_out: true, filter_exp: Exp.eq( Exp.int_bin("bin"), - Exp.int_val(25), - ), + Exp.int_val(25) + ) } client.put(key, { "bin" => 26 }, opts) end @@ -327,20 +329,20 @@ def query_method(exp, ops = {}) fail_on_filtered_out: true, filter_exp: Exp.eq( Exp.int_bin("bin"), - Exp.int_val(15), - ), + Exp.int_val(15) + ) } - expect { + expect do client.get(key, nil, opts) - }.to raise_aerospike_error(Aerospike::ResultCode::FILTERED_OUT) + end.to raise_aerospike_error(Aerospike::ResultCode::FILTERED_OUT) opts = { fail_on_filtered_out: true, filter_exp: Exp.eq( Exp.int_bin("bin"), - Exp.int_val(35), - ), + Exp.int_val(35) + ) } client.get(key, ["bin"], opts) end @@ -351,19 +353,19 @@ def query_method(exp, ops = {}) fail_on_filtered_out: true, filter_exp: Exp.eq( Exp.int_bin("bin"), - Exp.int_val(15), - ), + Exp.int_val(15) + ) } - expect { + expect do client.exists(key, opts) - }.to raise_aerospike_error(Aerospike::ResultCode::FILTERED_OUT) + end.to raise_aerospike_error(Aerospike::ResultCode::FILTERED_OUT) opts = { fail_on_filtered_out: true, filter_exp: Exp.eq( Exp.int_bin("bin"), - Exp.int_val(45), - ), + Exp.int_val(45) + ) } client.exists(key, opts) end @@ -374,19 +376,19 @@ def query_method(exp, ops = {}) fail_on_filtered_out: true, filter_exp: Exp.eq( Exp.int_bin("bin"), - Exp.int_val(15), - ), + Exp.int_val(15) + ) } - expect { + expect do client.add(key, { "test55" => "test" }, opts) - }.to raise_aerospike_error(Aerospike::ResultCode::FILTERED_OUT) + end.to raise_aerospike_error(Aerospike::ResultCode::FILTERED_OUT) opts = { fail_on_filtered_out: true, filter_exp: Exp.eq( Exp.int_bin("bin"), - Exp.int_val(55), - ), + Exp.int_val(55) + ) } client.add(key, { "test55" => "test" }, opts) end @@ -397,19 +399,19 @@ def query_method(exp, ops = {}) fail_on_filtered_out: true, filter_exp: Exp.eq( Exp.int_bin("bin"), - Exp.int_val(15), - ), + Exp.int_val(15) + ) } - expect { + expect do client.prepend(key, { "test55" => "test" }, opts) - }.to raise_aerospike_error(Aerospike::ResultCode::FILTERED_OUT) + end.to raise_aerospike_error(Aerospike::ResultCode::FILTERED_OUT) opts = { fail_on_filtered_out: true, filter_exp: Exp.eq( Exp.int_bin("bin"), - Exp.int_val(55), - ), + Exp.int_val(55) + ) } client.prepend(key, { "test55" => "test" }, opts) end @@ -420,19 +422,19 @@ def query_method(exp, ops = {}) fail_on_filtered_out: true, filter_exp: Exp.eq( Exp.int_bin("bin"), - Exp.int_val(15), - ), + Exp.int_val(15) + ) } - expect { + expect do client.touch(key, opts) - }.to raise_aerospike_error(Aerospike::ResultCode::FILTERED_OUT) + end.to raise_aerospike_error(Aerospike::ResultCode::FILTERED_OUT) opts = { fail_on_filtered_out: true, filter_exp: Exp.eq( Exp.int_bin("bin"), - Exp.int_val(65), - ), + Exp.int_val(65) + ) } client.touch(key, opts) end @@ -442,8 +444,8 @@ def query_method(exp, ops = {}) fail_on_filtered_out: true, filter_exp: Exp.eq( Exp.int_bin("bin"), - Exp.int_val(75), - ), + Exp.int_val(75) + ) } rs = client.scan_all(@namespace, @set, nil, opts) @@ -498,7 +500,7 @@ def query_method(exp, ops = {}) ), Exp.eq( Exp.int_and(Exp.int_bin(bin_a), Exp.int_val(0xFFFF)), - Exp.int_val(1), + Exp.int_val(1) ) ) ), key_a, key_a, bin_a, 1, true], @@ -510,7 +512,7 @@ def query_method(exp, ops = {}) ), Exp.eq( Exp.int_or(Exp.int_bin(bin_a), Exp.int_val(0xFF)), - Exp.int_val(0xFF), + Exp.int_val(0xFF) ) ) ), key_a, key_a, bin_a, 1, true], @@ -522,7 +524,7 @@ def query_method(exp, ops = {}) ), Exp.eq( Exp.int_xor(Exp.int_bin(bin_a), Exp.int_val(0xFF)), - Exp.int_val(0xFE), + Exp.int_val(0xFE) ) ) ), key_a, key_a, bin_a, 1, true], @@ -596,7 +598,7 @@ def query_method(exp, ops = {}) Exp.def("val", Exp.add(Exp.float_bin(bin_b), Exp.float_val(1.1))), Exp.and( Exp.ge(Exp.var("val"), Exp.float_val(3.2999)), - Exp.le(Exp.var("val"), Exp.float_val(3.3001)), + Exp.le(Exp.var("val"), Exp.float_val(3.3001)) ) ), key_a, key_b, bin_a, 2, false], @@ -613,26 +615,28 @@ def query_method(exp, ops = {}) Exp.ge(Exp.var("val"), Exp.float_val(4.8399)), Exp.le(Exp.var("val"), Exp.float_val(4.8401)) ) - ), key_a, key_b, bin_a, 2, false], + ), key_a, key_b, bin_a, 2, false] ] matrix.each do |title, exp, key, exp_key, bin, expected, reverse_exp| it "#{title} should work" do opts = { fail_on_filtered_out: true, - filter_exp: exp, + filter_exp: exp } - expect { + expect do client.get(key, nil, opts) - }.to raise_error (Aerospike::Exceptions::Aerospike) { |error| + end.to raise_error(Aerospike::Exceptions::Aerospike) { |error| error.result_code == Aerospike::ResultCode::FILTERED_OUT } - opts = { - fail_on_filtered_out: true, - filter_exp: Exp.not(exp), - } if reverse_exp + if reverse_exp + opts = { + fail_on_filtered_out: true, + filter_exp: Exp.not(exp) + } + end r = client.get(exp_key, nil, opts) client.get(key) expect(r.bins[bin]).to eq expected diff --git a/spec/aerospike/pred_exp_spec.rb b/spec/aerospike/pred_exp_spec.rb deleted file mode 100644 index 0849e245..00000000 --- a/spec/aerospike/pred_exp_spec.rb +++ /dev/null @@ -1,536 +0,0 @@ -# frozen_string_literal: true - -describe Aerospike::PredExp, skip: Support.min_version?("6") do - let(:client) { Support.client } - - before :all do - @namespace = "test" - @set = "predexp" - @record_count = 5 - @record_count.times do |i| - key = Aerospike::Key.new(@namespace, @set, i) - bin_map = { - 'bin1' => "value#{i}", - 'bin2' => i, - 'bin3' => [ i, i + 1_000, i + 1_000_000 ], - 'bin4' => { "key#{i}" => i } - } - Support.client.put(key, bin_map, ttl: i * 10) - end - - tasks = [] - tasks << Support.client.create_index(@namespace, @set, "predexp_index_str_bin1", "bin1", :string) - tasks << Support.client.create_index(@namespace, @set, "predexp_index_int_bin2", "bin2", :numeric) - tasks << Support.client.create_index(@namespace, @set, "predexp_index_lst_bin3", "bin3", :numeric, :list) - tasks << Support.client.create_index(@namespace, @set, "predexp_index_mapkey_bin4", "bin4", :string, :mapkeys) - tasks << Support.client.create_index(@namespace, @set, "predexp_index_mapval_bin4", "bin4", :numeric, :mapvalues) - tasks.each(&:wait_till_completed) - expect(tasks.all?(&:completed?)).to be true - end - - let(:statement) { Aerospike::Statement.new(@namespace, @set) } - let(:string_bin) { 'bin1' } - let(:integer_bin) { 'bin2' } - let(:list_bin) { 'bin3' } - let(:map_bin) { 'bin4' } - - context 'void time' do - let(:seconds_from_now) { 30 } - let(:time) { Time.now + seconds_from_now } - let(:value) { Support.time_in_nanoseconds(time) } - let(:predexp) do - [ - Aerospike::PredExp.integer_value(value), - Aerospike::PredExp.void_time, - Aerospike::PredExp.integer_less - ] - end - - it 'returns records expiring later than set time' do - statement.predexp = predexp - rs = client.query(statement) - - count = 0 - rs.each do |r| - expect(r.ttl).to be > seconds_from_now - count += 1 - end - - expect(count).to be > 0 - end - end - - describe 'expressions for integer bins' do - let(:value) { 3 } - let(:predexp) do - [ - Aerospike::PredExp.integer_bin(integer_bin), - Aerospike::PredExp.integer_value(value) - ] - end - - it 'returns records with bin equal to value' do - predexp << Aerospike::PredExp.integer_equal - statement.predexp = predexp - rs = client.query(statement) - rs.each do |r| - expect(r.bins[integer_bin]).to eq(value) - end - end - - it 'returns records not equal to value' do - predexp << Aerospike::PredExp.integer_unequal - statement.predexp = predexp - rs = client.query(statement) - rs.each do |r| - expect(r.bins[integer_bin]).not_to eq(value) - end - end - - it 'returns records with bin less than value' do - predexp << Aerospike::PredExp.integer_less - statement.predexp = predexp - rs = client.query(statement) - rs.each do |r| - expect(r.bins[integer_bin]).to be < value - end - end - - it 'returns records with bin less or equal than value' do - predexp << Aerospike::PredExp.integer_less_eq - statement.predexp = predexp - rs = client.query(statement) - rs.each do |r| - expect(r.bins[integer_bin]).to be <= value - end - end - - it 'returns records with bin greater than value' do - predexp << Aerospike::PredExp.integer_greater - statement.predexp = predexp - rs = client.query(statement) - rs.each do |r| - expect(r.bins[integer_bin]).to be > value - end - end - - it 'returns records with bin greater or equal to value' do - predexp << Aerospike::PredExp.integer_greater_eq - statement.predexp = predexp - rs = client.query(statement) - rs.each do |r| - expect(r.bins[integer_bin]).to be >= value - end - end - end - - describe 'expressions for string bins' do - let(:value) { 'value3' } - let(:predexp) do - [ - Aerospike::PredExp.string_bin(string_bin), - Aerospike::PredExp.string_value(value) - ] - end - - it 'returns records equal to value' do - predexp << Aerospike::PredExp.string_equal - statement.predexp = predexp - rs = client.query(statement) - rs.each do |r| - expect(r.bins[string_bin]).to eq(value) - end - end - - it 'returns records not equal to value' do - predexp << Aerospike::PredExp.string_unequal - statement.predexp = predexp - rs = client.query(statement) - rs.each do |r| - expect(r.bins[string_bin]).not_to eq(value) - end - end - - context 'regex' do - let(:value) { 'lue3' } - context 'default flag' do - it 'returns records matching regex' do - predexp << Aerospike::PredExp.string_regex(Aerospike::PredExp::RegexFlags::NONE) - statement.predexp = predexp - rs = client.query(statement) - count = 0 - rs.each do |r| - count += 1 - end - - expect(count).to eq(1) - end - end - end - end - - context 'expressions for GeoJSON bins' do - before :all do - @lat = 1.3083 - @lng = 103.9114 - - @point = Aerospike::GeoJSON.new(type: "Point", coordinates: [@lng, @lat]) - @record_count.times do |i| - key = Aerospike::Key.new(@namespace, @set, i) - sub_point = Support::Geo.destination_point(@lng, @lat, i * 200, rand(0..359)) - - sub_point_cords = sub_point.coordinates - - polygon_points = [315, 45, 135, 225].map { |x| - Support::Geo.destination_point(sub_point_cords.first, sub_point_cords.last, 100, x).coordinates - } - polygon_points << polygon_points.first - polygon = Aerospike::GeoJSON.new(type: 'Polygon', coordinates: [polygon_points]) - bin_map = { - 'geo_point' => sub_point, - 'geo_polygon' => polygon - } - Support.client.put(key, bin_map, ttl: 10) - end - - - tasks = [] - tasks << Support.client.create_index(@namespace, @set, "index_geo_bin5", "geo_point", :geo2dsphere) - tasks << Support.client.create_index(@namespace, @set, "index_geo_bin5", "geo_polygon", :geo2dsphere) - tasks.each(&:wait_till_completed) - expect(tasks.all?(&:completed?)).to be true - end - - let(:point_bin) { 'geo_point' } - let(:polygon_bin) { 'geo_polygon' } - - context 'contains' do - let(:geo_json_area_circle) { Aerospike::GeoJSON.new(type: 'AeroCircle', coordinates: [[@lng, @lat], 500]) } - - let(:predexp) do - [ - Aerospike::PredExp.geojson_bin(point_bin), - Aerospike::PredExp.geojson_value(geo_json_area_circle), - Aerospike::PredExp.geojson_contains - ] - end - - it 'returns records with points within a circle' do - statement.predexp = predexp - rs = client.query(statement) - count = 0 - rs.each do |r| - count += 1 - end - expect(count).to eq(3) - end - end - - context 'within' do - let(:predexp) do - [ - Aerospike::PredExp.geojson_bin(polygon_bin), - Aerospike::PredExp.geojson_value(@point), - Aerospike::PredExp.geojson_within - ] - end - - it 'returns records with polygons which contain point' do - statement.predexp = predexp - rs = client.query(statement) - count = 0 - rs.each do |r| - count += 1 - end - expect(count).to eq(1) - end - end - end - - context 'expressions for bins with lists' do - let(:value) { 3 } - - context 'list_or' do - let(:predexp) do - [ - Aerospike::PredExp.integer_value(value), - Aerospike::PredExp.integer_var('x'), - Aerospike::PredExp.integer_equal, - Aerospike::PredExp.list_bin(list_bin), - Aerospike::PredExp.list_iterate_or('x') - ] - end - - it 'returns items which has any item equal to value' do - statement.predexp = predexp - rs = client.query(statement) - count = 0 - rs.each do |r| - expect(r.bins[list_bin]).to include(value) - count += 1 - end - - expect(count).to eq(1) - end - end - - context 'list_and' do - let(:predexp) do - [ - Aerospike::PredExp.integer_value(value), - Aerospike::PredExp.integer_var('x'), - Aerospike::PredExp.integer_unequal, - Aerospike::PredExp.list_bin(list_bin), - Aerospike::PredExp.list_iterate_and('x') - ] - end - - it 'returns items which do NOT contain an item equal to value' do - statement.predexp = predexp - rs = client.query(statement) - count = 0 - rs.each do |r| - expect(r.bins[list_bin]).not_to include(value) - count += 1 - end - - expect(count).to eq(4) - end - end - end - - context 'expressions for bins with mapkeys' do - let(:bin) { 'bin4' } - let(:value) { 3 } - let(:key) { 'key3' } - - context 'keys' do - context 'iterate_or' do - let(:predexp) do - [ - Aerospike::PredExp.string_value(key), - Aerospike::PredExp.string_var('k'), - Aerospike::PredExp.string_equal, - Aerospike::PredExp.map_bin(map_bin), - Aerospike::PredExp.mapkey_iterate_or('k') - ] - end - - it 'returns records with map bins containing chosen key' do - statement.predexp = predexp - rs = client.query(statement) - - count = 0 - rs.each do |r| - expect(r.bins[map_bin].keys).to include(key) - count += 1 - end - - expect(count).to eq(1) - end - end - - context 'iterate_and' do - let(:predexp) do - [ - Aerospike::PredExp.string_value(key), - Aerospike::PredExp.string_var('k'), - Aerospike::PredExp.string_unequal, - Aerospike::PredExp.map_bin(map_bin), - Aerospike::PredExp.mapkey_iterate_and('k') - ] - end - - it 'returns records with map bins NOT containing chosen key' do - statement.predexp = predexp - rs = client.query(statement) - - count = 0 - rs.each do |r| - expect(r.bins[map_bin].keys).not_to include(key) - count += 1 - end - - expect(count).to eq(4) - end - end - end - - context 'values' do - context 'iterate_or' do - let(:predexp) do - [ - Aerospike::PredExp.integer_value(value), - Aerospike::PredExp.integer_var('v'), - Aerospike::PredExp.integer_equal, - Aerospike::PredExp.map_bin(map_bin), - Aerospike::PredExp.mapval_iterate_or('v') - ] - end - - it 'returns records with map bins containing chosen value' do - statement.predexp = predexp - rs = client.query(statement) - - count = 0 - rs.each do |r| - expect(r.bins[map_bin].values).to include(value) - count += 1 - end - - expect(count).to eq(1) - end - end - - context 'iterate_and' do - let(:predexp) do - [ - Aerospike::PredExp.integer_value(value), - Aerospike::PredExp.integer_var('v'), - Aerospike::PredExp.integer_unequal, - Aerospike::PredExp.map_bin(map_bin), - Aerospike::PredExp.mapval_iterate_and('v') - ] - end - - it 'returns records with map bins NOT containing chosen value' do - statement.predexp = predexp - rs = client.query(statement) - - count = 0 - rs.each do |r| - expect(r.bins[map_bin].values).not_to include(value) - count += 1 - end - - expect(count).to eq(4) - end - end - end - end - - context '.and' do - let(:min_value) { 2 } - - # return records with bin2 > 2 AND bin1 equal to 'value4' - let(:predexp) do - [ - Aerospike::PredExp.integer_bin(integer_bin), - Aerospike::PredExp.integer_value(min_value), - Aerospike::PredExp.integer_greater, - Aerospike::PredExp.string_bin(string_bin), - Aerospike::PredExp.string_value('value4'), - Aerospike::PredExp.string_equal, - Aerospike::PredExp.and(2) - ] - end - - it 'returns records fulfilling multiple predicates' do - statement.predexp = predexp - rs = client.query(statement) - - count = 0 - rs.each do |r| - bins = r.bins - expect(bins[integer_bin]).to be > min_value - expect(bins[string_bin]).to eq('value4') - count += 1 - end - - expect(count).to eq(1) - end - end - - context '.or' do - let(:max_value) { 2 } - - # return all records with bin2 <=2 OR bin1 equal to 'value4' - let(:predexp) do - [ - Aerospike::PredExp.integer_bin(integer_bin), - Aerospike::PredExp.integer_value(max_value), - Aerospike::PredExp.integer_less_eq, - Aerospike::PredExp.string_bin(string_bin), - Aerospike::PredExp.string_value('value4'), - Aerospike::PredExp.string_equal, - Aerospike::PredExp.or(2) - ] - end - - it 'returns records fulfilling one of the predicates' do - statement.predexp = predexp - rs = client.query(statement) - - count = 0 - rs.each do |r| - bins = r.bins - if bins[integer_bin] > max_value - expect(bins[string_bin]).to eq('value4') - else - expect(bins[integer_bin]).to be <= max_value - end - - count += 1 - end - - expect(count).to eq(4) - end - end - - context '.not' do - let(:value) { 3 } - # return all records with bin2 not equal 3 - let(:predexp) do - [ - Aerospike::PredExp.integer_bin(integer_bin), - Aerospike::PredExp.integer_value(value), - Aerospike::PredExp.integer_equal, - Aerospike::PredExp.not - ] - end - - it 'returns records NOT fulfilling the predicate' do - statement.predexp = predexp - rs = client.query(statement) - - count = 0 - rs.each do |r| - expect(r.bins[integer_bin]).not_to eq(value) - count += 1 - end - - expect(count).to eq(4) - end - end - - context 'last update' do - before :all do - @current_time = Support.time_in_nanoseconds(Time.now) - sleep 0.1 - key = Aerospike::Key.new(@namespace, @set, "new_rec") - bin_map = { - 'bin1' => 'val' - } - Support.client.put(key, bin_map, ttl: 10) - end - - let(:predexp) do - [ - Aerospike::PredExp.integer_value(@current_time), - Aerospike::PredExp.last_update, - Aerospike::PredExp.integer_less - ] - end - - it 'returns records updated at chosen time' do - statement.predexp = predexp - rs = client.query(statement) - - count = 0 - rs.each do |r| - count += 1 - end - expect(count).to eq(1) - end - end -end diff --git a/spec/aerospike/predexp_ops_spec.rb b/spec/aerospike/predexp_ops_spec.rb deleted file mode 100644 index 2c40d05c..00000000 --- a/spec/aerospike/predexp_ops_spec.rb +++ /dev/null @@ -1,329 +0,0 @@ -# Copyright 2014-2020 Aerospike, Inc. -# -# Portions may be licensed to Aerospike, Inc. under one or more contributor -# license agreements. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may not -# use this file except in compliance with the License. You may obtain a copy of -# the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations under -# the License. - -require 'aerospike' -require 'benchmark' - -describe Aerospike::Client do - - describe "Predicates", skip: Support.min_version?("6") do - - let(:key) { Aerospike::Key.new(Support.namespace, 'predexp_ops_spec', 0) } - let(:client) { Support.client } - let(:valid_predicate) { - [ - Aerospike::PredExp.integer_bin('bin2'), - Aerospike::PredExp.integer_value(9), - Aerospike::PredExp.integer_less_eq - ] - } - - let(:invalid_predicate) { - [ - Aerospike::PredExp.string_bin('bin1'), - Aerospike::PredExp.string_value('value'), - Aerospike::PredExp.string_unequal - ] - } - - before :each do - client.delete(key) - client.put(key, {'bin1' => 'value', 'bin2' => 9}) - end - - describe "#put" do - - it "should put the key if the predicate is valid" do - client.put(key, {'bin3' => 1.1}, predexp: valid_predicate) - rec = client.get(key) - expect(rec.bins['bin1']).to eq 'value' - expect(rec.bins['bin2']).to eq 9 - expect(rec.bins['bin3']).to eq 1.1 - end - - it "should NOT put the key if the predicate is invalid" do - client.put(key, {'bin3' => 1.1}, predexp: invalid_predicate) - rec = client.get(key) - expect(rec.bins['bin1']).to eq 'value' - expect(rec.bins['bin2']).to eq 9 - expect(rec.bins['bin3']).to be_nil - end - - it "should raise exception if the predicate is invalid" do - expect { - client.put(key, {'bin3' => 1.1}, predexp: invalid_predicate, fail_on_filtered_out: true) - }.to raise_error (Aerospike::Exceptions::Aerospike){ |error| - error.result_code == Aerospike::ResultCode::FILTERED_OUT - } - end - - end - - describe "#get" do - - it "should get the key if the predicate is valid" do - rec = client.get(key, [], predexp: valid_predicate) - expect(rec.bins['bin1']).to eq 'value' - expect(rec.bins['bin2']).to eq 9 - end - - it "should NOT get the key if the predicate is invalid" do - rec = client.get(key, [], predexp: invalid_predicate) - expect(rec).to be_nil - end - - it "should raise exception if the predicate is invalid" do - expect { - client.get(key, [], predexp: invalid_predicate, fail_on_filtered_out: true) - }.to raise_error (Aerospike::Exceptions::Aerospike){ |error| - error.result_code == Aerospike::ResultCode::FILTERED_OUT - } - end - - end - - describe "#get_header" do - - it "should get the key if the predicate is valid" do - rec = client.get_header(key, predexp: valid_predicate) - expect(rec).not_to be_nil - end - - it "should NOT get the key if the predicate is invalid" do - rec = client.get_header(key, predexp: invalid_predicate) - expect(rec).to be_nil - end - - it "should raise exception if the predicate is invalid" do - expect { - client.get_header(key, predexp: invalid_predicate, fail_on_filtered_out: true) - }.to raise_error (Aerospike::Exceptions::Aerospike){ |error| - error.result_code == Aerospike::ResultCode::FILTERED_OUT - } - end - - end - - describe "#delete" do - - it "should delete a key if the predicate is valid" do - existed = client.delete(key, predexp: valid_predicate) - expect(existed).to eq true - end - - it "should NOT delete a key if the predicate is invalid" do - existed = client.delete(key, predexp: invalid_predicate) - expect(existed).to eq true - rec = client.get(key, []) - expect(rec.bins['bin1']).to eq 'value' - expect(rec.bins['bin2']).to eq 9 - end - - it "should raise exception if the predicate is invalid" do - expect { - client.delete(key, predexp: invalid_predicate, fail_on_filtered_out: true) - }.to raise_error (Aerospike::Exceptions::Aerospike){ |error| - error.result_code == Aerospike::ResultCode::FILTERED_OUT - } - end - - end - - describe "#touch" do - - it "should touch the record to bump its generation if the predicate is valid" do - client.touch(key, predexp: valid_predicate) - record = client.get_header(key) - expect(record.generation).to eq 2 - end - - it "should NOT touch the record to bump its generation if the predicate is invalid" do - client.touch(key, predexp: invalid_predicate) - record = client.get_header(key) - expect(record.generation).to eq 1 - end - - it "should raise exception if the predicate is invalid" do - expect { - client.touch(key, predexp: invalid_predicate, fail_on_filtered_out: true) - }.to raise_error (Aerospike::Exceptions::Aerospike){ |error| - error.result_code == Aerospike::ResultCode::FILTERED_OUT - } - end - - end - - describe "#exists" do - - it "should check existence of the record if the predicate is valid" do - existed = client.exists(key, predexp: valid_predicate) - expect(existed).to eq true - end - - it "should NOT check existence of the record if the predicate is invalid" do - existed = client.exists(key, predexp: invalid_predicate) - expect(existed).to eq true - end - - it "should raise exception if the predicate is invalid" do - expect { - client.exists(key, predexp: invalid_predicate, fail_on_filtered_out: true) - }.to raise_error (Aerospike::Exceptions::Aerospike){ |error| - error.result_code == Aerospike::ResultCode::FILTERED_OUT - } - end - - end - - describe "#operate" do - - let(:bin_int) do - Aerospike::Bin.new('bin2', 5) - end - - it "should #add, #get if the predicate is valid" do - client.operate(key, [ - Aerospike::Operation.add(bin_int), - ], predexp: valid_predicate) - rec = client.get(key) - expect(rec.bins[bin_int.name]).to eq bin_int.value + 9 - expect(rec.generation).to eq 2 - end - - it "should NOT #add, #get if the predicate is invalid" do - client.operate(key, [ - Aerospike::Operation.add(bin_int), - ], predexp: invalid_predicate) - rec = client.get(key) - expect(rec.bins[bin_int.name]).to eq 9 - expect(rec.generation).to eq 1 - end - - it "should raise exception if the predicate is invalid" do - expect { - client.operate(key, [ - Aerospike::Operation.add(bin_int), - ], predexp: invalid_predicate, fail_on_filtered_out: true) - }.to raise_error (Aerospike::Exceptions::Aerospike){ |error| - error.result_code == Aerospike::ResultCode::FILTERED_OUT - } - end - - end - - describe "#batch" do - - it "should batch_get if the predicate is valid" do - result = client.batch_get([key], [], predexp: valid_predicate) - expect(result[0].bins['bin1']).to eq 'value' - end - - it "should NOT batch_get if the predicate is invalid" do - result = client.batch_get([key], [], predexp: invalid_predicate) - expect(result[0]).to be_nil - end - - end - - describe "#scan" do - - it "should scan and return records if the predicate is valid" do - rs = client.scan_all(key.namespace, key.set_name, nil, predexp: valid_predicate) - count = 0 - rs.each do |rs| - count += 1 - end - - expect(count).to eq 1 - end - - it "should NOT scan and return records if the predicate is invalid" do - rs = client.scan_all(key.namespace, key.set_name, nil, predexp: invalid_predicate) - count = 0 - rs.each do |rs| - count += 1 - end - - expect(count).to eq 0 - end - - end - - describe "#query" do - - let(:stmt) { stmt = Aerospike::Statement.new(key.namespace, key.set_name) } - - it "should query and return records if the predicate is valid" do - stmt.predexp = valid_predicate - rs = client.query(stmt) - count = 0 - rs.each do |rs| - count += 1 - end - - expect(count).to eq 1 - end - - it "should query and return records if the predicate is invalid - predexp on policy" do - rs = client.query(stmt, predexp: valid_predicate) - count = 0 - rs.each do |rs| - count += 1 - end - - expect(count).to eq 1 - end - - it "should query and return records if the predicate is valid - predexp on policy" do - # policy value takes precedence - stmt.predexp = invalid_predicate - rs = client.query(stmt, predexp: valid_predicate) - count = 0 - rs.each do |rs| - count += 1 - end - - expect(count).to eq 1 - end - - it "should NOT query and return records if the predicate is valid - predexp on policy" do - # policy value takes precedence - stmt.predexp = invalid_predicate - rs = client.query(stmt) - count = 0 - rs.each do |rs| - count += 1 - end - - expect(count).to eq 0 - end - - it "should NOT query and return records if the predicate is valid - predexp on policy" do - rs = client.query(stmt, predexp: invalid_predicate) - count = 0 - rs.each do |rs| - count += 1 - end - - expect(count).to eq 0 - end - - end - - end - -end diff --git a/spec/aerospike/scan_spec.rb b/spec/aerospike/scan_spec.rb index 3d02b4d4..eb7f5f14 100644 --- a/spec/aerospike/scan_spec.rb +++ b/spec/aerospike/scan_spec.rb @@ -23,7 +23,7 @@ let(:client) { Support.client } before :all do - @namespace = "test" + @namespace = Support.namespace @set = "scan1000" @record_count = 1000 @record_count.times do |i| @@ -31,8 +31,8 @@ bin_map = { 'bin1' => "value#{i}", 'bin2' => i, - 'bin4' => ['value4', {'map1' => 'map val'}], - 'bin5' => {'value5' => [124, "string value"]}, + 'bin4' => ['value4', { 'map1' => 'map val' }], + 'bin5' => { 'value5' => [124, "string value"] } } Support.client.put(key, bin_map, :send_key => true) end @@ -53,7 +53,7 @@ def scan_method(type, compressed, bin_names=[], ops={}) [true, false].each do |compressed| [:single_node, :multiple_nodes].each do |type| - context "#{type.to_s}" do + context "#{type}" do it "should return all records with all bins" do rs_list = scan_method(type, compressed, nil, :record_queue_size => 10) @@ -94,7 +94,7 @@ def scan_method(type, compressed, bin_names=[], ops={}) end # it it "should return only the selected bins" do - rs_list = scan_method(type, compressed, ['bin1', 'bin2'], :record_queue_size => 10) + rs_list = scan_method(type, compressed, %w[bin1 bin2], :record_queue_size => 10) count = 0 rs_list.each do |rs| @@ -127,14 +127,14 @@ def scan_method(type, compressed, bin_names=[], ops={}) rs_list.each do |rs| sleep(1) # fill the queue to make sure deadlock doesn't happen rs.cancel - expect {rs.next_record}.to raise_exception(Aerospike::ResultCode.message(Aerospike::ResultCode::SCAN_TERMINATED)) + expect { rs.next_record }.to raise_exception(Aerospike::ResultCode.message(Aerospike::ResultCode::SCAN_TERMINATED)) end rs_list = scan_method(type, compressed) rs_list.each do |rs| rs = rs_list.first rs.cancel - expect {rs.next_record}.to raise_exception(Aerospike::ResultCode.message(Aerospike::ResultCode::SCAN_TERMINATED)) + expect { rs.next_record }.to raise_exception(Aerospike::ResultCode.message(Aerospike::ResultCode::SCAN_TERMINATED)) end end # it @@ -146,12 +146,12 @@ def scan_method(type, compressed, bin_names=[], ops={}) i = 0 rs.each do |rec| i +=1 - break if (i == 15) + break if i == 15 end expect(i).to eq 15 rs.cancel - expect {rs.next_record}.to raise_exception(Aerospike::ResultCode.message(Aerospike::ResultCode::SCAN_TERMINATED)) + expect { rs.next_record }.to raise_exception(Aerospike::ResultCode.message(Aerospike::ResultCode::SCAN_TERMINATED)) end end # it diff --git a/spec/aerospike/udf_spec.rb b/spec/aerospike/udf_spec.rb index 41e3c16c..90af6e5a 100644 --- a/spec/aerospike/udf_spec.rb +++ b/spec/aerospike/udf_spec.rb @@ -145,14 +145,14 @@ end it "should execute a UDF on all records" do - ns = 'test' + ns = Support.namespace set = Support.rand_string(10) div = 2 number_of_records = 100 - number_of_records.times do |i| - key = Support.gen_random_key(50, {:set => set}) - bin1 = Aerospike::Bin.new('bin1', i * div) + number_of_records.times do + key = Support.gen_random_key(50, { :set => set }) + bin1 = Aerospike::Bin.new('bin1', div) bin2 = Aerospike::Bin.new('bin2', -1) client.put(key, [bin1, bin2]) end @@ -170,7 +170,7 @@ recordset = client.scan_all(ns, set) cnt = 0 recordset.each do |rec| - expect(rec.bins['bin2']).to eq (rec.bins['bin1'] / div) + expect(rec.bins['bin2']).to eq(rec.bins['bin1'] / div) cnt += 1 end expect(cnt).to eq number_of_records @@ -184,7 +184,7 @@ number_of_records = 100 number_of_records.times do |i| - key = Support.gen_random_key(50, {:set => set}) + key = Support.gen_random_key(50, { :set => set }) bin1 = Aerospike::Bin.new('bin1', i * div) bin2 = Aerospike::Bin.new('bin2', -1) client.put(key, [bin1, bin2]) @@ -212,7 +212,7 @@ cnt = 0 recordset.each do |rec| if rec.bins['bin1'] <= number_of_records / 2 - expect(rec.bins['bin2']).to eq (rec.bins['bin1'] / div) + expect(rec.bins['bin2']).to eq(rec.bins['bin1'] / div) else expect(rec.bins['bin2']).to eq(-1) end diff --git a/spec/support/utils.rb b/spec/support/utils.rb index 9a975f15..7366e2a0 100644 --- a/spec/support/utils.rb +++ b/spec/support/utils.rb @@ -20,10 +20,10 @@ module Support RAND_CHARS = ('a'..'z').to_a.concat(('A'..'Z').to_a).concat(('0'..'9').to_a) - VERSION_REGEX = /\d+(?:.\d+)+(:?-\d+)?(?:-[a-z0-9]{8})?/.freeze + VERSION_REGEX = /\d+(?:.\d+)+(:?-\d+)?(?:-[a-z0-9]{8})?/ def self.rand_string(len) - RAND_CHARS.shuffle[0,len].join + RAND_CHARS.shuffle[0, len].join end def self.namespace @@ -37,7 +37,7 @@ def self.set_name def self.gen_random_key(len=50, opts = {}) key_val = opts[:key_val] || rand_string(len) set_name = opts[:set] || self.set_name - ns_name = opts[:ns] || self.namespace + ns_name = opts[:ns] || namespace Aerospike::Key.new(ns_name, set_name, key_val) end @@ -47,52 +47,69 @@ def self.delete_set(client, namespace, set_name) end package = "test_utils_delete_record.lua" - function = <