aerospike 1.0.10 → 1.0.11

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7cb691e571b83e9b270f5de08850950f8cb61be6
4
+ data.tar.gz: 20ef98d1d88b118da73e6230c3bd2c1cc86cbdfa
5
+ SHA512:
6
+ metadata.gz: 093c7bc1db1b636d607d63921cc50acb122a523314b64ce6f4de719672480c359473d5083ef07ae53b516e049cc4cacc9e041686bb717b4e42fd662e0a240a69
7
+ data.tar.gz: c5168149d2c0ce5ab34f29bd4c968126c61dec7e9923beec9727a77ccfb57cad926ce9554c59e726d9c6fc764886b1fa7830991dc478fa46faf04bb649f9b6ec
@@ -1,12 +1,41 @@
1
+ ## December 4 2015 (1.0.11)
2
+
3
+ Major feature and bug fix release.
4
+
5
+ * **Fixes**:
6
+
7
+ * Fix `ClientPolicy` to actually accept `fail_if_not_connected` parameter from constructor opts. PR #29, thanks to [Nick Recobra](https://github.com/oruen)
8
+
9
+ * Fix record initialization issue. PR #28, thanks to [jzhua](https://github.com/jzhua)
10
+
11
+ * Consume the rest of the stream when scan/query is finished.
12
+
13
+ * **Improvements**:
14
+
15
+ * Support for double precision floating point data type in record bins.
16
+ Requires server version 3.6.0 or later. [CLIENT-599]
17
+
18
+ * Support for geospatial data in record bins using GeoJSON format; support
19
+ for querying geospatial indexes using points-within-region and
20
+ region-contains-point filters. Requires server version 3.7.0 or later.
21
+ [CLIENT-594]
22
+
23
+ * Tend intervale is now configurable via the client policy. Default is 1
24
+ second as before.
25
+
26
+ * Only logs tend messages when the number of cluster nodes have changed.
27
+
28
+ * Scan and Query termination has been fixed.
29
+
1
30
  ## September 22 2015 (1.0.10)
2
31
 
3
32
  Major fix release.
4
33
 
5
34
  * **Fixes**:
6
35
 
7
- * Fixes find_node_in_partition_map logic.
36
+ * Fixes `find_node_in_partition_map` logic.
8
37
 
9
- * Fixes Node.Refresh logic.
38
+ * Fixes `Node.Refresh` logic.
10
39
 
11
40
  * Fixes an issue with dead connections that would cause an infinite loop.
12
41
 
data/README.md CHANGED
@@ -104,7 +104,6 @@ To run all the test cases:
104
104
  ## Examples
105
105
 
106
106
  A variety of example applications are provided in the [`examples`](examples) directory.
107
- See the [`examples/README.md`](examples/README.md) for details.
108
107
 
109
108
  <a name="Tools"></a>
110
109
  ### Tools
@@ -37,8 +37,10 @@ require 'aerospike/command/batch_command_exists'
37
37
  require 'aerospike/command/read_command'
38
38
  require 'aerospike/command/delete_command'
39
39
  require 'aerospike/command/admin_command'
40
+ require 'aerospike/command/unsupported_particle_type_validator'
40
41
  require 'aerospike/key'
41
42
  require 'aerospike/operation'
43
+ require 'aerospike/geo_json'
42
44
 
43
45
  require 'aerospike/policy/client_policy'
44
46
  require 'aerospike/policy/priority'
@@ -37,6 +37,10 @@ module Aerospike
37
37
  @value
38
38
  end
39
39
 
40
+ def type
41
+ @value.type if @value
42
+ end
43
+
40
44
  def to_s
41
45
  "#{@name}:#{@value.to_s}"
42
46
  end
@@ -49,6 +49,8 @@ module Aerospike
49
49
  policy = opt_to_client_policy(options)
50
50
 
51
51
  @cluster = Cluster.new(policy, Host.new(host, port))
52
+ @cluster.add_cluster_config_change_listener(self)
53
+ @cluster.connect
52
54
 
53
55
  self
54
56
  end
@@ -65,7 +67,10 @@ module Aerospike
65
67
 
66
68
  policy = client.send(:opt_to_client_policy , options)
67
69
 
68
- client.send(:cluster=, Cluster.new(policy, *hosts))
70
+ cluster = Cluster.new(policy, *hosts)
71
+ cluster.add_cluster_config_change_listener(client)
72
+ client.send(:cluster=, cluster)
73
+ cluster.connect
69
74
 
70
75
  client
71
76
  end
@@ -119,7 +124,7 @@ module Aerospike
119
124
  def put(key, bins, options={})
120
125
  policy = opt_to_write_policy(options)
121
126
  command = WriteCommand.new(@cluster, policy, key, hash_to_bins(bins), Aerospike::Operation::WRITE)
122
- command.execute
127
+ execute_command(command)
123
128
  end
124
129
 
125
130
  #-------------------------------------------------------
@@ -142,7 +147,7 @@ module Aerospike
142
147
  def append(key, bins, options={})
143
148
  policy = opt_to_write_policy(options)
144
149
  command = WriteCommand.new(@cluster, policy, key, hash_to_bins(bins), Aerospike::Operation::APPEND)
145
- command.execute
150
+ execute_command(command)
146
151
  end
147
152
 
148
153
  ##
@@ -161,7 +166,7 @@ module Aerospike
161
166
  def prepend(key, bins, options={})
162
167
  policy = opt_to_write_policy(options)
163
168
  command = WriteCommand.new(@cluster, policy, key, hash_to_bins(bins), Aerospike::Operation::PREPEND)
164
- command.execute
169
+ execute_command(command)
165
170
  end
166
171
 
167
172
  #-------------------------------------------------------
@@ -184,7 +189,7 @@ module Aerospike
184
189
  def add(key, bins, options={})
185
190
  policy = opt_to_write_policy(options)
186
191
  command = WriteCommand.new(@cluster, policy, key, hash_to_bins(bins), Aerospike::Operation::ADD)
187
- command.execute
192
+ execute_command(command)
188
193
  end
189
194
 
190
195
  #-------------------------------------------------------
@@ -206,7 +211,7 @@ module Aerospike
206
211
  def delete(key, options={})
207
212
  policy = opt_to_write_policy(options)
208
213
  command = DeleteCommand.new(@cluster, policy, key)
209
- command.execute
214
+ execute_command(command)
210
215
  command.existed
211
216
  end
212
217
 
@@ -227,7 +232,7 @@ module Aerospike
227
232
  def touch(key, options={})
228
233
  policy = opt_to_write_policy(options)
229
234
  command = TouchCommand.new(@cluster, policy, key)
230
- command.execute
235
+ execute_command(command)
231
236
  end
232
237
 
233
238
  #-------------------------------------------------------
@@ -240,7 +245,7 @@ module Aerospike
240
245
  def exists(key, options={})
241
246
  policy = opt_to_policy(options)
242
247
  command = ExistsCommand.new(@cluster, policy, key)
243
- command.execute
248
+ execute_command(command)
244
249
  command.exists
245
250
  end
246
251
 
@@ -274,7 +279,7 @@ module Aerospike
274
279
  policy = opt_to_policy(options)
275
280
 
276
281
  command = ReadCommand.new(@cluster, policy, key, bin_names)
277
- command.execute
282
+ execute_command(command)
278
283
  command.record
279
284
  end
280
285
 
@@ -283,7 +288,7 @@ module Aerospike
283
288
  def get_header(key, options={})
284
289
  policy = opt_to_policy(options)
285
290
  command = ReadHeaderCommand.new(@cluster, policy, key)
286
- command.execute
291
+ execute_command(command)
287
292
  command.record
288
293
  end
289
294
 
@@ -355,7 +360,7 @@ module Aerospike
355
360
  policy = opt_to_write_policy(options)
356
361
 
357
362
  command = OperateCommand.new(@cluster, policy, key, operations)
358
- command.execute
363
+ execute_command(command)
359
364
  command.record
360
365
  end
361
366
 
@@ -506,7 +511,7 @@ module Aerospike
506
511
  policy = opt_to_write_policy(options)
507
512
 
508
513
  command = ExecuteCommand.new(@cluster, policy, key, package_name, function_name, args)
509
- command.execute
514
+ execute_command(command)
510
515
 
511
516
  record = command.record
512
517
 
@@ -553,7 +558,7 @@ module Aerospike
553
558
  abort_on_exception = true
554
559
  begin
555
560
  command = QueryCommand.new(node, policy, statement, nil)
556
- command.execute
561
+ execute_command(command)
557
562
  rescue => e
558
563
  Aerospike.logger.error(e)
559
564
  raise e
@@ -571,7 +576,7 @@ module Aerospike
571
576
  # IndexTask instance.
572
577
  #
573
578
  # This method is only supported by Aerospike 3 servers.
574
- # index_type should be between :string or :numeric
579
+ # index_type should be :string, :numeric or :geo2dsphere (requires server version 3.7 or later)
575
580
  def create_index(namespace, set_name, index_name, bin_name, index_type, options={})
576
581
  policy = opt_to_write_policy(options)
577
582
  str_cmd = "sindex-create:ns=#{namespace}"
@@ -651,7 +656,7 @@ module Aerospike
651
656
  abort_on_exception = true
652
657
  command = ScanCommand.new(node, new_policy, namespace, set_name, bin_names, recordset)
653
658
  begin
654
- command.execute
659
+ execute_command(command)
655
660
  rescue => e
656
661
  Aerospike.logger.error(e.backtrace.join("\n")) unless e == SCAN_TERMINATED_EXCEPTION
657
662
  recordset.cancel(e)
@@ -666,7 +671,7 @@ module Aerospike
666
671
  nodes.each do |node|
667
672
  command = ScanCommand.new(node, new_policy, namespace, set_name, bin_names, recordset)
668
673
  begin
669
- command.execute
674
+ execute_command(command)
670
675
  rescue => e
671
676
  Aerospike.logger.error(e.backtrace.join("\n")) unless e == SCAN_TERMINATED_EXCEPTION
672
677
  recordset.cancel(e)
@@ -701,7 +706,7 @@ module Aerospike
701
706
  abort_on_exception = true
702
707
  command = ScanCommand.new(node, new_policy, namespace, set_name, bin_names, recordset)
703
708
  begin
704
- command.execute
709
+ execute_command(command)
705
710
  rescue => e
706
711
  Aerospike.logger.error(e.backtrace.join("\n")) unless e == SCAN_TERMINATED_EXCEPTION
707
712
  recordset.cancel(e)
@@ -741,7 +746,7 @@ module Aerospike
741
746
  abort_on_exception = true
742
747
  command = QueryCommand.new(node, new_policy, statement, recordset)
743
748
  begin
744
- command.execute
749
+ execute_command(command)
745
750
  rescue => e
746
751
  Aerospike.logger.error(e.backtrace.join("\n")) unless e == QUERY_TERMINATED_EXCEPTION
747
752
  recordset.cancel(e)
@@ -906,6 +911,36 @@ module Aerospike
906
911
  @cluster = cluster
907
912
  end
908
913
 
914
+ def cluster_config_changed(cluster)
915
+ Aerospike.logger.debug { "Cluster config change detected; active nodes: #{cluster.nodes.map(&:name)}" }
916
+ setup_command_validators
917
+ end
918
+
919
+ def setup_command_validators
920
+ Aerospike.logger.debug { "Cluster features: #{@cluster.features.get.to_a}" }
921
+ validators = []
922
+
923
+ # guard against unsupported particle types
924
+ unsupported_particle_types = []
925
+ unsupported_particle_types << Aerospike::ParticleType::DOUBLE unless @cluster.supports_feature?("float")
926
+ unsupported_particle_types << Aerospike::ParticleType::GEOJSON unless @cluster.supports_feature?("geo")
927
+ validators << UnsupportedParticleTypeValidator.new(*unsupported_particle_types) unless unsupported_particle_types.empty?
928
+
929
+ @command_validators = validators
930
+ end
931
+
932
+ def validate_command(command)
933
+ return unless @command_validators
934
+ @command_validators.each do |validator|
935
+ validator.call(command)
936
+ end
937
+ end
938
+
939
+ def execute_command(command)
940
+ validate_command(command)
941
+ command.execute
942
+ end
943
+
909
944
  def batch_execute(keys, &cmd_gen)
910
945
  batch_nodes = BatchNode.generate_list(@cluster, keys)
911
946
  threads = []
@@ -918,7 +953,7 @@ module Aerospike
918
953
  threads << Thread.new do
919
954
  abort_on_exception=true
920
955
  command = cmd_gen.call(bn.node, bns)
921
- command.execute
956
+ execute_command(command)
922
957
  end
923
958
  end
924
959
  end
@@ -14,6 +14,7 @@
14
14
  # License for the specific language governing permissions and limitations under
15
15
  # the License.
16
16
 
17
+ require 'set'
17
18
  require 'thread'
18
19
  require 'timeout'
19
20
 
@@ -26,17 +27,24 @@ module Aerospike
26
27
  class Cluster
27
28
 
28
29
  attr_reader :connection_timeout, :connection_queue_size, :user, :password
30
+ attr_reader :features
29
31
 
30
32
  def initialize(policy, *hosts)
31
33
  @cluster_seeds = hosts
34
+ @fail_if_not_connected = policy.fail_if_not_connected
32
35
  @connection_queue_size = policy.connection_queue_size
33
36
  @connection_timeout = policy.timeout
37
+ @tend_interval = policy.tend_interval
34
38
  @aliases = {}
35
39
  @cluster_nodes = []
36
40
  @partition_write_map = {}
37
41
  @node_index = Atomic.new(0)
42
+ @features = Atomic.new(Set.new)
38
43
  @closed = Atomic.new(true)
39
44
  @mutex = Mutex.new
45
+ @cluster_config_change_listeners = Atomic.new([])
46
+
47
+ @old_node_cound = 0
40
48
 
41
49
  # setup auth info for cluster
42
50
  if policy.requires_authentication
@@ -44,9 +52,13 @@ module Aerospike
44
52
  @password = AdminCommand.hash_password(policy.password)
45
53
  end
46
54
 
55
+ self
56
+ end
57
+
58
+ def connect
47
59
  wait_till_stablized
48
60
 
49
- if policy.fail_if_not_connected && !connected?
61
+ if @fail_if_not_connected && !connected?
50
62
  raise Aerospike::Exceptions::Aerospike.new(Aerospike::ResultCode::SERVER_NOT_AVAILABLE)
51
63
  end
52
64
 
@@ -175,11 +187,27 @@ module Aerospike
175
187
  end
176
188
  end
177
189
 
190
+ def supports_feature?(feature)
191
+ @features.get.include?(feature.to_s)
192
+ end
193
+
178
194
  def change_password(user, password)
179
195
  # change password ONLY if the user is the same
180
196
  @password = password if @user == user
181
197
  end
182
198
 
199
+ def add_cluster_config_change_listener(listener)
200
+ @cluster_config_change_listeners.update do |listeners|
201
+ listeners.push(listener)
202
+ end
203
+ end
204
+
205
+ def remove_cluster_config_change_listener(listener)
206
+ @cluster_config_change_listeners.update do |listeners|
207
+ listeners.delete(listener)
208
+ end
209
+ end
210
+
183
211
  private
184
212
 
185
213
  def launch_tend_thread
@@ -188,7 +216,7 @@ module Aerospike
188
216
  while true
189
217
  begin
190
218
  tend
191
- sleep 1 # 1 second
219
+ sleep(@tend_interval / 1000.0)
192
220
  rescue => e
193
221
  Aerospike.logger.error("Exception occured during tend: #{e}")
194
222
  end
@@ -198,12 +226,14 @@ module Aerospike
198
226
 
199
227
  def tend
200
228
  nodes = self.nodes
229
+ cluster_config_changed = false
201
230
 
202
231
  # All node additions/deletions are performed in tend thread.
203
232
  # If active nodes don't exist, seed cluster.
204
233
  if nodes.empty?
205
234
  Aerospike.logger.info("No connections available; seeding...")
206
235
  seed_nodes
236
+ cluster_config_changed = true
207
237
 
208
238
  # refresh nodes list after seeding
209
239
  nodes = self.nodes
@@ -231,14 +261,33 @@ module Aerospike
231
261
 
232
262
  # Add nodes in a batch.
233
263
  add_list = find_nodes_to_add(friend_list)
234
- add_nodes(add_list) unless add_list.empty?
264
+ unless add_list.empty?
265
+ add_nodes(add_list)
266
+ cluster_config_changed = true
267
+ end
235
268
 
236
269
  # Handle nodes changes determined from refreshes.
237
270
  # Remove nodes in a batch.
238
271
  remove_list = find_nodes_to_remove(refresh_count)
239
- remove_nodes(remove_list) unless remove_list.empty?
272
+ unless remove_list.empty?
273
+ remove_nodes(remove_list)
274
+ cluster_config_changed = true
275
+ end
276
+
277
+ if cluster_config_changed
278
+ update_cluster_features
279
+
280
+ # only log the tend finish IF the number of nodes has been changed.
281
+ # This prevents spamming the log on every tend interval
282
+ if @old_node_cound > nodes.length
283
+ Aerospike.logger.info("Tend finished. #{@old_node_cound - nodes.length} nodes have left the cluster. Old node count: #{@old_node_cound}, New node count #{nodes.length} #{nodes}")
284
+ else
285
+ Aerospike.logger.info("Tend finished. #{nodes.length - @old_node_cound} nodes have joined the cluster. Old node count: #{@old_node_cound}, New node count #{nodes.length} #{nodes}")
286
+ end
287
+ @old_node_cound = nodes.length
240
288
 
241
- Aerospike.logger.info("Tend finished. Live node count: #{nodes.length} #{nodes}")
289
+ notify_cluster_config_changed
290
+ end
242
291
  end
243
292
 
244
293
  def wait_till_stablized
@@ -263,7 +312,7 @@ module Aerospike
263
312
 
264
313
  # wait for the thread to finish or timeout
265
314
  begin
266
- Timeout.timeout(1) do
315
+ Timeout.timeout(@connection_timeout) do
267
316
  thr.join
268
317
  end
269
318
  rescue Timeout::Error
@@ -274,6 +323,21 @@ module Aerospike
274
323
 
275
324
  end
276
325
 
326
+ def update_cluster_features
327
+ # Cluster supports features that are supported by all nodes
328
+ @features.update do |cluster_features|
329
+ node_featues = self.nodes.map(&:features)
330
+ cluster_features.replace(node_featues.reduce(&:intersection))
331
+ end
332
+ end
333
+
334
+ def notify_cluster_config_changed
335
+ listeners = @cluster_config_change_listeners.get
336
+ listeners.each do |listener|
337
+ listener.send(:cluster_config_changed, self)
338
+ end
339
+ end
340
+
277
341
  def set_partitions(part_map)
278
342
  @mutex.synchronize do
279
343
  @partition_write_map = part_map
@@ -22,7 +22,7 @@ module Aerospike
22
22
 
23
23
  class Node
24
24
 
25
- attr_reader :reference_count, :responded, :name
25
+ attr_reader :reference_count, :responded, :name, :features
26
26
 
27
27
  PARTITIONS = 4096
28
28
  FULL_HEALTH = 100
@@ -34,6 +34,7 @@ module Aerospike
34
34
  @aliases = Atomic.new(nv.aliases)
35
35
  @host = nv.host
36
36
  @use_new_info = Atomic.new(nv.use_new_info)
37
+ @features = nv.features
37
38
 
38
39
  # Assign host to first IP alias because the server identifies nodes
39
40
  # by IP address (not hostname).
@@ -168,7 +169,10 @@ module Aerospike
168
169
  close_connections
169
170
  end
170
171
 
171
- # Implements stringer interface
172
+ def supports_feature?(feature)
173
+ @features.include?(feature.to_s)
174
+ end
175
+
172
176
  def to_s
173
177
  "#{@name}:#{@host}"
174
178
  end
@@ -20,11 +20,12 @@ module Aerospike
20
20
 
21
21
  class NodeValidator # :nodoc:
22
22
 
23
- attr_reader :host, :aliases, :name, :use_new_info
23
+ attr_reader :host, :aliases, :name, :use_new_info, :features
24
24
 
25
25
  def initialize(cluster, host, timeout)
26
26
  @cluster = cluster
27
27
  @use_new_info = true
28
+ @features = Set.new
28
29
  @host = host
29
30
 
30
31
  set_aliases(host)
@@ -70,10 +71,15 @@ module Aerospike
70
71
  end
71
72
  end
72
73
 
73
- info_map= Info.request(conn, 'node', 'build')
74
+ info_map = Info.request(conn, 'node', 'build', 'features')
74
75
  if node_name = info_map['node']
75
76
  @name = node_name
76
77
 
78
+ # Set features
79
+ if features = info_map['features']
80
+ @features = features.split(';').to_set
81
+ end
82
+
77
83
  # Check new info protocol support for >= 2.6.6 build
78
84
  if build_version = info_map['build']
79
85
  v1, v2, v3 = parse_version_string(build_version)
@@ -81,6 +81,12 @@ module Aerospike
81
81
  self
82
82
  end
83
83
 
84
+ # List of all bins that this command will write to - sub-classes should
85
+ # overrite this as appropriate.
86
+ def write_bins
87
+ []
88
+ end
89
+
84
90
  # Writes the command for write operations
85
91
  def set_write(policy, operation, key, bins)
86
92
  begin_cmd
@@ -447,7 +453,13 @@ module Aerospike
447
453
  begin
448
454
  parse_result
449
455
  rescue => e
450
- Aerospike.logger.error(e)
456
+ case e
457
+ # do not log the following exceptions
458
+ when Aerospike::Exceptions::ScanTerminated
459
+ when Aerospike::Exceptions::QueryTerminated
460
+ else
461
+ Aerospike.logger.error(e)
462
+ end
451
463
 
452
464
  # close the connection
453
465
  # cancelling/closing the batch/multi commands will return an error, which will
@@ -28,6 +28,10 @@ module Aerospike
28
28
  @operations = operations
29
29
  end
30
30
 
31
+ def write_bins
32
+ @operations.select{|op| op.op_type == Aerospike::Operation::WRITE}.map(&:bin).compact
33
+ end
34
+
31
35
  def write_buffer
32
36
  set_operate(@policy, @key, @operations)
33
37
  end
@@ -92,8 +92,7 @@ module Aerospike
92
92
  end
93
93
 
94
94
  if op_count == 0
95
- # data Bin was not returned.
96
- @record = Record.new(@node, @key, generation, expiration)
95
+ @record = Record.new(@node, @key, nil, generation, expiration)
97
96
  return
98
97
  end
99
98
 
@@ -19,12 +19,15 @@ module Aerospike
19
19
  # Pre-defined user roles.
20
20
  module Role
21
21
 
22
- # Manage users their roles.
22
+ # Manage users and their roles.
23
23
  USER_ADMIN = 'user-admin'
24
24
 
25
- # Manage indicies, user defined functions and server configuration.
25
+ # Manage indicies, user-defined functions and server configuration.
26
26
  SYS_ADMIN = 'sys-admin'
27
27
 
28
+ # Allow read, write and UDF transactions with the database.
29
+ READ_WRITE_UDF = "read-write-udf"
30
+
28
31
  # Allow read and write transactions with the database.
29
32
  READ_WRITE = 'read-write'
30
33
 
@@ -0,0 +1,34 @@
1
+ # encoding: utf-8
2
+ # Copyright 2015 Aerospike, Inc.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License")
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http:#www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ module Aerospike
17
+
18
+ class UnsupportedParticleTypeValidator
19
+
20
+ def initialize(*particle_types)
21
+ @unsupported_types = particle_types.to_set
22
+ end
23
+
24
+ def call(*commands)
25
+ used = commands.flat_map(&:write_bins).map(&:type)
26
+ unsupported = @unsupported_types.intersection(used)
27
+ unless unsupported.empty?
28
+ fail Aerospike::Exceptions::Aerospike.new(Aerospike::ResultCode::TYPE_NOT_SUPPORTED, "Particle type(s) not supported by cluster: #{@unsupported_types.to_a}")
29
+ end
30
+ end
31
+
32
+ end # class
33
+
34
+ end # module
@@ -33,6 +33,10 @@ module Aerospike
33
33
  self
34
34
  end
35
35
 
36
+ def write_bins
37
+ @bins
38
+ end
39
+
36
40
  def write_buffer
37
41
  set_write(@policy, @operation, @key, @bins)
38
42
  end
@@ -0,0 +1,61 @@
1
+ # encoding: utf-8
2
+ # Copyright 2015 Aerospike, Inc.
3
+ #
4
+ # Portions may be licensed to Aerospike, Inc. under one or more contributor
5
+ # license agreements.
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License"); you may not
8
+ # use this file except in compliance with the License. You may obtain a copy of
9
+ # the License at http:#www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13
+ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14
+ # License for the specific language governing permissions and limitations under
15
+ # the License.
16
+
17
+ require "json"
18
+
19
+ module Aerospike
20
+
21
+ ##
22
+ # Wrapper for GeoJSON data.
23
+ # GeoJSON data needs to be wrapped to allow the client to distinguish
24
+ # geospatial data from string (or hash) data. Geospatial data from a record's
25
+ # bin will be returned as an instance of this class.
26
+ # The wrapper accepts GeoJSON data either as a String or a Hash.
27
+
28
+ class GeoJSON
29
+
30
+ def initialize(data)
31
+ self.json_data =
32
+ case data
33
+ when String
34
+ data
35
+ else
36
+ data.to_json
37
+ end
38
+ end
39
+
40
+ def to_json
41
+ json_data
42
+ end
43
+ alias_method :to_s, :to_json
44
+
45
+ def to_hash
46
+ JSON.parse(json_data)
47
+ end
48
+ alias_method :to_h, :to_hash
49
+
50
+ def ==(other)
51
+ return false unless other.class == self.class
52
+ other.to_json == self.to_json
53
+ end
54
+
55
+ protected
56
+
57
+ attr_accessor :json_data
58
+
59
+ end # class
60
+
61
+ end # module
@@ -37,6 +37,10 @@ module Aerospike
37
37
  self
38
38
  end
39
39
 
40
+ def bin
41
+ Aerospike::Bin.new(bin_name, bin_value) if bin_name && bin_value
42
+ end
43
+
40
44
  def self.get(bin_name=nil)
41
45
  Operation.new(READ, bin_name)
42
46
  end
@@ -19,7 +19,7 @@ module Aerospike
19
19
  class ClientPolicy
20
20
 
21
21
  attr_accessor :user, :password
22
- attr_accessor :timeout, :connection_queue_size, :fail_if_not_connected
22
+ attr_accessor :timeout, :connection_queue_size, :fail_if_not_connected, :tend_interval
23
23
 
24
24
  def initialize(opt={})
25
25
  # Initial host connection timeout in seconds. The timeout when opening a connection
@@ -30,7 +30,11 @@ module Aerospike
30
30
  @connection_queue_size = opt[:connection_queue_size] || 64
31
31
 
32
32
  # Throw exception if host connection fails during add_host.
33
- @fail_if_not_connected = opt[:fail_if_not_connected] || true
33
+ @fail_if_not_connected = opt.has_key?(:fail_if_not_connected) ? opt[:fail_if_not_connected] : true
34
+
35
+ # Tend interval in milliseconds; determines the interval at
36
+ # which the client checks for cluster state changes. Minimum interval is 10ms.
37
+ self.tend_interval = opt[:tend_interval] || 1000 # 1 second
34
38
 
35
39
  # user name
36
40
  @user = opt[:user]
@@ -43,6 +47,14 @@ module Aerospike
43
47
  (@user && @user != '') || (@password && @password != '')
44
48
  end
45
49
 
50
+ def tend_interval=(interval)
51
+ if interval < 10
52
+ Aerospike.logger.warn("Minimum tend interval is 10 milliseconds (client policy: #{interval}).")
53
+ interval = 10
54
+ end
55
+ @tend_interval = interval
56
+ end
57
+
46
58
  end # class
47
59
 
48
60
  end # module
@@ -26,6 +26,26 @@ module Aerospike
26
26
  Filter.new(bin_name, from, to)
27
27
  end
28
28
 
29
+ def self.geoWithinGeoJSONRegion(bin_name, region)
30
+ region = region.to_json
31
+ Filter.new(bin_name, region, region, ParticleType::GEOJSON)
32
+ end
33
+
34
+ def self.geoWithinRadius(bin_name, lon, lat, radius_meter)
35
+ region = GeoJSON.new({type: "AeroCircle", coordinates: [[lon, lat], radius_meter]})
36
+ geoWithinGeoJSONRegion(bin_name, region)
37
+ end
38
+
39
+ def self.geoContainsGeoJSONPoint(bin_name, point)
40
+ point = point.to_json
41
+ Filter.new(bin_name, point, point, ParticleType::GEOJSON)
42
+ end
43
+
44
+ def self.geoContainsPoint(bin_name, lon, lat)
45
+ point = GeoJSON.new({type: "Point", coordinates: [lon, lat]})
46
+ geoContainsGeoJSONPoint(bin_name, point)
47
+ end
48
+
29
49
  def estimate_size
30
50
  return @name.bytesize + @begin.estimate_size + @end.estimate_size + 10
31
51
  end
@@ -37,7 +57,7 @@ module Aerospike
37
57
  offset += len + 1
38
58
 
39
59
  # Write particle type.
40
- buf.write_byte(@begin.type, offset)
60
+ buf.write_byte(@val_type, offset)
41
61
  offset+=1
42
62
 
43
63
  # Write filter begin.
@@ -63,10 +83,14 @@ module Aerospike
63
83
 
64
84
  private
65
85
 
66
- def initialize(bin_name, begin_value, end_value)
86
+ def initialize(bin_name, begin_value, end_value, val_type = nil)
67
87
  @name = bin_name
68
88
  @begin = Aerospike::Value.of(begin_value)
69
89
  @end = Aerospike::Value.of(end_value)
90
+
91
+ # The type of the filter values can usually be inferred automatically;
92
+ # but in certain cases caller can override the type.
93
+ @val_type = val_type || @begin.type
70
94
  end
71
95
 
72
96
  end # class
@@ -115,7 +115,7 @@ module Aerospike
115
115
 
116
116
  private
117
117
 
118
- SCAN_TERMINATED_EXCEPTION = Aerospike::Exceptions::Aerospike.new(Aerospike::ResultCode::SCAN_TERMINATED)
119
- QUERY_TERMINATED_EXCEPTION = Aerospike::Exceptions::Aerospike.new(Aerospike::ResultCode::SCAN_TERMINATED)
118
+ SCAN_TERMINATED_EXCEPTION = Aerospike::Exceptions::ScanTerminated.new()
119
+ QUERY_TERMINATED_EXCEPTION = Aerospike::Exceptions::QueryTerminated.new()
120
120
 
121
121
  end
@@ -39,6 +39,9 @@ module Aerospike
39
39
  # if there is no recordset defined, it means this is an Execute UDF On Query command
40
40
  # return successfully
41
41
  if (@recordset == nil) && (result_code == Aerospike::ResultCode::KEY_NOT_FOUND_ERROR)
42
+ # consume the rest of the input buffer from the socket
43
+ read_bytes(receive_size - data_offset) if @data_offset < receive_size
44
+
42
45
  return nil
43
46
  end
44
47
  raise Aerospike::Exceptions::Aerospike.new(result_code)
@@ -36,7 +36,6 @@ module Aerospike
36
36
  INVALID_NODE_ERROR = -3
37
37
 
38
38
  # Client parse error.
39
-
40
39
  PARSE_ERROR = -2
41
40
 
42
41
  # Client serialization error.
@@ -34,6 +34,7 @@ module Aerospike
34
34
  UINT32 = 'N'
35
35
  INT64 = 'q>'
36
36
  UINT64 = 'Q>'
37
+ DOUBLE = 'G'
37
38
 
38
39
  DEFAULT_BUFFER_SIZE = 16 * 1024
39
40
  MAX_BUFFER_SIZE = 10 * 1024 * 1024
@@ -104,6 +105,11 @@ module Aerospike
104
105
  8
105
106
  end
106
107
 
108
+ def write_double(f, offset)
109
+ @buf[offset, 8] = [f].pack(DOUBLE)
110
+ 8
111
+ end
112
+
107
113
  def read(offset, len=nil)
108
114
  start = offset
109
115
 
@@ -140,6 +146,11 @@ module Aerospike
140
146
  val
141
147
  end
142
148
 
149
+ def read_double(offset)
150
+ vals = @buf[offset..offset+7]
151
+ vals.unpack(DOUBLE)[0]
152
+ end
153
+
143
154
  def to_s
144
155
  @buf[0..@slice_end-1]
145
156
  end
@@ -20,7 +20,7 @@ module Aerospike
20
20
  # Server particle types. Unsupported types are commented out.
21
21
  NULL = 0
22
22
  INTEGER = 1
23
- #BIGNUM = 2
23
+ DOUBLE = 2
24
24
  STRING = 3
25
25
  BLOB = 4
26
26
  #TIMESTAMP = 5
@@ -39,6 +39,7 @@ module Aerospike
39
39
  #LUA_BLOB = 18
40
40
  MAP = 19
41
41
  LIST = 20
42
+ GEOJSON = 23
42
43
 
43
44
  end # module
44
45
 
@@ -51,6 +51,8 @@ module Aerospike
51
51
  # big nums > 2**63 are not supported
52
52
  raise Aerospike::Exceptions::Aerospike.new(Aerospike::ResultCode::TYPE_NOT_SUPPORTED, "Value type #{value.class} not supported.")
53
53
  end
54
+ when Float
55
+ res = FloatValue.new(value)
54
56
  when String
55
57
  res = StringValue.new(value)
56
58
  when Symbol
@@ -61,6 +63,8 @@ module Aerospike
61
63
  res = MapValue.new(value)
62
64
  when Array
63
65
  res = ListValue.new(value)
66
+ when GeoJSON
67
+ res = GeoJSONValue.new(value)
64
68
  else
65
69
  # throw an exception for anything that is not supported.
66
70
  raise Aerospike::Exceptions::Aerospike.new(Aerospike::ResultCode::TYPE_NOT_SUPPORTED, "Value type #{value.class} not supported.")
@@ -234,6 +238,49 @@ module Aerospike
234
238
 
235
239
  end # IntegerValue
236
240
 
241
+ #######################################
242
+
243
+ # Float value.
244
+ class FloatValue < Value #:nodoc:
245
+
246
+ def initialize(val)
247
+ @value = val || 0.0
248
+ self
249
+ end
250
+
251
+ def estimate_size
252
+ 8
253
+ end
254
+
255
+ def write(buffer, offset)
256
+ buffer.write_double(@value, offset)
257
+ 8
258
+ end
259
+
260
+ def pack(packer)
261
+ packer.write(@value)
262
+ end
263
+
264
+ def type
265
+ Aerospike::ParticleType::DOUBLE
266
+ end
267
+
268
+ def get
269
+ @value
270
+ end
271
+
272
+ def to_bytes
273
+ [@value].pack('G')
274
+ end
275
+
276
+ def to_s
277
+ @value.to_s
278
+ end
279
+
280
+ end # FloatValue
281
+
282
+ #######################################
283
+
237
284
  # List value.
238
285
  # Supported by Aerospike 3 servers only.
239
286
  class ListValue < Value #:nodoc:
@@ -335,6 +382,51 @@ module Aerospike
335
382
 
336
383
  end
337
384
 
385
+ # #######################################/
386
+
387
+ # GeoJSON value.
388
+ # Supported by Aerospike server version 3.7 and later.
389
+ class GeoJSONValue < Value #:nodoc:
390
+
391
+ def initialize(json)
392
+ @json = json
393
+ @bytes = json.to_json
394
+ self
395
+ end
396
+
397
+ def estimate_size
398
+ # flags + ncells + jsonstr
399
+ 1 + 2 + @bytes.bytesize
400
+ end
401
+
402
+ def write(buffer, offset)
403
+ buffer.write_byte(0, offset) # flags
404
+ buffer.write_uint16(0, offset + 1) # ncells
405
+ return 1 + 2 + buffer.write_binary(@bytes, offset + 3) # JSON string
406
+ end
407
+
408
+ def pack(packer)
409
+ raise Aerospike::Exceptions::Aerospike.new(Aerospike::ResultCode::PARAMETER_ERROR, "Can't pack GeoJSON")
410
+ end
411
+
412
+ def type
413
+ Aerospike::ParticleType::GEOJSON
414
+ end
415
+
416
+ def get
417
+ @json
418
+ end
419
+
420
+ def to_bytes
421
+ @bytes
422
+ end
423
+
424
+ def to_s
425
+ @json
426
+ end
427
+
428
+ end
429
+
338
430
  #######################################
339
431
 
340
432
  protected
@@ -371,6 +463,9 @@ module Aerospike
371
463
  when Aerospike::ParticleType::INTEGER
372
464
  buf.read_int64(offset)
373
465
 
466
+ when Aerospike::ParticleType::DOUBLE
467
+ buf.read_double(offset)
468
+
374
469
  when Aerospike::ParticleType::BLOB
375
470
  buf.read(offset,length)
376
471
 
@@ -380,6 +475,12 @@ module Aerospike
380
475
  when Aerospike::ParticleType::MAP
381
476
  normalize_strings_in_map(MessagePack.unpack(buf.read(offset, length)))
382
477
 
478
+ when Aerospike::ParticleType::GEOJSON
479
+ # ignore the flags for now
480
+ ncells = buf.read_int16(offset + 1)
481
+ hdrsz = 1 + 2 + (ncells * 8)
482
+ Aerospike::GeoJSON.new(buf.read(offset + hdrsz, length - hdrsz))
483
+
383
484
  else
384
485
  nil
385
486
  end
@@ -1,4 +1,4 @@
1
1
  # encoding: utf-8
2
2
  module Aerospike
3
- VERSION = "1.0.10"
3
+ VERSION = "1.0.11"
4
4
  end
metadata CHANGED
@@ -1,46 +1,41 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aerospike
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.10
5
- prerelease:
4
+ version: 1.0.11
6
5
  platform: ruby
7
6
  authors:
8
7
  - Khosrow Afroozeh
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2015-09-22 00:00:00.000000000 Z
11
+ date: 2015-12-07 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: msgpack
16
15
  requirement: !ruby/object:Gem::Requirement
17
- none: false
18
16
  requirements:
19
- - - ~>
17
+ - - "~>"
20
18
  - !ruby/object:Gem::Version
21
19
  version: '0.5'
22
20
  type: :runtime
23
21
  prerelease: false
24
22
  version_requirements: !ruby/object:Gem::Requirement
25
- none: false
26
23
  requirements:
27
- - - ~>
24
+ - - "~>"
28
25
  - !ruby/object:Gem::Version
29
26
  version: '0.5'
30
27
  - !ruby/object:Gem::Dependency
31
28
  name: bcrypt
32
29
  requirement: !ruby/object:Gem::Requirement
33
- none: false
34
30
  requirements:
35
- - - ~>
31
+ - - "~>"
36
32
  - !ruby/object:Gem::Version
37
33
  version: '3.1'
38
34
  type: :runtime
39
35
  prerelease: false
40
36
  version_requirements: !ruby/object:Gem::Requirement
41
- none: false
42
37
  requirements:
43
- - - ~>
38
+ - - "~>"
44
39
  - !ruby/object:Gem::Version
45
40
  version: '3.1'
46
41
  description: Official Aerospike Client for ruby. Access your Aerospike cluster with
@@ -51,6 +46,10 @@ executables: []
51
46
  extensions: []
52
47
  extra_rdoc_files: []
53
48
  files:
49
+ - CHANGELOG.md
50
+ - LICENSE
51
+ - README.md
52
+ - lib/aerospike.rb
54
53
  - lib/aerospike/aerospike_exception.rb
55
54
  - lib/aerospike/atomic/atomic.rb
56
55
  - lib/aerospike/bin.rb
@@ -79,7 +78,9 @@ files:
79
78
  - lib/aerospike/command/roles.rb
80
79
  - lib/aerospike/command/single_command.rb
81
80
  - lib/aerospike/command/touch_command.rb
81
+ - lib/aerospike/command/unsupported_particle_type_validator.rb
82
82
  - lib/aerospike/command/write_command.rb
83
+ - lib/aerospike/geo_json.rb
83
84
  - lib/aerospike/host.rb
84
85
  - lib/aerospike/info.rb
85
86
  - lib/aerospike/key.rb
@@ -124,35 +125,30 @@ files:
124
125
  - lib/aerospike/value/particle_type.rb
125
126
  - lib/aerospike/value/value.rb
126
127
  - lib/aerospike/version.rb
127
- - lib/aerospike.rb
128
- - CHANGELOG.md
129
- - LICENSE
130
- - README.md
131
128
  homepage: http://www.github.com/aerospike/aerospike-client-ruby
132
129
  licenses:
133
130
  - Apache2.0
134
- post_install_message: ! 'Thank you for using Aerospike!
135
-
136
- You can report issues on github.com/aerospike/aerospike-client-ruby'
131
+ metadata: {}
132
+ post_install_message: |-
133
+ Thank you for using Aerospike!
134
+ You can report issues on github.com/aerospike/aerospike-client-ruby
137
135
  rdoc_options: []
138
136
  require_paths:
139
137
  - lib
140
138
  required_ruby_version: !ruby/object:Gem::Requirement
141
- none: false
142
139
  requirements:
143
- - - ! '>='
140
+ - - ">="
144
141
  - !ruby/object:Gem::Version
145
142
  version: 1.9.3
146
143
  required_rubygems_version: !ruby/object:Gem::Requirement
147
- none: false
148
144
  requirements:
149
- - - ! '>='
145
+ - - ">="
150
146
  - !ruby/object:Gem::Version
151
147
  version: '0'
152
148
  requirements: []
153
149
  rubyforge_project:
154
- rubygems_version: 1.8.23.2
150
+ rubygems_version: 2.4.6
155
151
  signing_key:
156
- specification_version: 3
152
+ specification_version: 4
157
153
  summary: An Aerospike driver for Ruby.
158
154
  test_files: []