zermelo 1.1.0 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -0
- data/README.md +76 -52
- data/lib/zermelo/associations/association_data.rb +4 -3
- data/lib/zermelo/associations/class_methods.rb +37 -50
- data/lib/zermelo/associations/index.rb +3 -1
- data/lib/zermelo/associations/multiple.rb +247 -0
- data/lib/zermelo/associations/range_index.rb +44 -0
- data/lib/zermelo/associations/singular.rb +193 -0
- data/lib/zermelo/associations/unique_index.rb +4 -3
- data/lib/zermelo/backend.rb +120 -0
- data/lib/zermelo/backends/{influxdb_backend.rb → influxdb.rb} +87 -31
- data/lib/zermelo/backends/{redis_backend.rb → redis.rb} +53 -58
- data/lib/zermelo/backends/stub.rb +43 -0
- data/lib/zermelo/filter.rb +194 -0
- data/lib/zermelo/filters/index_range.rb +22 -0
- data/lib/zermelo/filters/{influxdb_filter.rb → influxdb.rb} +12 -11
- data/lib/zermelo/filters/redis.rb +173 -0
- data/lib/zermelo/filters/steps/list_step.rb +48 -30
- data/lib/zermelo/filters/steps/set_step.rb +148 -89
- data/lib/zermelo/filters/steps/sort_step.rb +2 -2
- data/lib/zermelo/record.rb +53 -0
- data/lib/zermelo/records/attributes.rb +32 -0
- data/lib/zermelo/records/class_methods.rb +12 -25
- data/lib/zermelo/records/{influxdb_record.rb → influxdb.rb} +3 -4
- data/lib/zermelo/records/instance_methods.rb +9 -8
- data/lib/zermelo/records/key.rb +3 -1
- data/lib/zermelo/records/redis.rb +17 -0
- data/lib/zermelo/records/stub.rb +17 -0
- data/lib/zermelo/version.rb +1 -1
- data/spec/lib/zermelo/associations/index_spec.rb +70 -1
- data/spec/lib/zermelo/associations/multiple_spec.rb +1084 -0
- data/spec/lib/zermelo/associations/range_index_spec.rb +77 -0
- data/spec/lib/zermelo/associations/singular_spec.rb +149 -0
- data/spec/lib/zermelo/associations/unique_index_spec.rb +58 -2
- data/spec/lib/zermelo/filter_spec.rb +363 -0
- data/spec/lib/zermelo/locks/redis_lock_spec.rb +3 -3
- data/spec/lib/zermelo/records/instance_methods_spec.rb +206 -0
- data/spec/spec_helper.rb +9 -1
- data/spec/support/mock_logger.rb +48 -0
- metadata +31 -46
- data/lib/zermelo/associations/belongs_to.rb +0 -115
- data/lib/zermelo/associations/has_and_belongs_to_many.rb +0 -128
- data/lib/zermelo/associations/has_many.rb +0 -120
- data/lib/zermelo/associations/has_one.rb +0 -109
- data/lib/zermelo/associations/has_sorted_set.rb +0 -124
- data/lib/zermelo/backends/base.rb +0 -115
- data/lib/zermelo/filters/base.rb +0 -212
- data/lib/zermelo/filters/redis_filter.rb +0 -111
- data/lib/zermelo/filters/steps/sorted_set_step.rb +0 -156
- data/lib/zermelo/records/base.rb +0 -62
- data/lib/zermelo/records/redis_record.rb +0 -27
- data/spec/lib/zermelo/associations/belongs_to_spec.rb +0 -6
- data/spec/lib/zermelo/associations/has_many_spec.rb +0 -6
- data/spec/lib/zermelo/associations/has_one_spec.rb +0 -6
- data/spec/lib/zermelo/associations/has_sorted_set.spec.rb +0 -6
- data/spec/lib/zermelo/backends/influxdb_backend_spec.rb +0 -0
- data/spec/lib/zermelo/backends/moneta_backend_spec.rb +0 -0
- data/spec/lib/zermelo/filters/influxdb_filter_spec.rb +0 -0
- data/spec/lib/zermelo/filters/redis_filter_spec.rb +0 -0
- data/spec/lib/zermelo/records/influxdb_record_spec.rb +0 -434
- data/spec/lib/zermelo/records/key_spec.rb +0 -6
- data/spec/lib/zermelo/records/redis_record_spec.rb +0 -1461
- data/spec/lib/zermelo/records/type_validator_spec.rb +0 -6
- data/spec/lib/zermelo/version_spec.rb +0 -6
- data/spec/lib/zermelo_spec.rb +0 -6
@@ -1,10 +1,10 @@
|
|
1
|
-
require 'zermelo/
|
1
|
+
require 'zermelo/filter'
|
2
2
|
|
3
3
|
module Zermelo
|
4
4
|
module Filters
|
5
|
-
class
|
5
|
+
class InfluxDB
|
6
6
|
|
7
|
-
include Zermelo::
|
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.
|
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
|
-
|
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::
|
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
|
-
|
41
|
-
|
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
|
-
|
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
|
-
|
54
|
+
l = (Zermelo.redis.llen(r_source) - o) if (l < 1)
|
49
55
|
|
50
|
-
|
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
|
-
|
59
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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::
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
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.
|
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,
|
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
|
-
|
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
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
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::
|
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
|
-
|
92
|
-
|
93
|
-
|
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
|
-
|
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
|
-
|
104
|
-
|
105
|
-
|
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
|
-
|
108
|
-
|
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
|
-
|
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::
|
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::
|
95
|
+
Zermelo::Filters::Redis::SHORTCUTS[:list][shortcut].
|
96
96
|
call(*([r_dest_list] + opts[:shortcut_args]))
|
97
97
|
end
|
98
98
|
end
|