zermelo 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +16 -0
  3. data/.rspec +10 -0
  4. data/.travis.yml +27 -0
  5. data/Gemfile +20 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +512 -0
  8. data/Rakefile +1 -0
  9. data/lib/zermelo/associations/association_data.rb +24 -0
  10. data/lib/zermelo/associations/belongs_to.rb +115 -0
  11. data/lib/zermelo/associations/class_methods.rb +244 -0
  12. data/lib/zermelo/associations/has_and_belongs_to_many.rb +128 -0
  13. data/lib/zermelo/associations/has_many.rb +120 -0
  14. data/lib/zermelo/associations/has_one.rb +109 -0
  15. data/lib/zermelo/associations/has_sorted_set.rb +124 -0
  16. data/lib/zermelo/associations/index.rb +50 -0
  17. data/lib/zermelo/associations/index_data.rb +18 -0
  18. data/lib/zermelo/associations/unique_index.rb +44 -0
  19. data/lib/zermelo/backends/base.rb +115 -0
  20. data/lib/zermelo/backends/influxdb_backend.rb +178 -0
  21. data/lib/zermelo/backends/redis_backend.rb +281 -0
  22. data/lib/zermelo/filters/base.rb +235 -0
  23. data/lib/zermelo/filters/influxdb_filter.rb +162 -0
  24. data/lib/zermelo/filters/redis_filter.rb +558 -0
  25. data/lib/zermelo/filters/steps/base_step.rb +22 -0
  26. data/lib/zermelo/filters/steps/diff_range_step.rb +17 -0
  27. data/lib/zermelo/filters/steps/diff_step.rb +17 -0
  28. data/lib/zermelo/filters/steps/intersect_range_step.rb +17 -0
  29. data/lib/zermelo/filters/steps/intersect_step.rb +17 -0
  30. data/lib/zermelo/filters/steps/limit_step.rb +17 -0
  31. data/lib/zermelo/filters/steps/offset_step.rb +17 -0
  32. data/lib/zermelo/filters/steps/sort_step.rb +17 -0
  33. data/lib/zermelo/filters/steps/union_range_step.rb +17 -0
  34. data/lib/zermelo/filters/steps/union_step.rb +17 -0
  35. data/lib/zermelo/locks/no_lock.rb +16 -0
  36. data/lib/zermelo/locks/redis_lock.rb +221 -0
  37. data/lib/zermelo/records/base.rb +62 -0
  38. data/lib/zermelo/records/class_methods.rb +127 -0
  39. data/lib/zermelo/records/collection.rb +14 -0
  40. data/lib/zermelo/records/errors.rb +24 -0
  41. data/lib/zermelo/records/influxdb_record.rb +35 -0
  42. data/lib/zermelo/records/instance_methods.rb +224 -0
  43. data/lib/zermelo/records/key.rb +19 -0
  44. data/lib/zermelo/records/redis_record.rb +27 -0
  45. data/lib/zermelo/records/type_validator.rb +20 -0
  46. data/lib/zermelo/version.rb +3 -0
  47. data/lib/zermelo.rb +102 -0
  48. data/spec/lib/zermelo/associations/belongs_to_spec.rb +6 -0
  49. data/spec/lib/zermelo/associations/has_many_spec.rb +6 -0
  50. data/spec/lib/zermelo/associations/has_one_spec.rb +6 -0
  51. data/spec/lib/zermelo/associations/has_sorted_set.spec.rb +6 -0
  52. data/spec/lib/zermelo/associations/index_spec.rb +6 -0
  53. data/spec/lib/zermelo/associations/unique_index_spec.rb +6 -0
  54. data/spec/lib/zermelo/backends/influxdb_backend_spec.rb +0 -0
  55. data/spec/lib/zermelo/backends/moneta_backend_spec.rb +0 -0
  56. data/spec/lib/zermelo/filters/influxdb_filter_spec.rb +0 -0
  57. data/spec/lib/zermelo/filters/redis_filter_spec.rb +0 -0
  58. data/spec/lib/zermelo/locks/redis_lock_spec.rb +170 -0
  59. data/spec/lib/zermelo/records/influxdb_record_spec.rb +258 -0
  60. data/spec/lib/zermelo/records/key_spec.rb +6 -0
  61. data/spec/lib/zermelo/records/redis_record_spec.rb +1426 -0
  62. data/spec/lib/zermelo/records/type_validator_spec.rb +6 -0
  63. data/spec/lib/zermelo/version_spec.rb +6 -0
  64. data/spec/lib/zermelo_spec.rb +6 -0
  65. data/spec/spec_helper.rb +67 -0
  66. data/spec/support/profile_all_formatter.rb +44 -0
  67. data/spec/support/uncolored_doc_formatter.rb +74 -0
  68. data/zermelo.gemspec +30 -0
  69. 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