Skip to content

Commit

Permalink
Merge pull request #8346 from sodabrew/patch-1
Browse files Browse the repository at this point in the history
Add `variables:` to database.yml for mysql, mysql2, and postgresql adapters
  • Loading branch information
jeremy committed Dec 8, 2012
2 parents 86269b4 + 97d06e8 commit 61b528e
Show file tree
Hide file tree
Showing 8 changed files with 144 additions and 34 deletions.
8 changes: 8 additions & 0 deletions activerecord/CHANGELOG.md
Original file line number Original file line Diff line number Diff line change
@@ -1,5 +1,13 @@
## Rails 4.0.0 (unreleased) ## ## Rails 4.0.0 (unreleased) ##


* Session variables can be set for the `mysql`, `mysql2`, and `postgresql` adapters
in the `variables: <hash>` parameter in `database.yml`. The key-value pairs of this
hash will be sent in a `SET key = value` query on new database connections. See also:
http://dev.mysql.com/doc/refman/5.0/en/set-statement.html
http://www.postgresql.org/docs/8.3/static/sql-set.html

*Aaron Stone*

* Allow `Relation#where` with no arguments to be chained with new `not` query method. * Allow `Relation#where` with no arguments to be chained with new `not` query method.


Example: Example:
Expand Down
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -704,6 +704,45 @@ def column_for(table_name, column_name)
end end
column column
end end

def configure_connection
variables = @config[:variables] || {}

# By default, MySQL 'where id is null' selects the last inserted id.
# Turn this off. http://dev.rubyonrails.org/ticket/6778
variables[:sql_auto_is_null] = 0

# Increase timeout so the server doesn't disconnect us.
wait_timeout = @config[:wait_timeout]
wait_timeout = 2147483 unless wait_timeout.is_a?(Fixnum)
variables[:wait_timeout] = wait_timeout

# Make MySQL reject illegal values rather than truncating or blanking them, see
# http://dev.mysql.com/doc/refman/5.0/en/server-sql-mode.html#sqlmode_strict_all_tables
# If the user has provided another value for sql_mode, don't replace it.
if strict_mode? && !variables.has_key?(:sql_mode)
variables[:sql_mode] = 'STRICT_ALL_TABLES'
end

# NAMES does not have an equals sign, see
# http://dev.mysql.com/doc/refman/5.0/en/set-statement.html#id944430
# (trailing comma because variable_assignments will always have content)
encoding = "NAMES #{@config[:encoding]}, " if @config[:encoding]

# Gather up all of the SET variables...
variable_assignments = variables.map do |k, v|
if v == ':default' || v == :default
"@@SESSION.#{k.to_s} = DEFAULT" # Sets the value to the global or compile default
elsif !v.nil?
"@@SESSION.#{k.to_s} = #{quote(v)}"
end
# or else nil; compact to clear nils out
end.compact.join(', ')

# ...and send them all in one query
execute("SET #{encoding} #{variable_assignments}", :skip_logging)
end

end end
end end
end end
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -251,27 +251,7 @@ def connect


def configure_connection def configure_connection
@connection.query_options.merge!(:as => :array) @connection.query_options.merge!(:as => :array)

super
# By default, MySQL 'where id is null' selects the last inserted id.
# Turn this off. http://dev.rubyonrails.org/ticket/6778
variable_assignments = ['SQL_AUTO_IS_NULL=0']

# Make MySQL reject illegal values rather than truncating or
# blanking them. See
# http://dev.mysql.com/doc/refman/5.5/en/server-sql-mode.html#sqlmode_strict_all_tables
variable_assignments << "SQL_MODE='STRICT_ALL_TABLES'" if strict_mode?

encoding = @config[:encoding]

# make sure we set the encoding
variable_assignments << "NAMES '#{encoding}'" if encoding

# increase timeout so mysql server doesn't disconnect us
wait_timeout = @config[:wait_timeout]
wait_timeout = 2147483 unless wait_timeout.is_a?(Fixnum)
variable_assignments << "@@wait_timeout = #{wait_timeout}"

execute("SET #{variable_assignments.join(', ')}", :skip_logging)
end end


def version def version
Expand Down
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ module ConnectionAdapters
# * <tt>:database</tt> - The name of the database. No default, must be provided. # * <tt>:database</tt> - The name of the database. No default, must be provided.
# * <tt>:encoding</tt> - (Optional) Sets the client encoding by executing "SET NAMES <encoding>" after connection. # * <tt>:encoding</tt> - (Optional) Sets the client encoding by executing "SET NAMES <encoding>" after connection.
# * <tt>:reconnect</tt> - Defaults to false (See MySQL documentation: http://dev.mysql.com/doc/refman/5.0/en/auto-reconnect.html). # * <tt>:reconnect</tt> - Defaults to false (See MySQL documentation: http://dev.mysql.com/doc/refman/5.0/en/auto-reconnect.html).
# * <tt>:strict</tt> - Defaults to true. Enable STRICT_ALL_TABLES. (See MySQL documentation: http://dev.mysql.com/doc/refman/5.5/en/server-sql-mode.html) # * <tt>:strict</tt> - Defaults to true. Enable STRICT_ALL_TABLES. (See MySQL documentation: http://dev.mysql.com/doc/refman/5.0/en/server-sql-mode.html)
# * <tt>:variables</tt> - (Optional) A hash session variables to send as `SET @@SESSION.key = value` on each database connection. Use the value `:default` to set a variable to its DEFAULT value. (See MySQL documentation: http://dev.mysql.com/doc/refman/5.0/en/set-statement.html).
# * <tt>:sslca</tt> - Necessary to use MySQL with an SSL connection. # * <tt>:sslca</tt> - Necessary to use MySQL with an SSL connection.
# * <tt>:sslkey</tt> - Necessary to use MySQL with an SSL connection. # * <tt>:sslkey</tt> - Necessary to use MySQL with an SSL connection.
# * <tt>:sslcert</tt> - Necessary to use MySQL with an SSL connection. # * <tt>:sslcert</tt> - Necessary to use MySQL with an SSL connection.
Expand Down Expand Up @@ -535,18 +536,10 @@ def connect
configure_connection configure_connection
end end


# Many Rails applications monkey-patch a replacement of the configure_connection method
# and don't call 'super', so leave this here even though it looks superfluous.
def configure_connection def configure_connection
encoding = @config[:encoding] super
execute("SET NAMES '#{encoding}'", :skip_logging) if encoding

# By default, MySQL 'where id is null' selects the last inserted id.
# Turn this off. http://dev.rubyonrails.org/ticket/6778
execute("SET SQL_AUTO_IS_NULL=0", :skip_logging)

# Make MySQL reject illegal values rather than truncating or
# blanking them. See
# http://dev.mysql.com/doc/refman/5.5/en/server-sql-mode.html#sqlmode_strict_all_tables
execute("SET SQL_MODE='STRICT_ALL_TABLES'", :skip_logging) if strict_mode?
end end


def select(sql, name = nil, binds = []) def select(sql, name = nil, binds = [])
Expand Down
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def postgresql_connection(config) # :nodoc:
# Forward any unused config params to PGconn.connect. # Forward any unused config params to PGconn.connect.
[:statement_limit, :encoding, :min_messages, :schema_search_path, [:statement_limit, :encoding, :min_messages, :schema_search_path,
:schema_order, :adapter, :pool, :checkout_timeout, :template, :schema_order, :adapter, :pool, :checkout_timeout, :template,
:reaping_frequency, :insert_returning].each do |key| :reaping_frequency, :insert_returning, :variables].each do |key|
conn_params.delete key conn_params.delete key
end end
conn_params.delete_if { |k,v| v.nil? } conn_params.delete_if { |k,v| v.nil? }
Expand Down Expand Up @@ -238,6 +238,8 @@ def simplified_type(field_type)
# <encoding></tt> call on the connection. # <encoding></tt> call on the connection.
# * <tt>:min_messages</tt> - An optional client min messages that is used in a # * <tt>:min_messages</tt> - An optional client min messages that is used in a
# <tt>SET client_min_messages TO <min_messages></tt> call on the connection. # <tt>SET client_min_messages TO <min_messages></tt> call on the connection.
# * <tt>:variables</tt> - An optional hash of additional parameters that
# will be used in <tt>SET SESSION key = val</tt> calls on the connection.
# * <tt>:insert_returning</tt> - An optional boolean to control the use or <tt>RETURNING</tt> for <tt>INSERT</tt> statements # * <tt>:insert_returning</tt> - An optional boolean to control the use or <tt>RETURNING</tt> for <tt>INSERT</tt> statements
# defaults to true. # defaults to true.
# #
Expand Down Expand Up @@ -706,11 +708,24 @@ def configure_connection


# If using Active Record's time zone support configure the connection to return # If using Active Record's time zone support configure the connection to return
# TIMESTAMP WITH ZONE types in UTC. # TIMESTAMP WITH ZONE types in UTC.
# (SET TIME ZONE does not use an equals sign like other SET variables)
if ActiveRecord::Base.default_timezone == :utc if ActiveRecord::Base.default_timezone == :utc
execute("SET time zone 'UTC'", 'SCHEMA') execute("SET time zone 'UTC'", 'SCHEMA')
elsif @local_tz elsif @local_tz
execute("SET time zone '#{@local_tz}'", 'SCHEMA') execute("SET time zone '#{@local_tz}'", 'SCHEMA')
end end

# SET statements from :variables config hash
# http://www.postgresql.org/docs/8.3/static/sql-set.html
variables = @config[:variables] || {}
variables.map do |k, v|
if v == ':default' || v == :default
# Sets the value to the global or compile default
execute("SET SESSION #{k.to_s} TO DEFAULT", 'SCHEMA')
elsif !v.nil?
execute("SET SESSION #{k.to_s} TO #{quote(v)}", 'SCHEMA')
end
end
end end


# Returns the current ID of a table's sequence. # Returns the current ID of a table's sequence.
Expand Down
17 changes: 17 additions & 0 deletions activerecord/test/cases/adapters/mysql/connection_test.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -137,6 +137,23 @@ def test_mysql_strict_mode_disabled_dont_override_global_sql_mode
end end
end end


def test_mysql_set_session_variable
run_without_connection do |orig_connection|
ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:default_week_format => 3}}))
session_mode = ActiveRecord::Base.connection.exec_query "SELECT @@SESSION.DEFAULT_WEEK_FORMAT"
assert_equal 3, session_mode.rows.first.first.to_i
end
end

def test_mysql_set_session_variable_to_default
run_without_connection do |orig_connection|
ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:default_week_format => :default}}))
global_mode = ActiveRecord::Base.connection.exec_query "SELECT @@GLOBAL.DEFAULT_WEEK_FORMAT"
session_mode = ActiveRecord::Base.connection.exec_query "SELECT @@SESSION.DEFAULT_WEEK_FORMAT"
assert_equal global_mode.rows, session_mode.rows
end
end

private private


def run_without_connection def run_without_connection
Expand Down
17 changes: 17 additions & 0 deletions activerecord/test/cases/adapters/mysql2/connection_test.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -53,6 +53,23 @@ def test_mysql_strict_mode_disabled_dont_override_global_sql_mode
end end
end end


def test_mysql_set_session_variable
run_without_connection do |orig_connection|
ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:default_week_format => 3}}))
session_mode = ActiveRecord::Base.connection.exec_query "SELECT @@SESSION.DEFAULT_WEEK_FORMAT"
assert_equal 3, session_mode.rows.first.first.to_i
end
end

def test_mysql_set_session_variable_to_default
run_without_connection do |orig_connection|
ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:default_week_format => :default}}))
global_mode = ActiveRecord::Base.connection.exec_query "SELECT @@GLOBAL.DEFAULT_WEEK_FORMAT"
session_mode = ActiveRecord::Base.connection.exec_query "SELECT @@SESSION.DEFAULT_WEEK_FORMAT"
assert_equal global_mode.rows, session_mode.rows
end
end

def test_logs_name_structure_dump def test_logs_name_structure_dump
@connection.structure_dump @connection.structure_dump
assert_equal "SCHEMA", @connection.logged[0][1] assert_equal "SCHEMA", @connection.logged[0][1]
Expand Down
41 changes: 41 additions & 0 deletions activerecord/test/cases/adapters/postgresql/connection_test.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -154,5 +154,46 @@ def test_reconnection_after_actual_disconnection_with_verify
end end
end end


def test_set_session_variable_true
run_without_connection do |orig_connection|
ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:debug_print_plan => true}}))
set_true = ActiveRecord::Base.connection.exec_query "SHOW DEBUG_PRINT_PLAN"
assert_equal set_true.rows, [["on"]]
end
end

def test_set_session_variable_false
run_without_connection do |orig_connection|
ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:debug_print_plan => false}}))
set_false = ActiveRecord::Base.connection.exec_query "SHOW DEBUG_PRINT_PLAN"
assert_equal set_false.rows, [["off"]]
end
end

def test_set_session_variable_nil
run_without_connection do |orig_connection|
# This should be a no-op that does not raise an error
ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:debug_print_plan => nil}}))
end
end

def test_set_session_variable_default
run_without_connection do |orig_connection|
# This should execute a query that does not raise an error
ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:debug_print_plan => :default}}))
end
end

private

def run_without_connection
original_connection = ActiveRecord::Base.remove_connection
begin
yield original_connection
ensure
ActiveRecord::Base.establish_connection(original_connection)
end
end

end end
end end

0 comments on commit 61b528e

Please sign in to comment.