module NewRelic::Agent::Database::ExplainPlanHelpers

Constants

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.

QUERY_PLAN
SELECT
SQLITE_EXPLAIN_COLUMNS
SUPPORTED_ADAPTERS_FOR_EXPLAIN

Public Instance Methods

handle_exception_in_explain() { || ... } click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# File lib/new_relic/agent/database/explain_plan_helpers.rb, line 30
def multiple_queries?(sql)
  sql =~ MULTIPLE_QUERIES
end
parameterized?(sql) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# 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) click to toggle source

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.

# File lib/new_relic/agent/database/explain_plan_helpers.rb, line 93
def string_explain_plan_results(results)
  [nil, [results]]
end