Skip to content

Commit

Permalink
Disable database prepared statements when query logs are enabled
Browse files Browse the repository at this point in the history
Fixes #48398

Prepared Statements and Query Logs are incompatible features due to query logs making every query unique.

Co-authored-by: Jean Boussier <jean.boussier@gmail.com>
  • Loading branch information
zzak and byroot committed Jul 3, 2023
1 parent d867313 commit a234669
Show file tree
Hide file tree
Showing 7 changed files with 43 additions and 1 deletion.
6 changes: 6 additions & 0 deletions activerecord/CHANGELOG.md
@@ -1,3 +1,9 @@
* Disable database prepared statements when query logs are enabled

Prepared Statements and Query Logs are incompatible features due to query logs making every query unique.

*zzak, Jean Boussier*

* Support decrypting data encrypted non-deterministically with a SHA1 hash digest.

This adds a new Active Record encryption option to support decrypting data encrypted
Expand Down
3 changes: 3 additions & 0 deletions activerecord/lib/active_record.rb
Expand Up @@ -174,6 +174,9 @@ module Tasks
autoload :SQLiteDatabaseTasks, "active_record/tasks/sqlite_database_tasks"
end

singleton_class.attr_accessor :disable_prepared_statements
self.disable_prepared_statements = false

# Lazily load the schema cache. This option will load the schema cache
# when a connection is established rather than on boot. If set,
# +config.active_record.use_schema_cache_dump+ will be set to false.
Expand Down
Expand Up @@ -160,7 +160,7 @@ def initialize(config_or_deprecated_connection, deprecated_logger = nil, depreca
@statements = build_statement_pool
self.lock_thread = nil

@prepared_statements = self.class.type_cast_config_to_boolean(
@prepared_statements = !ActiveRecord.disable_prepared_statements && self.class.type_cast_config_to_boolean(
@config.fetch(:prepared_statements) { default_prepared_statements }
)

Expand Down
1 change: 1 addition & 0 deletions activerecord/lib/active_record/railtie.rb
Expand Up @@ -396,6 +396,7 @@ class Railtie < Rails::Railtie # :nodoc:
db_host: ->(context) { context[:connection].pool.db_config.host },
database: ->(context) { context[:connection].pool.db_config.database }
)
ActiveRecord.disable_prepared_statements = true

if app.config.active_record.query_log_tags.present?
ActiveRecord::QueryLogs.tags = app.config.active_record.query_log_tags
Expand Down
17 changes: 17 additions & 0 deletions activerecord/test/cases/adapter_test.rb
Expand Up @@ -158,6 +158,23 @@ def test_not_specifying_database_name_for_cross_database_selects
end
end

unless in_memory_db?
def test_disable_prepared_statements
original_prepared_statements = ActiveRecord.disable_prepared_statements
db_config = ActiveRecord::Base.configurations.configs_for(env_name: "arunit", name: "primary")
ActiveRecord::Base.establish_connection(db_config.configuration_hash.merge(prepared_statements: true))

assert_predicate ActiveRecord::Base.connection, :prepared_statements?

ActiveRecord.disable_prepared_statements = true
ActiveRecord::Base.establish_connection(db_config.configuration_hash.merge(prepared_statements: true))
assert_not_predicate ActiveRecord::Base.connection, :prepared_statements?
ensure
ActiveRecord.disable_prepared_statements = original_prepared_statements
ActiveRecord::Base.establish_connection :arunit
end
end

def test_table_alias
def @connection.test_table_alias_length() 10; end
class << @connection
Expand Down
2 changes: 2 additions & 0 deletions guides/source/configuring.md
Expand Up @@ -1345,6 +1345,8 @@ The default value depends on the `config.load_defaults` target version:
Specifies whether or not to enable adapter-level query comments. Defaults to
`false`.

NOTE: When this is set to `true` database prepared statements will be automatically disabled.

#### `config.active_record.query_log_tags`

Define an `Array` specifying the key/value tags to be inserted in an SQL
Expand Down
13 changes: 13 additions & 0 deletions railties/test/application/query_logs_test.rb
Expand Up @@ -10,6 +10,7 @@ class QueryLogsTest < ActiveSupport::TestCase

def setup
build_app(multi_db: true)
add_to_config "config.active_record.sqlite3_production_warning = false"
rails("generate", "scaffold", "Pet", "name:string", "--database=animals")
app_file "app/models/user.rb", <<-RUBY
class User < ActiveRecord::Base
Expand Down Expand Up @@ -78,8 +79,20 @@ def app
assert_includes ActiveRecord.query_transformers, ActiveRecord::QueryLogs
end

test "disables prepared statements when enabled" do
add_to_config "config.active_record.query_log_tags_enabled = true"

boot_app

assert_predicate ActiveRecord, :disable_prepared_statements
end

test "controller and job tags are defined by default" do
add_to_config "config.active_record.query_log_tags_enabled = true"
app_file "config/initializers/active_record.rb", <<-RUBY
raise "Expected prepared_statements to be enabled" unless ActiveRecord::Base.connection.prepared_statements
ActiveRecord::Base.connection.execute("SELECT 1")
RUBY

boot_app

Expand Down

0 comments on commit a234669

Please sign in to comment.