Module: NewRelic::Agent::Database::ExplainPlanHelpers

Included in:
Statement
Defined in:
lib/new_relic/agent/database/explain_plan_helpers.rb

Constant Summary collapse

SUPPORTED_ADAPTERS_FOR_EXPLAIN =
[:postgres, :mysql2, :mysql, :sqlite]
SELECT =
'select'.freeze
MULTIPLE_QUERIES =

SQL containing a semicolon in the middle (with something other than whitespace after it) may contain two or more queries. It’s not safe to EXPLAIN this kind of expression, since it could lead to executing unwanted SQL.

Regexp.new(';\s*\S+')
QUERY_PLAN =
'QUERY PLAN'.freeze
SQLITE_EXPLAIN_COLUMNS =
%w[addr opcode p1 p2 p3 p4 p5 comment]

Instance Method Summary collapse

Instance Method Details

#handle_exception_in_explainObject

[View source]

34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/new_relic/agent/database/explain_plan_helpers.rb', line 34

def handle_exception_in_explain
  yield
rescue => e
  begin
    # guarantees no throw from explain_sql
    ::NewRelic::Agent.logger.error('Error getting query plan:', e)
    nil
  rescue
    # double exception. throw up your hands
    nil
  end
end

#is_select?(sql) ⇒ Boolean

Returns:

  • (Boolean)
[View source]

15
16
17
# File 'lib/new_relic/agent/database/explain_plan_helpers.rb', line 15

def is_select?(sql)
  NewRelic::Agent::Database.parse_operation_from_query(sql) == SELECT
end

#multiple_queries?(sql) ⇒ Boolean

Returns:

  • (Boolean)
[View source]

30
31
32
# File 'lib/new_relic/agent/database/explain_plan_helpers.rb', line 30

def multiple_queries?(sql)
  sql =~ MULTIPLE_QUERIES
end

#parameterized?(sql) ⇒ Boolean

Returns:

  • (Boolean)
[View source]

19
20
21
# File 'lib/new_relic/agent/database/explain_plan_helpers.rb', line 19

def parameterized?(sql)
  Obfuscator.instance.obfuscate_single_quote_literals(sql) =~ /\$\d+/
end

#process_explain_results_mysql(results) ⇒ Object

[View source]

97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/new_relic/agent/database/explain_plan_helpers.rb', line 97

def process_explain_results_mysql(results)
  headers = []
  values = []
  if results.is_a?(Array)
    # We're probably using the jdbc-mysql gem for JRuby, which will give
    # us an array of hashes.
    headers = results.first.keys
    results.each do |row|
      values << headers.map { |h| row[h] }
    end
  else
    # We're probably using the native mysql driver gem, which will give us
    # a Mysql::Result object that responds to each_hash
    results.each_hash do |row|
      headers = row.keys
      values << headers.map { |h| row[h] }
    end
  end
  [headers, values]
end

#process_explain_results_mysql2(results) ⇒ Object

[View source]

118
119
120
121
122
123
# File 'lib/new_relic/agent/database/explain_plan_helpers.rb', line 118

def process_explain_results_mysql2(results)
  headers = results.fields
  values = []
  results.each { |row| values << row }
  [headers, values]
end

#process_explain_results_postgres(results) ⇒ Object

[View source]

69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/new_relic/agent/database/explain_plan_helpers.rb', line 69

def process_explain_results_postgres(results)
  if defined?(::ActiveRecord::Result) && results.is_a?(::ActiveRecord::Result)
    query_plan_string = results.rows.join("\n")
  elsif results.is_a?(String)
    query_plan_string = results
  else
    lines = []
    results.each { |row| lines << row[QUERY_PLAN] }
    query_plan_string = lines.join("\n")
  end

  unless NewRelic::Agent::Database.record_sql_method == :raw
    query_plan_string = NewRelic::Agent::Database::PostgresExplainObfuscator.obfuscate(query_plan_string)
  end
  values = query_plan_string.split("\n").map { |line| [line] }

  [[QUERY_PLAN], values]
end

#process_explain_results_sqlite(results) ⇒ Object

[View source]

127
128
129
130
131
132
133
134
# File 'lib/new_relic/agent/database/explain_plan_helpers.rb', line 127

def process_explain_results_sqlite(results)
  headers = SQLITE_EXPLAIN_COLUMNS
  values = []
  results.each do |row|
    values << headers.map { |h| row[h] }
  end
  [headers, values]
end

#process_resultset(results, adapter) ⇒ Object

[View source]

47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/new_relic/agent/database/explain_plan_helpers.rb', line 47

def process_resultset(results, adapter)
  if adapter == :postgres
    return process_explain_results_postgres(results)
  elsif defined?(::ActiveRecord::Result) && results.is_a?(::ActiveRecord::Result)
    # Note if adapter is mysql, will only have headers, not values
    return [results.columns, results.rows]
  elsif results.is_a?(String)
    return string_explain_plan_results(results)
  end

  case adapter
  when :mysql2
    process_explain_results_mysql2(results)
  when :mysql
    process_explain_results_mysql(results)
  when :sqlite
    process_explain_results_sqlite(results)
  end
end

#string_explain_plan_results(results) ⇒ Object

Sequel returns explain plans as just one big pre-formatted String In that case, we send a nil headers array, and the single string wrapped in an array for the values. Note that we don’t use this method for Postgres explain plans, since they need to be passed through the explain plan obfuscator first.

[View source]

93
94
95
# File 'lib/new_relic/agent/database/explain_plan_helpers.rb', line 93

def string_explain_plan_results(results)
  [nil, [results]]
end