Module: TingYun::Agent::Database::ExplainPlanHelpers

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

Constant Summary collapse

SQL_COMMENT_REGEX =
Regexp.new('/\*.*?\*/', Regexp::MULTILINE).freeze
EMPTY_STRING =
''.freeze
KNOWN_OPERATIONS =
[
    'alter',
    'select',
    'update',
    'delete',
    'insert',
    'create',
    'show',
    'set',
    'exec',
    'execute',
    'call'
]
POSTGRES_PREFIX =
'postgres'.freeze
MYSQL_PREFIX =
'mysql'.freeze
MYSQL2_PREFIX =
'mysql2'.freeze
SQLITE_PREFIX =
'sqlite'.freeze
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



9
10
11
12
13
14
15
16
17
18
19
20
# File 'lib/ting_yun/agent/database/explain_plan_helpers.rb', line 9

def handle_exception_in_explain
  yield
rescue => e
  begin
    # guarantees no throw from explain_sql
    ::TingYun::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)


22
23
24
# File 'lib/ting_yun/agent/database/explain_plan_helpers.rb', line 22

def is_select?(sql)
  parse_operation_from_query(sql) == 'select'
end

#parameterized?(sql) ⇒ Boolean

Returns:

  • (Boolean)


26
27
28
# File 'lib/ting_yun/agent/database/explain_plan_helpers.rb', line 26

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

#parse_operation_from_query(sql) ⇒ Object



48
49
50
51
52
53
54
# File 'lib/ting_yun/agent/database/explain_plan_helpers.rb', line 48

def parse_operation_from_query(sql)
  sql = TingYun::Helper.correctly_encoded(sql).gsub(SQL_COMMENT_REGEX, EMPTY_STRING)
  if sql =~ /(\w+)/
    op = $1.downcase
    return op if KNOWN_OPERATIONS.include?(op)
  end
end

#process_explain_results_mysql(results) ⇒ Object



136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/ting_yun/agent/database/explain_plan_helpers.rb', line 136

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
  {"dialect"=> "MySQL", "keys"=>headers, "values"=>values}
end

#process_explain_results_mysql2(results) ⇒ Object



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

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

#process_explain_results_postgres(results) ⇒ Object



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/ting_yun/agent/database/explain_plan_helpers.rb', line 105

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 TingYun::Agent::Database.record_sql_method("nbs.action_tracer.record_sql") == :raw
    query_plan_string = TingYun::Agent::Database::Obfuscator.instance.obfuscate_postgres_explain(query_plan_string)
  end
  values = query_plan_string.split("\n").map { |line| [line] }

  {"dialect"=> "PostgreSQL", "keys"=>[QUERY_PLAN], "values"=>values}
end

#process_explain_results_sqlite(results) ⇒ Object



159
160
161
162
163
164
165
166
# File 'lib/ting_yun/agent/database/explain_plan_helpers.rb', line 159

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

#process_resultset(results, adapter) ⇒ Object



81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/ting_yun/agent/database/explain_plan_helpers.rb', line 81

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)
    else
      return {}
  end
end

#string_explain_plan_results(adpater, results) ⇒ Object



125
126
127
# File 'lib/ting_yun/agent/database/explain_plan_helpers.rb', line 125

def string_explain_plan_results(adpater, results)
  {"dialect"=> adpater, "keys"=>[], "values"=>[results]}
end

#symbolized_adapter(adapter) ⇒ Object



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/ting_yun/agent/database/explain_plan_helpers.rb', line 64

def symbolized_adapter(adapter)
  if adapter.start_with? POSTGRES_PREFIX
    :postgres
  elsif adapter == MYSQL_PREFIX
    :mysql
    # For the purpose of fetching explain plans, we need to maintain the distinction
    # between usage of mysql and mysql2. Obfuscation is the same, though.
  elsif adapter == MYSQL2_PREFIX
    :mysql2
  elsif adapter.start_with? SQLITE_PREFIX
    :sqlite
  else
    adapter.to_sym
  end
end