active_record-sql_analyzer 0.2.2 → 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
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.