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



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)


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)


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)


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



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



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



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



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



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.



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

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