Class: WtActiverecordIndexSpy::QueryAnalyser

Inherits:
Object
  • Object
show all
Defined in:
lib/wt_activerecord_index_spy/query_analyser.rb,
lib/wt_activerecord_index_spy/query_analyser/mysql.rb,
lib/wt_activerecord_index_spy/query_analyser/postgres.rb

Overview

It runs an EXPLAIN query given a query and analyses the result to see if some index is missing.

Defined Under Namespace

Modules: Mysql, Postgres

Instance Method Summary collapse

Constructor Details

#initializeQueryAnalyser

Returns a new instance of QueryAnalyser.



8
9
10
11
12
# File 'lib/wt_activerecord_index_spy/query_analyser.rb', line 8

def initialize
  # This is a cache to not run the same EXPLAIN again
  # It sets the query as key and the result (certain, uncertain) as the value
  @analysed_queries = {}
end

Instance Method Details

#analyse(sql:, connection: ActiveRecord::Base.connection, binds: []) ⇒ Object

The sql and binds vary depend on the adapter.

  • Mysql2: sends sql complete and binds = []

  • Postregs: sends sql in a form of prepared statement and its values in binds

rubocop:disable Metrics/MethodLength



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/wt_activerecord_index_spy/query_analyser.rb', line 18

def analyse(sql:, connection: ActiveRecord::Base.connection, binds: [])
  query = sql
  # TODO: this could be more intelligent to not duplicate similar queries
  # with different WHERE values, example:
  # - WHERE lala = 1 AND popo = 1
  # - WHERE lala = 2 AND popo = 2
  # Notes:
  # - The Postgres adapter uses prepared statements as default, so it
  # will save the queries without the values.
  # - The Mysql2 adapter does not use prepared statements as default, so it
  # will analyse very similar queries as described above.
  return @analysed_queries[query] if @analysed_queries.key?(query)

  adapter = select_adapter(connection)

  # We need a thread to use a different connection that it's used by the
  # application otherwise, it can change some ActiveRecord internal state
  # such as number_of_affected_rows that is returned by the method
  # `update_all`
  Thread.new do
    results = ActiveRecord::Base.connection_pool.with_connection do |conn|
      conn.exec_query("EXPLAIN #{query}", "SQL", binds)
    end

    adapter.analyse(results).tap do |certainity_level|
      @analysed_queries[query] = certainity_level
    end
  end.join.value
end