aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/test/cases/explain_test.rb
blob: aa2a6d75095b11ac9f3defab774433d1dcf0f23b (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
require 'cases/helper'
require 'models/car'
require 'active_support/core_ext/string/strip'

if ActiveRecord::Base.connection.supports_explain?
  class ExplainTest < ActiveRecord::TestCase
    fixtures :cars

    def base
      ActiveRecord::Base
    end

    def connection
      base.connection
    end

    def test_logging_query_plan_with_logger
      base.logger.expects(:warn).with do |message|
        message.starts_with?('EXPLAIN for:')
      end

      with_threshold(0) do
        Car.where(:name => 'honda').to_a
      end
    end

    def test_logging_query_plan_without_logger
      original = base.logger
      base.logger = nil

      class << base.logger
        def warn; raise "Should not be called" end
      end

      with_threshold(0) do
        car = Car.where(:name => 'honda').first
        assert_equal 'honda', car.name
      end
    ensure
      base.logger = original
    end

    def test_collect_queries_for_explain
      base.auto_explain_threshold_in_seconds = nil
      queries = Thread.current[:available_queries_for_explain] = []

      with_threshold(0) do
        Car.where(:name => 'honda').to_a
      end

      sql, binds = queries[0]
      assert_match "SELECT", sql
      assert_match "honda", sql
      assert_equal [], binds
    ensure
      Thread.current[:available_queries_for_explain] = nil
    end

    def test_collecting_queries_for_explain
      result, queries = ActiveRecord::Base.collecting_queries_for_explain do
        Car.where(:name => 'honda').to_a
      end

      sql, binds = queries[0]
      assert_match "SELECT", sql
      assert_match "honda", sql
      assert_equal [], binds
      assert_equal [cars(:honda)], result
    end

    def test_logging_query_plan_when_counting_by_sql
      base.logger.expects(:warn).with do |message|
        message.starts_with?('EXPLAIN for:')
      end

      with_threshold(0) do
        Car.count_by_sql "SELECT COUNT(*) FROM cars WHERE name = 'honda'"
      end
    end

    def test_exec_explain_with_no_binds
      sqls    = %w(foo bar)
      binds   = [[], []]
      queries = sqls.zip(binds)

      connection.stubs(:explain).returns('query plan foo', 'query plan bar')
      expected = sqls.map {|sql| "EXPLAIN for: #{sql}\nquery plan #{sql}"}.join("\n")
      assert_equal expected, base.exec_explain(queries)
    end

    def test_exec_explain_with_binds
      cols = [Object.new, Object.new]
      cols[0].expects(:name).returns('wadus')
      cols[1].expects(:name).returns('chaflan')

      sqls    = %w(foo bar)
      binds   = [[[cols[0], 1]], [[cols[1], 2]]]
      queries = sqls.zip(binds)

      connection.stubs(:explain).returns("query plan foo\n", "query plan bar\n")
      expected = <<-SQL.strip_heredoc
        EXPLAIN for: #{sqls[0]} [["wadus", 1]]
        query plan foo

        EXPLAIN for: #{sqls[1]} [["chaflan", 2]]
        query plan bar
      SQL
      assert_equal expected, base.exec_explain(queries)
    end

    def test_unsupported_connection_adapter
      connection.stubs(:supports_explain?).returns(false)

      base.logger.expects(:warn).never

      with_threshold(0) do
        Car.where(:name => 'honda').to_a
      end
    end

    def test_silence_auto_explain
      base.expects(:collecting_sqls_for_explain).never
      base.logger.expects(:warn).never
      base.silence_auto_explain do
        with_threshold(0) { Car.all.to_a }
      end
    end

    def with_threshold(threshold)
      current_threshold = base.auto_explain_threshold_in_seconds
      base.auto_explain_threshold_in_seconds = threshold
      yield
    ensure
      base.auto_explain_threshold_in_seconds = current_threshold
    end
  end
end