n_plus_one_control 0.4.0 → 0.4.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 227fb8257ab67628ffd449aff0358d729dd3ff3eafc291f9fca427aa355e0650
4
- data.tar.gz: 2e65e5f09da3ad989fbb1e2418a657c8142ea8d25540a0daf729adeb9f0b0d5b
3
+ metadata.gz: b20b8f4269aac76f3da642b271b93ddee2adf508539bba6852e02225695de155
4
+ data.tar.gz: e739f342b4d46cc451229f3a065cb0e2b83ee28c301d99045d0f094e74bc137b
5
5
  SHA512:
6
- metadata.gz: e79cf5bd9ec1afd8b10ca184a483edd9e8d8fbbeaca62e147d277102715ead2fa909d720eb0085fc6d2c2441abb5e61aa08f8a081a4b37079b3f552694d3e220
7
- data.tar.gz: 59c76b177dc8b6ad4d58cfbad1f3c3e89204eef6e92d2d39989b5829f714939c77d0d38806e935d3624e20469447607f7b403af2b2bd633a02486e82c6cc4f6c
6
+ metadata.gz: 74805b4ea497ff96bb882557ed8a14e04d32cf5ea5c62e9c65167647c880275aec9d0f8a405a298554e9cfecbb20049d99f630b977475a75122d847c7b27e1a9
7
+ data.tar.gz: 19063ec1fb2a84edcfd0e38075a3858e239a438803ffb40e03916bc8eb5d49dda9e8b03a30a60788e4e00f9c5484811740448fe3241e64658a78ccf884619321
@@ -1,3 +1,9 @@
1
+ ## master (unreleased)
2
+
3
+ ## 0.4.1 (2020-09-04)
4
+
5
+ - Enhance failure message by showing differences in table hits. ([@palkan][])
6
+
1
7
  ## 0.4.0 (2020-07-20)
2
8
 
3
9
  - Make scale factor available in tests via `#current_scale` method. ([@Earendil95][])
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
 
@@ -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: population_proc(populate),
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 population_proc(populate)
43
- (populate || methods.include?(:populate) ? method(:populate) : nil)
42
+ def population_method
43
+ methods.include?(:populate) ? method(:populate) : nil
44
44
  end
45
45
  end
46
46
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module NPlusOneControl
4
- VERSION = "0.4.0"
4
+ VERSION = "0.4.1"
5
5
  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.0
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-07-20 00:00:00.000000000 Z
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.3
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