zermelo 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +16 -0
- data/.rspec +10 -0
- data/.travis.yml +27 -0
- data/Gemfile +20 -0
- data/LICENSE.txt +22 -0
- data/README.md +512 -0
- data/Rakefile +1 -0
- data/lib/zermelo/associations/association_data.rb +24 -0
- data/lib/zermelo/associations/belongs_to.rb +115 -0
- data/lib/zermelo/associations/class_methods.rb +244 -0
- data/lib/zermelo/associations/has_and_belongs_to_many.rb +128 -0
- data/lib/zermelo/associations/has_many.rb +120 -0
- data/lib/zermelo/associations/has_one.rb +109 -0
- data/lib/zermelo/associations/has_sorted_set.rb +124 -0
- data/lib/zermelo/associations/index.rb +50 -0
- data/lib/zermelo/associations/index_data.rb +18 -0
- data/lib/zermelo/associations/unique_index.rb +44 -0
- data/lib/zermelo/backends/base.rb +115 -0
- data/lib/zermelo/backends/influxdb_backend.rb +178 -0
- data/lib/zermelo/backends/redis_backend.rb +281 -0
- data/lib/zermelo/filters/base.rb +235 -0
- data/lib/zermelo/filters/influxdb_filter.rb +162 -0
- data/lib/zermelo/filters/redis_filter.rb +558 -0
- data/lib/zermelo/filters/steps/base_step.rb +22 -0
- data/lib/zermelo/filters/steps/diff_range_step.rb +17 -0
- data/lib/zermelo/filters/steps/diff_step.rb +17 -0
- data/lib/zermelo/filters/steps/intersect_range_step.rb +17 -0
- data/lib/zermelo/filters/steps/intersect_step.rb +17 -0
- data/lib/zermelo/filters/steps/limit_step.rb +17 -0
- data/lib/zermelo/filters/steps/offset_step.rb +17 -0
- data/lib/zermelo/filters/steps/sort_step.rb +17 -0
- data/lib/zermelo/filters/steps/union_range_step.rb +17 -0
- data/lib/zermelo/filters/steps/union_step.rb +17 -0
- data/lib/zermelo/locks/no_lock.rb +16 -0
- data/lib/zermelo/locks/redis_lock.rb +221 -0
- data/lib/zermelo/records/base.rb +62 -0
- data/lib/zermelo/records/class_methods.rb +127 -0
- data/lib/zermelo/records/collection.rb +14 -0
- data/lib/zermelo/records/errors.rb +24 -0
- data/lib/zermelo/records/influxdb_record.rb +35 -0
- data/lib/zermelo/records/instance_methods.rb +224 -0
- data/lib/zermelo/records/key.rb +19 -0
- data/lib/zermelo/records/redis_record.rb +27 -0
- data/lib/zermelo/records/type_validator.rb +20 -0
- data/lib/zermelo/version.rb +3 -0
- data/lib/zermelo.rb +102 -0
- data/spec/lib/zermelo/associations/belongs_to_spec.rb +6 -0
- data/spec/lib/zermelo/associations/has_many_spec.rb +6 -0
- data/spec/lib/zermelo/associations/has_one_spec.rb +6 -0
- data/spec/lib/zermelo/associations/has_sorted_set.spec.rb +6 -0
- data/spec/lib/zermelo/associations/index_spec.rb +6 -0
- data/spec/lib/zermelo/associations/unique_index_spec.rb +6 -0
- data/spec/lib/zermelo/backends/influxdb_backend_spec.rb +0 -0
- data/spec/lib/zermelo/backends/moneta_backend_spec.rb +0 -0
- data/spec/lib/zermelo/filters/influxdb_filter_spec.rb +0 -0
- data/spec/lib/zermelo/filters/redis_filter_spec.rb +0 -0
- data/spec/lib/zermelo/locks/redis_lock_spec.rb +170 -0
- data/spec/lib/zermelo/records/influxdb_record_spec.rb +258 -0
- data/spec/lib/zermelo/records/key_spec.rb +6 -0
- data/spec/lib/zermelo/records/redis_record_spec.rb +1426 -0
- data/spec/lib/zermelo/records/type_validator_spec.rb +6 -0
- data/spec/lib/zermelo/version_spec.rb +6 -0
- data/spec/lib/zermelo_spec.rb +6 -0
- data/spec/spec_helper.rb +67 -0
- data/spec/support/profile_all_formatter.rb +44 -0
- data/spec/support/uncolored_doc_formatter.rb +74 -0
- data/zermelo.gemspec +30 -0
- metadata +174 -0
@@ -0,0 +1,281 @@
|
|
1
|
+
require 'zermelo/backends/base'
|
2
|
+
|
3
|
+
require 'zermelo/filters/redis_filter'
|
4
|
+
require 'zermelo/locks/redis_lock'
|
5
|
+
|
6
|
+
module Zermelo
|
7
|
+
|
8
|
+
module Backends
|
9
|
+
|
10
|
+
class RedisBackend
|
11
|
+
|
12
|
+
include Zermelo::Backends::Base
|
13
|
+
|
14
|
+
def generate_lock
|
15
|
+
Zermelo::Locks::RedisLock.new
|
16
|
+
end
|
17
|
+
|
18
|
+
def filter(ids_key, record)
|
19
|
+
Zermelo::Filters::RedisFilter.new(self, ids_key, record)
|
20
|
+
end
|
21
|
+
|
22
|
+
def get_multiple(*attr_keys)
|
23
|
+
attr_keys.inject({}) do |memo, attr_key|
|
24
|
+
redis_attr_key = key_to_redis_key(attr_key)
|
25
|
+
|
26
|
+
memo[attr_key.klass] ||= {}
|
27
|
+
memo[attr_key.klass][attr_key.id] ||= {}
|
28
|
+
memo[attr_key.klass][attr_key.id][attr_key.name.to_s] = if Zermelo::COLLECTION_TYPES.has_key?(attr_key.type)
|
29
|
+
|
30
|
+
case attr_key.type
|
31
|
+
when :list
|
32
|
+
if attr_key.accessor.nil?
|
33
|
+
Zermelo.redis.lrange(redis_attr_key, 0, -1)
|
34
|
+
else
|
35
|
+
|
36
|
+
end
|
37
|
+
when :set
|
38
|
+
if attr_key.accessor.nil?
|
39
|
+
Set.new( Zermelo.redis.smembers(redis_attr_key) )
|
40
|
+
else
|
41
|
+
|
42
|
+
end
|
43
|
+
when :hash
|
44
|
+
if attr_key.accessor.nil?
|
45
|
+
Zermelo.redis.hgetall(redis_attr_key)
|
46
|
+
else
|
47
|
+
|
48
|
+
end
|
49
|
+
when :sorted_set
|
50
|
+
# TODO should this be something that preserves order?
|
51
|
+
if attr_key.accessor.nil?
|
52
|
+
Set.new( Zermelo.redis.zrange(redis_attr_key, 0, -1) )
|
53
|
+
else
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
else
|
59
|
+
value = Zermelo.redis.hget(redis_attr_key, attr_key.name.to_s)
|
60
|
+
|
61
|
+
if value.nil?
|
62
|
+
nil
|
63
|
+
else
|
64
|
+
case attr_key.type
|
65
|
+
when :string
|
66
|
+
value.to_s
|
67
|
+
when :integer
|
68
|
+
value.to_i
|
69
|
+
when :float
|
70
|
+
value.to_f
|
71
|
+
when :timestamp
|
72
|
+
Time.at(value.to_f)
|
73
|
+
when :boolean
|
74
|
+
case value
|
75
|
+
when TrueClass
|
76
|
+
true
|
77
|
+
when FalseClass
|
78
|
+
false
|
79
|
+
when String
|
80
|
+
'true'.eql?(value.downcase)
|
81
|
+
else
|
82
|
+
nil
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
memo
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def exists?(key)
|
92
|
+
Zermelo.redis.exists(key_to_redis_key(key))
|
93
|
+
end
|
94
|
+
|
95
|
+
def include?(key, id)
|
96
|
+
case key.type
|
97
|
+
when :set
|
98
|
+
Zermelo.redis.sismember(key_to_redis_key(key), id)
|
99
|
+
else
|
100
|
+
raise "Not implemented"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def begin_transaction
|
105
|
+
return false unless @transaction_redis.nil?
|
106
|
+
@transaction_redis = Zermelo.redis
|
107
|
+
@transaction_redis.multi
|
108
|
+
@changes = []
|
109
|
+
true
|
110
|
+
end
|
111
|
+
|
112
|
+
def commit_transaction
|
113
|
+
return false if @transaction_redis.nil?
|
114
|
+
apply_changes(@changes)
|
115
|
+
@transaction_redis.exec
|
116
|
+
@transaction_redis = nil
|
117
|
+
@changes = []
|
118
|
+
true
|
119
|
+
end
|
120
|
+
|
121
|
+
def abort_transaction
|
122
|
+
return false if @transaction_redis.nil?
|
123
|
+
@transaction_redis.discard
|
124
|
+
@transaction_redis = nil
|
125
|
+
@changes = []
|
126
|
+
true
|
127
|
+
end
|
128
|
+
|
129
|
+
# used by redis_filter
|
130
|
+
def key_to_redis_key(key)
|
131
|
+
obj = case key.object
|
132
|
+
when :attribute
|
133
|
+
'attrs'
|
134
|
+
when :association
|
135
|
+
'assocs'
|
136
|
+
when :index
|
137
|
+
'indices'
|
138
|
+
end
|
139
|
+
|
140
|
+
name = Zermelo::COLLECTION_TYPES.has_key?(key.type) ? ":#{key.name}" : ''
|
141
|
+
|
142
|
+
"#{key.klass}:#{key.id.nil? ? '' : key.id}:#{obj}#{name}"
|
143
|
+
end
|
144
|
+
|
145
|
+
private
|
146
|
+
|
147
|
+
def change(op, key, value = nil, key_to = nil)
|
148
|
+
ch = [op, key, value, key_to]
|
149
|
+
if @in_transaction
|
150
|
+
@changes << ch
|
151
|
+
else
|
152
|
+
apply_changes([ch])
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def apply_changes(changes)
|
157
|
+
simple_attrs = {}
|
158
|
+
|
159
|
+
purges = []
|
160
|
+
|
161
|
+
changes.each do |ch|
|
162
|
+
op = ch[0]
|
163
|
+
key = ch[1]
|
164
|
+
value = ch[2]
|
165
|
+
key_to = ch[3]
|
166
|
+
|
167
|
+
# TODO check that collection types handle nil value for whole thing
|
168
|
+
if Zermelo::COLLECTION_TYPES.has_key?(key.type)
|
169
|
+
|
170
|
+
complex_attr_key = key_to_redis_key(key)
|
171
|
+
|
172
|
+
case op
|
173
|
+
when :add, :set
|
174
|
+
case key.type
|
175
|
+
when :list
|
176
|
+
Zermelo.redis.del(complex_attr_key) if :set.eql?(op)
|
177
|
+
Zermelo.redis.rpush(complex_attr_key, value)
|
178
|
+
when :set
|
179
|
+
Zermelo.redis.del(complex_attr_key) if :set.eql?(op)
|
180
|
+
case value
|
181
|
+
when Set
|
182
|
+
Zermelo.redis.sadd(complex_attr_key, value.to_a) unless value.empty?
|
183
|
+
when Array
|
184
|
+
Zermelo.redis.sadd(complex_attr_key, value) unless value.empty?
|
185
|
+
else
|
186
|
+
Zermelo.redis.sadd(complex_attr_key, value)
|
187
|
+
end
|
188
|
+
when :hash
|
189
|
+
Zermelo.redis.del(complex_attr_key) if :set.eql?(op)
|
190
|
+
unless value.nil?
|
191
|
+
kv = value.inject([]) do |memo, (k, v)|
|
192
|
+
memo += [k, v]
|
193
|
+
memo
|
194
|
+
end
|
195
|
+
Zermelo.redis.hmset(complex_attr_key, *kv)
|
196
|
+
end
|
197
|
+
when :sorted_set
|
198
|
+
Zermelo.redis.zadd(complex_attr_key, *value)
|
199
|
+
end
|
200
|
+
when :move
|
201
|
+
case key.type
|
202
|
+
when :set
|
203
|
+
Zermelo.redis.smove(complex_attr_key, key_to_redis_key(key_to), value)
|
204
|
+
when :list
|
205
|
+
# TODO would do via sort 'nosort', except for
|
206
|
+
# https://github.com/antirez/redis/issues/2079 -- instead,
|
207
|
+
# copy the workaround from redis_filter.rb
|
208
|
+
raise "Not yet implemented"
|
209
|
+
when :hash
|
210
|
+
values = value.to_a.flatten
|
211
|
+
Zermelo.redis.hdel(complex_attr_key, values)
|
212
|
+
Zermelo.redis.hset(key_to_redis_key(key_to), *values)
|
213
|
+
when :sorted_set
|
214
|
+
raise "Not yet implemented"
|
215
|
+
end
|
216
|
+
when :delete
|
217
|
+
case key.type
|
218
|
+
when :list
|
219
|
+
Zermelo.redis.lrem(complex_attr_key, value, 0)
|
220
|
+
when :set
|
221
|
+
Zermelo.redis.srem(complex_attr_key, value)
|
222
|
+
when :hash
|
223
|
+
Zermelo.redis.hdel(complex_attr_key, value)
|
224
|
+
when :sorted_set
|
225
|
+
Zermelo.redis.zrem(complex_attr_key, value)
|
226
|
+
end
|
227
|
+
when :clear
|
228
|
+
Zermelo.redis.del(complex_attr_key)
|
229
|
+
end
|
230
|
+
|
231
|
+
elsif :purge.eql?(op)
|
232
|
+
# TODO get keys for all assocs & indices, purge them too
|
233
|
+
purges << ["#{key.klass}:#{key.id}:attrs"]
|
234
|
+
else
|
235
|
+
simple_attr_key = key_to_redis_key(key)
|
236
|
+
simple_attrs[simple_attr_key] ||= {}
|
237
|
+
|
238
|
+
case op
|
239
|
+
when :set
|
240
|
+
simple_attrs[simple_attr_key][key.name] = if value.nil?
|
241
|
+
nil
|
242
|
+
else
|
243
|
+
case key.type
|
244
|
+
when :string, :integer
|
245
|
+
value.to_s
|
246
|
+
when :float, :timestamp
|
247
|
+
value.to_f
|
248
|
+
when :boolean
|
249
|
+
(!!value).to_s
|
250
|
+
end
|
251
|
+
end
|
252
|
+
when :clear
|
253
|
+
simple_attrs[simple_attr_key][key.name] = nil
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
unless simple_attrs.empty?
|
259
|
+
simple_attrs.each_pair do |simple_attr_key, values|
|
260
|
+
hset = []
|
261
|
+
hdel = []
|
262
|
+
values.each_pair do |k, v|
|
263
|
+
if v.nil?
|
264
|
+
hdel << k
|
265
|
+
else
|
266
|
+
hset += [k, v]
|
267
|
+
end
|
268
|
+
end
|
269
|
+
Zermelo.redis.hmset(simple_attr_key, *hset) if hset.present?
|
270
|
+
Zermelo.redis.hdel(simple_attr_key, hdel) if hdel.present?
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
purges.each {|purge_key | Zermelo.redis.del(purge_key) }
|
275
|
+
end
|
276
|
+
|
277
|
+
end
|
278
|
+
|
279
|
+
end
|
280
|
+
|
281
|
+
end
|
@@ -0,0 +1,235 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
|
3
|
+
require 'zermelo/records/errors'
|
4
|
+
|
5
|
+
require 'zermelo/filters/steps/diff_range_step'
|
6
|
+
require 'zermelo/filters/steps/diff_step'
|
7
|
+
require 'zermelo/filters/steps/intersect_range_step'
|
8
|
+
require 'zermelo/filters/steps/intersect_step'
|
9
|
+
require 'zermelo/filters/steps/limit_step'
|
10
|
+
require 'zermelo/filters/steps/offset_step'
|
11
|
+
require 'zermelo/filters/steps/sort_step'
|
12
|
+
require 'zermelo/filters/steps/union_range_step'
|
13
|
+
require 'zermelo/filters/steps/union_step'
|
14
|
+
|
15
|
+
module Zermelo
|
16
|
+
|
17
|
+
module Filters
|
18
|
+
|
19
|
+
module Base
|
20
|
+
|
21
|
+
extend ActiveSupport::Concern
|
22
|
+
|
23
|
+
attr_reader :backend, :steps
|
24
|
+
|
25
|
+
# initial set a Zermelo::Record::Key object
|
26
|
+
# associated_class the class of the result record
|
27
|
+
def initialize(data_backend, initial_set, associated_class, ancestor = nil, step = nil)
|
28
|
+
@backend = data_backend
|
29
|
+
@initial_set = initial_set
|
30
|
+
@associated_class = associated_class
|
31
|
+
@steps = ancestor.nil? ? [] : ancestor.steps.dup
|
32
|
+
@steps << step unless step.nil?
|
33
|
+
end
|
34
|
+
|
35
|
+
# TODO each step type have class methods list its acceptable input types, and
|
36
|
+
# have a class method giving its output type
|
37
|
+
|
38
|
+
def intersect(attrs = {})
|
39
|
+
self.class.new(@backend, @initial_set, @associated_class, self,
|
40
|
+
::Zermelo::Filters::Steps::IntersectStep.new({}, attrs))
|
41
|
+
end
|
42
|
+
|
43
|
+
def union(attrs = {})
|
44
|
+
self.class.new(@backend, @initial_set, @associated_class, self,
|
45
|
+
::Zermelo::Filters::Steps::UnionStep.new({}, attrs))
|
46
|
+
end
|
47
|
+
|
48
|
+
def diff(attrs = {})
|
49
|
+
self.class.new(@backend, @initial_set, @associated_class, self,
|
50
|
+
::Zermelo::Filters::Steps::DiffStep.new({}, attrs))
|
51
|
+
end
|
52
|
+
|
53
|
+
def sort(keys, opts = {})
|
54
|
+
self.class.new(@backend, @initial_set, @associated_class, self,
|
55
|
+
::Zermelo::Filters::Steps::SortStep.new({:keys => keys,
|
56
|
+
:desc => opts[:desc], :limit => opts[:limit],
|
57
|
+
:offset => opts[:offset]}, {})
|
58
|
+
)
|
59
|
+
end
|
60
|
+
|
61
|
+
def limit(amount)
|
62
|
+
self.class.new(@backend, @initial_set, @associated_class, self,
|
63
|
+
::Zermelo::Filters::Steps::LimitStep.new({:amount => amount}, {}))
|
64
|
+
end
|
65
|
+
|
66
|
+
def offset(amount)
|
67
|
+
self.class.new(@backend, @initial_set, @associated_class, self,
|
68
|
+
::Zermelo::Filters::Steps::OffsetStep.new({:amount => amount}, {}))
|
69
|
+
end
|
70
|
+
|
71
|
+
def intersect_range(start, finish, attrs_opts = {})
|
72
|
+
self.class.new(@backend, @initial_set, @associated_class, self,
|
73
|
+
::Zermelo::Filters::Steps::IntersectRangeStep.new(
|
74
|
+
{:start => start, :finish => finish,
|
75
|
+
:desc => attrs_opts.delete(:desc),
|
76
|
+
:by_score => attrs_opts.delete(:by_score)},
|
77
|
+
attrs_opts)
|
78
|
+
)
|
79
|
+
end
|
80
|
+
|
81
|
+
def union_range(start, finish, attrs_opts = {})
|
82
|
+
self.class.new(@backend, @initial_set, @associated_class, self,
|
83
|
+
::Zermelo::Filters::Steps::UnionRangeStep.new(
|
84
|
+
{:start => start, :finish => finish,
|
85
|
+
:desc => attrs_opts.delete(:desc),
|
86
|
+
:by_score => attrs_opts.delete(:by_score)},
|
87
|
+
attrs_opts)
|
88
|
+
)
|
89
|
+
end
|
90
|
+
|
91
|
+
def diff_range(start, finish, attrs_opts = {})
|
92
|
+
self.class.new(@backend, @initial_set, @associated_class, self,
|
93
|
+
::Zermelo::Filters::Steps::DiffRangeStep.new(
|
94
|
+
{:start => start, :finish => finish,
|
95
|
+
:desc => attrs_opts.delete(:desc),
|
96
|
+
:by_score => attrs_opts.delete(:by_score)},
|
97
|
+
attrs_opts)
|
98
|
+
)
|
99
|
+
end
|
100
|
+
|
101
|
+
# step users
|
102
|
+
def exists?(e_id)
|
103
|
+
lock(false) { _exists?(e_id) }
|
104
|
+
end
|
105
|
+
|
106
|
+
def find_by_id(f_id)
|
107
|
+
lock { _find_by_id(f_id) }
|
108
|
+
end
|
109
|
+
|
110
|
+
def find_by_id!(f_id)
|
111
|
+
ret = lock { _find_by_id(f_id) }
|
112
|
+
raise ::Zermelo::Records::Errors::RecordNotFound.new(@associated_class, f_id) if ret.nil?
|
113
|
+
ret
|
114
|
+
end
|
115
|
+
|
116
|
+
def find_by_ids(*f_ids)
|
117
|
+
lock { f_ids.collect {|f_id| _find_by_id(f_id) } }
|
118
|
+
end
|
119
|
+
|
120
|
+
def find_by_ids!(*f_ids)
|
121
|
+
ret = lock { f_ids.collect {|f_id| _find_by_id(f_id) } }
|
122
|
+
if ret.any? {|r| r.nil? }
|
123
|
+
raise ::Zermelo::Records::Errors::RecordsNotFound.new(@associated_class, f_ids - ret.compact.map(&:id))
|
124
|
+
end
|
125
|
+
ret
|
126
|
+
end
|
127
|
+
|
128
|
+
def ids
|
129
|
+
lock(false) { _ids }
|
130
|
+
end
|
131
|
+
|
132
|
+
def count
|
133
|
+
lock(false) { _count }
|
134
|
+
end
|
135
|
+
|
136
|
+
def empty?
|
137
|
+
lock(false) { _count == 0 }
|
138
|
+
end
|
139
|
+
|
140
|
+
def all
|
141
|
+
lock { _all }
|
142
|
+
end
|
143
|
+
|
144
|
+
# NB makes no sense to apply this without order clause
|
145
|
+
def page(num, opts = {})
|
146
|
+
ret = nil
|
147
|
+
per_page = opts[:per_page].to_i || 20
|
148
|
+
if (num > 0) && (per_page > 0)
|
149
|
+
lock do
|
150
|
+
start = per_page * (num - 1)
|
151
|
+
finish = start + (per_page - 1)
|
152
|
+
@steps += [Zermelo::Filters::Steps::OffsetStep.new({:amount => start}, {}),
|
153
|
+
Zermelo::Filters::Steps::LimitStep.new({:amount => per_page}, {})]
|
154
|
+
page_ids = _ids
|
155
|
+
ret = page_ids.collect {|f_id| _load(f_id)} unless page_ids.nil?
|
156
|
+
end
|
157
|
+
end
|
158
|
+
ret || []
|
159
|
+
end
|
160
|
+
|
161
|
+
def collect(&block)
|
162
|
+
lock { _ids.collect {|id| block.call(_load(id))} }
|
163
|
+
end
|
164
|
+
alias_method :map, :collect
|
165
|
+
|
166
|
+
def each(&block)
|
167
|
+
lock { _ids.each {|id| block.call(_load(id)) } }
|
168
|
+
end
|
169
|
+
|
170
|
+
def select(&block)
|
171
|
+
lock { _all.select {|obj| block.call(obj) } }
|
172
|
+
end
|
173
|
+
alias_method :find_all, :select
|
174
|
+
|
175
|
+
def reject(&block)
|
176
|
+
lock { _all.reject {|obj| block.call(obj)} }
|
177
|
+
end
|
178
|
+
|
179
|
+
def destroy_all
|
180
|
+
lock(*@associated_class.send(:associated_classes)) do
|
181
|
+
_all.each {|r| r.destroy }
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def associated_ids_for(name, options = {})
|
186
|
+
klass = @associated_class.send(:with_association_data, name.to_sym) do |data|
|
187
|
+
data.type_klass
|
188
|
+
end
|
189
|
+
|
190
|
+
lock {
|
191
|
+
case klass.name
|
192
|
+
when ::Zermelo::Associations::BelongsTo.name
|
193
|
+
klass.send(:associated_ids_for, @backend,
|
194
|
+
@associated_class.send(:class_key), name,
|
195
|
+
options[:inversed].is_a?(TrueClass), *_ids)
|
196
|
+
else
|
197
|
+
klass.send(:associated_ids_for, @backend,
|
198
|
+
@associated_class.send(:class_key), name, *_ids)
|
199
|
+
end
|
200
|
+
}
|
201
|
+
end
|
202
|
+
|
203
|
+
protected
|
204
|
+
|
205
|
+
def lock(when_steps_empty = true, *klasses, &block)
|
206
|
+
return(block.call) if !when_steps_empty && @steps.empty?
|
207
|
+
klasses += [@associated_class] if !klasses.include?(@associated_class)
|
208
|
+
@backend.lock(*klasses, &block)
|
209
|
+
end
|
210
|
+
|
211
|
+
private
|
212
|
+
|
213
|
+
def _find_by_id(id)
|
214
|
+
if !id.nil? && _exists?(id)
|
215
|
+
_load(id.to_s)
|
216
|
+
else
|
217
|
+
nil
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
def _load(id)
|
222
|
+
object = @associated_class.new
|
223
|
+
object.load(id)
|
224
|
+
object
|
225
|
+
end
|
226
|
+
|
227
|
+
def _all
|
228
|
+
_ids.map {|id| _load(id) }
|
229
|
+
end
|
230
|
+
|
231
|
+
end
|
232
|
+
|
233
|
+
end
|
234
|
+
|
235
|
+
end
|
@@ -0,0 +1,162 @@
|
|
1
|
+
require 'zermelo/filters/base'
|
2
|
+
|
3
|
+
module Zermelo
|
4
|
+
module Filters
|
5
|
+
class InfluxDBFilter
|
6
|
+
|
7
|
+
include Zermelo::Filters::Base
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def _exists?(id)
|
12
|
+
return if id.nil?
|
13
|
+
@steps << Zermelo::Filters::Steps::IntersectStep.new({}, {:id => id})
|
14
|
+
resolve_steps(:count) > 0
|
15
|
+
end
|
16
|
+
|
17
|
+
def lock(when_steps_empty = true, *klasses, &block)
|
18
|
+
# no-op
|
19
|
+
block.call
|
20
|
+
end
|
21
|
+
|
22
|
+
def _ids
|
23
|
+
resolve_steps(:ids)
|
24
|
+
end
|
25
|
+
|
26
|
+
def _count
|
27
|
+
resolve_steps(:count)
|
28
|
+
end
|
29
|
+
|
30
|
+
def resolve_step(step)
|
31
|
+
query = ''
|
32
|
+
|
33
|
+
options = step.options || {}
|
34
|
+
values = step.attributes
|
35
|
+
|
36
|
+
case step
|
37
|
+
when Zermelo::Filters::Steps::IntersectStep,
|
38
|
+
Zermelo::Filters::Steps::UnionStep
|
39
|
+
|
40
|
+
query += values.collect {|k, v|
|
41
|
+
op, value = case v
|
42
|
+
when String
|
43
|
+
["=~", "/^#{Regexp.escape(v).gsub(/\\\\/, "\\")}$/"]
|
44
|
+
else
|
45
|
+
["=", "'#{v}'"]
|
46
|
+
end
|
47
|
+
|
48
|
+
"#{k} #{op} #{value}"
|
49
|
+
}.join(' AND ')
|
50
|
+
|
51
|
+
when Zermelo::Filters::Steps::DiffStep
|
52
|
+
|
53
|
+
query += values.collect {|k, v|
|
54
|
+
op, value = case v
|
55
|
+
when String
|
56
|
+
["!~", "/^#{Regexp.escape(v).gsub(/\\\\/, "\\")}$/"]
|
57
|
+
else
|
58
|
+
["!=", "'#{v}'"]
|
59
|
+
end
|
60
|
+
|
61
|
+
"#{k} #{op} #{value}"
|
62
|
+
}.join(' AND ')
|
63
|
+
else
|
64
|
+
raise "Unhandled filter operation '#{step_type}'"
|
65
|
+
end
|
66
|
+
|
67
|
+
query
|
68
|
+
end
|
69
|
+
|
70
|
+
def escaped_id(id)
|
71
|
+
if id.is_a?(Numeric)
|
72
|
+
id
|
73
|
+
else
|
74
|
+
"'" + id.gsub(/'/, "\\'").gsub(/\\/, "\\\\'") + "'"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def resolve_steps(result_type)
|
79
|
+
query = case result_type
|
80
|
+
when :ids
|
81
|
+
"SELECT id FROM /#{@associated_class.send(:class_key)}\\/.*/"
|
82
|
+
when :count
|
83
|
+
"SELECT COUNT(id) FROM /#{@associated_class.send(:class_key)}\\/.*/"
|
84
|
+
end
|
85
|
+
|
86
|
+
unless @initial_set.id.nil?
|
87
|
+
query += ' WHERE '
|
88
|
+
|
89
|
+
ii_query = "SELECT #{@initial_set.name} FROM \"#{@initial_set.klass}/#{@initial_set.id}\" " +
|
90
|
+
"LIMIT 1"
|
91
|
+
|
92
|
+
begin
|
93
|
+
initial_id_data =
|
94
|
+
Zermelo.influxdb.query(ii_query)["#{@initial_set.klass}/#{@initial_set.id}"]
|
95
|
+
rescue InfluxDB::Error => ide
|
96
|
+
raise unless
|
97
|
+
/^Field #{@initial_set.name} doesn't exist in series #{@initial_set.klass}\/#{@initial_set.id}$/ === ide.message
|
98
|
+
|
99
|
+
initial_id_data = nil
|
100
|
+
end
|
101
|
+
|
102
|
+
return [] if initial_id_data.nil?
|
103
|
+
|
104
|
+
inital_ids = initial_id_data.first[@initial_set.name]
|
105
|
+
|
106
|
+
if inital_ids.nil? || inital_ids.empty?
|
107
|
+
# make it impossible for the query to return anything
|
108
|
+
query += '(1 = 0)'
|
109
|
+
else
|
110
|
+
query += '((' + inital_ids.collect {|id|
|
111
|
+
"id = #{escaped_id(id)}"
|
112
|
+
}.join(') OR (') + '))'
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
unless @steps.empty?
|
117
|
+
query += (@initial_set.id.nil? ? ' WHERE ' : ' AND ') +
|
118
|
+
('(' * @steps.size)
|
119
|
+
|
120
|
+
@steps.each_with_index do |step, idx|
|
121
|
+
if idx > 0
|
122
|
+
case step
|
123
|
+
when Zermelo::Filters::Steps::IntersectStep,
|
124
|
+
Zermelo::Filters::Steps::DiffStep
|
125
|
+
query += ' AND '
|
126
|
+
when Zermelo::Filters::Steps::UnionStep
|
127
|
+
query += ' OR '
|
128
|
+
else
|
129
|
+
raise "Unhandled filter operation '#{step.class.name}'"
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
query += resolve_step(step)
|
134
|
+
|
135
|
+
query += ")"
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
query += " LIMIT 1"
|
140
|
+
|
141
|
+
begin
|
142
|
+
result = Zermelo.influxdb.query(query)
|
143
|
+
rescue InfluxDB::Error => ide
|
144
|
+
raise unless /^Couldn't look up columns$/ === ide.message
|
145
|
+
result = {}
|
146
|
+
end
|
147
|
+
|
148
|
+
data_keys = result.keys.select {|k| k =~ /^#{@associated_class.send(:class_key)}\// }
|
149
|
+
|
150
|
+
case result_type
|
151
|
+
when :ids
|
152
|
+
data_keys.empty? ? [] : data_keys.collect {|k| k =~ /^#{@associated_class.send(:class_key)}\/(.+)$/; $1 }
|
153
|
+
when :count
|
154
|
+
data_keys.empty? ? 0 : data_keys.inject(0) do |memo, k|
|
155
|
+
memo += result[k].first['count']
|
156
|
+
memo
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|