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