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