aerospike-redis 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE.txt +22 -0
- data/README.md +90 -0
- data/lib/aerospike/redis.rb +7 -0
- data/lib/aerospike/redis/version.rb +5 -0
- data/lib/redis/connection/aerospike.rb +695 -0
- data/lib/redis/connection/redis_udf.lua +365 -0
- metadata +112 -0
checksums.yaml
ADDED
@@ -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
|
data/LICENSE.txt
ADDED
@@ -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.
|
data/README.md
ADDED
@@ -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,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: []
|