redlics 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,30 @@
1
+ module Redlics
2
+
3
+ # Exception namespace
4
+ module Exception
5
+
6
+ # Error Pattern namespace
7
+ module ErrorPatterns
8
+ NOSCRIPT = /^NOSCRIPT/.freeze
9
+ end
10
+
11
+
12
+ # Lua Range Error class
13
+ #
14
+ # Maximal Lua stack size for the method `unpack` is by default 8000.
15
+ # To change this parameter in Redis an own make and build of Redis is needed.
16
+ # @see https://github.com/antirez/redis/blob/3.2/deps/lua/src/luaconf.h
17
+ class LuaRangeError < StandardError;
18
+
19
+ # Initialization with default error message.
20
+ #
21
+ # @param msg [String] the error message
22
+ # @return [Redlics::Exception::LuaRangeError] error message
23
+ def initialize(msg = 'Too many keys (max. 8000 keys defined by LUAI_MAXCSTACK)')
24
+ super(msg)
25
+ end
26
+
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,49 @@
1
+ module Redlics
2
+
3
+ # Granularity namespace
4
+ module Granularity
5
+
6
+ extend self
7
+
8
+
9
+ # Validate granularities by given context.
10
+ #
11
+ # @param context [Hash] the hash of a context defined in Redlics::CONTEXTS
12
+ # @param granularities [Range] granularity range
13
+ # @param granularities [String] single granularity
14
+ # @param granularities [Array] granularity array
15
+ # @return [Array] includes all valid granularities
16
+ def validate(context, granularities)
17
+ check(granularities) || default(context)
18
+ end
19
+
20
+
21
+ # Get default granularities by given context.
22
+ #
23
+ # @param context [Hash] the hash of a context defined in Redlics::CONTEXTS
24
+ # @return [Array] includes all valid default granularities
25
+ def default(context)
26
+ check(Redlics.config["#{context[:long]}_granularity"]) || [Redlics.config.granularities.keys.first]
27
+ end
28
+
29
+
30
+ private
31
+
32
+ # Check if granularities are defined in the configuration.
33
+ #
34
+ # @param granularities [Range] granularity range
35
+ # @param granularities [String] single granularity
36
+ # @param granularities [Array] granularity array
37
+ # @return [Array] includes all valid granularities
38
+ def check(granularities)
39
+ keys = Redlics.config.granularities.keys
40
+ checked = if granularities.is_a?(Range)
41
+ keys[keys.index(granularities.first)..keys.index(granularities.last)]
42
+ else
43
+ [granularities].flatten & Redlics.config.granularities.keys
44
+ end
45
+ checked.any? ? checked : nil
46
+ end
47
+
48
+ end
49
+ end
@@ -0,0 +1,244 @@
1
+ module Redlics
2
+
3
+ # Key namespace
4
+ module Key
5
+
6
+ extend self
7
+
8
+
9
+ # Construct the key name with given parameters.
10
+ #
11
+ # @param context [Hash] the hash of a context defined in Redlics::CONTEXTS
12
+ # @param event [String] event name with eventual Redis namespace separator
13
+ # @param granularity [Symbol] existing granularity
14
+ # @param past [Time] a time object
15
+ # @param options [Hash] configuration options
16
+ # @return [String] unbucketized key name
17
+ # @return [Array] bucketized key name
18
+ def name(context, event, granularity, past, options = {})
19
+ past ||= Time.now
20
+ granularity = Granularity.validate(context, granularity).first
21
+ event = encode_event(event) if Redlics.config.encode[:events]
22
+ key = "#{context[:short]}#{Redlics.config.separator}#{event}#{Redlics.config.separator}#{time_format(granularity, past)}"
23
+ key = with_namespace(key) if options[:namespaced]
24
+ return bucketize(key, options[:id]) if bucketize?(context, options)
25
+ return unbucketize(key, options[:id]) if context[:long] == :counter && !options[:id].nil?
26
+ key
27
+ end
28
+
29
+
30
+ # Construct an array with all keys of a time frame in a given granularity.
31
+ #
32
+ # @param context [Hash] the hash of a context defined in Redlics::CONTEXTS
33
+ # @param event [String] event name with eventual Redis namespace separator
34
+ # @param time_object [Symbol] time object predefined in Redlics::TimeFrame.init_with_symbol
35
+ # @param time_object [Hash] time object with keys `from` and `to`
36
+ # @param time_object [Range] time object as range
37
+ # @param time_object [Time] time object
38
+ # @param options [Hash] configuration options
39
+ # @return [Array] array with all keys of a time frame in a given granularity
40
+ def timeframed(context, event, time_object, options = {})
41
+ options = { namespaced: true }.merge(options)
42
+ timeframe = TimeFrame.new(context, time_object, options)
43
+ timeframe.splat do |time|
44
+ name(context, event, timeframe.granularity, time, options)
45
+ end
46
+ end
47
+
48
+
49
+ # Prepend namespace to a key.
50
+ #
51
+ # @param key [String] the key name
52
+ # @return [String] the key name with prepended namespace
53
+ def with_namespace(key)
54
+ return key unless Redlics.config.namespace.length > 0
55
+ return key if key.split(Redlics.config.separator).first == Redlics.config.namespace.to_s
56
+ "#{Redlics.config.namespace}#{Redlics.config.separator}#{key}"
57
+ end
58
+
59
+
60
+ # Encode a number with a mapping table.
61
+ #
62
+ # @param number [Integer] the number to encode
63
+ # @return [String] the encoded number as string
64
+ def encode(number)
65
+ encoded = ''
66
+ number = number.to_s
67
+ number = (number.size % 2) != 0 ? "0#{number}" : number
68
+ token = 0
69
+ while token <= number.size - 1
70
+ encoded += encode_map[number[token..token+1].to_i.to_s.to_sym].to_s
71
+ token += 2
72
+ end
73
+ encoded
74
+ end
75
+
76
+
77
+ # Decode a number with a mapping table.
78
+ #
79
+ # @param string [String] the string to encode
80
+ # @return [Integer] the decoded string as integer
81
+ def decode(string)
82
+ decoded = ''
83
+ string = string.to_s
84
+ token = 0
85
+ while token <= string.size - 1
86
+ number = decode_map[string[token].to_s.to_sym].to_s
87
+ decoded += number.size == 1 ? "0#{number}" : number
88
+ token += 1
89
+ end
90
+ decoded.to_i
91
+ end
92
+
93
+
94
+ # Check if a key exists in Redis.
95
+ #
96
+ # @param string [String] the key name to check
97
+ # @return [Boolean] true id key exists, false if not
98
+ def exists?(key)
99
+ Redlics.redis.exists(key) == 1
100
+ end
101
+
102
+
103
+ # Check if Redlics can bucketize.
104
+ #
105
+ # @param context [Hash] the hash of a context defined in Redlics::CONTEXTS
106
+ # @param options [Hash] configuration options
107
+ # @return [Boolean] true if can bucketize, false if not
108
+ def bucketize?(context, options = {})
109
+ context[:long] == :counter && Redlics.config.bucket && !options[:id].nil?
110
+ end
111
+
112
+
113
+ # Create a unique operation key in Redis.
114
+ # @return [String] the created unique operation key
115
+ def unique_namespace
116
+ loop do
117
+ ns = operation
118
+ unless exists?(ns)
119
+ Redlics.redis.pipelined do |redis|
120
+ redis.set(ns, 0)
121
+ redis.expire(ns, Redlics.config.operation_expiration)
122
+ end
123
+ break ns
124
+ end
125
+ end
126
+ end
127
+
128
+
129
+ private
130
+
131
+ # Create a operation key.
132
+ # @return [String] the created operation key
133
+ def operation
134
+ "#{Redlics::CONTEXTS[:operation][:short]}#{Redlics.config.separator}#{SecureRandom.uuid}"
135
+ end
136
+
137
+
138
+ # Get the time format pattern of a granularity.
139
+ #
140
+ # @param granularity [Symbol] existing granularity
141
+ # @param past [Time] a time object
142
+ # @return [String] pattern of defined granularity
143
+ def time_format(granularity, past)
144
+ past.strftime(Redlics.config.granularities[granularity][:pattern])
145
+ end
146
+
147
+
148
+ # Encode ids in event names.
149
+ #
150
+ # @param event [String] event name with eventual Redis namespace separator
151
+ # @return [String] event name with encoded ids
152
+ def encode_event(event)
153
+ event.to_s.split(Redlics.config.separator).map { |v| v.match(/\A\d+\z/) ? encode(v) : v }.join(Redlics.config.separator)
154
+ end
155
+
156
+
157
+ # Bucketize key name with id.
158
+ #
159
+ # @param key [String] key name
160
+ # @param id [Integer] object id
161
+ # @return [Array] bucketized key name and value
162
+ def bucketize(key, id)
163
+ bucket = id.to_i / Redlics.config.bucket_size.to_i
164
+ value = id.to_i % Redlics.config.bucket_size.to_i
165
+ if Redlics.config.encode[:ids]
166
+ bucket = encode(bucket)
167
+ value = encode(value)
168
+ end
169
+ ["#{key}#{Redlics.config.separator}#{bucket}", value]
170
+ end
171
+
172
+
173
+ # Unbucketize key name with id. Encode the id if configured to encode.
174
+ #
175
+ # @param key [String] key name
176
+ # @param id [Integer] object id
177
+ # @return [String] unbucketized key name with eventual encoded object id
178
+ def unbucketize(key, id)
179
+ id = encode(id) if Redlics.config.encode[:ids]
180
+ "#{key}#{Redlics.config.separator}#{id}"
181
+ end
182
+
183
+
184
+ # Defined encode map.
185
+ # @return [Hash] the encode map with numbers as keys
186
+ def encode_map
187
+ @encode_map ||= replace_separator_encode({
188
+ '0' => '1', '1' => '2', '2' => '3', '3' => '4', '4' => '5', '5' => '6', '6' => '7', '7' => '8', '8' => '9', '9' => '0',
189
+ '10' => '-', '11' => '=', '12' => '!', '13' => '@', '14' => '#', '15' => '$', '16' => '%', '17' => '^', '18' => '&', '19' => '*',
190
+ '20' => '(', '21' => ')', '22' => '_', '23' => '+', '24' => 'a', '25' => 'b', '26' => 'c', '27' => 'd', '28' => 'e', '29' => 'f',
191
+ '30' => 'g', '31' => 'h', '32' => 'i', '33' => 'j', '34' => 'k', '35' => 'l', '36' => 'm', '37' => 'n', '38' => 'o', '39' => 'p',
192
+ '40' => 'q', '41' => 'r', '42' => 's', '43' => 't', '44' => 'u', '45' => 'v', '46' => 'w', '47' => 'x', '48' => 'y', '49' => 'z',
193
+ '50' => 'A', '51' => 'B', '52' => 'C', '53' => 'D', '54' => 'E', '55' => 'F', '56' => 'G', '57' => 'H', '58' => 'I', '59' => 'J',
194
+ '60' => 'K', '61' => 'L', '62' => 'M', '63' => 'N', '64' => 'O', '65' => 'P', '66' => 'Q', '67' => 'R', '68' => 'S', '69' => 'T',
195
+ '70' => 'U', '71' => 'V', '72' => 'W', '73' => 'X', '74' => 'Y', '75' => 'Z', '76' => '[', '77' => ']', '78' => '\\', '79' => ';',
196
+ '80' => ',', '81' => '.', '82' => '/', '83' => '{', '84' => '}', '85' => '|', '86' => '§', '87' => '<', '88' => '>', '89' => '?',
197
+ '90' => '`', '91' => '~', '92' => 'ä', '93' => 'Ä', '94' => 'ü', '95' => 'Ü', '96' => 'ö', '97' => 'Ö', '98' => 'é', '99' => 'É' }).freeze
198
+ end
199
+
200
+
201
+ # Defined decode map.
202
+ # @return [Hash] the decode map with numbers as values
203
+ def decode_map
204
+ @decode_map ||= replace_separator_decode({
205
+ '1' => '0', '2' => '1', '3' => '2', '4' => '3', '5' => '4', '6' => '5', '7' => '6', '8' => '7', '9' => '8', '0' => '9',
206
+ '-' => '10', '=' => '11', '!' => '12', '@' => '13', '#' => '14', '$' => '15', '%' => '16', '^' => '17', '&' => '18', '*' => '19',
207
+ '(' => '20', ')' => '21', '_' => '22', '+' => '23', 'a' => '24', 'b' => '25', 'c' => '26', 'd' => '27', 'e' => '28', 'f' => '29',
208
+ 'g' => '30', 'h' => '31', 'i' => '32', 'j' => '33', 'k' => '34', 'l' => '35', 'm' => '36', 'n' => '37', 'o' => '38', 'p' => '39',
209
+ 'q' => '40', 'r' => '41', 's' => '42', 't' => '43', 'u' => '44', 'v' => '45', 'w' => '46', 'x' => '47', 'y' => '48', 'z' => '49',
210
+ 'A' => '50', 'B' => '51', 'C' => '52', 'D' => '53', 'E' => '54', 'F' => '55', 'G' => '56', 'H' => '57', 'I' => '58', 'J' => '59',
211
+ 'K' => '60', 'L' => '61', 'M' => '62', 'N' => '63', 'O' => '64', 'P' => '65', 'Q' => '66', 'R' => '67', 'S' => '68', 'T' => '69',
212
+ 'U' => '70', 'V' => '71', 'W' => '72', 'X' => '73', 'Y' => '74', 'Z' => '75', '[' => '76', ']' => '77', '\\' => '78', ';' => '79',
213
+ ',' => '80', '.' => '81', '/' => '82', '{' => '83', '}' => '84', '|' => '85', '§' => '86', '<' => '87', '>' => '88', '?' => '89',
214
+ '`' => '90', '~' => '91', 'ä' => '92', 'Ä' => '93', 'ü' => '94', 'Ü' => '95', 'ö' => '96', 'Ö' => '97', 'é' => '98', 'É' => '99' }).freeze
215
+ end
216
+
217
+
218
+ # Replace defined separator in configuration from the encode map.
219
+ #
220
+ # @param map [Hash] encode map hash
221
+ # @return [Hash] encode map hash without defined separator in configuration.
222
+ def replace_separator_encode(map)
223
+ unless Redlics.config.separator == ':'
224
+ key = map.key(Redlics.config.separator)
225
+ map[key] = ':' if key
226
+ end
227
+ map
228
+ end
229
+
230
+
231
+ # Replace defined separator in configuration from the decode map.
232
+ #
233
+ # @param map [Hash] decode map hash
234
+ # @return [Hash] decode map hash without defined separator in configuration.
235
+ def replace_separator_decode(map)
236
+ unless Redlics.config.separator == ':'
237
+ key = Redlics.config.separator.to_s.to_sym
238
+ map[':'.to_sym] = map.delete(key) if map.key?(key)
239
+ end
240
+ map
241
+ end
242
+
243
+ end
244
+ end
@@ -0,0 +1,82 @@
1
+ ---
2
+ redis.log(redis.LOG_NOTICE, 'Redlics')
3
+
4
+ local func = cmsgpack.unpack(ARGV[1])
5
+ local keys = cmsgpack.unpack(ARGV[2])
6
+ local options = cmsgpack.unpack(ARGV[3])
7
+
8
+
9
+ local function operate(operator, keys)
10
+ redis.call('BITOP', operator, options['dest'], unpack(keys))
11
+ return options['dest']
12
+ end
13
+
14
+
15
+ local function AND(keys) return operate('AND', keys) end
16
+ local function OR(keys) return operate('OR', keys) end
17
+ local function XOR(keys) return operate('XOR', keys) end
18
+ local function NOT(keys) return operate('NOT', keys) end
19
+ local function MINUS(keys)
20
+ local items = keys
21
+ local src = table.remove(items, 1)
22
+ local and_op = AND(keys)
23
+ return XOR({ src, and_op })
24
+ end
25
+
26
+
27
+ local function operation(keys, options)
28
+ if options['operator'] == 'MINUS' then
29
+ return MINUS(keys)
30
+ else
31
+ return operate(options['operator'], keys)
32
+ end
33
+ end
34
+
35
+
36
+ local function counts(keys, options)
37
+ local result
38
+ if options['bucketized'] then
39
+ result = 0
40
+ for i,v in ipairs(keys) do
41
+ result = result + (redis.call('HGET', v[1], v[2]) or 0)
42
+ end
43
+ else
44
+ result = redis.call('MGET', unpack(keys))
45
+ end
46
+ return result
47
+ end
48
+
49
+
50
+ local function plot_counts(keys, options)
51
+ local plot = {}
52
+ if options['bucketized'] then
53
+ for i,v in ipairs(keys) do
54
+ plot[v[1]..v[2]] = (redis.call('HGET', v[1], v[2]) or 0)
55
+ end
56
+ else
57
+ local values = redis.call('MGET', unpack(keys))
58
+ for i,v in ipairs(keys) do
59
+ plot[v] = values[i]
60
+ end
61
+ end
62
+ return cjson.encode(plot)
63
+ end
64
+
65
+
66
+ local function plot_tracks(keys, options)
67
+ local plot = {}
68
+ for i,v in ipairs(keys) do
69
+ plot[v] = redis.call('bitcount', keys[i])
70
+ end
71
+ return cjson.encode(plot)
72
+ end
73
+
74
+
75
+ local exportFuncs = {
76
+ operation = operation,
77
+ counts = counts,
78
+ plot_counts = plot_counts,
79
+ plot_tracks = plot_tracks
80
+ }
81
+
82
+ return exportFuncs[func](keys, options)
@@ -0,0 +1,51 @@
1
+ module Redlics
2
+
3
+ # Operators namespace
4
+ module Operators
5
+
6
+ # AND (&) operator.
7
+ #
8
+ # @param query [Redlics::Query] Redlics query object
9
+ # @return [Redlics::Query::Operation] a Redlics query operation object
10
+ def &(query)
11
+ Query::Operation.new('AND', [self, query])
12
+ end
13
+
14
+
15
+ # OR (|) operator.
16
+ #
17
+ # @param query [Redlics::Query] Redlics query object
18
+ # @return [Redlics::Query::Operation] a Redlics query operation object
19
+ def |(query)
20
+ Query::Operation.new('OR', [self, query])
21
+ end
22
+ alias_method :+, :|
23
+
24
+
25
+ # XOR (^) operator.
26
+ #
27
+ # @param query [Redlics::Query] Redlics query object
28
+ # @return [Redlics::Query::Operation] a Redlics query operation object
29
+ def ^(query)
30
+ Query::Operation.new('XOR', [self, query])
31
+ end
32
+
33
+
34
+ # NOT (-, ~) operator.
35
+ # @return [Redlics::Query::Operation] a Redlics query operation object
36
+ def -@()
37
+ Query::Operation.new('NOT', [self])
38
+ end
39
+ alias_method :~@, :-@
40
+
41
+
42
+ # MINUS (-) operator.
43
+ #
44
+ # @param query [Redlics::Query] Redlics query object
45
+ # @return [Redlics::Query::Operation] a Redlics query operation object
46
+ def -(query)
47
+ Query::Operation.new('MINUS', [self, query])
48
+ end
49
+
50
+ end
51
+ end