aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/test/cases/query_cache_test.rb
blob: dfc6ea2457789d1cd33a4588e6c6a00ad3d8aeae (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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
require "cases/helper"
require 'models/topic'
require 'models/task'
require 'models/category'
require 'models/post'


class QueryCacheTest < ActiveRecord::TestCase
  fixtures :tasks, :topics, :categories, :posts, :categories_posts

  def setup
    Task.connection.clear_query_cache
    ActiveRecord::Base.connection.disable_query_cache!
  end

  def test_exceptional_middleware_clears_and_disables_cache_on_error
    assert !ActiveRecord::Base.connection.query_cache_enabled, 'cache off'

    mw = ActiveRecord::QueryCache.new lambda { |env|
      Task.find 1
      Task.find 1
      assert_equal 1, ActiveRecord::Base.connection.query_cache.length
      raise "lol borked"
    }
    assert_raises(RuntimeError) { mw.call({}) }

    assert_equal 0, ActiveRecord::Base.connection.query_cache.length
    assert !ActiveRecord::Base.connection.query_cache_enabled, 'cache off'
  end

  def test_exceptional_middleware_leaves_enabled_cache_alone
    ActiveRecord::Base.connection.enable_query_cache!

    mw = ActiveRecord::QueryCache.new lambda { |env|
      raise "lol borked"
    }
    assert_raises(RuntimeError) { mw.call({}) }

    assert ActiveRecord::Base.connection.query_cache_enabled, 'cache on'
  end

  def test_middleware_delegates
    called = false
    mw = ActiveRecord::QueryCache.new lambda { |env|
      called = true
    }
    mw.call({})
    assert called, 'middleware should delegate'
  end

  def test_middleware_caches
    mw = ActiveRecord::QueryCache.new lambda { |env|
      Task.find 1
      Task.find 1
      assert_equal 1, ActiveRecord::Base.connection.query_cache.length
    }
    mw.call({})
  end

  def test_cache_enabled_during_call
    assert !ActiveRecord::Base.connection.query_cache_enabled, 'cache off'

    mw = ActiveRecord::QueryCache.new lambda { |env|
      assert ActiveRecord::Base.connection.query_cache_enabled, 'cache on'
    }
    mw.call({})
  end

  def test_cache_on_during_body_write
    streaming = Class.new do
      def each
        yield ActiveRecord::Base.connection.query_cache_enabled
      end
    end

    mw = ActiveRecord::QueryCache.new lambda { |env|
      [200, {}, streaming.new]
    }
    body = mw.call({}).last
    body.each { |x| assert x, 'cache should be on' }
    body.close
    assert !ActiveRecord::Base.connection.query_cache_enabled, 'cache disabled'
  end

  def test_cache_off_after_close
    mw = ActiveRecord::QueryCache.new lambda { |env| }
    body = mw.call({}).last

    assert ActiveRecord::Base.connection.query_cache_enabled, 'cache enabled'
    body.close
    assert !ActiveRecord::Base.connection.query_cache_enabled, 'cache disabled'
  end

  def test_cache_clear_after_close
    mw = ActiveRecord::QueryCache.new lambda { |env|
      Post.find(:first)
    }
    body = mw.call({}).last

    assert !ActiveRecord::Base.connection.query_cache.empty?, 'cache not empty'
    body.close
    assert ActiveRecord::Base.connection.query_cache.empty?, 'cache should be empty'
  end

  def test_find_queries
    assert_queries(ActiveRecord::IdentityMap.enabled? ? 1 : 2) { Task.find(1); Task.find(1) }
  end

  def test_find_queries_with_cache
    Task.cache do
      assert_queries(1) { Task.find(1); Task.find(1) }
    end
  end

  def test_find_queries_with_cache_multi_record
    Task.cache do
      assert_queries(2) { Task.find(1); Task.find(1); Task.find(2) }
    end
  end

  def test_count_queries_with_cache
    Task.cache do
      assert_queries(1) { Task.count; Task.count }
    end
  end

  def test_query_cache_dups_results_correctly
    Task.cache do
      now  = Time.now.utc
      task = Task.find 1
      assert_not_equal now, task.starting
      task.starting = now
      task.reload
      assert_not_equal now, task.starting
    end
  end

  def test_cache_is_flat
    Task.cache do
      Topic.columns # don't count this query
      assert_queries(1) { Topic.find(1); Topic.find(1); }
    end

    ActiveRecord::Base.cache do
      assert_queries(1) { Task.find(1); Task.find(1) }
    end
  end

  def test_cache_does_not_wrap_string_results_in_arrays
    if current_adapter?(:SQLite3Adapter)
      require 'sqlite3/version'
      sqlite3_version = RUBY_PLATFORM =~ /java/ ? Jdbc::SQLite3::VERSION : SQLite3::VERSION
    end

    Task.cache do
      # Oracle adapter returns count() as Fixnum or Float
      if current_adapter?(:OracleAdapter)
        assert_kind_of Numeric, Task.connection.select_value("SELECT count(*) AS count_all FROM tasks")
      elsif current_adapter?(:SQLite3Adapter) && sqlite3_version > '1.2.5' || current_adapter?(:Mysql2Adapter)
        # Future versions of the sqlite3 adapter will return numeric
        assert_instance_of Fixnum,
         Task.connection.select_value("SELECT count(*) AS count_all FROM tasks")
      else
        assert_instance_of String, Task.connection.select_value("SELECT count(*) AS count_all FROM tasks")
      end
    end
  end

  def test_cache_is_ignored_for_locked_relations
    task = Task.find 1

    Task.cache do
      assert_queries(2) { task.lock!; task.lock! }
    end
  end

  def test_cache_is_available_when_connection_is_connected
    conf = ActiveRecord::Base.configurations

    ActiveRecord::Base.configurations = {}
    Task.cache do
      assert_queries(1) { Task.find(1); Task.find(1) }
    end
  ensure
    ActiveRecord::Base.configurations = conf
  end
end

class QueryCacheExpiryTest < ActiveRecord::TestCase
  fixtures :tasks, :posts, :categories, :categories_posts

  def test_cache_gets_cleared_after_migration
    # warm the cache
    Post.find(1)

    # change the column definition
    Post.connection.change_column :posts, :title, :string, :limit => 80
    assert_nothing_raised { Post.find(1) }

    # restore the old definition
    Post.connection.change_column :posts, :title, :string
  end

  def test_find
    Task.connection.expects(:clear_query_cache).times(1)

    assert !Task.connection.query_cache_enabled
    Task.cache do
      assert Task.connection.query_cache_enabled
      Task.find(1)

      Task.uncached do
        assert !Task.connection.query_cache_enabled
        Task.find(1)
      end

      assert Task.connection.query_cache_enabled
    end
    assert !Task.connection.query_cache_enabled
  end

  def test_update
    Task.connection.expects(:clear_query_cache).times(2)

    Task.cache do
      task = Task.find(1)
      task.starting = Time.now.utc
      task.save!
    end
  end

  def test_destroy
    Task.connection.expects(:clear_query_cache).times(2)

    Task.cache do
      Task.find(1).destroy
    end
  end

  def test_insert
    ActiveRecord::Base.connection.expects(:clear_query_cache).times(2)

    Task.cache do
      Task.create!
    end
  end

  def test_cache_is_expired_by_habtm_update
    ActiveRecord::Base.connection.expects(:clear_query_cache).times(2)
    ActiveRecord::Base.cache do
      c = Category.find(:first)
      p = Post.find(:first)
      p.categories << c
    end
  end

  def test_cache_is_expired_by_habtm_delete
    ActiveRecord::Base.connection.expects(:clear_query_cache).times(2)
    ActiveRecord::Base.cache do
      p = Post.find(1)
      assert p.categories.any?
      p.categories.delete_all
    end
  end
end

class QueryCacheBodyProxyTest < ActiveRecord::TestCase

  test "is polite to it's body and responds to it" do
    body = Class.new(String) { def to_path; "/path"; end }.new
    proxy = ActiveRecord::QueryCache::BodyProxy.new(nil, body, ActiveRecord::Base.connection_id)
    assert proxy.respond_to?(:to_path)
    assert_equal proxy.to_path, "/path"
  end

end