diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 2006558ab2f455767d447fa404abc2532676e6e6..14089b5c1d55f769146bc4297cefa11f6558f83a 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,12 @@ +* Log bind variables after they are type casted. This makes it more + transparent what values are actually sent to the database. + + irb(main):002:0> Event.find("im-no-integer") + # Before: ... WHERE "events"."id" = $1 LIMIT 1 [["id", "im-no-integer"]] + # After: ... WHERE "events"."id" = $1 LIMIT 1 [["id", 0]] + + *Yves Senn* + * Fix uninitialized constant TransactionState error when Marshall.load is used on an Active Record result. Fixes #12790 diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 88c9494fc66631f731e69d56af168514cfeec1ff..fc37588f09f9ff8587c199a8822ae49bbe0b7fca 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -476,7 +476,11 @@ def begin_db_transaction #:nodoc: def exec_stmt(sql, name, binds) cache = {} - log(sql, name, binds) do + type_casted_binds = binds.map { |col, val| + [col, type_cast(val, col)] + } + + log(sql, name, type_casted_binds) do if binds.empty? stmt = @connection.prepare(sql) else @@ -487,7 +491,7 @@ def exec_stmt(sql, name, binds) end begin - stmt.execute(*binds.map { |col, val| type_cast(val, col) }) + stmt.execute(*type_casted_binds.map(&:second)) rescue Mysql::Error => e # Older versions of MySQL leave the prepared statement in a bad # place when an error occurs. To support older mysql versions, we diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 3668aecd4b8f18517438a894c69e23788e1769b5..5fd322310fd06e54a277d0d40187570b579ed6a8 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -783,11 +783,12 @@ def exec_no_cache(sql, name, binds) def exec_cache(sql, name, binds) stmt_key = prepare_statement(sql) + type_casted_binds = binds.map { |col, val| + [col, type_cast(val, col)] + } - log(sql, name, binds, stmt_key) do - @connection.send_query_prepared(stmt_key, binds.map { |col, val| - type_cast(val, col) - }) + log(sql, name, type_casted_binds, stmt_key) do + @connection.send_query_prepared(stmt_key, type_casted_binds.map(&:second)) @connection.block @connection.get_last_result end diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index e5ad08b6b02332ed287e7e0b26e52398fb861a6f..f09178c93b01b18369bd35564aa0e1968ac309c9 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -291,8 +291,11 @@ def pp(result) # :nodoc: end def exec_query(sql, name = nil, binds = []) - log(sql, name, binds) do + type_casted_binds = binds.map { |col, val| + [col, type_cast(val, col)] + } + log(sql, name, type_casted_binds) do # Don't cache statements if they are not prepared if without_prepared_statement?(binds) stmt = @connection.prepare(sql) @@ -307,9 +310,7 @@ def exec_query(sql, name = nil, binds = []) stmt = cache[:stmt] cols = cache[:cols] ||= stmt.columns stmt.reset! - stmt.bind_params binds.map { |col, val| - type_cast(val, col) - } + stmt.bind_params type_casted_binds.map(&:second) end ActiveRecord::Result.new(cols, stmt.to_a) diff --git a/activerecord/test/cases/bind_parameter_test.rb b/activerecord/test/cases/bind_parameter_test.rb index bc00364e85012041cdcbcf5d06890dee66ae843b..291751c435fc07f73f88c0b02c00b2fa4d128918 100644 --- a/activerecord/test/cases/bind_parameter_test.rb +++ b/activerecord/test/cases/bind_parameter_test.rb @@ -41,6 +41,17 @@ def test_binds_are_logged assert_equal binds, message[4][:binds] end + def test_binds_are_logged_after_type_cast + sub = @connection.substitute_at(@pk, 0) + binds = [[@pk, "3"]] + sql = "select * from topics where id = #{sub}" + + @connection.exec_query(sql, 'SQL', binds) + + message = @listener.calls.find { |args| args[4][:sql] == sql } + assert_equal [[@pk, 3]], message[4][:binds] + end + def test_find_one_uses_binds Topic.find(1) binds = [[@pk, 1]]