module Testoscope

Constants

VERSION

Public Class Methods

add_index_used( sql, explain, index_used ) click to toggle source
# File lib/testoscope.rb, line 47
def self.add_index_used( sql, explain, index_used )
  results[:indexes][index_used] ||= []
  results[:indexes][index_used] << { explain: explain, sql: sql }
end
add_unintended_behaviour( sql, explain, backtrace ) click to toggle source
# File lib/testoscope.rb, line 39
def self.add_unintended_behaviour( sql, explain, backtrace )
  results[:unintended_behaviour][sql] ||= {}
  results[:unintended_behaviour][sql][:explain] = explain
  results[:unintended_behaviour][sql][:backtrace] ||= []
  results[:unintended_behaviour][sql][:backtrace] << backtrace.to_a unless results[:unintended_behaviour][sql][:backtrace].include?(backtrace)
end
analyze( sql ) { || ... } click to toggle source
# File lib/testoscope.rb, line 96
def self.analyze( sql )
  if config.analyze && sql_has_analyzing_tables?(sql)

    explain = yield

    explain = config.pp_class.method(:pp).arity.abs == 1 ? config.pp_class.new.pp( explain ) : config.pp_class.new.pp( explain, 0 )

    app_trace = caller_locations( 2 ).map(&:to_s).select { |st|
      self.config.back_trace_paths.any?{|pth| st[pth]} && !self.config.back_trace_exclude_paths.any?{|epth| st[epth]}
    }
    # this is the case when we for example making query from test files,
    # we may omit some params for where clause and so.
    return if app_trace.length == 0

    unintended_found = self.config.unintened_key_words.select{|ukw| explain[ukw] }

    if unintended_found.length > 0
      raise StandardError.new("#{unintended_found.join(', ')} found!\n #{explain}") if config.raise_when_unintended
      self.add_unintended_behaviour( sql, explain, app_trace )
    end

    explain.scan(/Index Scan using (\w+)|Index Scan on (\w+)|Index Scan Backward using (\w+)/)
      .each{|found| add_index_used(sql, explain, found.compact.first ) }
  end
end
config() click to toggle source
# File lib/testoscope.rb, line 18
def self.config; @@config ||= Config.new end
configure() { |config| ... } click to toggle source
# File lib/testoscope.rb, line 20
def self.configure
  yield(config) if block_given?

  ::ActiveRecord::Base.connection.class.include(AdapterUpgrade)

  # since test table is small planner may want to just deal with Seq scans and skip all the index fuss
  ::ActiveRecord::Base.connection.execute( 'SET enable_seqscan=off;' ) if ::ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'


  config.tables.map!{|table| /.*[ "]#{table}[ "].*/ } if config.tables != :all
end
get_all_indexes() click to toggle source
Alternative way to get index names "without" ActiveRecord

“SELECT i.relname as indname FROM pg_index as idx JOIN pg_class as i ON i.oid = idx.indexrelid

WHERE idx.indrelid::regclass = ANY( ARRAY['#{(ActiveRecord::Base.connection.tables).join("','")}']::regclass[] )")
    .to_a.map(&:values).flatten
# File lib/testoscope.rb, line 57
def self.get_all_indexes
  ActiveRecord::Base.connection.tables.map { |table|
    [table, ActiveRecord::Base.connection.indexes(table).map(&:name)]
  }.to_h
end
print_results() { |results| ... } click to toggle source
results() click to toggle source
# File lib/testoscope.rb, line 32
def self.results
  @results ||= {
    unintended_behaviour: {},
    indexes: {},
  }
end
sql_has_analyzing_tables?(sql) click to toggle source
# File lib/testoscope.rb, line 92
def self.sql_has_analyzing_tables?(sql)
  config.tables == :all || config.tables.any?{ |table| sql[table] }
end
suspend_global_analyze( analyze ) { || ... } click to toggle source
# File lib/testoscope.rb, line 122
def self.suspend_global_analyze( analyze )
  was, config.analyze = config.analyze, analyze
  yield
  config.analyze = was
end