n_plus_one_control 0.4.0 → 0.4.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -0
- data/README.md +11 -3
- data/lib/n_plus_one_control.rb +48 -3
- data/lib/n_plus_one_control/minitest.rb +3 -3
- data/lib/n_plus_one_control/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b20b8f4269aac76f3da642b271b93ddee2adf508539bba6852e02225695de155
|
4
|
+
data.tar.gz: e739f342b4d46cc451229f3a065cb0e2b83ee28c301d99045d0f094e74bc137b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 74805b4ea497ff96bb882557ed8a14e04d32cf5ea5c62e9c65167647c880275aec9d0f8a405a298554e9cfecbb20049d99f630b977475a75122d847c7b27e1a9
|
7
|
+
data.tar.gz: 19063ec1fb2a84edcfd0e38075a3858e239a438803ffb40e03916bc8eb5d49dda9e8b03a30a60788e4e00f9c5484811740448fe3241e64658a78ccf884619321
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -186,9 +186,9 @@ If you use caching you can face the problem when first request performs more DB
|
|
186
186
|
|
187
187
|
context "N + 1", :n_plus_one do
|
188
188
|
populate { |n| create_list :post, n }
|
189
|
-
|
189
|
+
|
190
190
|
warmup { get :index } # cache something must be cached
|
191
|
-
|
191
|
+
|
192
192
|
specify do
|
193
193
|
expect { get :index }.to perform_constant_number_of_queries
|
194
194
|
end
|
@@ -215,7 +215,7 @@ end
|
|
215
215
|
def test_no_n_plus_one
|
216
216
|
populate = ->(n) { create_list(:post, n) }
|
217
217
|
warmup = -> { get :index }
|
218
|
-
|
218
|
+
|
219
219
|
assert_perform_constant_number_of_queries population: populate, warmup: warmup do
|
220
220
|
get :index
|
221
221
|
end
|
@@ -240,6 +240,14 @@ NPlusOneControl.default_scale_factors = [2, 3]
|
|
240
240
|
# You can activate verbosity through env variable NPLUSONE_VERBOSE=1
|
241
241
|
NPlusOneControl.verbose = false
|
242
242
|
|
243
|
+
# Print table hits difference, for example:
|
244
|
+
#
|
245
|
+
# Unmatched query numbers by tables:
|
246
|
+
# users (SELECT): 2 != 3
|
247
|
+
# events (INSERT): 1 != 2
|
248
|
+
#
|
249
|
+
self.show_table_stats = true
|
250
|
+
|
243
251
|
# Ignore matching queries
|
244
252
|
NPlusOneControl.ignore = /^(BEGIN|COMMIT|SAVEPOINT|RELEASE)/
|
245
253
|
|
data/lib/n_plus_one_control.rb
CHANGED
@@ -5,17 +5,59 @@ require "n_plus_one_control/executor"
|
|
5
5
|
|
6
6
|
# RSpec and Minitest matchers to prevent N+1 queries problem.
|
7
7
|
module NPlusOneControl
|
8
|
+
# Used to extract a table name from a query
|
9
|
+
EXTRACT_TABLE_RXP = /(insert into|update|delete from|from) ['"](\S+)['"]/i.freeze
|
10
|
+
|
11
|
+
# Used to convert a query part extracted by the regexp above to the corresponding
|
12
|
+
# human-friendly type
|
13
|
+
QUERY_PART_TO_TYPE = {
|
14
|
+
"insert into" => "INSERT",
|
15
|
+
"update" => "UPDATE",
|
16
|
+
"delete from" => "DELETE",
|
17
|
+
"from" => "SELECT"
|
18
|
+
}.freeze
|
19
|
+
|
8
20
|
class << self
|
9
|
-
attr_accessor :default_scale_factors, :verbose, :ignore, :event
|
21
|
+
attr_accessor :default_scale_factors, :verbose, :show_table_stats, :ignore, :event
|
10
22
|
|
11
|
-
def failure_message(queries)
|
23
|
+
def failure_message(queries) # rubocop:disable Metrics/MethodLength
|
12
24
|
msg = ["Expected to make the same number of queries, but got:\n"]
|
13
25
|
queries.each do |(scale, data)|
|
14
26
|
msg << " #{data.size} for N=#{scale}\n"
|
15
|
-
msg << data.map { |sql| " #{sql}\n" }.join.to_s if verbose
|
16
27
|
end
|
28
|
+
|
29
|
+
msg.concat(table_usage_stats(queries.map(&:last))) if show_table_stats
|
30
|
+
|
31
|
+
if verbose
|
32
|
+
queries.each do |(scale, data)|
|
33
|
+
msg << " Queries for N=#{scale}\n"
|
34
|
+
msg << data.map { |sql| " #{sql}\n" }.join.to_s
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
17
38
|
msg.join
|
18
39
|
end
|
40
|
+
|
41
|
+
def table_usage_stats(runs) # rubocop:disable Metrics/MethodLength
|
42
|
+
msg = ["\nUnmatched query numbers by tables:\n"]
|
43
|
+
|
44
|
+
before, after = runs.map do |queries|
|
45
|
+
queries.group_by do |query|
|
46
|
+
matches = query.match(EXTRACT_TABLE_RXP)
|
47
|
+
next unless matches
|
48
|
+
|
49
|
+
" #{matches[2]} (#{QUERY_PART_TO_TYPE[matches[1].downcase]})"
|
50
|
+
end.transform_values(&:count)
|
51
|
+
end
|
52
|
+
|
53
|
+
before.keys.each do |k|
|
54
|
+
next if before[k] == after[k]
|
55
|
+
|
56
|
+
msg << "#{k}: #{before[k]} != #{after[k]}\n"
|
57
|
+
end
|
58
|
+
|
59
|
+
msg
|
60
|
+
end
|
19
61
|
end
|
20
62
|
|
21
63
|
# Scale factors to use.
|
@@ -25,6 +67,9 @@ module NPlusOneControl
|
|
25
67
|
# Print performed queries if true
|
26
68
|
self.verbose = ENV['NPLUSONE_VERBOSE'] == '1'
|
27
69
|
|
70
|
+
# Print table hits difference
|
71
|
+
self.show_table_stats = true
|
72
|
+
|
28
73
|
# Ignore matching queries
|
29
74
|
self.ignore = /^(BEGIN|COMMIT|SAVEPOINT|RELEASE)/
|
30
75
|
|
@@ -17,7 +17,7 @@ module NPlusOneControl
|
|
17
17
|
warming_up warmup
|
18
18
|
|
19
19
|
@executor = NPlusOneControl::Executor.new(
|
20
|
-
population:
|
20
|
+
population: populate || population_method,
|
21
21
|
matching: matching || /^SELECT/i,
|
22
22
|
scale_factors: scale_factors || NPlusOneControl.default_scale_factors
|
23
23
|
)
|
@@ -39,8 +39,8 @@ module NPlusOneControl
|
|
39
39
|
(warmup || methods.include?(:warmup) ? method(:warmup) : nil)&.call
|
40
40
|
end
|
41
41
|
|
42
|
-
def
|
43
|
-
|
42
|
+
def population_method
|
43
|
+
methods.include?(:populate) ? method(:populate) : nil
|
44
44
|
end
|
45
45
|
end
|
46
46
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: n_plus_one_control
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- palkan
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-09-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -181,7 +181,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
181
181
|
- !ruby/object:Gem::Version
|
182
182
|
version: '0'
|
183
183
|
requirements: []
|
184
|
-
rubygems_version: 3.0.
|
184
|
+
rubygems_version: 3.0.6
|
185
185
|
signing_key:
|
186
186
|
specification_version: 4
|
187
187
|
summary: RSpec and Minitest matchers to prevent N+1 queries problem
|