module OneApm::Agent::Database

Constants

OA_KNOWN_OPERATIONS
OA_MAX_QUERY_LENGTH
OA_QUERY_PLAN
OA_RECORD_FOR
OA_SQLITE_EXPLAIN_COLUMNS
OA_SQL_COMMENT_REGEX
OA_SUPPORTED_ADAPTERS_FOR_EXPLAIN

Public Instance Methods

adapter_from_config(config) click to toggle source
# File lib/one_apm/agent/database.rb, line 82
def adapter_from_config(config)
  if config[:adapter]
    return config[:adapter].to_s
  elsif config[:uri] && config[:uri].to_s =~ /^jdbc:([^:]+):/
    return $1
  end
end
capture_query(query) click to toggle source
# File lib/one_apm/agent/database.rb, line 28
def capture_query(query)
  Helper.correctly_encoded(truncate_query(query))
end
close_connections() click to toggle source
# File lib/one_apm/agent/database.rb, line 78
def close_connections
  ConnectionManager.instance.close_connections
end
explain_sql(sql, connection_config, &explainer) click to toggle source
# File lib/one_apm/agent/database.rb, line 90
def explain_sql(sql, connection_config, &explainer)
  return nil unless sql && connection_config
  statement = sql.split(";\n")[0] # only explain the first
  explain_plan = explain_statement(statement, connection_config, &explainer)
  return explain_plan || []
end
explain_statement(statement, config, &explainer) click to toggle source
# File lib/one_apm/agent/database.rb, line 99
def explain_statement(statement, config, &explainer)
  return unless is_select?(statement)

  if statement[-3,3] == '...'
    OneApm::Manager.logger.debug('Unable to collect explain plan for truncated query.')
    return
  end

  if parameterized?(statement)
    OneApm::Manager.logger.debug('Unable to collect explain plan for parameterized query.')
    return
  end

  adapter = adapter_from_config(config)
  if !OA_SUPPORTED_ADAPTERS_FOR_EXPLAIN.include?(adapter)
    OneApm::Manager.logger.debug("Not collecting explain plan because an unknown connection adapter ('#{adapter}') was used.")
    return
  end

  handle_exception_in_explain do
    start = Time.now
    plan = explainer.call(config, statement)
    OneApm::Manager.record_metric("Supportability/Database/execute_explain_plan", Time.now - start)
    return process_resultset(plan, adapter) if plan
  end
end
get_connection(config, &connector) click to toggle source
# File lib/one_apm/agent/database.rb, line 74
def get_connection(config, &connector)
  ConnectionManager.instance.get_connection(config, &connector)
end
handle_exception_in_explain() { || ... } click to toggle source
# File lib/one_apm/agent/database.rb, line 200
def handle_exception_in_explain
  yield
rescue => e
  begin
    # guarantees no throw from explain_sql
    OneApm::Manager.logger.error("Error getting query plan:", e)
    nil
  rescue
    # double exception. throw up your hands
    nil
  end
end
is_select?(statement) click to toggle source
# File lib/one_apm/agent/database.rb, line 237
def is_select?(statement)
  parse_operation_from_query(statement) == 'select'
end
obfuscate_sql(sql) click to toggle source
# File lib/one_apm/agent/database.rb, line 40
def obfuscate_sql(sql)
  Obfuscator.instance.obfuscator.call(sql)
end
parameterized?(statement) click to toggle source
# File lib/one_apm/agent/database.rb, line 241
def parameterized?(statement)
  Obfuscator.instance.obfuscate_single_quote_literals(statement) =~ /\$\d+/
end
parse_operation_from_query(sql) click to toggle source
# File lib/one_apm/agent/database.rb, line 229
def parse_operation_from_query(sql)
  sql = sql.gsub(OA_SQL_COMMENT_REGEX, '')
  if sql =~ /(\w+)/
    op = $1.downcase
    return op if OA_KNOWN_OPERATIONS.include?(op)
  end
end
process_explain_results_mysql(results) click to toggle source
# File lib/one_apm/agent/database.rb, line 162
def process_explain_results_mysql(results)
  return string_explain_plan_results(results) if results.is_a?(String)
  headers = []
  values  = []
  if results.is_a?(Array)
    headers = results.first.keys
    results.each do |row|
      values << headers.map { |h| row[h] }
    end
  else
    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/one_apm/agent/database.rb, line 180
def process_explain_results_mysql2(results)
  return string_explain_plan_results(results) if results.is_a?(String)
  headers = results.fields
  values  = []
  results.each { |row| values << row }
  [headers, values]
end
process_explain_results_postgres(results) click to toggle source
# File lib/one_apm/agent/database.rb, line 141
def process_explain_results_postgres(results)
  if results.is_a?(String)
    query_plan_string = results
  else
    lines = []
    results.each { |row| lines << row[OA_QUERY_PLAN] }
    query_plan_string = lines.join("\n")
  end

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

  [[OA_QUERY_PLAN], values]
end
process_explain_results_sqlite(results) click to toggle source
# File lib/one_apm/agent/database.rb, line 190
def process_explain_results_sqlite(results)
  return string_explain_plan_results(results) if results.is_a?(String)
  headers = OA_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/one_apm/agent/database.rb, line 126
def process_resultset(results, adapter)
  case adapter.to_s
  when 'postgres', 'postgresql'
    process_explain_results_postgres(results)
  when 'mysql2'
    process_explain_results_mysql2(results)
  when 'mysql'
    process_explain_results_mysql(results)
  when 'sqlite'
    process_explain_results_sqlite(results)
  end
end
record_sql_method(config_section=:transaction_tracer) click to toggle source
# File lib/one_apm/agent/database.rb, line 48
def record_sql_method(config_section=:transaction_tracer)
  case OneApm::Manager.config["#{config_section}.record_sql".to_sym].to_s
  when 'off'
    :off
  when 'none'
    :off
  when 'false'
    :off
  when 'raw'
    :raw
  else
    :obfuscated
  end
end
set_sql_obfuscator(type, &block) click to toggle source
# File lib/one_apm/agent/database.rb, line 44
def set_sql_obfuscator(type, &block)
  Obfuscator.instance.set_sql_obfuscator(type, &block)
end
should_collect_explain_plans?(config_section=:transaction_tracer) click to toggle source
# File lib/one_apm/agent/database.rb, line 69
def should_collect_explain_plans?(config_section=:transaction_tracer)
  should_record_sql?(config_section) &&
    OneApm::Manager.config["#{config_section}.explain_enabled".to_sym]
end
should_record_sql?(config_section=:transaction_tracer) click to toggle source
# File lib/one_apm/agent/database.rb, line 65
def should_record_sql?(config_section=:transaction_tracer)
  OA_RECORD_FOR.include?(record_sql_method(config_section))
end
string_explain_plan_results(results) click to toggle source
# File lib/one_apm/agent/database.rb, line 158
def string_explain_plan_results(results)
  [nil, [results]]
end
truncate_query(query) click to toggle source
# File lib/one_apm/agent/database.rb, line 32
def truncate_query(query)
  if query.length > (OA_MAX_QUERY_LENGTH - 4)
    query[0..OA_MAX_QUERY_LENGTH - 4] + '...'
  else
    query
  end
end