redlics 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,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