redis-memo 0.1.1 → 1.1.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/lib/redis_memo.rb +48 -36
- data/lib/redis_memo/after_commit.rb +2 -2
- data/lib/redis_memo/batch.rb +36 -11
- data/lib/redis_memo/cache.rb +36 -19
- data/lib/redis_memo/connection_pool.rb +4 -3
- data/lib/redis_memo/errors.rb +9 -0
- data/lib/redis_memo/future.rb +22 -13
- data/lib/redis_memo/memoizable.rb +109 -72
- data/lib/redis_memo/memoizable/bump_version.lua +39 -0
- data/lib/redis_memo/memoizable/dependency.rb +10 -11
- data/lib/redis_memo/memoizable/invalidation.rb +68 -66
- data/lib/redis_memo/memoize_method.rb +169 -131
- data/lib/redis_memo/memoize_query.rb +135 -92
- data/lib/redis_memo/memoize_query/cached_select.rb +73 -62
- data/lib/redis_memo/memoize_query/cached_select/bind_params.rb +202 -70
- data/lib/redis_memo/memoize_query/cached_select/connection_adapter.rb +19 -10
- data/lib/redis_memo/memoize_query/invalidation.rb +22 -20
- data/lib/redis_memo/memoize_query/memoize_table_column.rb +1 -0
- data/lib/redis_memo/middleware.rb +3 -1
- data/lib/redis_memo/options.rb +111 -5
- data/lib/redis_memo/railtie.rb +11 -0
- data/lib/redis_memo/redis.rb +15 -5
- data/lib/redis_memo/testing.rb +49 -0
- data/lib/redis_memo/thread_local_var.rb +16 -0
- data/lib/redis_memo/tracer.rb +1 -0
- data/lib/redis_memo/util.rb +25 -0
- metadata +80 -4
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
##
|
4
4
|
# Inspect a SQL's AST to memoize SELECT statements
|
5
5
|
#
|
6
6
|
# As Rails applies additional logic on top of the rows returned from the
|
@@ -94,6 +94,12 @@ class RedisMemo::MemoizeQuery::CachedSelect
|
|
94
94
|
|
95
95
|
@@enabled_models = {}
|
96
96
|
|
97
|
+
# Thread locals to exchange information between RedisMemo and ActiveRecord
|
98
|
+
RedisMemo::ThreadLocalVar.define :arel
|
99
|
+
RedisMemo::ThreadLocalVar.define :substitues
|
100
|
+
RedisMemo::ThreadLocalVar.define :arel_bind_params
|
101
|
+
|
102
|
+
# @return [Hash] models enabled for caching
|
97
103
|
def self.enabled_models
|
98
104
|
@@enabled_models
|
99
105
|
end
|
@@ -107,10 +113,7 @@ class RedisMemo::MemoizeQuery::CachedSelect
|
|
107
113
|
|
108
114
|
memoize_method(
|
109
115
|
:exec_query,
|
110
|
-
method_id: proc
|
111
|
-
sql.gsub(/(\$\d+)/, '?') # $1 -> ?
|
112
|
-
.gsub(/((, *)*\?)+/, '?') # (?, ?, ? ...) -> (?)
|
113
|
-
end,
|
116
|
+
method_id: proc { |_, sql, *| RedisMemo::Util.tagify_parameterized_sql(sql) },
|
114
117
|
) do |_, sql, _, binds, **|
|
115
118
|
depends_on RedisMemo::MemoizeQuery::CachedSelect.current_query_bind_params
|
116
119
|
|
@@ -123,7 +126,7 @@ class RedisMemo::MemoizeQuery::CachedSelect
|
|
123
126
|
# In activerecord >= 6, a bind could be an actual database value
|
124
127
|
bind
|
125
128
|
end
|
126
|
-
end
|
129
|
+
end,
|
127
130
|
)
|
128
131
|
end
|
129
132
|
end
|
@@ -152,61 +155,74 @@ class RedisMemo::MemoizeQuery::CachedSelect
|
|
152
155
|
end
|
153
156
|
end
|
154
157
|
|
158
|
+
# Extract bind params from the query by inspecting the SQL's AST recursively
|
159
|
+
# The bind params will be passed into the local thread variables. See
|
160
|
+
# +construct_bind_params_recurse+ for how to construct binding params
|
161
|
+
# recursively.
|
162
|
+
#
|
163
|
+
# @param sql [String] SQL query
|
164
|
+
# @return [Boolean] indicating whether a query should be cached
|
155
165
|
def self.extract_bind_params(sql)
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
166
|
+
RedisMemo::Tracer.trace(
|
167
|
+
'redis_memo.memoize_query.extract_bind_params',
|
168
|
+
RedisMemo::Util.tagify_parameterized_sql(sql),
|
169
|
+
) do
|
170
|
+
ast = RedisMemo::ThreadLocalVar.arel&.ast
|
171
|
+
return false unless ast.is_a?(Arel::Nodes::SelectStatement)
|
172
|
+
return false unless ast.to_sql == sql
|
173
|
+
|
174
|
+
RedisMemo::ThreadLocalVar.substitues ||= {}
|
175
|
+
# Iterate through the Arel AST in a Depth First Search
|
176
|
+
bind_params = construct_bind_params_recurse(ast)
|
177
|
+
return false unless bind_params&.should_cache?
|
178
|
+
|
179
|
+
bind_params.extract!
|
180
|
+
RedisMemo::ThreadLocalVar.arel_bind_params = bind_params
|
181
|
+
true
|
182
|
+
end
|
170
183
|
end
|
171
184
|
|
172
185
|
def self.current_query_bind_params
|
173
|
-
|
186
|
+
RedisMemo::ThreadLocalVar.arel_bind_params
|
174
187
|
end
|
175
188
|
|
176
189
|
def self.current_query=(arel)
|
177
|
-
|
190
|
+
RedisMemo::ThreadLocalVar.arel = arel
|
178
191
|
end
|
179
192
|
|
180
193
|
def self.current_substitutes=(substitutes)
|
181
|
-
|
194
|
+
RedisMemo::ThreadLocalVar.substitues = substitutes
|
182
195
|
end
|
183
196
|
|
184
197
|
def self.reset_current_query
|
185
|
-
|
186
|
-
|
187
|
-
|
198
|
+
RedisMemo::ThreadLocalVar.arel = nil
|
199
|
+
RedisMemo::ThreadLocalVar.substitues = nil
|
200
|
+
RedisMemo::ThreadLocalVar.arel_bind_params = nil
|
188
201
|
end
|
189
202
|
|
190
203
|
def self.with_new_query_context
|
191
|
-
prev_arel =
|
192
|
-
prev_substitutes =
|
193
|
-
prev_bind_params =
|
204
|
+
prev_arel = RedisMemo::ThreadLocalVar.arel
|
205
|
+
prev_substitutes = RedisMemo::ThreadLocalVar.substitues
|
206
|
+
prev_bind_params = RedisMemo::ThreadLocalVar.arel_bind_params
|
194
207
|
RedisMemo::MemoizeQuery::CachedSelect.reset_current_query
|
195
208
|
|
196
209
|
yield
|
197
210
|
ensure
|
198
|
-
|
199
|
-
|
200
|
-
|
211
|
+
RedisMemo::ThreadLocalVar.arel = prev_arel
|
212
|
+
RedisMemo::ThreadLocalVar.substitues = prev_substitutes
|
213
|
+
RedisMemo::ThreadLocalVar.arel_bind_params = prev_bind_params
|
201
214
|
end
|
202
215
|
|
203
|
-
private
|
204
|
-
|
205
216
|
# A pre-order Depth First Search
|
206
217
|
#
|
207
218
|
# Note: Arel::Nodes#each returns a list in post-order, and it does not step
|
208
219
|
# into Union nodes. So we're implementing our own DFS
|
209
|
-
|
220
|
+
#
|
221
|
+
# @param node [Arel::Nodes::Node]
|
222
|
+
#
|
223
|
+
# @return [RedisMemo::MemoizeQuery::CachedSelect::BindParams]
|
224
|
+
def self.construct_bind_params_recurse(node)
|
225
|
+
# rubocop: disable Lint/NonLocalExitFromIterator
|
210
226
|
bind_params = BindParams.new
|
211
227
|
|
212
228
|
case node
|
@@ -229,7 +245,7 @@ class RedisMemo::MemoizeQuery::CachedSelect
|
|
229
245
|
return unless binding_relation
|
230
246
|
|
231
247
|
rights = node.right.is_a?(Array) ? node.right : [node.right]
|
232
|
-
substitutes =
|
248
|
+
substitutes = RedisMemo::ThreadLocalVar.substitues
|
233
249
|
|
234
250
|
rights.each do |right|
|
235
251
|
case right
|
@@ -256,20 +272,13 @@ class RedisMemo::MemoizeQuery::CachedSelect
|
|
256
272
|
end,
|
257
273
|
}
|
258
274
|
else
|
259
|
-
bind_params = bind_params.union(
|
260
|
-
if bind_params
|
261
|
-
next
|
262
|
-
else
|
263
|
-
return
|
264
|
-
end
|
275
|
+
bind_params = bind_params.union(construct_bind_params_recurse(right))
|
276
|
+
return if !bind_params
|
265
277
|
end
|
266
278
|
end
|
267
279
|
|
268
280
|
bind_params
|
269
281
|
when Arel::Nodes::SelectStatement
|
270
|
-
# No OREDER BY
|
271
|
-
return unless node.orders.empty?
|
272
|
-
|
273
282
|
node.cores.each do |core|
|
274
283
|
# We don't support JOINs
|
275
284
|
return unless core.source.right.empty?
|
@@ -284,7 +293,7 @@ class RedisMemo::MemoizeQuery::CachedSelect
|
|
284
293
|
return if core.wheres.empty? || binding_relation.nil?
|
285
294
|
when Arel::Nodes::TableAlias
|
286
295
|
bind_params = bind_params.union(
|
287
|
-
|
296
|
+
construct_bind_params_recurse(source_node.left),
|
288
297
|
)
|
289
298
|
|
290
299
|
return unless bind_params
|
@@ -295,7 +304,7 @@ class RedisMemo::MemoizeQuery::CachedSelect
|
|
295
304
|
# Binds wheres before havings
|
296
305
|
core.wheres.each do |where|
|
297
306
|
bind_params = bind_params.union(
|
298
|
-
|
307
|
+
construct_bind_params_recurse(where),
|
299
308
|
)
|
300
309
|
|
301
310
|
return unless bind_params
|
@@ -303,26 +312,23 @@ class RedisMemo::MemoizeQuery::CachedSelect
|
|
303
312
|
|
304
313
|
core.havings.each do |having|
|
305
314
|
bind_params = bind_params.union(
|
306
|
-
|
315
|
+
construct_bind_params_recurse(having),
|
307
316
|
)
|
308
317
|
|
309
318
|
return unless bind_params
|
310
319
|
end
|
311
|
-
|
312
|
-
# Reject any unbound select queries
|
313
|
-
return if binding_relation && bind_params.params[binding_relation].empty?
|
314
320
|
end
|
315
321
|
|
316
322
|
bind_params
|
317
323
|
when Arel::Nodes::Grouping
|
318
324
|
# Inline SQL
|
319
|
-
|
320
|
-
|
321
|
-
|
325
|
+
construct_bind_params_recurse(node.expr)
|
326
|
+
when Arel::Nodes::LessThan, Arel::Nodes::LessThanOrEqual, Arel::Nodes::GreaterThan, Arel::Nodes::GreaterThanOrEqual, Arel::Nodes::NotEqual
|
327
|
+
bind_params
|
322
328
|
when Arel::Nodes::And
|
323
329
|
node.children.each do |child|
|
324
330
|
bind_params = bind_params.product(
|
325
|
-
|
331
|
+
construct_bind_params_recurse(child),
|
326
332
|
)
|
327
333
|
|
328
334
|
return unless bind_params
|
@@ -332,7 +338,7 @@ class RedisMemo::MemoizeQuery::CachedSelect
|
|
332
338
|
when Arel::Nodes::Union, Arel::Nodes::Or
|
333
339
|
[node.left, node.right].each do |child|
|
334
340
|
bind_params = bind_params.union(
|
335
|
-
|
341
|
+
construct_bind_params_recurse(child),
|
336
342
|
)
|
337
343
|
|
338
344
|
return unless bind_params
|
@@ -341,14 +347,24 @@ class RedisMemo::MemoizeQuery::CachedSelect
|
|
341
347
|
bind_params
|
342
348
|
else
|
343
349
|
# Not yet supported
|
344
|
-
|
350
|
+
nil
|
345
351
|
end
|
352
|
+
# rubocop: enable Lint/NonLocalExitFromIterator
|
346
353
|
end
|
347
354
|
|
355
|
+
# Retrieve the model info from the table node
|
356
|
+
# table node is an Arel::Table object, e.g. <Arel::Table @name="sites" ...>
|
357
|
+
# and we can retrieve the model info by inspecting thhe table name
|
358
|
+
# See +RedisMemo::MemoizeQuery::memoize_table_column+ for how to construct enabled_models
|
359
|
+
#
|
360
|
+
# @params table_node [Arel::Table]
|
348
361
|
def self.extract_binding_relation(table_node)
|
349
362
|
enabled_models[table_node.try(:name)]
|
350
363
|
end
|
351
364
|
|
365
|
+
#
|
366
|
+
# Identify whether the node has filter condition
|
367
|
+
#
|
352
368
|
class NodeHasFilterCondition
|
353
369
|
def self.===(node)
|
354
370
|
case node
|
@@ -365,9 +381,4 @@ class RedisMemo::MemoizeQuery::CachedSelect
|
|
365
381
|
end
|
366
382
|
end
|
367
383
|
end
|
368
|
-
|
369
|
-
# Thread locals to exchange information between RedisMemo and ActiveRecord
|
370
|
-
THREAD_KEY_AREL = :__redis_memo_memoize_query_cached_select_arel__
|
371
|
-
THREAD_KEY_SUBSTITUTES = :__redis_memo_memoize_query_cached_select_substitues__
|
372
|
-
THREAD_KEY_AREL_BIND_PARAMS = :__redis_memo_memoize_query_cached_select_arel_bind_params__
|
373
384
|
end
|
@@ -2,47 +2,122 @@
|
|
2
2
|
|
3
3
|
class RedisMemo::MemoizeQuery::CachedSelect
|
4
4
|
class BindParams
|
5
|
-
def
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
#
|
10
|
-
# {
|
11
|
-
# Site => [
|
12
|
-
# {name: 'a', city: 'b'},
|
13
|
-
# {name: 'a', city: 'c'},
|
14
|
-
# {name: 'b', city: 'b'},
|
15
|
-
# {name: 'b', city: 'c'},
|
16
|
-
# ],
|
17
|
-
# }
|
18
|
-
#
|
19
|
-
@params ||= Hash.new do |models, model|
|
20
|
-
models[model] = []
|
21
|
-
end
|
5
|
+
def initialize(left = nil, right = nil, operator = nil)
|
6
|
+
@left = left
|
7
|
+
@right = right
|
8
|
+
@operator = operator
|
22
9
|
end
|
23
10
|
|
24
11
|
def union(other)
|
25
12
|
return unless other
|
26
13
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
14
|
+
self.class.new(self, other, __method__)
|
15
|
+
end
|
16
|
+
|
17
|
+
def product(other)
|
18
|
+
return unless other
|
19
|
+
|
20
|
+
self.class.new(self, other, __method__)
|
21
|
+
end
|
22
|
+
|
23
|
+
def should_cache?
|
24
|
+
plan!
|
25
|
+
|
26
|
+
if plan.model_attrs.empty? || plan.dependency_size_estimation.to_i > RedisMemo::DefaultOptions.max_query_dependency_size
|
27
|
+
return false
|
28
|
+
end
|
29
|
+
|
30
|
+
plan.model_attrs.each do |model, attrs_set|
|
31
|
+
return false if attrs_set.empty?
|
32
|
+
|
33
|
+
attrs_set.each do |attrs|
|
34
|
+
return false unless RedisMemo::MemoizeQuery
|
35
|
+
.memoized_columns(model)
|
36
|
+
.include?(attrs.keys.sort)
|
36
37
|
end
|
37
38
|
end
|
38
39
|
|
39
|
-
|
40
|
+
true
|
40
41
|
end
|
41
42
|
|
42
|
-
|
43
|
+
#
|
44
|
+
# Extracted bind params is hash of sets: each key is a model class, each
|
45
|
+
# value is a set of hashes for memoized column conditions. Example:
|
46
|
+
#
|
47
|
+
# {
|
48
|
+
# Site => [
|
49
|
+
# {name: 'a', city: 'b'},
|
50
|
+
# {name: 'a', city: 'c'},
|
51
|
+
# {name: 'b', city: 'b'},
|
52
|
+
# {name: 'b', city: 'c'},
|
53
|
+
# ],
|
54
|
+
# }
|
55
|
+
#
|
56
|
+
def extract!
|
57
|
+
return if operator.nil?
|
58
|
+
|
59
|
+
left.extract!
|
60
|
+
right.extract!
|
61
|
+
__send__(:"#{operator}!")
|
62
|
+
end
|
63
|
+
|
64
|
+
def params
|
65
|
+
@params ||= Hash.new do |models, model|
|
66
|
+
models[model] = Set.new
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
protected
|
71
|
+
|
72
|
+
# BindParams is built recursively when iterating through the Arel AST
|
73
|
+
# nodes. BindParams represents a binary tree. Query parameters are added to
|
74
|
+
# the leaf nodes of the tree, and the leaf nodes are connected by
|
75
|
+
# operators, such as `union` (or conditions) or `product` (and conditions).
|
76
|
+
attr_accessor :left
|
77
|
+
attr_accessor :right
|
78
|
+
attr_accessor :operator
|
79
|
+
attr_accessor :plan
|
80
|
+
|
81
|
+
def plan!
|
82
|
+
self.plan = Plan.new(self)
|
83
|
+
return if operator.nil?
|
84
|
+
|
85
|
+
left.plan!
|
86
|
+
right.plan!
|
87
|
+
__send__(:"plan_#{operator}")
|
88
|
+
end
|
89
|
+
|
90
|
+
def plan_union
|
91
|
+
plan.dependency_size_estimation = left.plan.dependency_size_estimation + right.plan.dependency_size_estimation
|
92
|
+
plan.model_attrs = union_attrs_set(left.plan.model_attrs, right.plan.model_attrs)
|
93
|
+
end
|
94
|
+
|
95
|
+
def plan_product
|
96
|
+
plan.dependency_size_estimation = left.plan.dependency_size_estimation * right.plan.dependency_size_estimation
|
97
|
+
plan.model_attrs = product_attrs_set(left.plan.model_attrs, right.plan.model_attrs)
|
98
|
+
end
|
99
|
+
|
100
|
+
def union!
|
101
|
+
@params = union_attrs_set(left.params, right.params)
|
102
|
+
end
|
103
|
+
|
104
|
+
def product!
|
105
|
+
@params = product_attrs_set(left.params, right.params)
|
106
|
+
end
|
107
|
+
|
108
|
+
def union_attrs_set(left, right)
|
109
|
+
left.merge(right) do |_, attrs_set, other_attrs_set|
|
110
|
+
next attrs_set if other_attrs_set.empty?
|
111
|
+
next other_attrs_set if attrs_set.empty?
|
112
|
+
|
113
|
+
attrs_set + other_attrs_set
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def product_attrs_set(left, right)
|
43
118
|
# Example:
|
44
119
|
#
|
45
|
-
#
|
120
|
+
# product(
|
46
121
|
# [{a: 1}, {a: 2}],
|
47
122
|
# [{b: 1}, {b: 2}],
|
48
123
|
# )
|
@@ -55,29 +130,16 @@ class RedisMemo::MemoizeQuery::CachedSelect
|
|
55
130
|
# {a: 2, b: 1},
|
56
131
|
# {a: 2, b: 2},
|
57
132
|
# ]
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
# performance.
|
62
|
-
params.each do |model, attrs_set|
|
63
|
-
next if attrs_set.empty?
|
64
|
-
|
65
|
-
# The other model does not have any conditions so far: carry the
|
66
|
-
# attributes over to the other node
|
67
|
-
if other.params[model].empty?
|
68
|
-
other.params[model] = attrs_set
|
69
|
-
next
|
70
|
-
end
|
71
|
-
|
72
|
-
# Distribute the current attrs into the other
|
73
|
-
other_attrs_set_size = other.params[model].size
|
74
|
-
other_attrs_set = other.params[model]
|
75
|
-
merged_attrs_set = Array.new(other_attrs_set_size * attrs_set.size)
|
133
|
+
left.merge(right) do |_, attrs_set, other_attrs_set|
|
134
|
+
next attrs_set if other_attrs_set.empty?
|
135
|
+
next other_attrs_set if attrs_set.empty?
|
76
136
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
137
|
+
# distribute the current attrs into the other
|
138
|
+
merged_attrs_set = Set.new
|
139
|
+
attrs_set.each do |attrs|
|
140
|
+
other_attrs_set.each do |other_attrs|
|
141
|
+
merged_attrs = other_attrs.dup
|
142
|
+
should_add_attrs = true
|
81
143
|
attrs.each do |name, val|
|
82
144
|
# Conflict detected. For example:
|
83
145
|
#
|
@@ -86,42 +148,112 @@ class RedisMemo::MemoizeQuery::CachedSelect
|
|
86
148
|
# Keep: a = 1 and b = 2, a = 2 and b = 1
|
87
149
|
# Discard: a = 1 and a = 2, b = 1 and b = 2
|
88
150
|
if merged_attrs.include?(name) && merged_attrs[name] != val
|
89
|
-
|
151
|
+
should_add_attrs = false
|
90
152
|
break
|
91
153
|
end
|
92
154
|
|
93
155
|
merged_attrs[name] = val
|
94
156
|
end
|
157
|
+
merged_attrs_set << merged_attrs if should_add_attrs
|
95
158
|
end
|
96
159
|
end
|
97
160
|
|
98
|
-
merged_attrs_set
|
99
|
-
other.params[model] = merged_attrs_set
|
161
|
+
merged_attrs_set
|
100
162
|
end
|
101
|
-
|
102
|
-
other
|
103
163
|
end
|
104
164
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
165
|
+
# Prior to actually extracting the bind parameters, we first quickly
|
166
|
+
# estimate if it makes sense to do so. If a query contains too many
|
167
|
+
# dependencies, or contains dependencies that have not been memoized, then
|
168
|
+
# the query itself cannot be cached correctly/efficiently, so there’s no
|
169
|
+
# point to actually extract.
|
170
|
+
#
|
171
|
+
# The planning phase is similar to the extraction phase. Though in the
|
172
|
+
# planning phase, we can ignore all the actual attribute values and only
|
173
|
+
# look at the attribute names. This way, we can precompute the dependency
|
174
|
+
# size without populating their actual values.
|
175
|
+
#
|
176
|
+
# For example, in the planning phase,
|
177
|
+
#
|
178
|
+
# {a:nil} x {b: nil} => {a: nil, b: nil}
|
179
|
+
# {a:nil, b:nil} x {a: nil: b: nil} => {a: nil, b: nil}
|
180
|
+
#
|
181
|
+
# and in the extraction phase, that's where the # of dependency can
|
182
|
+
# actually grow significantly:
|
183
|
+
#
|
184
|
+
# {a: [1,2,3]} x {b: [1,2,3]} => [{a: 1, b: 1}, ....]
|
185
|
+
# {a:[1,2], b:[1,2]} x {a: [1,2,3]: b: [1,2,3]} => [{a: 1, b: 1}, ...]
|
186
|
+
#
|
187
|
+
class Plan
|
188
|
+
class DependencySizeEstimation
|
189
|
+
def initialize(hash = nil)
|
190
|
+
@hash = hash
|
191
|
+
end
|
110
192
|
|
111
|
-
|
112
|
-
|
193
|
+
def +(other)
|
194
|
+
merged_hash = hash.dup
|
195
|
+
other.hash.each do |k, v|
|
196
|
+
merged_hash[k] += v
|
197
|
+
end
|
198
|
+
self.class.new(merged_hash)
|
199
|
+
end
|
113
200
|
|
114
|
-
|
115
|
-
|
201
|
+
def *(other)
|
202
|
+
merged_hash = hash.dup
|
203
|
+
other.hash.each do |k, v|
|
204
|
+
if merged_hash.include?(k)
|
205
|
+
merged_hash[k] *= v
|
206
|
+
else
|
207
|
+
merged_hash[k] = v
|
208
|
+
end
|
209
|
+
end
|
210
|
+
self.class.new(merged_hash)
|
211
|
+
end
|
116
212
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
213
|
+
def [](key)
|
214
|
+
hash[key]
|
215
|
+
end
|
216
|
+
|
217
|
+
def []=(key, val)
|
218
|
+
hash[key] = val
|
219
|
+
end
|
220
|
+
|
221
|
+
def to_i
|
222
|
+
ret = 0
|
223
|
+
hash.each do |_, v|
|
224
|
+
ret += v
|
225
|
+
end
|
226
|
+
ret
|
227
|
+
end
|
228
|
+
|
229
|
+
protected
|
230
|
+
|
231
|
+
def hash
|
232
|
+
@hash ||= Hash.new(0)
|
121
233
|
end
|
122
234
|
end
|
123
235
|
|
124
|
-
|
236
|
+
attr_accessor :dependency_size_estimation
|
237
|
+
attr_accessor :model_attrs
|
238
|
+
|
239
|
+
def initialize(bind_params)
|
240
|
+
@dependency_size_estimation = DependencySizeEstimation.new
|
241
|
+
@model_attrs = Hash.new do |models, model|
|
242
|
+
models[model] = Set.new
|
243
|
+
end
|
244
|
+
|
245
|
+
# An aggregated bind_params node can only obtain params by combining
|
246
|
+
# its children nodes
|
247
|
+
return if !bind_params.__send__(:operator).nil?
|
248
|
+
|
249
|
+
bind_params.params.each do |model, attrs_set|
|
250
|
+
@dependency_size_estimation[model] += attrs_set.size
|
251
|
+
attrs_set.each do |attrs|
|
252
|
+
# [k, nil]: Ignore the attr value and keep the name only
|
253
|
+
@model_attrs[model] << attrs.keys.map { |k| [k, nil] }.to_h
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|
125
257
|
end
|
126
258
|
end
|
127
259
|
end
|