aerospike 2.10.0 → 2.11.0

Sign up to get free protection for your applications and to get access to all the features.
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