rails: Rails 7 is not clearing query cache

Steps to reproduce

After changing my app to config.load_defaults 7.0, Rails is seemingly caching SQL run with ActiveRecord#connection#select_value.

The test is asserting that a record is inserted when an action is performed on the site (insert is run from a ActiveRecord#connection#execute). Looking at the test log, the insert is indeed happening when the test runs, yet select_value doesn’t see it. Test passed in Rails 6.1, as well as 7.0 with config.load_defaults 6.1.

Failing test: https://github.com/JasonBarnabe/greasyfork/blob/a41a00a375bb70df11b4b24d7c9aa096b74b5079/test/system/scripts/install_test.rb#L7 CircleCI result: https://app.circleci.com/pipelines/github/JasonBarnabe/greasyfork/650/workflows/101728fe-c4e2-4a67-a5ad-6bc1db438ba5/jobs/652

If I add a breakpoint and run the select query with a minor change (e.g. whitespace), then I get expected results.

(byebug) Script.connection.select_value('select count(*) from daily_install_counts')
0
(byebug) Script.connection.select_value('select  count(*) from daily_install_counts')
1

So it seems that perhaps the result is getting cached by the query string? I’ve been unable to create a minimal test case that shows the behaviour.

System configuration

Rails version: 7.0.0.0 Ruby version: 3.0.3p157 mysql2: 0.5.3 minitest: 5.15.0

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Reactions: 1
  • Comments: 32 (31 by maintainers)

Most upvoted comments

I believe I am hitting this.

I generated a vanilla Rails 7 application with a User model and wrote this test:

class UserTest < ActiveSupport::TestCase
  test "INSERT with execute" do
    assert_difference "User.count", 1 do
      User.connection.execute("INSERT INTO users (name) VALUES ('John Smith')")
    end
  end
end

This test fails, and the reason is the second User.count hits the query cache.

If execute is not invalidating the cache, I think it should.

@fxn yeah, I meant to do it, but it requires a significant refactor of the MySQL2 adapter has it uses execute both internally and externally, and even for other adapters, they all have their own execute implementation, it’s a bit of a mess.

In the meantime, the better method to use in your case is:

class UserTest < ActiveSupport::TestCase
  test "INSERT with execute" do
    assert_difference "User.count", 1 do
      User.connection.insert("INSERT INTO users (name) VALUES ('John Smith')")
    end
  end
end

Executable test case:

# frozen_string_literal: true

require "bundler/inline"

gemfile(true) do
  source "https://rubygems.org"

  git_source(:github) { |repo| "https://github.com/#{repo}.git" }

  gem "rails", github: "rails/rails", branch: "main"
  gem "sqlite3"
end

require "rails/all"
require "minitest/autorun"
require "logger"

class TestApp < Rails::Application
  config.root = __dir__
  config.hosts << "example.org"
  config.eager_load = false
  config.session_store :cookie_store, key: "cookie_store_key"
  secrets.secret_key_base = "secret_key_base"

  config.logger = Logger.new($stdout)
  Rails.logger  = config.logger

  config.active_support.executor_around_test_case = true
end

ENV["DATABASE_URL"] = "sqlite3::memory:"

Rails.application.initialize!

ActiveRecord::Schema.define do
  create_table :users, force: true do |t|
    t.text :name
  end
end

class User < ActiveRecord::Base
end

class BugTest < ActiveSupport::TestCase
  def test_stuff
    assert_equal [], User.pluck(:name)
    ActiveRecord::Base.connection.execute "INSERT INTO users (name) VALUES (1)"
    assert_equal ["1"], User.pluck(:name)
  end
end

If you change line 28, the test passes.

The pertinent difference between load_defaults 6.1 and 7.0 is active_support.executor_around_test_case. With:

config.load_defaults 7.0
config.active_support.executor_around_test_case = false

the problem no longer occurs.

So I guess my question would be: is it expected behaviour for ActiveRecord#connection#execute to not clear the query cache?