zermelo 1.0.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.
- 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
|