zermelo 1.1.0 → 1.2.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 (66) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +10 -0
  3. data/README.md +76 -52
  4. data/lib/zermelo/associations/association_data.rb +4 -3
  5. data/lib/zermelo/associations/class_methods.rb +37 -50
  6. data/lib/zermelo/associations/index.rb +3 -1
  7. data/lib/zermelo/associations/multiple.rb +247 -0
  8. data/lib/zermelo/associations/range_index.rb +44 -0
  9. data/lib/zermelo/associations/singular.rb +193 -0
  10. data/lib/zermelo/associations/unique_index.rb +4 -3
  11. data/lib/zermelo/backend.rb +120 -0
  12. data/lib/zermelo/backends/{influxdb_backend.rb → influxdb.rb} +87 -31
  13. data/lib/zermelo/backends/{redis_backend.rb → redis.rb} +53 -58
  14. data/lib/zermelo/backends/stub.rb +43 -0
  15. data/lib/zermelo/filter.rb +194 -0
  16. data/lib/zermelo/filters/index_range.rb +22 -0
  17. data/lib/zermelo/filters/{influxdb_filter.rb → influxdb.rb} +12 -11
  18. data/lib/zermelo/filters/redis.rb +173 -0
  19. data/lib/zermelo/filters/steps/list_step.rb +48 -30
  20. data/lib/zermelo/filters/steps/set_step.rb +148 -89
  21. data/lib/zermelo/filters/steps/sort_step.rb +2 -2
  22. data/lib/zermelo/record.rb +53 -0
  23. data/lib/zermelo/records/attributes.rb +32 -0
  24. data/lib/zermelo/records/class_methods.rb +12 -25
  25. data/lib/zermelo/records/{influxdb_record.rb → influxdb.rb} +3 -4
  26. data/lib/zermelo/records/instance_methods.rb +9 -8
  27. data/lib/zermelo/records/key.rb +3 -1
  28. data/lib/zermelo/records/redis.rb +17 -0
  29. data/lib/zermelo/records/stub.rb +17 -0
  30. data/lib/zermelo/version.rb +1 -1
  31. data/spec/lib/zermelo/associations/index_spec.rb +70 -1
  32. data/spec/lib/zermelo/associations/multiple_spec.rb +1084 -0
  33. data/spec/lib/zermelo/associations/range_index_spec.rb +77 -0
  34. data/spec/lib/zermelo/associations/singular_spec.rb +149 -0
  35. data/spec/lib/zermelo/associations/unique_index_spec.rb +58 -2
  36. data/spec/lib/zermelo/filter_spec.rb +363 -0
  37. data/spec/lib/zermelo/locks/redis_lock_spec.rb +3 -3
  38. data/spec/lib/zermelo/records/instance_methods_spec.rb +206 -0
  39. data/spec/spec_helper.rb +9 -1
  40. data/spec/support/mock_logger.rb +48 -0
  41. metadata +31 -46
  42. data/lib/zermelo/associations/belongs_to.rb +0 -115
  43. data/lib/zermelo/associations/has_and_belongs_to_many.rb +0 -128
  44. data/lib/zermelo/associations/has_many.rb +0 -120
  45. data/lib/zermelo/associations/has_one.rb +0 -109
  46. data/lib/zermelo/associations/has_sorted_set.rb +0 -124
  47. data/lib/zermelo/backends/base.rb +0 -115
  48. data/lib/zermelo/filters/base.rb +0 -212
  49. data/lib/zermelo/filters/redis_filter.rb +0 -111
  50. data/lib/zermelo/filters/steps/sorted_set_step.rb +0 -156
  51. data/lib/zermelo/records/base.rb +0 -62
  52. data/lib/zermelo/records/redis_record.rb +0 -27
  53. data/spec/lib/zermelo/associations/belongs_to_spec.rb +0 -6
  54. data/spec/lib/zermelo/associations/has_many_spec.rb +0 -6
  55. data/spec/lib/zermelo/associations/has_one_spec.rb +0 -6
  56. data/spec/lib/zermelo/associations/has_sorted_set.spec.rb +0 -6
  57. data/spec/lib/zermelo/backends/influxdb_backend_spec.rb +0 -0
  58. data/spec/lib/zermelo/backends/moneta_backend_spec.rb +0 -0
  59. data/spec/lib/zermelo/filters/influxdb_filter_spec.rb +0 -0
  60. data/spec/lib/zermelo/filters/redis_filter_spec.rb +0 -0
  61. data/spec/lib/zermelo/records/influxdb_record_spec.rb +0 -434
  62. data/spec/lib/zermelo/records/key_spec.rb +0 -6
  63. data/spec/lib/zermelo/records/redis_record_spec.rb +0 -1461
  64. data/spec/lib/zermelo/records/type_validator_spec.rb +0 -6
  65. data/spec/lib/zermelo/version_spec.rb +0 -6
  66. data/spec/lib/zermelo_spec.rb +0 -6
@@ -1,10 +1,10 @@
1
- require 'zermelo/filters/base'
1
+ require 'zermelo/filter'
2
2
 
3
3
  module Zermelo
4
4
  module Filters
5
- class InfluxDBFilter
5
+ class InfluxDB
6
6
 
7
- include Zermelo::Filters::Base
7
+ include Zermelo::Filter
8
8
 
9
9
  private
10
10
 
@@ -35,6 +35,7 @@ module Zermelo
35
35
  end
36
36
  end
37
37
 
38
+ # TODO support the new before/after_read callbacks
38
39
  def resolve_steps(result_type)
39
40
  class_key = @associated_class.send(:class_key)
40
41
 
@@ -56,7 +57,7 @@ module Zermelo
56
57
  begin
57
58
  initial_id_data =
58
59
  Zermelo.influxdb.query(ii_query)["#{initial_class_key}/#{@initial_key.id}"]
59
- rescue InfluxDB::Error => ide
60
+ rescue ::InfluxDB::Error => ide
60
61
  raise unless
61
62
  /^Field #{@initial_key.name} doesn't exist in series #{initial_class_key}\/#{@initial_key.id}$/ === ide.message
62
63
 
@@ -83,16 +84,19 @@ module Zermelo
83
84
 
84
85
  first_step = steps.first
85
86
 
87
+ attr_types = @associated_class.send(:attribute_types)
88
+
86
89
  query += @steps.collect {|step|
87
- step.resolve(backend, @associated_class, :first => (step == first_step))
90
+ step.resolve(backend, @associated_class, :first => (step == first_step),
91
+ :attr_types => attr_types)
88
92
  }.join("")
89
93
  end
90
94
 
91
- query += " LIMIT 1"
95
+ query += " ORDER ASC LIMIT 1"
92
96
 
93
97
  begin
94
98
  result = Zermelo.influxdb.query(query)
95
- rescue InfluxDB::Error => ide
99
+ rescue ::InfluxDB::Error => ide
96
100
  raise unless /^Couldn't look up columns$/ === ide.message
97
101
  result = {}
98
102
  end
@@ -103,10 +107,7 @@ module Zermelo
103
107
  when :ids
104
108
  data_keys.empty? ? [] : data_keys.collect {|k| k =~ /^#{class_key}\/(.+)$/; $1 }
105
109
  when :count
106
- data_keys.empty? ? 0 : data_keys.inject(0) do |memo, k|
107
- memo += result[k].first['count']
108
- memo
109
- end
110
+ data_keys.empty? ? 0 : data_keys.size
110
111
  end
111
112
  end
112
113
  end
@@ -0,0 +1,173 @@
1
+ require 'zermelo/filter'
2
+
3
+ require 'zermelo/filters/index_range'
4
+
5
+ # TODO check escaping of ids and index_keys -- shouldn't allow bare :
6
+
7
+ module Zermelo
8
+
9
+ module Filters
10
+
11
+ class Redis
12
+
13
+ include Zermelo::Filter
14
+
15
+ SHORTCUTS = {
16
+ :list => {
17
+ :ids => proc {|key| Zermelo.redis.lrange(key, 0, -1) },
18
+ :count => proc {|key| Zermelo.redis.llen(key) },
19
+ :exists? => proc {|key, id| Zermelo.redis.lrange(key, 0, -1).include?(id) },
20
+ :first => proc {|key| Zermelo.redis.lrange(key, 0, 0).first },
21
+ :last => proc {|key| Zermelo.redis.lrevrange(key, 0, 0).first }
22
+ },
23
+ :set => {
24
+ :ids => proc {|key| Zermelo.redis.smembers(key) },
25
+ :count => proc {|key| Zermelo.redis.scard(key) },
26
+ :exists? => proc {|key, id| Zermelo.redis.sismember(key, id) }
27
+ },
28
+ :sorted_set => {
29
+ :ids => proc {|key, order|
30
+ Zermelo.redis.send((:desc.eql?(order) ? :zrevrange : :zrange), key, 0, -1)
31
+ },
32
+ :count => proc {|key, order| Zermelo.redis.zcard(key) },
33
+ :exists? => proc {|key, order, id| !Zermelo.redis.zscore(key, id).nil? },
34
+ :first => proc {|key, order|
35
+ Zermelo.redis.send((:desc.eql?(order) ? :zrevrange : :zrange), key, 0, 0).first
36
+ },
37
+ :last => proc {|key, order|
38
+ Zermelo.redis.send((:desc.eql?(order) ? :zrange : :zrevrange), key, 0, 0).first
39
+ }
40
+ }
41
+ }
42
+
43
+ # TODO polite error when first/last applied to set
44
+
45
+ # more step users
46
+ def first
47
+ lock {
48
+ first_id = resolve_steps(:first)
49
+ first_id.nil? ? nil : _load(first_id)
50
+ }
51
+ end
52
+
53
+ def last
54
+ lock {
55
+ last_id = resolve_steps(:last)
56
+ last_id.nil? ? nil : _load(last_id)
57
+ }
58
+ end
59
+ # end step users
60
+
61
+ private
62
+
63
+ def _count
64
+ resolve_steps(:count)
65
+ end
66
+
67
+ def _ids
68
+ resolve_steps(:ids)
69
+ end
70
+
71
+ def _exists?(id)
72
+ return if id.nil?
73
+ resolve_steps(:exists?, id)
74
+ end
75
+
76
+ # If called with a block -- takes a block and passes the name of a set to
77
+ # it; deletes all temporary sets once done
78
+
79
+ # If called with any arguments -- treats them as a hash of shortcuts
80
+
81
+ # If not called with any arguments -- returns two values, the first is
82
+ # the name of a set containing the filtered ids, the second is a boolean
83
+ # for whether or not to clear up that set once it's been used
84
+
85
+ def resolve_steps(shortcut, *args)
86
+ if @steps.empty?
87
+ unless @callback_target_class.nil? || @callbacks.nil?
88
+ br = @callbacks[:before_read]
89
+ if !br.nil? && @callback_target_class.respond_to?(br)
90
+ @callback_target_class.send(br, @callback_target_id)
91
+ end
92
+ end
93
+
94
+ sc = Zermelo::Filters::Redis::SHORTCUTS[@initial_key.type][shortcut]
95
+ ret = if sc.nil?
96
+ yield(@initial_key)
97
+ else
98
+ r_key = backend.key_to_redis_key(@initial_key)
99
+ shortcut_params = if @initial_key.type == :sorted_set
100
+ [r_key, @sort_order] + args
101
+ else
102
+ [r_key] + args
103
+ end
104
+ sc.call(*shortcut_params)
105
+ end
106
+
107
+ unless @callback_target_class.nil? || @callbacks.nil?
108
+ ar = @callbacks[:after_read]
109
+ if !ar.nil? && @callback_target_class.respond_to?(ar)
110
+ @callback_target_class.send(ar, @callback_target_id)
111
+ end
112
+ end
113
+
114
+ return(ret)
115
+ end
116
+
117
+ idx_attrs = @associated_class.send(:with_index_data) do |d|
118
+ d.each_with_object({}) do |(name, data), memo|
119
+ memo[name.to_s] = data.index_klass
120
+ end
121
+ end
122
+
123
+ attr_types = @associated_class.send(:attribute_types)
124
+
125
+ backend.temp_key_wrap do |temp_keys|
126
+ result = nil
127
+ last_step = @steps.last
128
+
129
+ step_opts = {
130
+ :index_attrs => idx_attrs,
131
+ :attr_types => attr_types,
132
+ :temp_keys => temp_keys,
133
+ :source => @initial_key,
134
+ :initial_key => @initial_key,
135
+ :sort_order => @sort_order
136
+ }
137
+
138
+ @steps.each do |step|
139
+ unless step.class.accepted_types.include?(step_opts[:source].type)
140
+ raise "'#{step.class.name}' does not accept input type #{step_opts[:source].type}"
141
+ end
142
+
143
+ if step == last_step
144
+ step_opts.update(:shortcut => shortcut, :shortcut_args => args)
145
+ end
146
+
147
+ unless @callback_target_class.nil? || @callbacks.nil?
148
+ br = @callbacks[:before_read]
149
+ if !br.nil? && @callback_target_class.respond_to?(br)
150
+ @callback_target_class.send(br, @callback_target_id)
151
+ end
152
+ end
153
+
154
+ result = step.resolve(backend, @associated_class, step_opts)
155
+
156
+ unless @callback_target_class.nil? || @callbacks.nil?
157
+ ar = @callbacks[:after_read]
158
+ if !ar.nil? && @callback_target_class.respond_to?(ar)
159
+ @callback_target_class.send(ar, @callback_target_id)
160
+ end
161
+ end
162
+
163
+ step_opts[:source] = result unless step == last_step
164
+ end
165
+
166
+ result
167
+ end
168
+ end
169
+ end
170
+
171
+ end
172
+
173
+ end
@@ -5,21 +5,13 @@ module Zermelo
5
5
  class Steps
6
6
  class ListStep < Zermelo::Filters::Steps::BaseStep
7
7
  def self.accepted_types
8
- [:list]
8
+ [:sorted_set, :list]
9
9
  end
10
10
 
11
11
  def self.returns_type
12
- :list
12
+ nil # same as the source type
13
13
  end
14
14
 
15
- REDIS_SHORTCUTS = {
16
- :ids => proc {|key| Zermelo.redis.lrange(key, 0, -1) },
17
- :count => proc {|key| Zermelo.redis.llen(key) },
18
- :exists? => proc {|key, id| Zermelo.redis.lrange(key, 0, -1).include?(id) },
19
- :first => proc {|key| Zermelo.redis.lrange(key, 0, 0).first },
20
- :last => proc {|key| Zermelo.redis.lrevrange(key, 0, 0).first }
21
- }
22
-
23
15
  def resolve(backend, associated_class, opts = {})
24
16
  shortcut = opts[:shortcut]
25
17
 
@@ -30,46 +22,72 @@ module Zermelo
30
22
  l = limit.to_i
31
23
 
32
24
  case backend
33
- when Zermelo::Backends::RedisBackend
25
+ when Zermelo::Backends::Redis
34
26
 
35
27
  source = opts[:source]
36
28
  idx_attrs = opts[:index_attrs]
37
29
  attr_types = opts[:attr_types]
38
30
  temp_keys = opts[:temp_keys]
39
31
 
40
- # TODO apply these transformations via a subset?
41
- # TODO need a guaranteed non-existing key for non-sorting 'sort'
32
+ # TODO apply these transformations via a subset?
33
+ # TODO need a guaranteed non-existing key for non-sorting 'sort'
42
34
 
43
35
  # TODO check if source is in temp_keys, use a generated temp_key instead if not
44
36
  r_source = backend.key_to_redis_key(source)
45
37
 
46
- l = (Zermelo.redis.llen(r_source) - o) if (l < 1)
38
+ result, r_result = case source.type
39
+ when :sorted_set
40
+ limited = associated_class.send(:temp_key, :sorted_set)
41
+ temp_keys << limited
42
+ r_limited = backend.key_to_redis_key(limited)
43
+
44
+ lim = Zermelo.redis.send(
45
+ :desc.eql?(opts[:sort_order]) ? :zrevrange : :zrange,
46
+ r_source, o, (o + l), :with_scores => true
47
+ )
48
+
49
+ Zermelo.redis.zadd(r_limited, lim.collect {|l| [l[1], l[0]]} )
50
+
51
+ [limited, r_limited]
52
+ when :list
47
53
 
48
- sort_opts = {:by => 'no_sort', :limit => [o, l]}
54
+ l = (Zermelo.redis.llen(r_source) - o) if (l < 1)
49
55
 
50
- # https://github.com/antirez/redis/issues/2079, fixed in redis 2.8.19
51
- result, r_result = if (Zermelo.redis_version.split('.') <=> ['2', '8', '18']) == 1
52
- sort_opts.update(:store => r_source)
53
- Zermelo.redis.sort(r_source, sort_opts)
54
- [source, r_source]
55
- else
56
- data = Zermelo.redis.sort(r_source, sort_opts)
56
+ sort_opts = {:by => 'no_sort', :limit => [o, l]}
57
57
 
58
- if data.empty?
59
- # TODO fix
58
+ # https://github.com/antirez/redis/issues/2079, fixed in redis 2.8.19
59
+ result, r_result = if (Zermelo.redis_version.split('.') <=> ['2', '8', '18']) == 1
60
+ sort_opts.update(:store => r_source)
61
+ Zermelo.redis.sort(r_source, sort_opts)
62
+ [source, r_source]
60
63
  else
61
- limited = associated_class.send(:temp_key, :list)
62
- temp_keys << limited
63
- r_limited = backend.key_to_redis_key(limited)
64
+ data = Zermelo.redis.sort(r_source, sort_opts)
64
65
 
65
- Zermelo.redis.rpush(r_limited, data)
66
+ if data.empty?
67
+ # TODO fix
68
+ else
69
+ limited = associated_class.send(:temp_key, :list)
70
+ temp_keys << limited
71
+ r_limited = backend.key_to_redis_key(limited)
66
72
 
67
- [limited, r_limited]
73
+ Zermelo.redis.rpush(r_limited, data)
74
+
75
+ [limited, r_limited]
76
+ end
68
77
  end
69
78
  end
70
79
 
71
80
  return result if shortcut.nil?
72
- REDIS_SHORTCUTS[shortcut].call(*([r_result] + opts[:shortcut_args]))
81
+
82
+ extra_args = case source.type
83
+ when :sorted_set
84
+ [opts[:sort_order]]
85
+ when :list
86
+ []
87
+ end
88
+
89
+ Zermelo::Filters::Redis::SHORTCUTS[source.type][shortcut].
90
+ call(*([r_result] + extra_args + opts[:shortcut_args]))
73
91
  end
74
92
  end
75
93
  end
@@ -6,29 +6,25 @@ module Zermelo
6
6
  class Steps
7
7
  class SetStep < Zermelo::Filters::Steps::BaseStep
8
8
  def self.accepted_types
9
- [:set, :sorted_set] # TODO should allow :list as well
9
+ [:set, :sorted_set] # TODO should allow :list as well?
10
10
  end
11
11
 
12
12
  def self.returns_type
13
- :set
13
+ nil # same as the source type
14
14
  end
15
15
 
16
- REDIS_SHORTCUTS = {
17
- :ids => proc {|key| Zermelo.redis.smembers(key) },
18
- :count => proc {|key| Zermelo.redis.scard(key) },
19
- :exists? => proc {|key, id| Zermelo.redis.sismember(key, id) }
20
- }
21
-
22
16
  def resolve(backend, associated_class, opts = {})
23
17
 
24
18
  case backend
25
- when Zermelo::Backends::RedisBackend
26
- source = opts[:source]
27
- idx_attrs = opts[:index_attrs]
28
- attr_types = opts[:attr_types]
29
- temp_keys = opts[:temp_keys]
19
+ when Zermelo::Backends::Redis
20
+ initial_key = opts[:initial_key]
21
+ source = opts[:source]
22
+ idx_attrs = opts[:index_attrs]
23
+ attr_types = opts[:attr_types]
24
+ temp_keys = opts[:temp_keys]
25
+ order = opts[:sort_order]
30
26
 
31
- source_keys = @attributes.inject([]) do |memo, (att, value)|
27
+ source_keys = @attributes.each_with_object([]) do |(att, value), memo|
32
28
 
33
29
  val = value.is_a?(Set) ? value.to_a : value
34
30
 
@@ -42,7 +38,7 @@ module Zermelo
42
38
  raise "'#{att}' property is not indexed" if idx_class.nil?
43
39
 
44
40
  if val.is_a?(Enumerable)
45
- conditions_set = associated_class.send(:temp_key, :set)
41
+ conditions_set = associated_class.send(:temp_key, source.type)
46
42
  r_conditions_set = backend.key_to_redis_key(conditions_set)
47
43
 
48
44
  backend.temp_key_wrap do |conditions_temp_keys|
@@ -52,7 +48,12 @@ module Zermelo
52
48
  backend.key_to_redis_key(il)
53
49
  }
54
50
 
55
- Zermelo.redis.sunionstore(r_conditions_set, *index_keys)
51
+ case source.type
52
+ when :set
53
+ Zermelo.redis.sunionstore(r_conditions_set, *index_keys)
54
+ when :sorted_set
55
+ Zermelo.redis.zunionstore(r_conditions_set, index_keys)
56
+ end
56
57
  end
57
58
  memo << conditions_set
58
59
  else
@@ -60,22 +61,90 @@ module Zermelo
60
61
  idx_class, val, attr_types[att], temp_keys)
61
62
  end
62
63
  end
63
-
64
- memo
65
64
  end
66
65
 
67
- case source.type
68
- when :sorted_set
69
- Zermelo::Filters::Steps::SortedSetStep.evaluate(backend,
70
- @options[:op], associated_class, source, source_keys, temp_keys, opts)
71
- when :set
72
- self.class.evaluate(backend, @options[:op], associated_class,
73
- source, source_keys, temp_keys, opts)
66
+ r_source_key = backend.key_to_redis_key(source)
67
+ r_source_keys = source_keys.collect {|sk| backend.key_to_redis_key(sk) }
68
+
69
+ op = @options[:op]
70
+ shortcut = opts[:shortcut]
71
+
72
+ if :ids.eql?(shortcut) && (source.type == :set)
73
+ case op
74
+ when :union
75
+ backend.temp_key_wrap do |shortcut_temp_keys|
76
+ dest_set = associated_class.send(:temp_key, :set)
77
+ shortcut_temp_keys << dest_set
78
+ r_dest_set = backend.key_to_redis_key(dest_set)
79
+
80
+ Zermelo.redis.sinterstore(r_dest_set, *r_source_keys)
81
+ Zermelo.redis.sunion(r_source_key, r_dest_set)
82
+ end
83
+ when :intersect
84
+ Zermelo.redis.sinter(r_source_key, *r_source_keys)
85
+ when :diff
86
+ backend.temp_key_wrap do |shortcut_temp_keys|
87
+ dest_set = associated_class.send(:temp_key, :set)
88
+ shortcut_temp_keys << dest_set
89
+ r_dest_set = backend.key_to_redis_key(dest_set)
90
+
91
+ Zermelo.redis.sinterstore(r_dest_set, *r_source_keys)
92
+ Zermelo.redis.sdiff(r_source_key, r_dest_set)
93
+ end
94
+ end
95
+ else
96
+ dest_set = associated_class.send(:temp_key, source.type)
97
+ r_dest_set = backend.key_to_redis_key(dest_set)
98
+ temp_keys << dest_set
99
+
100
+ case op
101
+ when :union
102
+ r_initial_key = backend.key_to_redis_key(initial_key)
103
+
104
+ if source.type == :sorted_set
105
+ Zermelo.redis.zinterstore(r_dest_set,
106
+ [r_initial_key] + r_source_keys,
107
+ :weights => [1.0] + ([0.0] * source_keys.length), :aggregate => 'max')
108
+
109
+ Zermelo.redis.zunionstore(r_dest_set, [r_source_key, r_dest_set], :aggregate => 'max')
110
+ else
111
+ Zermelo.redis.sinterstore(r_dest_set, r_initial_key, *r_source_keys)
112
+ Zermelo.redis.sunionstore(r_dest_set, r_dest_set, r_source_key)
113
+ end
114
+ when :intersect
115
+ if source.type == :sorted_set
116
+ Zermelo.redis.zinterstore(r_dest_set, [r_source_key] + r_source_keys, :aggregate => 'max')
117
+ else
118
+ Zermelo.redis.sinterstore(r_dest_set, r_source_key, *r_source_keys)
119
+ end
120
+ when :diff
121
+ if source.type == :sorted_set
122
+ Zermelo.redis.zinterstore(r_dest_set, r_source_keys, :aggregate => 'max')
123
+ Zermelo.redis.zunionstore(r_dest_set, [r_source_key, r_dest_set], :weights => [1.0, 0.0], :aggregate => 'min')
124
+ Zermelo.redis.zremrangebyscore(r_dest_set, "0", "0")
125
+ else
126
+ Zermelo.redis.sinterstore(r_dest_set, *r_source_keys)
127
+ Zermelo.redis.sdiffstore(r_dest_set, r_dest_set, r_source_key)
128
+ end
129
+ end
130
+
131
+ return dest_set if shortcut.nil?
132
+
133
+ shortcut_params = if source.type == :sorted_set
134
+ [r_dest_set, order] + opts[:shortcut_args]
135
+ else
136
+ [r_dest_set] + opts[:shortcut_args]
137
+ end
138
+
139
+ Zermelo::Filters::Redis::SHORTCUTS[source.type][shortcut].
140
+ call(*shortcut_params)
74
141
  end
75
142
 
76
- when Zermelo::Backends::InfluxDBBackend
143
+ when Zermelo::Backends::InfluxDB
77
144
  query = ''
78
145
 
146
+ attr_types = opts[:attr_types]
147
+
79
148
  unless opts[:first].is_a?(TrueClass)
80
149
  case @options[:op]
81
150
  when :intersect, :diff
@@ -88,27 +157,68 @@ module Zermelo
88
157
  case @options[:op]
89
158
  when :intersect, :union
90
159
  query += @attributes.collect {|k, v|
91
- op, value = case v
92
- when String
93
- ["=~", "/^#{Regexp.escape(v).gsub(/\\\\/, "\\")}$/"]
160
+
161
+ attr_type = attr_types[k]
162
+
163
+ if v.is_a?(Enumerable)
164
+ qq = v.each_with_object([]) do |vv, memo|
165
+ ov = case vv
166
+ when Regexp
167
+ raise "Can't query non-string values via regexp" unless :string.eql?(attr_type)
168
+ "=~ /#{vv.source.gsub(/\\\\/, "\\")}/"
169
+ when String
170
+ "=~ /^#{Regexp.escape(vv).gsub(/\\\\/, "\\")}$/"
171
+ else
172
+ "= '#{vv}'"
173
+ end
174
+ memo << "#{k} #{ov}"
175
+ end
176
+ "((#{qq.join(') OR (')}))"
94
177
  else
95
- ["=", "'#{v}'"]
178
+ op_value = case v
179
+ when Regexp
180
+ raise "Can't query non-string values via regexp" unless :string.eql?(attr_type)
181
+ "=~ /#{v.source.gsub(/\\\\/, "\\")}/"
182
+ when String
183
+ "=~ /^#{Regexp.escape(v).gsub(/\\\\/, "\\")}$/"
184
+ else
185
+ "= '#{v}'"
186
+ end
187
+ "(#{k} #{op_value})"
96
188
  end
97
-
98
- "#{k} #{op} #{value}"
99
189
  }.join(' AND ')
100
190
 
101
191
  when :diff
102
192
  query += @attributes.collect {|k, v|
103
- op, value = case v
104
- when String
105
- ["!~", "/^#{Regexp.escape(v).gsub(/\\\\/, "\\")}$/"]
193
+ if v.is_a?(Enumerable)
194
+ qq = v.each_with_object([]) do |vv, memo|
195
+ ov = case vv
196
+ when Regexp
197
+ raise "Can't query non-string values via regexp" unless :string.eql?(attr_type)
198
+ "!~ /#{vv.source.gsub(/\\\\/, "\\")}/"
199
+ when String
200
+ "!~ /^#{Regexp.escape(vv).gsub(/\\\\/, "\\")}$/"
201
+ else
202
+ "<> '#{vv}'"
203
+ end
204
+ memo << "#{k} #{ov}"
205
+ end
206
+ "((#{qq.join(') OR (')}))"
106
207
  else
107
- ["!=", "'#{v}'"]
108
- end
208
+ op_value = case v
209
+ when Regexp
210
+ raise "Can't query non-string values via regexp" unless :string.eql?(attr_type)
211
+ "!~ /#{v.source.gsub(/\\\\/, "\\")}/"
212
+ when String
213
+ "!~ /^#{Regexp.escape(v).gsub(/\\\\/, "\\")}$/"
214
+ else
215
+ "<> '#{v}'"
216
+ end
109
217
 
110
- "#{k} #{op} #{value}"
218
+ "(#{k} #{op_value})"
219
+ end
111
220
  }.join(' AND ')
221
+
112
222
  else
113
223
  raise "Unhandled filter operation '#{@options[:op]}'"
114
224
  end
@@ -119,57 +229,6 @@ module Zermelo
119
229
  end
120
230
  end
121
231
 
122
- def self.evaluate(backend, op, associated_class, source, source_keys, temp_keys, opts = {})
123
- shortcut = opts[:shortcut]
124
-
125
- r_source_key = backend.key_to_redis_key(source)
126
- r_source_keys = source_keys.collect {|sk| backend.key_to_redis_key(sk) }
127
-
128
- if :ids.eql?(shortcut)
129
- case op
130
- when :union
131
- backend.temp_key_wrap do |shortcut_temp_keys|
132
- dest_set = associated_class.send(:temp_key, :set)
133
- shortcut_temp_keys << dest_set
134
- r_dest_set = backend.key_to_redis_key(dest_set)
135
-
136
- Zermelo.redis.sinterstore(r_dest_set, *r_source_keys)
137
- Zermelo.redis.sunion(r_dest_set, r_source_key)
138
- end
139
- when :intersect
140
- Zermelo.redis.sinter(r_source_key, *r_source_keys)
141
- when :diff
142
- backend.temp_key_wrap do |shortcut_temp_keys|
143
- dest_set = associated_class.send(:temp_key, :set)
144
- shortcut_temp_keys << dest_set
145
- r_dest_set = backend.key_to_redis_key(dest_set)
146
-
147
- Zermelo.redis.sinterstore(r_dest_set, *r_source_keys)
148
- Zermelo.redis.sdiff(r_source_key, r_dest_set)
149
- end
150
- end
151
- else
152
- dest_set = associated_class.send(:temp_key, :set)
153
- r_dest_set = backend.key_to_redis_key(dest_set)
154
- temp_keys << dest_set
155
-
156
- case op
157
- when :union
158
- Zermelo.redis.sinterstore(r_dest_set, *r_source_keys)
159
- Zermelo.redis.sunionstore(r_dest_set, r_source_key, r_dest_set)
160
- when :intersect
161
- Zermelo.redis.sinterstore(r_dest_set, r_source_key, *r_source_keys)
162
- when :diff
163
- Zermelo.redis.sinterstore(r_dest_set, *r_source_keys)
164
- Zermelo.redis.sdiffstore(r_dest_set, r_source_key, r_dest_set)
165
- end
166
-
167
- return dest_set if shortcut.nil?
168
- REDIS_SHORTCUTS[shortcut].call(*([r_dest_set] + opts[:shortcut_args]))
169
- end
170
-
171
- end
172
-
173
232
  end
174
233
  end
175
234
  end
@@ -14,7 +14,7 @@ module Zermelo
14
14
 
15
15
  def resolve(backend, associated_class, opts = {})
16
16
  case backend
17
- when Zermelo::Backends::RedisBackend
17
+ when Zermelo::Backends::Redis
18
18
  source = opts[:source]
19
19
  idx_attrs = opts[:index_attrs]
20
20
  attr_types = opts[:attr_types]
@@ -92,7 +92,7 @@ module Zermelo
92
92
  shortcut = opts[:shortcut]
93
93
 
94
94
  return dest_list if shortcut.nil?
95
- Zermelo::Filters::Steps::ListStep::REDIS_SHORTCUTS[shortcut].
95
+ Zermelo::Filters::Redis::SHORTCUTS[:list][shortcut].
96
96
  call(*([r_dest_list] + opts[:shortcut_args]))
97
97
  end
98
98
  end