aerospike-redis 0.1.0

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: 78bff483bccb19c289b4446b28c96a33df6d3843
4
+ data.tar.gz: 4a959f3a78bbaa74d20d26876209985a66c45480
5
+ SHA512:
6
+ metadata.gz: 7f51b2b8b22f0fe323cdd39c046b81db65873d6ccaf370791bd67b8a0062f5d91f0e45ac9e76c440b4f1deec1d362fa9797cce526836060c8de8645949cb08b8
7
+ data.tar.gz: d867984c44354f18d9c6fd98a8e0590c76c7ea8feebf2c06e8597867ba31701175f3ec7224ec2d6582151b7d8ba4318d598edb2e798c855e3de3157f8f97ebe5
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 amirrf
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,90 @@
1
+ # Aerospike Ruby Adapter for Redis
2
+ [![Gem Version](https://badge.fury.io/rb/aerospike-redis.svg)](http://badge.fury.io/rb/aerospike-redis)
3
+
4
+ Try Aerospike as a back-end replacement for Redis in Ruby applications.
5
+
6
+ The aim of this gem is to facilitate trying Aerospike in your Redis-backed Ruby application without the need to learn a new API or modifications to existing codes, as long as the Redis commands used are within the subset supported by this gem.
7
+
8
+ If you are using Redis as a cache or session store in Ruby on Rails applications, an Aerospike-backed cache and session store is already available: [Aerospike::Store](https://github.com/amirrf/aerospike-store-rails).
9
+
10
+ Aerospike offers seamless clustering and SSD-optimized data persistence. Find out more about [Aerospike](http://www.aerospike.com).
11
+
12
+ ## Dependencies
13
+
14
+ - [Aerospike Ruby Client](https://github.com/aerospike/aerospike-client-ruby)
15
+ - [Redis Ruby Client](https://github.com/redis/redis-rb)
16
+
17
+ These gems will be installed automatically using `bundle`.
18
+
19
+ ## Installation
20
+
21
+ ### Installation from RubyGems:
22
+
23
+ $ gem install aerospike-redis
24
+
25
+ ## Usage
26
+
27
+ Specify Aerospike as the driver when creating the Redis client instance:
28
+
29
+ ```ruby
30
+ require 'redis'
31
+ require 'aerospike'
32
+ require 'aerospike/redis'
33
+
34
+ redis = Redis.new(:driver => :aerospike)
35
+ ```
36
+
37
+ It is possible to pass Aerospike config options here. Defaults are:
38
+
39
+ ```ruby
40
+ redis = Redis.new(:driver => :aerospike,
41
+ :host => '127.0.0.1',
42
+ :port => 3000,
43
+ :namespace => 'test',
44
+ :set => 'test',
45
+ :bin => 'redis')
46
+ ```
47
+
48
+ ## How does it work
49
+
50
+ It works as a driver for Redis client, redirecting Redis commands to Aerospike instead of writing to a Redis connection. Thanks to the extensible architecture of the [Redis Ruby Client](https://github.com/redis/redis-rb) that makes this possible.
51
+
52
+ Parts of the Redis API are directly supported by the Aerospike API, while most of the functions are implemented through calling UDFs.
53
+
54
+ ## Supported commands
55
+
56
+ Currently most of the Keys, Strings, and Lists commands are supported:
57
+
58
+ ### Keys
59
+ `DEL`, `EXISTS`, `EXPIRE`, `EXPIREAT`, `PERSIST`, `PEXPIRE`, `PTTL`, `TTL`
60
+
61
+ *Notes:* PEXPIRE and PTTL just convert milliseconds to seconds as Aerospike does not provide milliseconds precision to the client.
62
+
63
+ ### Strings
64
+ `APPEND`, `DECR`, `DECRBY`, `GET`, `GETRANGE`, `GETSET`, `INCR`, `INCRBY`, `INCRBYFLOAT`, `MGET`, `MSET`, `MSETNX`, `PSETEX`, `SET`, `SETEX`, `SETNX`, `SETRANGE` ,`STRLEN`
65
+
66
+ *Notes:* Implementation of `GETSET` is not atomic, it executes two separate commands from the client.
67
+
68
+ ### Lists
69
+ `LINDEX`, `LINSERT`, `LLEN`, `LPOP`, `LPUSH`, `LPUSHX`, `LRANGE`, `LREM`, `LSET`, `LTRIM`, `RPOP`, `RPOPLPUSH`, `RPUSH`, `RPUSHX`
70
+
71
+ *Notes:* Implementation of `RPOPLPUSHGETSET` is not atomic, it executes two separate commands from the client.
72
+
73
+ ## Testing
74
+ A test suite is provided comparing the result of calling the same function through Aerospike vs. Redis.
75
+
76
+ These tests are completely based on a subset of [Redis Ruby Client](https://github.com/redis/redis-rb) tests.
77
+
78
+ $ bundle exec rspec
79
+
80
+ ## Contributing
81
+
82
+ 1. Fork it ( https://github.com/amirrf/aerospike-redis-ruby/fork )
83
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
84
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
85
+ 4. Push to the branch (`git push origin my-new-feature`)
86
+ 5. Create a new Pull Request
87
+
88
+ ## License
89
+
90
+ The Aerospike-Redis-Ruby is made available under the terms of the Apache License, Version 2, as stated in the file `LICENSE`.
@@ -0,0 +1,7 @@
1
+ require "aerospike/redis/version"
2
+ require "redis/connection/aerospike"
3
+
4
+ module Aerospike
5
+ module Redis
6
+ end
7
+ end
@@ -0,0 +1,5 @@
1
+ module Aerospike
2
+ module Redis
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
@@ -0,0 +1,695 @@
1
+ require "redis/connection/registry"
2
+ require "redis/errors"
3
+ require "timeout"
4
+ require 'aerospike'
5
+
6
+ class Redis
7
+ module Connection
8
+ class Aerospike
9
+ REDIS_UDF = 'redis_udf'
10
+
11
+ def self.connect(config)
12
+ config[:host] ||= '127.0.0.1'
13
+ config[:port] ||= 3000
14
+ connection = ::Aerospike::Client.new(config[:host], config[:port])
15
+
16
+ task = connection.register_udf_from_file(File.join(File.dirname(File.expand_path(__FILE__)), REDIS_UDF) + '.lua',
17
+ REDIS_UDF + '.lua', ::Aerospike::Language::LUA)
18
+ task.wait_till_completed()
19
+
20
+ #todo: support config[:timeout]
21
+
22
+ instance = new(connection)
23
+ instance.set_options(config)
24
+ instance
25
+ rescue Errno::ETIMEDOUT
26
+ raise TimeoutError
27
+ end
28
+
29
+ def set_options(config)
30
+ @namespace = config[:namespace] || 'test'
31
+ @set = config[:set] || 'test'
32
+ @bin = config[:bin] || 'redis'
33
+ end
34
+
35
+ def initialize(connection)
36
+ @connection = connection
37
+ end
38
+
39
+ def connected?
40
+ @connection && @connection.connected?
41
+ end
42
+
43
+ def timeout=(timeout)
44
+ #todo: support setting timeout
45
+ end
46
+
47
+ def disconnect
48
+ @connection.close
49
+ @connection = nil
50
+ end
51
+
52
+ def as_key(key_name)
53
+ return ::Aerospike::Key.new(@namespace, @set, key_name.to_s)
54
+ end
55
+
56
+
57
+ # EXISTS key
58
+ # Returns if key exists.
59
+ # Integer reply, specifically:
60
+ # 1 if the key exists.
61
+ # 0 if the key does not exist.
62
+ def process_exists(command)
63
+ key = command.first
64
+ @connection.exists(as_key(key))? 1 : 0
65
+ end
66
+
67
+
68
+ # DEL key [key ...]
69
+ # Removes the specified keys. A key is ignored if it does not exist.
70
+ # Integer reply: The number of keys that were removed.
71
+ def process_del(command)
72
+ result = 0
73
+ command.each do |key|
74
+ result += 1 if @connection.delete(as_key(key))
75
+ end
76
+ result
77
+ end
78
+
79
+ # EXPIRE key seconds
80
+ # Set a timeout on key. After the timeout has expired, the key will automatically be deleted. A key with an associated timeout is often said to be volatile in Redis terminology.
81
+ # Return value
82
+ # Integer reply, specifically:
83
+ # 1 if the timeout was set.
84
+ # 0 if key does not exist or the timeout could not be set.
85
+ def process_expire(command)
86
+ key = command[0]
87
+ seconds = command[1]
88
+ options = {:expiration => seconds }
89
+ @connection.touch(as_key(key), options)? 0 : 1 # return 1 when result is OK = 0
90
+ rescue ::Aerospike::Exceptions::Aerospike => e
91
+ if (e.result_code == ::Aerospike::ResultCode::KEY_NOT_FOUND_ERROR)
92
+ return 0
93
+ else
94
+ raise
95
+ end
96
+ end
97
+
98
+ # PEXPIRE key milliseconds
99
+ def process_pexpire(command)
100
+ command[1] /= 1000.0
101
+ process_expire(command)
102
+ end
103
+
104
+ # EXPIREAT key timestamp
105
+ # EXPIREAT has the same effect and semantic as EXPIRE, but instead of specifying the number of seconds representing the TTL (time to live), it takes an absolute Unix timestamp (seconds since January 1, 1970).
106
+ # Please for the specific semantics of the command refer to the documentation of EXPIRE.# Return value
107
+ # Integer reply, specifically:
108
+ # 1 if the timeout was set.
109
+ # 0 if key does not exist or the timeout could not be set (see: EXPIRE).
110
+ def process_expireat(command)
111
+ command[1] -= Time.now.to_i
112
+ if command[1] > 0
113
+ process_expire(command)
114
+ else
115
+ process_del([command.first])
116
+ end
117
+ end
118
+
119
+ # PEXPIREAT key milliseconds-timestamp
120
+ def process_pexpireat(command)
121
+ command[1] /= 1000.0
122
+ process_expireat(command)
123
+ end
124
+
125
+ # TTL key
126
+ # Returns the remaining time to live of a key that has a timeout. This introspection capability allows a Redis client to check how many seconds a given key will continue to be part of the dataset.
127
+ # In Redis 2.6 or older the command returns -1 if the key does not exist or if the key exist but has no associated expire.
128
+ # Starting with Redis 2.8 the return value in case of error changed:
129
+ # The command returns -2 if the key does not exist.
130
+ # The command returns -1 if the key exists but has no associated expire.
131
+ # Return value
132
+ # Integer reply: TTL in seconds, or a negative value in order to signal an error (see the description above).
133
+ def process_ttl(command)
134
+ key = command.first
135
+ (header = @connection.get_header(as_key(key)))? (header.expiration == 0xFFFFFFFF? -1 : header.expiration) : -2
136
+ rescue ::Aerospike::Exceptions::Aerospike => e
137
+ if (e.result_code == ::Aerospike::ResultCode::KEY_NOT_FOUND_ERROR)
138
+ return -2
139
+ else
140
+ raise
141
+ end
142
+ end
143
+
144
+ # PTTL key
145
+ # Like TTL this command returns the remaining time to live of a key that has an expire set, with the sole difference that TTL returns the amount of remaining time in seconds while PTTL returns it in milliseconds
146
+ def process_pttl(command)
147
+ ttl = process_ttl(command)
148
+ (ttl > 0)? ttl * 1000 : ttl
149
+ end
150
+
151
+ # PERSIST key
152
+ # Remove the existing timeout on key, turning the key from volatile (a key with an expire set) to persistent (a key that will never expire as no timeout is associated).
153
+ # Return value
154
+ # Integer reply, specifically:
155
+ # 1 if the timeout was removed.
156
+ # 0 if key does not exist or does not have an associated timeout.
157
+ def process_persist(command)
158
+ key = command.first
159
+ options = {:expiration => -1 }
160
+ @connection.touch(as_key(key), options)? 0 : 1 # return 1 when result is OK = 0
161
+ rescue ::Aerospike::Exceptions::Aerospike => e
162
+ if (e.result_code == ::Aerospike::ResultCode::KEY_NOT_FOUND_ERROR)
163
+ return 0
164
+ else
165
+ raise
166
+ end
167
+ end
168
+
169
+ # TYPE key
170
+ # Returns the string representation of the type of the value stored at key. The different types that can be returned are: string, list, set, zset and hash.
171
+ # Return value
172
+ # Simple string reply: type of key, or none when key does not exist.
173
+ # def process_type(command)
174
+ # key = command.first
175
+ # if record = @connection.get(as_key(key))
176
+ # value = record.bins[@bin]
177
+ # if value.is_a? Integer
178
+ # @result = 'integer'
179
+ # else
180
+ # @result = nil
181
+ # end
182
+ # else
183
+ # @result = nil
184
+ # end
185
+ # rescue ::Aerospike::Exceptions::Aerospike => e
186
+ # if (e.result_code == ::Aerospike::ResultCode::KEY_NOT_FOUND_ERROR)
187
+ # return 0
188
+ # else
189
+ # raise
190
+ # end
191
+ # end
192
+
193
+
194
+
195
+ # SET key value [EX seconds] [PX milliseconds] [NX|XX]
196
+ # Set key to hold the string value. If key already holds a value, it is overwritten, regardless of its type. Any previous time to live associated with the key is discarded on successful SET operation.
197
+ # Options
198
+ # EX seconds -- Set the specified expire time, in seconds.
199
+ # PX milliseconds -- Set the specified expire time, in milliseconds.
200
+ # NX -- Only set the key if it does not already exist.
201
+ # XX -- Only set the key if it already exist.
202
+ # Simple string reply: OK if SET was executed correctly. Null reply: a Null Bulk Reply is returned if the SET operation was not performed becase the user specified the NX or XX option but the condition was not met.
203
+ def process_set(command)
204
+ key = command.slice!(0)
205
+ val = command.slice!(0)
206
+
207
+ options = {}
208
+ while !command.empty? do
209
+ case command.slice!(0)
210
+ when 'EX'
211
+ options[:expiration] = command.slice!(0)
212
+ when 'PX'
213
+ options[:expiration] = command.slice!(0) / 1000.0
214
+ when 'NX'
215
+ options[:record_exists_action] = ::Aerospike::RecordExistsAction::CREATE_ONLY
216
+ when 'XX'
217
+ options[:record_exists_action] = ::Aerospike::RecordExistsAction::UPDATE_ONLY
218
+ end
219
+ end
220
+
221
+ @connection.put(as_key(key), {@bin => val}, options)
222
+ return 'OK'
223
+ rescue ::Aerospike::Exceptions::Aerospike => e
224
+ if (e.result_code == ::Aerospike::ResultCode::KEY_NOT_FOUND_ERROR) || (e.result_code == ::Aerospike::ResultCode::KEY_EXISTS_ERROR)
225
+ return false
226
+ else
227
+ raise
228
+ end
229
+ end
230
+
231
+ # SETEX key seconds value
232
+ # Set key to hold the string value and set key to timeout after a given number of seconds. This command is equivalent to executing the following commands:
233
+ # SET mykey value
234
+ # EXPIRE mykey seconds
235
+ # Return value
236
+ # Simple string reply
237
+ def process_setex(command)
238
+ key = command[0]
239
+ seconds = command[1]
240
+ val = command[2]
241
+ options = {:expiration => seconds }
242
+ @connection.put(as_key(key), {@bin => val}, options)
243
+ return 'OK'
244
+ end
245
+
246
+ # PSETEX key milliseconds value
247
+ # PSETEX works exactly like SETEX with the sole difference that the expire time is specified in milliseconds instead of seconds.
248
+ def process_psetex(command)
249
+ command[1] /= 1000.0
250
+ process_setex(command)
251
+ end
252
+
253
+ # SETNX key value
254
+ # Set key to hold string value if key does not exist. In that case, it is equal to SET. When key already holds a value, no operation is performed. SETNX is short for "SET if N ot e X ists".
255
+ # Return value
256
+ # Integer reply, specifically:
257
+ # 1 if the key was set
258
+ # 0 if the key was not set
259
+ def process_setnx(command)
260
+ key = command[0]
261
+ val = command[1]
262
+ options = {:record_exists_action => ::Aerospike::RecordExistsAction::CREATE_ONLY}
263
+ @connection.put(as_key(key), {@bin => val}, options)
264
+ return 1
265
+ rescue ::Aerospike::Exceptions::Aerospike => e
266
+ if (e.result_code == ::Aerospike::ResultCode::KEY_EXISTS_ERROR)
267
+ return 0
268
+ else
269
+ raise
270
+ end
271
+ end
272
+
273
+ # GET key
274
+ # Get the value of key. If the key does not exist the special value nil is returned. An error is returned if the value stored at key is not a string, because GET only handles string values.
275
+ # Return value
276
+ # Bulk string reply: the value of key, or nil when key does not exist.
277
+ def process_get(command)
278
+ key = command.first
279
+ if record = @connection.get(as_key(key))
280
+ value = record.bins[@bin]
281
+ else
282
+ value = nil
283
+ end
284
+ value
285
+ rescue ::Aerospike::Exceptions::Aerospike => e
286
+ # puts e.inspect
287
+ if (e.result_code == ::Aerospike::ResultCode::KEY_NOT_FOUND_ERROR)
288
+ return false
289
+ else
290
+ raise
291
+ end
292
+ end
293
+
294
+ # APPEND key value
295
+ # If key already exists and is a string, this command appends the value at the end of the string. If key does not exist it is created and set as an empty string, so APPEND will be similar to SET in this special case.
296
+ # Integer reply: the length of the string after the append operation.
297
+ def process_append(command)
298
+ key = command.first
299
+ val = command[1]
300
+ @connection.append(as_key(key), {@bin => val})
301
+
302
+ @connection.execute_udf(as_key(key), REDIS_UDF, 'strlen', [::Aerospike::StringValue.new(@bin)])
303
+ rescue ::Aerospike::Exceptions::Aerospike => e
304
+ if (e.result_code == ::Aerospike::ResultCode::KEY_EXISTS_ERROR)
305
+ return 0
306
+ else
307
+ raise
308
+ end
309
+ end
310
+
311
+ # STRLEN key
312
+ # Returns the length of the string value stored at key. An error is returned when key holds a non-string value.
313
+ # Return value
314
+ # Integer reply: the length of the string at key, or 0 when key does not exist.
315
+ def process_strlen(command)
316
+ key = command.first
317
+ @connection.execute_udf(as_key(key), REDIS_UDF, 'strlen', [::Aerospike::StringValue.new(@bin)])
318
+ rescue ::Aerospike::Exceptions::Aerospike => e
319
+ if (e.result_code == ::Aerospike::ResultCode::KEY_NOT_FOUND_ERROR)
320
+ return 0
321
+ else
322
+ raise
323
+ end
324
+ end
325
+
326
+ # GETRANGE key start end
327
+ # Warning: this command was renamed to GETRANGE, it is called SUBSTR in Redis versions <= 2.0.
328
+ # Returns the substring of the string value stored at key, determined by the offsets start and end (both are inclusive). Negative offsets can be used in order to provide an offset starting from the end of the string. So -1 means the last character, -2 the penultimate and so forth.
329
+ # The function handles out of range requests by limiting the resulting range to the actual length of the string.
330
+ # Return value
331
+ # Bulk string reply
332
+ def process_getrange(command)
333
+ key = command.first
334
+ @connection.execute_udf(as_key(key), REDIS_UDF, 'getrange',
335
+ [::Aerospike::StringValue.new(@bin),
336
+ ::Aerospike::IntegerValue.new(command[1]),
337
+ ::Aerospike::IntegerValue.new(command[2])
338
+ ])
339
+ end
340
+
341
+ # GETSET key value
342
+ # Atomically sets key to value and returns the old value stored at key. Returns an error when key exists but does not hold a string value.
343
+ def process_getset(command)
344
+ key = command.first
345
+ val = command[1]
346
+
347
+ if record = @connection.get(as_key(key))
348
+ old_value = record.bins[@bin]
349
+ else
350
+ old_value = nil
351
+ end
352
+ @connection.put(as_key(key), {@bin => val})
353
+ old_value
354
+ end
355
+
356
+
357
+
358
+ # INCR key
359
+ # Increments the number stored at key by one. If the key does not exist, it is set to 0 before performing the operation. An error is returned if the key contains a value of the wrong type or contains a string that can not be represented as integer. This operation is limited to 64 bit signed integers.
360
+ # Return value
361
+ # Integer reply: the value of key after the increment
362
+ def perform_add(key_name, add_value)
363
+ # bin_int = ::Aerospike::Bin.new(@bin, add_value)
364
+ # rec = @connection.operate(as_key(key_name), [
365
+ # ::Aerospike::Operation.add(bin_int),
366
+ # ::Aerospike::Operation.get(bin_int.name)
367
+ # ])
368
+ # rec.bins[@bin]
369
+ #
370
+ # the above method does not work since add does not work on string bins! Error: Bin type error
371
+ @connection.execute_udf(as_key(key_name), REDIS_UDF, 'add',
372
+ [::Aerospike::StringValue.new(@bin),
373
+ ::Aerospike::IntegerValue.new(add_value)
374
+ ])
375
+ end
376
+
377
+ def process_incr(command)
378
+ perform_add(command.first, +1)
379
+ end
380
+
381
+ # INCRBY key increment
382
+ # Increments the number stored at key by increment. If the key does not exist, it is set to 0 before performing the operation. An error is returned if the key contains a value of the wrong type or contains a string that can not be represented as integer. This operation is limited to 64 bit signed integers.
383
+ # Return value
384
+ # Integer reply: the value of key after the increment
385
+ def process_incrby(command)
386
+ perform_add(command.first, command[1])
387
+ end
388
+
389
+ def process_decr(command)
390
+ perform_add(command.first, -1)
391
+ end
392
+
393
+ def process_decrby(command)
394
+ perform_add(command.first, -1 * command[1])
395
+ end
396
+
397
+ # INCRBYFLOAT key increment
398
+ # Increment the string representing a floating point number stored at key by the specified increment. If the key does not exist, it is set to 0 before performing the operation. An error is returned if one of the following conditions occur:
399
+ # The key contains a value of the wrong type (not a string).
400
+ # The current key content or the specified increment are not parsable as a double precision floating point number.
401
+ # If the command is successful the new incremented value is stored as the new value of the key (replacing the old one), and returned to the caller as a string.
402
+ # Both the value already contained in the string key and the increment argument can be optionally provided in exponential notation, however the value computed after the increment is stored consistently in the same format, that is, an integer number followed (if needed) by a dot, and a variable number of digits representing the decimal part of the number. Trailing zeroes are always removed.
403
+ # The precision of the output is fixed at 17 digits after the decimal point regardless of the actual internal precision of the computation.
404
+ # Return value
405
+ # Bulk string reply: the value of key after the increment.
406
+ def process_incrbyfloat(command)
407
+ key = command.first
408
+ add_value = command[1]
409
+
410
+ @connection.execute_udf(as_key(key), REDIS_UDF, 'incrbyfloat',
411
+ [::Aerospike::StringValue.new(@bin),
412
+ ::Aerospike::StringValue.new(add_value.to_s)
413
+ ]).to_s
414
+ end
415
+
416
+
417
+ # SETRANGE key offset value
418
+ # Overwrites part of the string stored at key, starting at the specified offset, for the entire length of value. If the offset is larger than the current length of the string at key, the string is padded with zero-bytes to make offset fit. Non-existing keys are considered as empty strings, so this command will make sure it holds a string large enough to be able to set value at offset.
419
+ # Return value
420
+ # Integer reply: the length of the string after it was modified by the command.
421
+ def process_setrange(command)
422
+ key = command.first
423
+ offset = command[1]
424
+ value = command[2]
425
+
426
+ @connection.execute_udf(as_key(key), REDIS_UDF, 'setrange',
427
+ [::Aerospike::StringValue.new(@bin),
428
+ ::Aerospike::IntegerValue.new(offset),
429
+ ::Aerospike::StringValue.new(value)
430
+ ])
431
+ # CAUTION: zero-bytes mentioned in function description is replaced with spaces here
432
+ end
433
+
434
+
435
+
436
+ # MGET key [key ...]
437
+ # Returns the values of all specified keys. For every key that does not hold a string value or does not exist, the special value nil is returned. Because of this, the operation never fails.
438
+ # Return value
439
+ # Array reply: list of values at the specified keys.
440
+ def process_mget(command)
441
+ keys = command.map { |key| as_key(key) }
442
+ records = @connection.batch_get(keys, [@bin])
443
+ records.map { |rec| rec.nil?? nil : rec.bins[@bin] }
444
+ end
445
+
446
+ # MSET key value [key value ...]
447
+ # Sets the given keys to their respective values. MSET replaces existing values with new values, just as regular SET. See MSETNX if you don't want to overwrite existing values.
448
+ # MSET is atomic, so all given keys are set at once. It is not possible for clients to see that some of the keys were updated while others are unchanged.
449
+ # Return value
450
+ # Simple string reply: always OK since MSET can't fail.
451
+ def process_mset(command)
452
+ command.each_slice(2) { |kv|
453
+ key = kv[0]
454
+ val = kv[1]
455
+ begin
456
+ @connection.put(as_key(key), {@bin => val})
457
+ rescue ::Aerospike::Exceptions::Aerospike
458
+ # ignore and continue to set other keys
459
+ end
460
+ }
461
+ return 'OK'
462
+ end
463
+
464
+ # MSETNX key value [key value ...]
465
+ # Sets the given keys to their respective values. MSETNX will not perform any operation at all even if just a single key already exists.
466
+ # Because of this semantic MSETNX can be used in order to set different keys representing different fields of an unique logic object in a way that ensures that either all the fields or none at all are set.
467
+ # MSETNX is atomic, so all given keys are set at once. It is not possible for clients to see that some of the keys were updated while others are unchanged.
468
+ # Return value
469
+ # Integer reply, specifically:
470
+ # 1 if the all the keys were set.
471
+ # 0 if no key was set (at least one key already existed).
472
+ def process_msetnx(command)
473
+ keys = command.select.each_with_index { |str, i| i.even? }
474
+ if @connection.batch_exists(keys.map{ |key| as_key(key) }).reduce{|r,e| r || e}
475
+ return 0
476
+ else
477
+ command.each_slice(2) { |kv|
478
+ key = kv[0]
479
+ val = kv[1]
480
+ @connection.put(as_key(key), {@bin => val})
481
+ }
482
+ return 1
483
+ end
484
+ end
485
+
486
+
487
+ # LIST function ##############################################
488
+
489
+ # LPUSH key value [value ...]
490
+ # Insert all the specified values at the head of the list stored at key. If key does not exist, it is created as empty list before performing the push operations. When key holds a value that is not a list, an error is returned.
491
+ # It is possible to push multiple elements using a single command call just specifying multiple arguments at the end of the command. Elements are inserted one after the other to the head of the list, from the leftmost element to the rightmost element. So for instance the command LPUSH mylist a b c will result into a list containing c as first element, b as second element and a as third element.
492
+ # Return value
493
+ # Integer reply: the length of the list after the push operations.
494
+ def process_lpush(command)
495
+ key = command.slice!(0)
496
+ @connection.execute_udf(as_key(key), REDIS_UDF, 'lpush',
497
+ [::Aerospike::StringValue.new(@bin),
498
+ ::Aerospike::ListValue.new(command)
499
+ ])
500
+ end
501
+
502
+ # LPUSHX key value
503
+ # Inserts value at the head of the list stored at key, only if key already exists and holds a list. In contrary to LPUSH, no operation will be performed when key does not yet exist.
504
+ # Return value
505
+ # Integer reply: the length of the list after the push operation.
506
+ def process_lpushx(command)
507
+ key = command.first
508
+ val = command[1]
509
+ @connection.execute_udf(as_key(key), REDIS_UDF, 'lpushx',
510
+ [::Aerospike::StringValue.new(@bin),
511
+ ::Aerospike::StringValue.new(val)
512
+ ])
513
+ end
514
+
515
+ # LPOP key
516
+ # Removes and returns the first element of the list stored at key.
517
+ # Return value
518
+ # Bulk string reply: the value of the first element, or nil when key does not exist.
519
+ def process_lpop(command)
520
+ key = command.first
521
+ @connection.execute_udf(as_key(key), REDIS_UDF, 'lpop', [::Aerospike::StringValue.new(@bin)])
522
+ end
523
+
524
+
525
+ # RPUSH key value [value ...]
526
+ def process_rpush(command)
527
+ key = command.slice!(0)
528
+ @connection.execute_udf(as_key(key), REDIS_UDF, 'rpush',
529
+ [::Aerospike::StringValue.new(@bin),
530
+ ::Aerospike::ListValue.new(command)
531
+ ])
532
+ end
533
+
534
+ # RPUSHX key value
535
+ def process_rpushx(command)
536
+ key = command.first
537
+ val = command[1]
538
+ @connection.execute_udf(as_key(key), REDIS_UDF, 'rpushx',
539
+ [::Aerospike::StringValue.new(@bin),
540
+ ::Aerospike::StringValue.new(val)
541
+ ])
542
+ end
543
+
544
+ # RPOP key
545
+ def process_rpop(command)
546
+ key = command.first
547
+ @connection.execute_udf(as_key(key), REDIS_UDF, 'rpop',[::Aerospike::StringValue.new(@bin)])
548
+ end
549
+
550
+ # RPOPLPUSH source destination
551
+ def process_rpoplpush(command)
552
+ key1 = command.first
553
+ key2 = command[1]
554
+ value = @connection.execute_udf(as_key(key1), REDIS_UDF, 'rpop', [::Aerospike::StringValue.new(@bin)])
555
+ if (value != nil) then
556
+ @connection.execute_udf(as_key(key2), REDIS_UDF, 'lpush',
557
+ [::Aerospike::StringValue.new(@bin),
558
+ ::Aerospike::ListValue.new([value])
559
+ ])
560
+ end
561
+ value
562
+ end
563
+
564
+ # LLEN key
565
+ # Returns the length of the list stored at key. If key does not exist, it is interpreted as an empty list and 0 is returned. An error is returned when the value stored at key is not a list.
566
+ # Return value
567
+ # Integer reply: the length of the list at key.
568
+ def process_llen(command)
569
+ key = command.first
570
+ @connection.execute_udf(as_key(key), REDIS_UDF, 'llen', [::Aerospike::StringValue.new(@bin)])
571
+ end
572
+
573
+ # LSET key index value
574
+ # Sets the list element at index to value. For more information on the index argument, see LINDEX.
575
+ # An error is returned for out of range indexes.
576
+ # Return value
577
+ # Simple string reply
578
+ def process_lset(command)
579
+ key = command.first
580
+ index = command[1]
581
+ val = command[2]
582
+ @connection.execute_udf(as_key(key), REDIS_UDF, 'lset',
583
+ [::Aerospike::StringValue.new(@bin),
584
+ ::Aerospike::IntegerValue.new(index),
585
+ ::Aerospike::StringValue.new(val)
586
+ ])
587
+ end
588
+
589
+ # LINDEX key index
590
+ def process_lindex(command)
591
+ key = command.first
592
+ index = command[1]
593
+ @connection.execute_udf(as_key(key), REDIS_UDF, 'lindex',
594
+ [::Aerospike::StringValue.new(@bin),
595
+ ::Aerospike::IntegerValue.new(index)
596
+ ])
597
+ end
598
+
599
+ # LINSERT key BEFORE|AFTER pivot value
600
+ def process_linsert(command)
601
+ key = command.first
602
+ placement = command[1].to_s
603
+ pivot = command[2]
604
+ value = command[3]
605
+
606
+ @connection.execute_udf(as_key(key), REDIS_UDF, 'linsert',
607
+ [::Aerospike::StringValue.new(@bin),
608
+ ::Aerospike::StringValue.new(placement),
609
+ ::Aerospike::StringValue.new(pivot),
610
+ ::Aerospike::StringValue.new(value)
611
+ ])
612
+ end
613
+
614
+ def process_lrange(command)
615
+ key = command.first
616
+ start = command[1]
617
+ stop = command[2]
618
+
619
+ @connection.execute_udf(as_key(key), REDIS_UDF, 'lrange',
620
+ [::Aerospike::StringValue.new(@bin),
621
+ ::Aerospike::IntegerValue.new(start),
622
+ ::Aerospike::IntegerValue.new(stop)
623
+ ])
624
+ end
625
+
626
+ # LREM key count value
627
+ def process_lrem(command)
628
+ key = command.first
629
+ count = command[1]
630
+ value = command[2]
631
+
632
+ @connection.execute_udf(as_key(key), REDIS_UDF, 'lrem',
633
+ [::Aerospike::StringValue.new(@bin),
634
+ ::Aerospike::IntegerValue.new(count),
635
+ ::Aerospike::StringValue.new(value)
636
+ ])
637
+ end
638
+
639
+ # LTRIM key start stop
640
+ def process_ltrim(command)
641
+ key = command.first
642
+ start = command[1]
643
+ stop = command[2]
644
+
645
+ @connection.execute_udf(as_key(key), REDIS_UDF, 'ltrim',
646
+ [::Aerospike::StringValue.new(@bin),
647
+ ::Aerospike::IntegerValue.new(start),
648
+ ::Aerospike::IntegerValue.new(stop)
649
+ ])
650
+ end
651
+
652
+ def write(command)
653
+ # @connection.write(command.flatten(1))
654
+ command = command.flatten(1)
655
+
656
+ # puts "write command: " + command.inspect
657
+
658
+ cmd = command.slice!(0)
659
+ # puts "command: " + cmd.to_s
660
+ # puts 'params: ' + command.to_s
661
+
662
+ method_name = 'process_' + cmd.to_s
663
+ if respond_to?(method_name)
664
+ @result = send(method_name, command)
665
+ else
666
+ raise "command not supported\n" + command.inspect
667
+ end
668
+ # puts "result was: " + @result.to_s
669
+ # return @result
670
+
671
+ rescue Errno::EAGAIN
672
+ raise TimeoutError
673
+ end # wrtie
674
+
675
+ def read
676
+ # reply = @connection.read
677
+ # reply = CommandError.new(reply.message) if reply.is_a?(RuntimeError)
678
+ # reply
679
+
680
+ reply = @result
681
+ @result = nil
682
+ return reply
683
+
684
+ #puts "it is reading!"
685
+ rescue Errno::EAGAIN
686
+ raise TimeoutError
687
+ rescue RuntimeError => err
688
+ raise ProtocolError.new(err.message)
689
+ end # read
690
+
691
+ end # class AerospikeRedis
692
+ end # module Connection
693
+ end # class Redis
694
+
695
+ Redis::Connection.drivers << Redis::Connection::Aerospike
@@ -0,0 +1,365 @@
1
+ -- General Error Codes -- this must line up with the Longevity LOOP code
2
+ local ERR_SUCCESS = 0; -- everything ok
3
+ local ERR_READ_NOT_FOUND = 1; -- the expected value is not there
4
+ local ERR_READ_FAILURE = -1; -- the bin or structure is wrong
5
+ local ERR_WRITE_FAILURE = -2; -- problems with write or create
6
+ local ERR_DELETE_FAILURE = -3; -- problems with delete
7
+ local ERR_UPDATE_FAILURE = -4; -- problems with udpate
8
+
9
+
10
+ function Hello(r)
11
+ return "Hello"
12
+ end
13
+
14
+ function echo_binName(topRec, binName)
15
+ return binName
16
+ end
17
+
18
+ -- String support functions -----------------------------
19
+
20
+ function strlen(topRec, binName)
21
+ if aerospike:exists(topRec) then
22
+ return string.len(topRec[binName])
23
+ else
24
+ return 0
25
+ end
26
+ end
27
+
28
+ function add(topRec, binName, value)
29
+ if not aerospike:exists(topRec) then
30
+ aerospike:create(topRec)
31
+ topRec[binName] = 0
32
+ end
33
+ topRec[binName] = topRec[binName] + value
34
+ aerospike:update(topRec)
35
+ return topRec[binName]
36
+ end
37
+
38
+ function incrbyfloat(topRec, binName, value)
39
+ if not aerospike:exists(topRec) then
40
+ aerospike:create(topRec)
41
+ topRec[binName] = '0'
42
+ end
43
+ topRec[binName] = tostring(tonumber(topRec[binName]) + tonumber(value))
44
+ aerospike:update(topRec)
45
+ return tostring(topRec[binName])
46
+ end
47
+
48
+
49
+ function getrange(topRec, binName, i, j)
50
+ if (i >= 0) then i = i + 1 end
51
+ if (j >= 0) then j = j + 1 end
52
+ if aerospike:exists(topRec) then
53
+ return string.sub(topRec[binName], i, j)
54
+ else
55
+ return ''
56
+ end
57
+ end
58
+
59
+ local function replace(s, offset, value)
60
+ if offset > string.len(s) then
61
+ -- s = s .. string.rep(string.char(0), offset - string.len(s))
62
+ -- this is not supported, so add spaces instead
63
+ s = s .. string.rep(' ', offset - string.len(s) - 1)
64
+ end
65
+ return string.sub(s, 1, offset - 1) .. value .. string.sub(s, offset + string.len(value), string.len(s))
66
+ end
67
+
68
+ function setrange(topRec, binName, offset, value)
69
+ if (offset >= 0) then offset = offset + 1 end
70
+ if not aerospike:exists(topRec) then
71
+ aerospike:create(topRec)
72
+ topRec[binName] = ''
73
+ end
74
+ topRec[binName] = replace(topRec[binName], offset, value)
75
+ aerospike:update(topRec)
76
+ return string.len(topRec[binName])
77
+ end
78
+
79
+
80
+ -- List support functions -------------------------------
81
+
82
+ function lpush(topRec, binName, values)
83
+ if not aerospike:exists(topRec) then
84
+ aerospike:create(topRec)
85
+ topRec[binName] = list()
86
+ end
87
+ local l = topRec[binName]
88
+ for value in list.iterator(values) do
89
+ list.prepend(l, value)
90
+ end
91
+ topRec[binName] = l
92
+ aerospike:update(topRec)
93
+ return list.size(topRec[binName])
94
+ end
95
+
96
+ function lpushx(topRec, binName, value)
97
+ if not aerospike:exists(topRec) then
98
+ return 0
99
+ else
100
+ local l = topRec[binName]
101
+ list.prepend(l, value)
102
+ topRec[binName] = l
103
+ aerospike:update(topRec)
104
+ return list.size(topRec[binName])
105
+ end
106
+ end
107
+
108
+ function lpop(topRec, binName)
109
+ if not aerospike:exists(topRec) then
110
+ return nil
111
+ else
112
+ local l = topRec[binName]
113
+ if (list.size(l) == 0) then
114
+ return nil
115
+ else
116
+ local val = l[1]
117
+ topRec[binName] = list.drop(l, 1)
118
+ aerospike:update(topRec)
119
+ return val
120
+ end
121
+ end
122
+ end
123
+
124
+ function rpush(topRec, binName, values)
125
+ if not aerospike:exists(topRec) then
126
+ aerospike:create(topRec)
127
+ topRec[binName] = list()
128
+ end
129
+ local l = topRec[binName]
130
+ for value in list.iterator(values) do
131
+ list.append(l, value)
132
+ end
133
+ topRec[binName] = l
134
+ aerospike:update(topRec)
135
+ return list.size(topRec[binName])
136
+ end
137
+
138
+ function rpushx(topRec, binName, value)
139
+ if not aerospike:exists(topRec) then
140
+ return 0
141
+ else
142
+ local l = topRec[binName]
143
+ list.append(l, value)
144
+ topRec[binName] = l
145
+ aerospike:update(topRec)
146
+ return list.size(topRec[binName])
147
+ end
148
+ end
149
+
150
+ function rpop(topRec, binName)
151
+ if not aerospike:exists(topRec) then
152
+ return nil
153
+ else
154
+ local l = topRec[binName]
155
+ if (list.size(l) == 0) then
156
+ return nil
157
+ else
158
+ local val = l[list.size(l)]
159
+ topRec[binName] = list.take(l, list.size(l) - 1)
160
+ aerospike:update(topRec)
161
+ return val
162
+ end
163
+ end
164
+ end
165
+
166
+ local function adjust_list_index(lst, index)
167
+ if (index >= 0) then
168
+ index = index + 1 -- lua lists are 1-based
169
+ else
170
+ index = list.size(lst) + index + 1
171
+ end
172
+ return index
173
+ end
174
+
175
+ --E, [2014-10-25T22:55:20.409949 #9060] ERROR -- : uninitialized constant IO::WaitWriteable
176
+ --trying to access a list by a negative index or maybe there was an extra 'end' after index = list.size(l) + index + 1
177
+ function lset(topRec, binName, index, value)
178
+ if not aerospike:exists(topRec) then
179
+ error( ERR_READ_NOT_FOUND )
180
+ else
181
+ local l = topRec[binName]
182
+ index = adjust_list_index(l, index)
183
+ if (index > list.size(l) or index < 1) then
184
+ error( ERR_UPDATE_FAILURE )
185
+ else
186
+ l[index] = value
187
+ topRec[binName] = l
188
+ aerospike:update(topRec)
189
+ return 'OK'
190
+ end
191
+ end
192
+ end
193
+
194
+ function lindex(topRec, binName, index)
195
+ if not aerospike:exists(topRec) then
196
+ error( ERR_READ_NOT_FOUND )
197
+ else
198
+ local l = topRec[binName]
199
+ index = adjust_list_index(l, index)
200
+ if (index > list.size(l)) or (index < 1) then
201
+ return nil
202
+ else
203
+ return l[index]
204
+ end
205
+ end
206
+ end
207
+
208
+ -- List utility functions returning an empty list instead of nil
209
+ local function merge_lists(list1, list2)
210
+ if not (list2 == nil) then
211
+ for val in list.iterator(list2) do
212
+ list.append(list1, val)
213
+ end
214
+ end
215
+ return list1
216
+ end
217
+
218
+ local function list_take(l, n)
219
+ if (n < 1) or (l == nil) then
220
+ return list()
221
+ else
222
+ return list.take(l, n)
223
+ end
224
+ end
225
+
226
+ local function list_drop(l, n)
227
+ if (l == nil) then
228
+ return list()
229
+ elseif (n < 1) then
230
+ return l
231
+ else
232
+ return list.drop(l, n)
233
+ end
234
+ end
235
+ -----------------------------------------------------------------
236
+
237
+ function linsert(topRec, binName, placement, pivot, value)
238
+ if not aerospike:exists(topRec) then
239
+ return nil
240
+ else
241
+ local l = topRec[binName]
242
+ local i = 0
243
+ for val in list.iterator(l) do
244
+ i = i + 1
245
+ if (val == pivot) then
246
+ if (string.upper(placement) == 'BEFORE') then
247
+ i = i - 1
248
+ end
249
+
250
+ new_list = list_take(l, i)
251
+ list.append(new_list, value)
252
+ new_list = merge_lists(new_list, list_drop(l, i))
253
+
254
+ topRec[binName] = new_list
255
+ aerospike:update(topRec)
256
+ return list.size(topRec[binName])
257
+ end
258
+ end
259
+ return -1 -- pivot not found
260
+ end
261
+ end
262
+
263
+ function llen(topRec, binName)
264
+ if not aerospike:exists(topRec) then
265
+ return 0
266
+ else
267
+ return list.size(topRec[binName])
268
+ end
269
+ end
270
+
271
+ function lrange(topRec, binName, start, stop)
272
+ if not aerospike:exists(topRec) then
273
+ return list()
274
+ else
275
+ local l = topRec[binName]
276
+ start = adjust_list_index(l, start)
277
+ stop = adjust_list_index(l, stop)
278
+ l = list.merge(l, list())
279
+ l = list_drop(l, start - 1)
280
+ l = list_take(l, stop - start + 1)
281
+ return l
282
+ end
283
+ end
284
+
285
+ function lrem(topRec, binName, count, value)
286
+ if not aerospike:exists(topRec) then
287
+ return 0
288
+ else
289
+ local l = topRec[binName]
290
+ local new_list = list()
291
+ local rem_count = 0
292
+
293
+ if (count == 0) then -- remove all instances
294
+ for val in list.iterator(l) do
295
+ if (val == value) then
296
+ rem_count = rem_count + 1
297
+ else
298
+ list.append(new_list, val)
299
+ end
300
+ end
301
+ elseif (count > 0) then
302
+ for val in list.iterator(l) do
303
+ if (rem_count < count) and (val == value) then
304
+ rem_count = rem_count + 1
305
+ else
306
+ list.append(new_list, val)
307
+ end
308
+ end
309
+ else
310
+ for i = list.size(l), 1, -1 do
311
+ local val = l[i]
312
+ if (rem_count < -1 * count) and (val == value) then
313
+ rem_count = rem_count + 1
314
+ else
315
+ list.prepend(new_list, val)
316
+ end
317
+ end
318
+ end
319
+
320
+ topRec[binName] = new_list
321
+ aerospike:update(topRec)
322
+ return rem_count
323
+ end
324
+ end
325
+
326
+ function ltrim(topRec, binName, start, stop)
327
+ if aerospike:exists(topRec) then
328
+ local l = topRec[binName]
329
+ start = adjust_list_index(l, start)
330
+ stop = adjust_list_index(l, stop)
331
+ l = list_drop(l, start - 1)
332
+ l = list_take(l, stop - start + 1)
333
+ if (list.size(l) == 0) then
334
+ aerospike:remove(topRec)
335
+ else
336
+ topRec[binName] = l
337
+ aerospike:update(topRec)
338
+ end
339
+ end
340
+ return 'OK'
341
+ end
342
+
343
+
344
+ -- Set support functions --------------------------------
345
+
346
+ function sadd(topRec, binName, values)
347
+ if not aerospike:exists(topRec) then
348
+ aerospike:create(topRec)
349
+ topRec[binName] = list()
350
+ end
351
+ l = topRec[binName]
352
+ for value in list.iterator(values) do
353
+ list.prepend(l, value)
354
+ end
355
+ topRec[binName] = l
356
+ aerospike:update(topRec)
357
+ return list.size(topRec[binName])
358
+ end
359
+
360
+
361
+
362
+
363
+ --TODO use list.merge and list.clone to speed up things
364
+
365
+
metadata ADDED
@@ -0,0 +1,112 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: aerospike-redis
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Amir Rahimi Farahani
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-11-04 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.7'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: aerospike
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.1'
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: 0.1.3
51
+ type: :runtime
52
+ prerelease: false
53
+ version_requirements: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - "~>"
56
+ - !ruby/object:Gem::Version
57
+ version: '0.1'
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: 0.1.3
61
+ - !ruby/object:Gem::Dependency
62
+ name: redis
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '3'
68
+ type: :runtime
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '3'
75
+ description: Try Aerospike as a back-end replacement for Redis in Ruby applications.
76
+ email:
77
+ - amirrf@gmail.com
78
+ executables: []
79
+ extensions: []
80
+ extra_rdoc_files: []
81
+ files:
82
+ - LICENSE.txt
83
+ - README.md
84
+ - lib/aerospike/redis.rb
85
+ - lib/aerospike/redis/version.rb
86
+ - lib/redis/connection/aerospike.rb
87
+ - lib/redis/connection/redis_udf.lua
88
+ homepage: https://github.com/amirrf/aerospike-redis-ruby
89
+ licenses:
90
+ - Apache2.0
91
+ metadata: {}
92
+ post_install_message:
93
+ rdoc_options: []
94
+ require_paths:
95
+ - lib
96
+ required_ruby_version: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ version: 1.9.3
101
+ required_rubygems_version: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - ">="
104
+ - !ruby/object:Gem::Version
105
+ version: '0'
106
+ requirements: []
107
+ rubyforge_project:
108
+ rubygems_version: 2.2.2
109
+ signing_key:
110
+ specification_version: 4
111
+ summary: Aerospike Ruby Adapter for Redis.
112
+ test_files: []