zermelo 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
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