active_record-sql_analyzer 0.2.2 → 0.2.3

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
  SHA1:
3
- metadata.gz: 36199dbc04d9d0e4e4a9cf5c936676acb546e091
4
- data.tar.gz: 36d99d4c4c1bc6e897c28c2e83903e831fb70249
3
+ metadata.gz: 77bac67b331f9db59ff2dbc8f57ec2bca04505c0
4
+ data.tar.gz: 870a62bd36006b4098d8e88a3a3af1cd3fa310b2
5
5
  SHA512:
6
- metadata.gz: 1e87e40d478a83c12e849df385cf4d98d4275d62793c25ad2253b24cd8a5b28608945fa1890c95d946d1400e6c0327457a16d85a8b9e5079bd19c2e21b335913
7
- data.tar.gz: 528b59ab97b3c4225a371cd8fd056e46cfa8253a2df5f9b404c9dda3ce02c2e4c1b1d1c090945729da9adde9033c7678adcdb9d703af3fe4413c62bcfb99afc5
6
+ metadata.gz: 88f4ef3bbe57d34b89194e045e2845f4fa4bd9d5800e47943b8e9b2561fc2cb9025a870d976fc36e29c43564bc98c3c3baa7497ea6bd5ae2e894b505205ab3bd
7
+ data.tar.gz: 7042bf53a81900be2c19a3bfa4e5564d2c2ca0d9fa23d3af22838e6322dd584e460db80236a5f35f1a755efba7a72781516d7c33ac9c7c3c24d2660518126cd8
@@ -0,0 +1,12 @@
1
+ AllCops:
2
+ DisplayCopNames: true
3
+ TargetRubyVersion: 2.3
4
+ Exclude:
5
+ - 'bin/*'
6
+ - 'spec/support/**/*'
7
+
8
+ Metrics:
9
+ Enabled: false
10
+
11
+ Style:
12
+ Enabled: false
@@ -1,15 +1,13 @@
1
1
  bundler_args: --without development
2
- env:
3
- global:
4
- - JRUBY_OPTS="$JRUBY_OPTS --debug"
5
2
  language: ruby
6
3
  rvm:
7
- - 2.1
8
4
  - 2.2
9
- - 2.3.0
5
+ - 2.3.3
6
+ - 2.4.0-preview3
10
7
  - ruby-head
11
8
  matrix:
12
9
  allow_failures:
10
+ - rvm: 2.4.0-preview3
13
11
  - rvm: ruby-head
14
12
  fast_finish: true
15
13
  sudo: false
data/Gemfile CHANGED
@@ -1,13 +1,14 @@
1
1
  source "https://rubygems.org"
2
2
 
3
+ gem "pry-byebug"
3
4
  gem "rake"
4
5
 
5
6
  group :test do
6
- gem 'mysql2', '~> 0.4', '>= 0.4.0'
7
- gem 'rspec', '~> 3.4'
8
- gem 'rubocop', '~> 0.30'
9
- gem 'timecop', '~> 0.8'
10
- gem 'sql-parser', git: 'https://github.com/nerdrew/sql-parser.git'
7
+ gem "mysql2", "~> 0.4", ">= 0.4.0"
8
+ gem "rspec", "~> 3.4"
9
+ gem "rubocop", "~> 0.30"
10
+ gem "sql-parser", git: "https://github.com/nerdrew/sql-parser.git"
11
+ gem "timecop", "~> 0.8"
11
12
  end
12
13
 
13
14
  gemspec
data/Rakefile CHANGED
@@ -1,6 +1,13 @@
1
1
  require 'bundler/gem_tasks'
2
2
 
3
- require 'rspec/core/rake_task'
4
- RSpec::Core::RakeTask.new
3
+ begin
4
+ require "rubocop/rake_task"
5
+ require "rspec/core/rake_task"
5
6
 
6
- task default: 'spec'
7
+ RuboCop::RakeTask.new
8
+ RSpec::Core::RakeTask.new(:spec)
9
+
10
+ task default: [:rubocop, :spec]
11
+ rescue LoadError
12
+ warn "rubocop, rspec only available in development"
13
+ end
@@ -35,7 +35,7 @@ module ActiveRecord
35
35
  logs.each { |l| queue << l }
36
36
 
37
37
  # Spin up threads to start processing the queue
38
- threads = concurrency.times.map do
38
+ threads = Array.new(concurrency) do
39
39
  Thread.new(queue) do |t_queue|
40
40
  # Create a local copy of each definitions then merge them in
41
41
  CLIProcessor.process_queue(t_queue) do |local_definitions, line|
@@ -63,7 +63,7 @@ module ActiveRecord
63
63
  logs.each { |l| queue << l }
64
64
 
65
65
  # Spin up threads to start processing the queue
66
- threads = concurrency.times.map do
66
+ threads = Array.new(concurrency) do
67
67
  Thread.new(queue) do |t_queue|
68
68
  # Create a local copy of the usage for each SHA then merge it in at the end
69
69
  CLIProcessor.process_queue(t_queue) do |local_usage, line|
@@ -108,7 +108,7 @@ module ActiveRecord
108
108
 
109
109
  # How many total lines to log when the caller is ambiguous
110
110
  def ambiguous_backtrace_lines(lines)
111
- if !lines.is_a?(Fixnum)
111
+ if !lines.is_a?(Integer)
112
112
  raise ArgumentError, "Lines must be a Fixnum"
113
113
  elsif lines <= 1
114
114
  raise ArgumentError, "Lines cannot be <= 1"
@@ -129,7 +129,7 @@ module ActiveRecord
129
129
 
130
130
  private
131
131
 
132
- def check_proc(proc, arity, msg)
132
+ def check_proc(proc, _arity, msg)
133
133
  if !proc.is_a?(Proc)
134
134
  raise ArgumentError, "You must pass a proc"
135
135
  elsif proc.arity != 1
@@ -8,23 +8,30 @@ module ActiveRecord
8
8
 
9
9
  def execute(sql, *args)
10
10
  return super unless SqlAnalyzer.config
11
+ safe_sql = nil
12
+ query_analyzer_call = nil
11
13
 
12
- if @_query_analyzer_private_transaction_queue
13
- @_query_analyzer_private_transaction_queue << QueryAnalyzerCall.new(sql, caller)
14
- else
15
- safe_sql = nil
16
-
17
- SqlAnalyzer.config[:analyzers].each do |analyzer|
18
- if SqlAnalyzer.config[:should_log_sample_proc].call(analyzer[:name])
19
- # This is here rather than above intentionally.
20
- # We assume we're not going to be analyzing 100% of queries and want to only re-encode
21
- # when it's actually relevant.
22
- safe_sql ||= sql.encode(Encoding::UTF_8, invalid: :replace, undef: :replace)
23
-
24
- if safe_sql =~ analyzer[:table_regex]
25
- SqlAnalyzer.background_processor <<
26
- _query_analyzer_private_query_stanza([QueryAnalyzerCall.new(safe_sql, caller)], analyzer)
27
- end
14
+ # Record "full" transactions (see below for more information about "full")
15
+ if @_query_analyzer_private_in_transaction
16
+ if @_query_analyzer_private_record_transaction
17
+ safe_sql ||= sql.encode(Encoding::UTF_8, invalid: :replace, undef: :replace)
18
+ query_analyzer_call ||= QueryAnalyzerCall.new(safe_sql, caller)
19
+ @_query_analyzer_private_transaction_queue << query_analyzer_call
20
+ end
21
+ end
22
+
23
+ # Record interesting queries
24
+ SqlAnalyzer.config[:analyzers].each do |analyzer|
25
+ if SqlAnalyzer.config[:should_log_sample_proc].call(analyzer[:name])
26
+ # This is here rather than above intentionally.
27
+ # We assume we're not going to be analyzing 100% of queries and want to only re-encode
28
+ # when it's actually relevant.
29
+ safe_sql ||= sql.encode(Encoding::UTF_8, invalid: :replace, undef: :replace)
30
+ query_analyzer_call ||= QueryAnalyzerCall.new(safe_sql, caller)
31
+
32
+ if safe_sql =~ analyzer[:table_regex]
33
+ SqlAnalyzer.background_processor <<
34
+ _query_analyzer_private_query_stanza([query_analyzer_call], analyzer)
28
35
  end
29
36
  end
30
37
  end
@@ -32,30 +39,62 @@ module ActiveRecord
32
39
  super
33
40
  end
34
41
 
42
+ def begin_db_transaction
43
+ @_query_analyzer_private_in_transaction = true
44
+
45
+ record_transaction = SqlAnalyzer.config[:analyzers].any? do |analyzer|
46
+ SqlAnalyzer.config[:should_log_sample_proc].call(analyzer[:name])
47
+ end
48
+ if record_transaction
49
+ @_query_analyzer_private_transaction_queue ||= []
50
+ @_query_analyzer_private_record_transaction = true
51
+ else
52
+ @_query_analyzer_private_record_transaction = nil
53
+ end
54
+ super
55
+ end
56
+
57
+ def commit_db_transaction
58
+ _query_analyzer_private_drain_transaction_queue("COMMIT")
59
+ super
60
+ ensure
61
+ @_query_analyzer_private_in_transaction = false
62
+ end
63
+
64
+ def exec_rollback_db_transaction
65
+ _query_analyzer_private_drain_transaction_queue("ROLLBACK")
66
+ super
67
+ ensure
68
+ @_query_analyzer_private_in_transaction = false
69
+ end
70
+
71
+ # "private" methods for this monkeypatch
72
+
35
73
  # Drain the transaction query queue. Log the current transaction out to any logger that samples it.
36
- def _query_analyzer_private_drain_transaction_queue
74
+ def _query_analyzer_private_drain_transaction_queue(last_query)
75
+ return unless @_query_analyzer_private_record_transaction
76
+
37
77
  reencoded_calls = nil
38
78
 
39
79
  SqlAnalyzer.config[:analyzers].each do |analyzer|
40
- if SqlAnalyzer.config[:should_log_sample_proc].call(analyzer[:name])
41
- # Again, trying to only re-encode and join strings if the transaction is actually
42
- # sampled.
43
- reencoded_calls ||= @_query_analyzer_private_transaction_queue.map do |call|
44
- QueryAnalyzerCall.new(call.sql.encode(Encoding::UTF_8, invalid: :replace, undef: :replace), call.caller)
45
- end
46
-
47
- has_matching_calls = reencoded_calls.any? { |call| call.sql =~ analyzer[:table_regex] }
80
+ reencoded_calls ||= @_query_analyzer_private_transaction_queue << QueryAnalyzerCall.new(last_query, caller)
48
81
 
49
- if has_matching_calls
50
- matching_calls = reencoded_calls.select do |call|
51
- (call.sql =~ /^(BEGIN|COMMIT|ROLLBACK|UPDATE|INSERT|DELETE)/) || (call.sql =~ analyzer[:table_regex])
52
- end
82
+ has_matching_calls = reencoded_calls.any? { |call| call.sql =~ analyzer[:table_regex] }
53
83
 
54
- SqlAnalyzer.background_processor <<
55
- _query_analyzer_private_query_stanza(matching_calls, analyzer)
84
+ # Record "full" transactions
85
+ # Record all INSERT, UPDATE, and DELETE
86
+ # Record all queries that match the analyzer's table_regex
87
+ if has_matching_calls
88
+ matching_calls = reencoded_calls.select do |call|
89
+ (call.sql =~ /^(BEGIN|COMMIT|ROLLBACK|UPDATE|INSERT|DELETE)/) || (call.sql =~ analyzer[:table_regex])
56
90
  end
91
+
92
+ SqlAnalyzer.background_processor << _query_analyzer_private_query_stanza(matching_calls, analyzer)
57
93
  end
58
94
  end
95
+
96
+ @_query_analyzer_private_transaction_queue.clear
97
+ @_query_analyzer_private_record_transaction = nil
59
98
  end
60
99
 
61
100
  # Helper method to construct the event for a query or transaction.
@@ -70,24 +109,6 @@ module ActiveRecord
70
109
  request_path: Thread.current[:_ar_analyzer_request_path],
71
110
  }
72
111
  end
73
-
74
- def transaction(requires_new: nil, isolation: nil, joinable: true)
75
- must_clear_transaction = false
76
-
77
- if SqlAnalyzer.config[:consolidate_transactions]
78
- if @_query_analyzer_private_transaction_queue.nil?
79
- must_clear_transaction = true
80
- @_query_analyzer_private_transaction_queue = []
81
- end
82
- end
83
-
84
- super
85
- ensure
86
- if must_clear_transaction
87
- _query_analyzer_private_drain_transaction_queue
88
- @_query_analyzer_private_transaction_queue = nil
89
- end
90
- end
91
112
  end
92
113
  end
93
114
  end
@@ -22,10 +22,10 @@ module ActiveRecord
22
22
 
23
23
  def filter_caller(kaller)
24
24
  kaller = if config[:ambiguous_tracers].any? { |regex| kaller.first =~ regex }
25
- kaller[0, config[:ambiguous_backtrace_lines]].join(", ")
26
- else
27
- kaller.first
28
- end
25
+ kaller[0, config[:ambiguous_backtrace_lines]].join(", ")
26
+ else
27
+ kaller.first
28
+ end
29
29
 
30
30
  return '' unless kaller
31
31
 
@@ -1,5 +1,5 @@
1
1
  module ActiveRecord
2
2
  module SqlAnalyzer
3
- VERSION = '0.2.2'
3
+ VERSION = '0.2.3'
4
4
  end
5
5
  end
@@ -216,6 +216,36 @@ RSpec.describe "End to End" do
216
216
  "COMMIT;")
217
217
  end
218
218
 
219
+ context "ActiveRecord generated transactions" do
220
+ before do
221
+ stub_const("Matching", Class.new(ActiveRecord::Base) do
222
+ self.table_name = "matching_table"
223
+
224
+ after_commit { self.class.last }
225
+ end)
226
+
227
+ stub_const("NonMatching", Class.new(ActiveRecord::Base) do
228
+ self.table_name = "nonmatching_table"
229
+
230
+ after_commit { self.class.last }
231
+ end)
232
+ end
233
+
234
+ it "Logs the matching statements in the transaction and logs after_commit hooks outside the transaction" do
235
+ Matching.transaction do
236
+ Matching.create!
237
+ NonMatching.create
238
+ NonMatching.last
239
+ end
240
+ NonMatching.create
241
+
242
+ expect(log_def_hash.map { |_hash, data| data["sql"] }).to match([
243
+ "INSERT INTO `matching_table` VALUES ()",
244
+ "BEGIN; INSERT INTO `matching_table` VALUES (); INSERT INTO `nonmatching_table` VALUES (); COMMIT;",
245
+ "SELECT `matching_table`.* FROM `matching_table` ORDER BY `matching_table`.`id` DESC"
246
+ ])
247
+ end
248
+ end
219
249
 
220
250
  it "Logs mixed matching-nonmatching with inserts correctly" do
221
251
  transaction do
@@ -278,15 +308,14 @@ RSpec.describe "End to End" do
278
308
  execute "SELECT * FROM matching_table WHERE id > 4 and id < 8"
279
309
  end
280
310
 
281
-
282
- transaction_executed_once_sha = log_reverse_hash[1]
283
-
284
- expect(log_def_hash.size).to eq(1)
285
- expect(log_def_hash[transaction_executed_once_sha]["sql"]).to eq(
286
- "BEGIN; " \
287
- "SELECT * FROM matching_table WHERE id = '[REDACTED]'; " \
288
- "SELECT * FROM matching_table WHERE test_string = '[REDACTED]'; " \
289
- "COMMIT;")
311
+ expect(log_def_hash.map { |_hash, data| data["sql"] }).to match([
312
+ "SELECT * FROM matching_table WHERE id = '[REDACTED]'",
313
+ "BEGIN; "\
314
+ "SELECT * FROM matching_table WHERE id = '[REDACTED]'; "\
315
+ "SELECT * FROM matching_table WHERE test_string = '[REDACTED]'; "\
316
+ "COMMIT;",
317
+ "SELECT * FROM matching_table WHERE id = '[REDACTED]' and id = '[REDACTED]'"
318
+ ])
290
319
  end
291
320
  end
292
321
 
@@ -19,6 +19,7 @@ class DBConnection
19
19
  end
20
20
 
21
21
  def self.setup_db
22
+ ActiveRecord::Base.raise_in_transactional_callbacks = true
22
23
  conn = ActiveRecord::Base.establish_connection(configuration)
23
24
  conn.connection.execute <<-SQL
24
25
  CREATE DATABASE IF NOT EXISTS ar_sql_analyzer_test
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_record-sql_analyzer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Zachary Anker
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2016-09-19 00:00:00.000000000 Z
12
+ date: 2016-12-16 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activerecord
@@ -56,6 +56,7 @@ extra_rdoc_files: []
56
56
  files:
57
57
  - ".gitignore"
58
58
  - ".rspec"
59
+ - ".rubocop.yml"
59
60
  - ".travis.yml"
60
61
  - CONTRIBUTING.md
61
62
  - Gemfile
@@ -110,7 +111,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
110
111
  version: '0'
111
112
  requirements: []
112
113
  rubyforge_project:
113
- rubygems_version: 2.6.6
114
+ rubygems_version: 2.5.1
114
115
  signing_key:
115
116
  specification_version: 4
116
117
  summary: Logs a subset of ActiveRecord queries and dumps them for analyses.