aerospike 1.0.10 → 1.0.11

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.
@@ -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: []