awesome_explain 0.3.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.github/workflows/mongodb.yml +53 -0
- data/.github/workflows/postgres.yml +56 -0
- data/.gitignore +11 -0
- data/Appraisals +11 -0
- data/Gemfile.lock +209 -49
- data/LICENSE.txt +4 -20
- data/README.md +155 -7
- data/Rakefile +35 -1
- data/app/models/awesome_explain/application_record.rb +5 -0
- data/app/models/awesome_explain/controller.rb +20 -0
- data/app/models/awesome_explain/delayed_job.rb +7 -0
- data/app/models/awesome_explain/explain.rb +23 -0
- data/app/models/awesome_explain/log.rb +7 -0
- data/app/models/awesome_explain/pg_dml_stat.rb +4 -0
- data/app/models/awesome_explain/pg_seq_scan.rb +4 -0
- data/app/models/awesome_explain/plan_node.rb +52 -0
- data/app/models/awesome_explain/plan_tree.rb +66 -0
- data/app/models/awesome_explain/sidekiq_worker.rb +7 -0
- data/app/models/awesome_explain/sql_explain.rb +14 -0
- data/app/models/awesome_explain/sql_plan_node.rb +73 -0
- data/app/models/awesome_explain/sql_plan_stats.rb +34 -0
- data/app/models/awesome_explain/sql_plan_tree.rb +133 -0
- data/app/models/awesome_explain/sql_query.rb +7 -0
- data/app/models/awesome_explain/stacktrace.rb +11 -0
- data/awesome_explain.gemspec +16 -5
- data/bin/rails +14 -0
- data/data/mongodb/customers.bson +0 -0
- data/data/mongodb/customers.metadata.json +1 -0
- data/data/mongodb/line_items.bson +0 -0
- data/data/mongodb/line_items.metadata.json +1 -0
- data/data/mongodb/orders.bson +0 -0
- data/data/mongodb/orders.metadata.json +1 -0
- data/data/mongodb/products.bson +0 -0
- data/data/mongodb/products.metadata.json +1 -0
- data/data/postgresql/dvdrental.tar +0 -0
- data/db/migrate/20200507214801_stacktraces.rb +12 -0
- data/db/migrate/20200507214949_controllers.rb +16 -0
- data/db/migrate/20200507215205_logs.rb +22 -0
- data/db/migrate/20200507215243_explains.rb +27 -0
- data/gemfiles/rails_4.gemfile +7 -0
- data/gemfiles/rails_4.gemfile.lock +208 -0
- data/gemfiles/rails_5.gemfile +7 -0
- data/gemfiles/rails_5.gemfile.lock +209 -0
- data/gemfiles/rails_6.gemfile +7 -0
- data/gemfiles/rails_6.gemfile.lock +233 -0
- data/images/universe.png +0 -0
- data/lib/awesome_explain.rb +79 -2
- data/lib/awesome_explain/config.rb +196 -0
- data/lib/awesome_explain/engine.rb +5 -0
- data/lib/awesome_explain/insights/active_record_insights.rb +137 -0
- data/lib/awesome_explain/insights/base.rb +18 -0
- data/lib/awesome_explain/insights/mongoid_insights.rb +44 -0
- data/lib/awesome_explain/insights/sql_plans_insights.rb +64 -0
- data/lib/awesome_explain/kernel.rb +17 -0
- data/lib/awesome_explain/mongodb/base.rb +4 -0
- data/lib/awesome_explain/mongodb/command_start.rb +84 -0
- data/lib/awesome_explain/mongodb/command_success.rb +58 -0
- data/lib/awesome_explain/mongodb/formatter.rb +62 -0
- data/lib/awesome_explain/mongodb/helpers.rb +71 -0
- data/lib/awesome_explain/queue/command.rb +17 -0
- data/lib/awesome_explain/queue/simple_queue.rb +88 -0
- data/lib/awesome_explain/renderers/active_record.rb +114 -0
- data/lib/awesome_explain/renderers/base.rb +2 -0
- data/lib/awesome_explain/renderers/mongoid.rb +20 -33
- data/lib/awesome_explain/sidekiq_middleware.rb +17 -0
- data/lib/awesome_explain/stats/postgresql.rb +54 -0
- data/lib/awesome_explain/subscribers/active_record_passive_subscriber.rb +82 -0
- data/lib/awesome_explain/subscribers/active_record_subscriber.rb +187 -0
- data/lib/awesome_explain/subscribers/base.rb +3 -0
- data/lib/awesome_explain/subscribers/command_subscriber.rb +53 -0
- data/lib/awesome_explain/tasks/db.rb +325 -0
- data/lib/awesome_explain/utils/color.rb +16 -0
- data/lib/awesome_explain/version.rb +1 -1
- data/lib/tasks/ae.rake +28 -0
- data/lib/tasks/awesome_explain_tasks.rake +4 -0
- metadata +242 -25
- data/.travis.yml +0 -19
@@ -0,0 +1,88 @@
|
|
1
|
+
# module AwesomeExplain::Queue
|
2
|
+
# class SimpleQueue
|
3
|
+
# def initialize
|
4
|
+
# @elems = []
|
5
|
+
# @mutex = Mutex.new
|
6
|
+
# @cond_var = ConditionVariable.new
|
7
|
+
# end
|
8
|
+
|
9
|
+
# def <<(elem)
|
10
|
+
# @mutex.synchronize do
|
11
|
+
# @elems << elem
|
12
|
+
# @cond_var.signal
|
13
|
+
# end
|
14
|
+
# end
|
15
|
+
|
16
|
+
# def pop(blocking = true, timeout = nil)
|
17
|
+
# @mutex.synchronize do
|
18
|
+
# if blocking
|
19
|
+
# if timeout.nil?
|
20
|
+
# while @elems.empty?
|
21
|
+
# @cond_var.wait(@mutex)
|
22
|
+
# end
|
23
|
+
# else
|
24
|
+
# timeout_time = Time.now.to_f + timeout
|
25
|
+
# while @elems.empty? && (remaining_time = timeout_time - Time.now.to_f) > 0
|
26
|
+
# @cond_var.wait(@mutex, remaining_time)
|
27
|
+
# end
|
28
|
+
# end
|
29
|
+
# end
|
30
|
+
# raise ThreadError, 'queue empty' if @elems.empty?
|
31
|
+
# sleep 1
|
32
|
+
# @elems.shift
|
33
|
+
# end
|
34
|
+
# end
|
35
|
+
# end
|
36
|
+
# end
|
37
|
+
|
38
|
+
module AwesomeExplain::Queue
|
39
|
+
class SimpleQueue
|
40
|
+
include Singleton
|
41
|
+
|
42
|
+
def initialize
|
43
|
+
@queue = Queue.new
|
44
|
+
# Thread.new do
|
45
|
+
# puts 'while true ==============================='
|
46
|
+
# command = @queue.pop(false)
|
47
|
+
# # command.run if command
|
48
|
+
# end
|
49
|
+
# @read_io, @write_io = IO.pipe
|
50
|
+
end
|
51
|
+
|
52
|
+
def <<(o)
|
53
|
+
# pop(false) until @queue.size < 2
|
54
|
+
if @queue.size >= 2
|
55
|
+
items = []
|
56
|
+
while @queue.size >= 2
|
57
|
+
items << @queue.pop
|
58
|
+
end
|
59
|
+
Thread.new { sleep 2; puts "Poped #{items}"; puts items.inspect }
|
60
|
+
end
|
61
|
+
puts "Adding to queue @@@@@@@@@@@@@"
|
62
|
+
@queue << o
|
63
|
+
# @write_io << '.'
|
64
|
+
self
|
65
|
+
end
|
66
|
+
|
67
|
+
def pop(nonblock=false)
|
68
|
+
# return unless @queue.size >= 5
|
69
|
+
# @queue.size.times.each do
|
70
|
+
|
71
|
+
# end
|
72
|
+
puts Thread.current.inspect
|
73
|
+
o = @queue.pop(nonblock)
|
74
|
+
# @read_io.read(1)
|
75
|
+
puts "<------ Element poped ------>"
|
76
|
+
puts o
|
77
|
+
o
|
78
|
+
end
|
79
|
+
|
80
|
+
def size
|
81
|
+
@queue.size
|
82
|
+
end
|
83
|
+
|
84
|
+
def to_io
|
85
|
+
@read_io
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
module AwesomeExplain
|
2
|
+
module Renderers
|
3
|
+
class ActiveRecord
|
4
|
+
attr_reader :result, :query, :sql_explain
|
5
|
+
|
6
|
+
def initialize(query, result = nil)
|
7
|
+
@query = query
|
8
|
+
@result = result || explain_query
|
9
|
+
end
|
10
|
+
|
11
|
+
def explain_query
|
12
|
+
explain = AwesomeExplain::Config.instance.connection.raw_connection.exec(
|
13
|
+
"EXPLAIN (ANALYZE true, COSTS true, FORMAT json) #{query.to_sql}"
|
14
|
+
)
|
15
|
+
explain = explain.map { |h| h.values.first }.join("\n")
|
16
|
+
|
17
|
+
@sql_explain = SqlExplain.new(explain_output: explain)
|
18
|
+
end
|
19
|
+
|
20
|
+
def print
|
21
|
+
table = Terminal::Table.new do |t|
|
22
|
+
general_stats_section t
|
23
|
+
table_stats_section t
|
24
|
+
node_types_section t
|
25
|
+
index_stats_section t
|
26
|
+
end
|
27
|
+
puts table
|
28
|
+
end
|
29
|
+
|
30
|
+
def plan_stats
|
31
|
+
@plan_stats ||= @sql_explain.tree.plan_stats
|
32
|
+
end
|
33
|
+
|
34
|
+
def table_stats
|
35
|
+
@table_stats ||= plan_stats.table_stats
|
36
|
+
end
|
37
|
+
|
38
|
+
def node_type_stats
|
39
|
+
@node_type_stats ||= plan_stats.node_type_stats
|
40
|
+
end
|
41
|
+
|
42
|
+
def index_stats
|
43
|
+
@index_stats ||= plan_stats.index_stats
|
44
|
+
end
|
45
|
+
|
46
|
+
def seq_scans_row
|
47
|
+
color = plan_stats.seq_scans.positive? ? :cyan : :green
|
48
|
+
|
49
|
+
seq_scans_label = AwesomeExplain::Utils::Color.fg_color(
|
50
|
+
color,
|
51
|
+
'Seq Scans'
|
52
|
+
)
|
53
|
+
|
54
|
+
seq_scans_val = AwesomeExplain::Utils::Color.fg_color(
|
55
|
+
color,
|
56
|
+
plan_stats.seq_scans.to_s
|
57
|
+
)
|
58
|
+
|
59
|
+
[seq_scans_label, seq_scans_val]
|
60
|
+
end
|
61
|
+
|
62
|
+
def general_stats_section(t)
|
63
|
+
title = AwesomeExplain::Utils::Color.fg_color :yellow, 'General Stats'
|
64
|
+
t << [{ value: title, alignment: :center, colspan: 2}]
|
65
|
+
t << :separator
|
66
|
+
t << ['Table', 'Count']
|
67
|
+
t << :separator
|
68
|
+
t << ['Total Rows Planned', plan_stats.total_rows_planned]
|
69
|
+
t << ['Total Rows', plan_stats.total_rows]
|
70
|
+
t << ['Total Loops', plan_stats.total_loops]
|
71
|
+
t << seq_scans_row
|
72
|
+
t << ['Indexes Used', plan_stats.index_stats.size]
|
73
|
+
end
|
74
|
+
|
75
|
+
def table_stats_section(t)
|
76
|
+
title = AwesomeExplain::Utils::Color.fg_color :yellow, 'Table Stats'
|
77
|
+
t << :separator
|
78
|
+
t << [{ value: title, alignment: :center, colspan: 2}]
|
79
|
+
t << :separator
|
80
|
+
t << ['Table', 'Count']
|
81
|
+
t << :separator
|
82
|
+
table_stats.each do |table_name, stats|
|
83
|
+
t << [table_name, stats.dig(:count)]
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def node_types_section(t)
|
88
|
+
title = AwesomeExplain::Utils::Color.fg_color :yellow, 'Node Type Stats'
|
89
|
+
t << :separator
|
90
|
+
t << [{ value: title, alignment: :center, colspan: 2}]
|
91
|
+
t << :separator
|
92
|
+
t << ['Node Type', 'Count']
|
93
|
+
t << :separator
|
94
|
+
node_type_stats.each do |node_type, stats|
|
95
|
+
t << [node_type, stats.dig(:count)]
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def index_stats_section(t)
|
100
|
+
if index_stats.size.positive?
|
101
|
+
title = AwesomeExplain::Utils::Color.fg_color :yellow, 'Index Stats'
|
102
|
+
t << :separator
|
103
|
+
t << [{ value: title, alignment: :center, colspan: 2}]
|
104
|
+
t << :separator
|
105
|
+
t << ['Index Name', 'Count']
|
106
|
+
t << :separator
|
107
|
+
index_stats.each do |index, stats|
|
108
|
+
t << [index, stats.dig(:count)]
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -3,41 +3,25 @@ module AwesomeExplain
|
|
3
3
|
class Mongoid
|
4
4
|
attr_reader :result, :query
|
5
5
|
|
6
|
-
|
7
|
-
none: 0, bright: 1, black: 30,
|
8
|
-
red: 31, green: 32, yellow: 33,
|
9
|
-
blue: 34, magenta: 35, cyan: 36,
|
10
|
-
white: 37, default: 39
|
11
|
-
}
|
12
|
-
|
13
|
-
def initialize(query)
|
6
|
+
def initialize(query, result = nil)
|
14
7
|
@query = query
|
8
|
+
@result = result || query.explain
|
15
9
|
end
|
16
10
|
|
17
11
|
def print
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
# Text foreground color
|
24
|
-
def fg_color(clr, text = nil)
|
25
|
-
"\x1B[" + (COLOR_ESCAPES[clr] || 0).to_s + 'm' + (text ? text + "\x1B[0m" : '')
|
26
|
-
end
|
27
|
-
|
28
|
-
# Text background color
|
29
|
-
def bg_color(clr, text = nil)
|
30
|
-
"\x1B[" + ((COLOR_ESCAPES[clr] || 0) + 10).to_s + 'm' + (text ? text + "\x1B[0m" : '')
|
12
|
+
ap result, indent: -2
|
13
|
+
puts
|
14
|
+
puts explain_summary
|
15
|
+
puts
|
31
16
|
end
|
32
17
|
|
33
|
-
def
|
34
|
-
ap result, indent: -2
|
18
|
+
def explain_summary
|
35
19
|
table = Terminal::Table.new do |t|
|
36
20
|
winning_plan_label = 'Winning Plan'
|
37
21
|
plan_data = winning_plan_data
|
38
22
|
winning_plan_str = plan_data[0]
|
39
23
|
used_indexes = plan_data[1]
|
40
|
-
winning_plan_label = fg_color :red, winning_plan_label if winning_plan_str =~ /COLLSCAN/
|
24
|
+
winning_plan_label = AwesomeExplain::Utils::Color.fg_color :red, winning_plan_label if winning_plan_str =~ /COLLSCAN/
|
41
25
|
t << [winning_plan_label, winning_plan_str]
|
42
26
|
t << :separator
|
43
27
|
t << ['Used Indexes', used_indexes.join(', ')]
|
@@ -59,8 +43,8 @@ module AwesomeExplain
|
|
59
43
|
exec_label_ms = 'Execution time(ms)'
|
60
44
|
|
61
45
|
if exec_time > 10
|
62
|
-
exec_label = fg_color :red, exec_label
|
63
|
-
exec_label_ms = fg_color :red, exec_label_ms
|
46
|
+
exec_label = AwesomeExplain::Utils::Color.fg_color :red, exec_label
|
47
|
+
exec_label_ms = AwesomeExplain::Utils::Color.fg_color :red, exec_label_ms
|
64
48
|
end
|
65
49
|
t << [exec_label_ms, exec_time_ms]
|
66
50
|
t << :separator
|
@@ -68,16 +52,18 @@ module AwesomeExplain
|
|
68
52
|
end
|
69
53
|
end
|
70
54
|
|
71
|
-
|
72
|
-
|
73
|
-
|
55
|
+
table
|
56
|
+
end
|
57
|
+
|
58
|
+
def parsed_query
|
59
|
+
root.dig('parsedQuery')
|
74
60
|
end
|
75
61
|
|
76
62
|
def winning_plan_data
|
77
63
|
used_indexes = []
|
78
64
|
plan = winning_plan
|
79
65
|
plan_str = stage_label_and_stats(plan)
|
80
|
-
plan_str = dig_input_stages(plan.dig('inputStage'), plan_str, used_indexes) if plan
|
66
|
+
plan_str = dig_input_stages(plan.dig('inputStage'), plan_str, used_indexes) if plan&.dig('inputStage')
|
81
67
|
|
82
68
|
[plan_str, used_indexes]
|
83
69
|
end
|
@@ -87,15 +73,15 @@ module AwesomeExplain
|
|
87
73
|
end
|
88
74
|
|
89
75
|
def winning_plan
|
90
|
-
root.dig('executionStats', 'executionStages') || root.dig('queryPlanner', 'winningPlan')
|
76
|
+
root.dig('executionStats', 'executionStages') || root.dig('queryPlanner', 'winningPlan') || root['stages'].first['$cursor'].dig('queryPlanner', 'winningPlan') || {}
|
91
77
|
end
|
92
78
|
|
93
79
|
def rejected_plans
|
94
|
-
root.dig('queryPlanner', 'rejectedPlans')
|
80
|
+
root.dig('queryPlanner', 'rejectedPlans') || root['stages']&.first['$cursor'].dig('queryPlanner', 'rejectedPlans') || {}
|
95
81
|
end
|
96
82
|
|
97
83
|
def execution_stats
|
98
|
-
root.dig('executionStats')
|
84
|
+
root.dig('executionStats') || root['stages']&.first&.dig('$cursor')&.dig('executionStats') || {}
|
99
85
|
end
|
100
86
|
|
101
87
|
def dig_input_stages(stage, str, used_indexes, input_stages = false)
|
@@ -123,6 +109,7 @@ module AwesomeExplain
|
|
123
109
|
end
|
124
110
|
|
125
111
|
def stage_label_and_stats(stage)
|
112
|
+
return unless stage.present?
|
126
113
|
str = "#{stage.dig('stage')} ("
|
127
114
|
str += "#{stage.dig('docsExamined')} / " if stage.dig('docsExamined').present?
|
128
115
|
str += stage.dig('nReturned').to_s if stage.dig('nReturned').present?
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module AwesomeExplain
|
2
|
+
class SidekiqMiddleware
|
3
|
+
# def call(worker_class, job, queue, redis_pool)
|
4
|
+
def call(worker_class, job, queue)
|
5
|
+
begin
|
6
|
+
Thread.current[:sidekiq_worker_class] = worker_class.class.name
|
7
|
+
Thread.current[:sidekiq_job] = job
|
8
|
+
Thread.current[:sidekiq_queue] = queue
|
9
|
+
Thread.current['ae_source'] = 'sidekiq'
|
10
|
+
rescue => exception
|
11
|
+
# Do nothing
|
12
|
+
end
|
13
|
+
|
14
|
+
yield
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module AwesomeExplain
|
2
|
+
module Stats
|
3
|
+
class PostgreSQL
|
4
|
+
def self.upsert!
|
5
|
+
upsert_seq_scans!
|
6
|
+
upsert_dml_stats!
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.upsert_seq_scans!
|
10
|
+
result = ActiveRecord::Base.connection.execute(seq_scans_sql).to_a
|
11
|
+
result.each do |row|
|
12
|
+
row = OpenStruct.new(row)
|
13
|
+
pg_seq_scan = AwesomeExplain::PgSeqScan.find_or_create_by({
|
14
|
+
schema_name: row.schema_name,
|
15
|
+
table_name: row.table_name
|
16
|
+
})
|
17
|
+
|
18
|
+
pg_seq_scan.update({
|
19
|
+
seq_scan: row.seq_scan,
|
20
|
+
seq_tup_read: row.seq_tup_read,
|
21
|
+
idx_scan: row.idx_scan,
|
22
|
+
idx_tup_fetch: row.idx_tup_fetch,
|
23
|
+
size_bytes: row.size_bytes
|
24
|
+
})
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.upsert_dml_stats!
|
29
|
+
result = ActiveRecord::Base.connection.execute(dml_stats_sql).to_a
|
30
|
+
result.each do |row|
|
31
|
+
row = OpenStruct.new(row)
|
32
|
+
pg_dml_stat = AwesomeExplain::PgDmlStat.find_or_create_by({
|
33
|
+
schema_name: row.schema_name,
|
34
|
+
table_name: row.table_name
|
35
|
+
})
|
36
|
+
|
37
|
+
pg_dml_stat.update({
|
38
|
+
total_inserts: row.n_tup_ins,
|
39
|
+
total_updates: row.n_tup_upd,
|
40
|
+
total_deletes: row.n_tup_del,
|
41
|
+
})
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.seq_scans_sql
|
46
|
+
"select schemaname as schema_name, relname as table_name, seq_scan, seq_tup_read, idx_scan, idx_tup_fetch, pg_relation_size(schemaname::text || '.'::text || relname::text) as size_bytes from pg_stat_user_tables"
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.dml_stats_sql
|
50
|
+
"SELECT schemaname as schema_name, relname as table_name, n_tup_ins, n_tup_upd, n_tup_del FROM pg_stat_user_tables"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module AwesomeExplain::Subscribers
|
2
|
+
class ActiveRecordPassiveSubscriber < ActiveSupport::LogSubscriber
|
3
|
+
def sql(event)
|
4
|
+
if track_sql(event)
|
5
|
+
sql = event.payload[:sql]
|
6
|
+
begin
|
7
|
+
table_name_and_schema = extract_table_name_and_schema(sql)
|
8
|
+
table_name = table_name_and_schema.first
|
9
|
+
schema_name = table_name_and_schema.last
|
10
|
+
request_id = event.payload[:connection_id]
|
11
|
+
binds = event.payload[:binds]
|
12
|
+
cached = event.payload[:name] == 'CACHE'
|
13
|
+
operation = extract_sql_operation(sql)
|
14
|
+
name = event.payload[:name]
|
15
|
+
|
16
|
+
connection_id = event.payload[:connection_id]
|
17
|
+
connection = ::AwesomeExplain::Config.instance.connection
|
18
|
+
explain_uuid = SecureRandom.uuid
|
19
|
+
explain = connection.raw_connection.exec("EXPLAIN (ANALYZE true, COSTS true, FORMAT json) #{sql}")
|
20
|
+
explain = explain.map { |h| h.values.first }.join("\n")
|
21
|
+
explain = ::AwesomeExplain::SqlExplain.new(explain_output: explain)
|
22
|
+
AwesomeExplain::Insights::SqlPlansInsights.add explain.tree.plan_stats
|
23
|
+
AwesomeExplain::Insights::SqlPlansInsights.add_query sql
|
24
|
+
rescue => exception
|
25
|
+
logger.warn sql
|
26
|
+
logger.warn exception.to_s
|
27
|
+
logger.warn exception.backtrace[0..5]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def track_sql(event)
|
33
|
+
return false unless Thread.current['ae_analyze']
|
34
|
+
return false if event.payload[:connection].class.name == 'ActiveRecord::ConnectionAdapters::SQLite3Adapter'
|
35
|
+
sql = event.payload[:sql]
|
36
|
+
!sql.match(/EXPLAIN|SAVEPOINT|nextval|CREATE|BEGIN|COMMIT|ROLLBACK|begin|commit|rollback|ar_|sql_|pg_|explain|logs|controllers|stacktraces|schema_migrations|delayed_jobs/) &&
|
37
|
+
sql.strip == sql &&
|
38
|
+
event.payload[:name] != 'SCHEMA'
|
39
|
+
end
|
40
|
+
|
41
|
+
def ddm_query?(sql)
|
42
|
+
matched = sql.match(/INSERT|DELETE|UPDATE/)
|
43
|
+
matched.present? && matched[0].present?
|
44
|
+
end
|
45
|
+
|
46
|
+
def resolve_source_name
|
47
|
+
Thread.current['ae_source'] || DEFAULT_SOURCE_NAME
|
48
|
+
end
|
49
|
+
|
50
|
+
def db_logging_enbled?
|
51
|
+
# return true if Thread.current['ae_analyze']
|
52
|
+
return false if Rails.const_defined?('Console')
|
53
|
+
true
|
54
|
+
end
|
55
|
+
|
56
|
+
def extract_sql_operation(sql)
|
57
|
+
sql.match(/SELECT|INSERT|DELETE|UPDATE/)[0]
|
58
|
+
end
|
59
|
+
|
60
|
+
def extract_table_name_and_schema(sql)
|
61
|
+
matched = sql.match(/FROM\s+(\"\w+\")\.?(\"\w+\")?/)
|
62
|
+
return reduce_table_and_schema(matched) if matched && matched[1].present?
|
63
|
+
|
64
|
+
matched = sql.match(/INSERT INTO\s+(\"\w+\")\.?(\"\w+\")?/)
|
65
|
+
return reduce_table_and_schema(matched) if matched && matched[1].present?
|
66
|
+
|
67
|
+
matched = sql.match(/UPDATE\s+(\"\w+\")\.?(\"\w+\")?/)
|
68
|
+
return reduce_table_and_schema(matched) if matched && matched[1].present?
|
69
|
+
end
|
70
|
+
|
71
|
+
def reduce_table_and_schema(matched)
|
72
|
+
if matched[1].present?
|
73
|
+
table_name = matched[2].nil? ? matched[1] : matched[2]
|
74
|
+
table_name = table_name.gsub(/\"/, '')
|
75
|
+
schema_name = matched[2].nil? ? 'public' : matched[1]
|
76
|
+
schema_name = schema_name.gsub(/\"/, '')
|
77
|
+
|
78
|
+
return [table_name, schema_name]
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|