aerospike 2.10.0 → 2.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 61a847f7addabd7b9e7bf77326537e94cc437e99041e664fcd3496ffb7cba843
4
- data.tar.gz: '033279bbc6e8410e585ace808aabf4ae469a4bd233c1d80f10c2a488894fc5ad'
3
+ metadata.gz: 02e8ccb3f64b4605bc10b58af760e35373209b7323b2c06cc04f133dd12c4b7d
4
+ data.tar.gz: 2b396aec824a2057daef8a6bd054c00bb103e2afd32f2d4c30de6e97853997f3
5
5
  SHA512:
6
- metadata.gz: 5b19a2653c7464e185a0d50faff2ac735b990188eee1021627d6eaab55020130797f14b20eb1f98a79a7cc846962d0d45ba47cd03f8e48ddafba48de6b4ac85f
7
- data.tar.gz: b550a9058c7c2eed384f1b3635a6f56836aa250248bd48837d72e1ac466a6bb8b06c051122b750e5ccc9f61c7f5736bad12627742e91cf61f215188d1e3dfce8
6
+ metadata.gz: 6eb54355ef98807e3523c93bce1b5c2b440a1340387d351da5fb310f11c513d69622f60e309129ac4ea8897d26f5f3bf38362b3c6995a8241d023e383e17a887
7
+ data.tar.gz: 217f6f2a2a0b782d0309e7a487416bf47be2e9225dce5899d1d9e54b2b7578a26e9e0535d9ce5dc1a53bf3b1be87c865218482a59ba4e216a68803328c7608ba
@@ -4,6 +4,15 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [2.11.0] - 2019-05-17
8
+
9
+ * **New Features**
10
+ * Support for predicate expressions in queries. Thanks to [@Minus10Degrees](https://github.com/Minus10Degrees)! [[#78](https://github.com/aerospike/aerospike-client-ruby/issues/78)]
11
+
12
+ * **Bug Fixes**
13
+ * Client#execute\_udf\_on\_query should not modify the statement argument. [[#79](https://github.com/aerospike/aerospike-client-ruby/issues/79)]
14
+ * Encoding::UndefinedConversionError when reading blob data from CDT list/map bin. [[#84](https://github.com/aerospike/aerospike-client-ruby/issues/84)]
15
+
7
16
  ## [2.10.0] - 2019-05-10
8
17
 
9
18
  * **New Features**
@@ -142,6 +142,15 @@ require 'aerospike/query/stream_command'
142
142
  require 'aerospike/query/query_command'
143
143
  require 'aerospike/query/scan_command'
144
144
  require 'aerospike/query/statement'
145
+ require 'aerospike/query/pred_exp'
146
+
147
+ require 'aerospike/query/pred_exp/and_or'
148
+ require 'aerospike/query/pred_exp/geo_json_value'
149
+ require 'aerospike/query/pred_exp/integer_value'
150
+ require 'aerospike/query/pred_exp/op'
151
+ require 'aerospike/query/pred_exp/regex'
152
+ require 'aerospike/query/pred_exp/regex_flags'
153
+ require 'aerospike/query/pred_exp/string_value'
145
154
 
146
155
  module Aerospike
147
156
  extend Loggable
@@ -526,7 +526,7 @@ module Aerospike
526
526
  raise Aerospike::Exceptions::Aerospike.new(Aerospike::ResultCode::SERVER_NOT_AVAILABLE, "Executing UDF failed because cluster is empty.")
527
527
  end
528
528
 
529
- # TODO: wait until all migrations are finished
529
+ statement = statement.clone
530
530
  statement.set_aggregate_function(package_name, function_name, function_args, false)
531
531
 
532
532
  # Use a thread per node
@@ -43,6 +43,7 @@ module Aerospike
43
43
  UDF_OP = 33
44
44
  QUERY_BINLIST = 40
45
45
  BATCH_INDEX = 41
46
+ PREDEXP = 43
46
47
 
47
48
  end # module
48
49
 
@@ -52,6 +52,75 @@ module Aerospike
52
52
  other.to_json == self.to_json
53
53
  end
54
54
 
55
+ def lng
56
+ case type
57
+ when 'Point'
58
+ coordinates.first
59
+ when 'AeroCircle'
60
+ coordinates.first.first
61
+ end
62
+ end
63
+
64
+ def lat
65
+ case type
66
+ when 'Point'
67
+ coordinates.last
68
+ when 'AeroCircle'
69
+ coordinates.first.last
70
+ end
71
+ end
72
+
73
+ def radius
74
+ return nil unless circle?
75
+
76
+ coordinates.last
77
+ end
78
+
79
+ def coordinates
80
+ to_h['coordinates']
81
+ end
82
+
83
+ def type
84
+ to_h['type']
85
+ end
86
+
87
+ def point?
88
+ type == 'Point'
89
+ end
90
+
91
+ def circle?
92
+ type == 'AeroCircle'
93
+ end
94
+
95
+ def polygon?
96
+ type == 'Polygon'
97
+ end
98
+
99
+ def self.point(lng, lat)
100
+ new(type: 'Point', coordinates: [lng, lat])
101
+ end
102
+
103
+ def self.circle(lng, lat, radius)
104
+ new(type: 'AeroCircle', coordinates: [[lng, lat], radius])
105
+ end
106
+
107
+ def self.polygon(coordinates)
108
+ new(type: 'Polygon', coordinates: coordinates)
109
+ end
110
+
111
+ def to_circle(radius)
112
+ raise TypeError, 'Cannot create a Circle from a Polygon' if polygon?
113
+
114
+ self.class.circle(lng, lat, radius)
115
+ end
116
+
117
+ def to_point
118
+ return self if point?
119
+ raise TypeError, 'Cannot create a Point from a Polygon' if polygon?
120
+
121
+ self.class.point(lng, lat)
122
+ end
123
+
55
124
  protected
56
125
 
57
126
  attr_accessor :json_data
@@ -0,0 +1,192 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aerospike
4
+ class PredExp
5
+ AND = 1
6
+ OR = 2
7
+ NOT = 3
8
+ INTEGER_VALUE = 10
9
+ STRING_VALUE = 11
10
+ GEOJSON_VALUE = 12
11
+ INTEGER_BIN = 100
12
+ STRING_BIN = 101
13
+ GEOJSON_BIN = 102
14
+ LIST_BIN = 103
15
+ MAP_BIN = 104
16
+ INTEGER_VAR = 120
17
+ STRING_VAR = 121
18
+ GEOJSON_VAR = 122
19
+ RECSIZE = 150
20
+ LAST_UPDATE = 151
21
+ VOID_TIME = 152
22
+ INTEGER_EQUAL = 200
23
+ INTEGER_UNEQUAL = 201
24
+ INTEGER_GREATER = 202
25
+ INTEGER_GREATEREQ = 203
26
+ INTEGER_LESS = 204
27
+ INTEGER_LESSEQ = 205
28
+ STRING_EQUAL = 210
29
+ STRING_UNEQUAL = 211
30
+ STRING_REGEX = 212
31
+ GEOJSON_WITHIN = 220
32
+ GEOJSON_CONTAINS = 221
33
+ LIST_ITERATE_OR = 250
34
+ MAPKEY_ITERATE_OR = 251
35
+ MAPVAL_ITERATE_OR = 252
36
+ LIST_ITERATE_AND = 253
37
+ MAPKEY_ITERATE_AND = 254
38
+ MAPVAL_ITERATE_AND = 255
39
+
40
+ def self.and(nexp)
41
+ AndOr.new(AND, nexp)
42
+ end
43
+
44
+ def self.or(nexp)
45
+ AndOr.new(OR, nexp)
46
+ end
47
+
48
+ def self.not
49
+ Op.new(NOT)
50
+ end
51
+
52
+ def self.integer_value(value)
53
+ IntegerValue.new(value, INTEGER_VALUE)
54
+ end
55
+
56
+ def self.string_value(value)
57
+ StringValue.new(value, STRING_VALUE)
58
+ end
59
+
60
+ def self.geojson_value(value)
61
+ raise(ArgumentError, "value must be a GeoJSON object!") unless value.is_a?(Aerospike::GeoJSON)
62
+ GeoJsonValue.new(value.to_s, GEOJSON_VALUE)
63
+ end
64
+
65
+ def self.integer_bin(name)
66
+ StringValue.new(name, INTEGER_BIN)
67
+ end
68
+
69
+ def self.string_bin(name)
70
+ StringValue.new(name, STRING_BIN)
71
+ end
72
+
73
+ def self.geojson_bin(name)
74
+ StringValue.new(name, GEOJSON_BIN)
75
+ end
76
+
77
+ def self.list_bin(name)
78
+ StringValue.new(name, LIST_BIN)
79
+ end
80
+
81
+ def self.map_bin(name)
82
+ StringValue.new(name, MAP_BIN)
83
+ end
84
+
85
+ def self.integer_var(name)
86
+ StringValue.new(name, INTEGER_VAR)
87
+ end
88
+
89
+ def self.string_var(name)
90
+ StringValue.new(name, STRING_VAR)
91
+ end
92
+
93
+ def self.geojson_var(name)
94
+ StringValue.new(name, GEOJSON_VAR)
95
+ end
96
+
97
+ def self.record_size
98
+ Op.new(RECSIZE)
99
+ end
100
+
101
+ def self.last_update
102
+ Op.new(LAST_UPDATE)
103
+ end
104
+
105
+ def self.void_time
106
+ Op.new(VOID_TIME)
107
+ end
108
+
109
+ def self.integer_equal
110
+ Op.new(INTEGER_EQUAL)
111
+ end
112
+
113
+ def self.integer_unequal
114
+ Op.new(INTEGER_UNEQUAL)
115
+ end
116
+
117
+ def self.integer_greater
118
+ Op.new(INTEGER_GREATER)
119
+ end
120
+
121
+ def self.integer_greater_eq
122
+ Op.new(INTEGER_GREATEREQ)
123
+ end
124
+
125
+ def self.integer_less
126
+ Op.new(INTEGER_LESS)
127
+ end
128
+
129
+ def self.integer_less_eq
130
+ Op.new(INTEGER_LESSEQ)
131
+ end
132
+
133
+ def self.string_equal
134
+ Op.new(STRING_EQUAL)
135
+ end
136
+
137
+ def self.string_unequal
138
+ Op.new(STRING_UNEQUAL)
139
+ end
140
+
141
+ def self.string_regex(flags)
142
+ Regex.new(STRING_REGEX, flags)
143
+ end
144
+
145
+ def self.geojson_within
146
+ Op.new(GEOJSON_WITHIN)
147
+ end
148
+
149
+ def self.geojson_contains
150
+ Op.new(GEOJSON_CONTAINS)
151
+ end
152
+
153
+ def self.list_iterate_or(var_name)
154
+ StringValue.new(var_name, LIST_ITERATE_OR)
155
+ end
156
+
157
+ def self.list_iterate_and(var_name)
158
+ StringValue.new(var_name, LIST_ITERATE_AND)
159
+ end
160
+
161
+ def self.mapkey_iterate_or(var_name)
162
+ StringValue.new(var_name, MAPKEY_ITERATE_OR)
163
+ end
164
+
165
+ def self.mapkey_iterate_and(var_name)
166
+ StringValue.new(var_name, MAPKEY_ITERATE_AND)
167
+ end
168
+
169
+ def self.mapval_iterate_or(var_name)
170
+ StringValue.new(var_name, MAPVAL_ITERATE_OR)
171
+ end
172
+
173
+ def self.mapval_iterate_and(var_name)
174
+ StringValue.new(var_name, MAPVAL_ITERATE_AND)
175
+ end
176
+
177
+
178
+
179
+ def self.estimate_size(predexp)
180
+ return 0 unless predexp
181
+ predexp.map(&:estimate_size).inject { |sum, size| sum + size }
182
+ end
183
+
184
+ def self.write(predexp, buffer, offset)
185
+ predexp.each do |p|
186
+ offset = p.write(buffer, offset)
187
+ end
188
+
189
+ offset
190
+ end
191
+ end
192
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aerospike
4
+ class PredExp
5
+ class AndOr < PredExp
6
+ def initialize(op, nexp)
7
+ @op = op
8
+ @nexp = nexp
9
+ end
10
+
11
+ def estimate_size
12
+ 8
13
+ end
14
+
15
+ def write(buffer, offset)
16
+ # write type
17
+ buffer.write_int16(@op, offset)
18
+ offset += 2
19
+
20
+ # write length
21
+ buffer.write_int32(2, offset)
22
+ offset += 4
23
+
24
+ # write predicate count
25
+ buffer.write_int16(@nexp, offset)
26
+ offset += 2
27
+
28
+ offset
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aerospike
4
+ class PredExp
5
+ class GeoJsonValue < PredExp
6
+ def initialize(value, type)
7
+ @value = value
8
+ @type = type
9
+ end
10
+
11
+ def estimate_size
12
+ @value.bytesize + 9
13
+ end
14
+
15
+ def write(buffer, offset)
16
+ # tag
17
+ buffer.write_uint16(@type, offset)
18
+ offset += 2
19
+
20
+ # len
21
+ buffer.write_uint32(@value.bytesize + 3, offset)
22
+ offset += 4
23
+
24
+ # flags
25
+
26
+ buffer.write_byte(0, offset)
27
+ offset += 1
28
+
29
+ # ncells
30
+ buffer.write_uint16(0, offset)
31
+ offset += 2
32
+
33
+ # value
34
+ len = buffer.write_binary(@value, offset)
35
+ offset += len
36
+
37
+ offset
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aerospike
4
+ class PredExp
5
+ class IntegerValue < PredExp
6
+ def initialize(value, type)
7
+ @value = value
8
+ @type = type
9
+ end
10
+
11
+ def estimate_size
12
+ 14
13
+ end
14
+
15
+ def write(buffer, offset)
16
+ # Write type
17
+ buffer.write_int16(@type, offset)
18
+ offset += 2
19
+
20
+ # Write length
21
+ buffer.write_int32(8, offset)
22
+ offset += 4
23
+
24
+ # Write value.
25
+ buffer.write_int64(@value, offset)
26
+ offset += 8
27
+
28
+ offset
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aerospike
4
+ class PredExp
5
+ class Op < PredExp
6
+ def initialize(op)
7
+ @op = op
8
+ end
9
+
10
+ def estimate_size
11
+ 6
12
+ end
13
+
14
+ def write(buffer, offset)
15
+ # write type
16
+ buffer.write_int16(@op, offset)
17
+ offset += 2
18
+
19
+ # write zero length
20
+ buffer.write_int32(0, offset)
21
+ offset += 4
22
+
23
+ offset
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aerospike
4
+ class PredExp
5
+ class Regex < PredExp
6
+ def initialize(op, flag = Flags::NONE)
7
+ @op = op
8
+ @flag = flag
9
+ end
10
+
11
+ def estimate_size
12
+ 10
13
+ end
14
+
15
+ def write(buffer, offset)
16
+ # write op type
17
+ buffer.write_int16(@op, offset)
18
+ offset += 2
19
+
20
+ # write length
21
+ buffer.write_int32(4, offset)
22
+ offset += 4
23
+
24
+ # write predicate count
25
+ buffer.write_int32(@flag, offset)
26
+ offset += 4
27
+
28
+ offset
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,23 @@
1
+ # fr# frozen_string_literal: true
2
+
3
+ module Aerospike
4
+ class PredExp
5
+ # Regex bit flags
6
+ module RegexFlags
7
+ # Regex defaults
8
+ NONE = 0
9
+
10
+ # Use POSIX Extended Regular Expression syntax when interpreting regex.
11
+ EXTENDED = 1
12
+
13
+ # Do not differentiate case.
14
+ ICASE = 2
15
+
16
+ # Do not report position of matches.
17
+ NOSUB = 4
18
+
19
+ # Match-any-character operators don't match a newline.
20
+ NEWLINE = 8
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aerospike
4
+ class PredExp
5
+ class StringValue < PredExp
6
+ def initialize(value, type)
7
+ @value = value
8
+ @type = type
9
+ end
10
+
11
+ def estimate_size
12
+ @value.bytesize + 6
13
+ end
14
+
15
+ def write(buffer, offset)
16
+ buffer.write_int16(@type, offset)
17
+ offset += 2
18
+
19
+ buffer.write_int32(@value.bytesize, offset)
20
+ offset += 4
21
+
22
+ len = buffer.write_binary(@value, offset)
23
+ offset += len
24
+
25
+ offset
26
+ end
27
+ end
28
+ end
29
+ end
@@ -35,6 +35,7 @@ module Aerospike
35
35
  fieldCount = 0
36
36
  filterSize = 0
37
37
  binNameSize = 0
38
+ predSize = 0
38
39
 
39
40
  begin_cmd
40
41
 
@@ -92,6 +93,13 @@ module Aerospike
92
93
  @data_offset += 8 + FIELD_HEADER_SIZE
93
94
  fieldCount+=1
94
95
 
96
+ if @statement.predexp
97
+ @data_offset += FIELD_HEADER_SIZE
98
+ predSize = Aerospike::PredExp.estimate_size(@statement.predexp)
99
+ @data_offset += predSize
100
+ fieldCount += 1
101
+ end
102
+
95
103
  if @statement.function_name
96
104
  @data_offset += FIELD_HEADER_SIZE + 1 # udf type
97
105
  @data_offset += @statement.package_name.bytesize + FIELD_HEADER_SIZE
@@ -176,6 +184,13 @@ module Aerospike
176
184
  @data_buffer.write_int64(@statement.task_id, @data_offset)
177
185
  @data_offset += 8
178
186
 
187
+ if @statement.predexp
188
+ write_field_header(predSize, Aerospike::FieldType::PREDEXP)
189
+ @data_offset = Aerospike::PredExp.write(
190
+ @statement.predexp, @data_buffer, @data_offset
191
+ )
192
+ end
193
+
179
194
  if @statement.function_name
180
195
  write_field_header(1, Aerospike::FieldType::UDF_OP)
181
196
  if @statement.return_data
@@ -20,7 +20,7 @@ module Aerospike
20
20
  # a producer is a thread that fetches records from one node and puts them on this queue
21
21
  # a consumer fetches records from this queue
22
22
  # so the production and the consumptoin are decoupled
23
- # there can be an unlimited count of producer threads and consumer threads
23
+ # there can be an unlimited count of producer threads and consumer threads
24
24
  class Recordset
25
25
 
26
26
  attr_reader :records
@@ -29,7 +29,7 @@ module Aerospike
29
29
  queue_size = thread_count if queue_size < thread_count
30
30
  @records = SizedQueue.new(queue_size)
31
31
 
32
- # holds the count of active threads.
32
+ # holds the count of active threads.
33
33
  # when it reaches zero it means the whole operations of fetching records from server nodes is finished
34
34
  @active_threads = Atomic.new(thread_count)
35
35
 
@@ -48,7 +48,7 @@ module Aerospike
48
48
  # if the operation is not finished and the queue is empty it blocks and waits for new records
49
49
  # it sets the exception if it reaches the EOF mark, and returns nil
50
50
  # EOF means the operation has finished and no more records are comming from server nodes
51
- # it re-raises the exception occurred in threads, or which was set after reaching the EOF in the previous call
51
+ # it re-raises the exception occurred in threads, or which was set after reaching the EOF in the previous call
52
52
  def next_record
53
53
  raise @thread_exception.get unless @thread_exception.get.nil?
54
54
 
@@ -76,7 +76,7 @@ module Aerospike
76
76
 
77
77
  # this is called by a thread who faced an exception to singnal to terminate the whole operation
78
78
  # it also may be called by the user to terminate the command in the middle of fetching records from server nodes
79
- # it clears the queue so that if any threads are waiting for the queue get unblocked and find out about the cancellation
79
+ # it clears the queue so that if any threads are waiting for the queue get unblocked and find out about the cancellation
80
80
  def cancel(expn=nil)
81
81
  set_exception(expn)
82
82
  @cancelled.set(true)
@@ -20,7 +20,7 @@ module Aerospike
20
20
 
21
21
  attr_accessor :namespace, :set_name, :index_name, :bin_names, :task_id
22
22
  attr_accessor :filters, :package_name, :function_name, :function_args
23
- attr_accessor :return_data
23
+ attr_accessor :predexp, :return_data
24
24
 
25
25
  def initialize(namespace, set_name, bin_names=[])
26
26
  # Namespace determines query Namespace
@@ -43,6 +43,9 @@ module Aerospike
43
43
  # aggregation function.
44
44
  @filters = []
45
45
 
46
+ # Predicate expressions
47
+ @predexp = nil
48
+
46
49
  @package_name = nil
47
50
  @function_name = nil
48
51
  @function_args = nil
@@ -76,5 +79,4 @@ module Aerospike
76
79
  RAND_MAX = 2**63
77
80
 
78
81
  end # class
79
-
80
82
  end
@@ -85,7 +85,12 @@ module Aerospike
85
85
  def normalize_elem(elem)
86
86
  case elem
87
87
  when String
88
- elem[1..-1].encode(Aerospike.encoding)
88
+ ptype = elem.ord
89
+ value = elem[1..-1]
90
+ if (ptype == ParticleType::STRING)
91
+ value.encode!(Aerospike.encoding)
92
+ end
93
+ value
89
94
  when Array
90
95
  normalize_strings_in_array(elem)
91
96
  when Hash
@@ -1,4 +1,4 @@
1
1
  # encoding: utf-8
2
2
  module Aerospike
3
- VERSION = "2.10.0"
3
+ VERSION = "2.11.0"
4
4
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aerospike
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.10.0
4
+ version: 2.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Khosrow Afroozeh
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2019-05-10 00:00:00.000000000 Z
12
+ date: 2019-05-17 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: msgpack
@@ -140,6 +140,14 @@ files:
140
140
  - lib/aerospike/policy/scan_policy.rb
141
141
  - lib/aerospike/policy/write_policy.rb
142
142
  - lib/aerospike/query/filter.rb
143
+ - lib/aerospike/query/pred_exp.rb
144
+ - lib/aerospike/query/pred_exp/and_or.rb
145
+ - lib/aerospike/query/pred_exp/geo_json_value.rb
146
+ - lib/aerospike/query/pred_exp/integer_value.rb
147
+ - lib/aerospike/query/pred_exp/op.rb
148
+ - lib/aerospike/query/pred_exp/regex.rb
149
+ - lib/aerospike/query/pred_exp/regex_flags.rb
150
+ - lib/aerospike/query/pred_exp/string_value.rb
143
151
  - lib/aerospike/query/query_command.rb
144
152
  - lib/aerospike/query/recordset.rb
145
153
  - lib/aerospike/query/scan_command.rb