Skip to content

Commit 97d06e8

Browse files
committed
Session variables for mysql, mysql2, and postgresql adapters can be set
in the new 'variables:' hash in each database config section in database.yml. The key-value pairs of this hash will be sent in a 'SET key = value, ...' query on new database connections. The configure_connection methods from mysql and mysql2 into are consolidated into the abstract_mysql base class.
1 parent 0a33fcd commit 97d06e8

File tree

8 files changed

+144
-34
lines changed

8 files changed

+144
-34
lines changed

activerecord/CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
## Rails 4.0.0 (unreleased) ##
22

3+
* Session variables can be set for the `mysql`, `mysql2`, and `postgresql` adapters
4+
in the `variables: <hash>` parameter in `database.yml`. The key-value pairs of this
5+
hash will be sent in a `SET key = value` query on new database connections. See also:
6+
http://dev.mysql.com/doc/refman/5.0/en/set-statement.html
7+
http://www.postgresql.org/docs/8.3/static/sql-set.html
8+
9+
*Aaron Stone*
10+
311
* Allow `Relation#where` with no arguments to be chained with new `not` query method.
412

513
Example:

activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -704,6 +704,45 @@ def column_for(table_name, column_name)
704704
end
705705
column
706706
end
707+
708+
def configure_connection
709+
variables = @config[:variables] || {}
710+
711+
# By default, MySQL 'where id is null' selects the last inserted id.
712+
# Turn this off. http://dev.rubyonrails.org/ticket/6778
713+
variables[:sql_auto_is_null] = 0
714+
715+
# Increase timeout so the server doesn't disconnect us.
716+
wait_timeout = @config[:wait_timeout]
717+
wait_timeout = 2147483 unless wait_timeout.is_a?(Fixnum)
718+
variables[:wait_timeout] = wait_timeout
719+
720+
# Make MySQL reject illegal values rather than truncating or blanking them, see
721+
# http://dev.mysql.com/doc/refman/5.0/en/server-sql-mode.html#sqlmode_strict_all_tables
722+
# If the user has provided another value for sql_mode, don't replace it.
723+
if strict_mode? && !variables.has_key?(:sql_mode)
724+
variables[:sql_mode] = 'STRICT_ALL_TABLES'
725+
end
726+
727+
# NAMES does not have an equals sign, see
728+
# http://dev.mysql.com/doc/refman/5.0/en/set-statement.html#id944430
729+
# (trailing comma because variable_assignments will always have content)
730+
encoding = "NAMES #{@config[:encoding]}, " if @config[:encoding]
731+
732+
# Gather up all of the SET variables...
733+
variable_assignments = variables.map do |k, v|
734+
if v == ':default' || v == :default
735+
"@@SESSION.#{k.to_s} = DEFAULT" # Sets the value to the global or compile default
736+
elsif !v.nil?
737+
"@@SESSION.#{k.to_s} = #{quote(v)}"
738+
end
739+
# or else nil; compact to clear nils out
740+
end.compact.join(', ')
741+
742+
# ...and send them all in one query
743+
execute("SET #{encoding} #{variable_assignments}", :skip_logging)
744+
end
745+
707746
end
708747
end
709748
end

activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -251,27 +251,7 @@ def connect
251251

252252
def configure_connection
253253
@connection.query_options.merge!(:as => :array)
254-
255-
# By default, MySQL 'where id is null' selects the last inserted id.
256-
# Turn this off. http://dev.rubyonrails.org/ticket/6778
257-
variable_assignments = ['SQL_AUTO_IS_NULL=0']
258-
259-
# Make MySQL reject illegal values rather than truncating or
260-
# blanking them. See
261-
# http://dev.mysql.com/doc/refman/5.5/en/server-sql-mode.html#sqlmode_strict_all_tables
262-
variable_assignments << "SQL_MODE='STRICT_ALL_TABLES'" if strict_mode?
263-
264-
encoding = @config[:encoding]
265-
266-
# make sure we set the encoding
267-
variable_assignments << "NAMES '#{encoding}'" if encoding
268-
269-
# increase timeout so mysql server doesn't disconnect us
270-
wait_timeout = @config[:wait_timeout]
271-
wait_timeout = 2147483 unless wait_timeout.is_a?(Fixnum)
272-
variable_assignments << "@@wait_timeout = #{wait_timeout}"
273-
274-
execute("SET #{variable_assignments.join(', ')}", :skip_logging)
254+
super
275255
end
276256

277257
def version

activerecord/lib/active_record/connection_adapters/mysql_adapter.rb

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ module ConnectionAdapters
5151
# * <tt>:database</tt> - The name of the database. No default, must be provided.
5252
# * <tt>:encoding</tt> - (Optional) Sets the client encoding by executing "SET NAMES <encoding>" after connection.
5353
# * <tt>:reconnect</tt> - Defaults to false (See MySQL documentation: http://dev.mysql.com/doc/refman/5.0/en/auto-reconnect.html).
54-
# * <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)
54+
# * <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)
55+
# * <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).
5556
# * <tt>:sslca</tt> - Necessary to use MySQL with an SSL connection.
5657
# * <tt>:sslkey</tt> - Necessary to use MySQL with an SSL connection.
5758
# * <tt>:sslcert</tt> - Necessary to use MySQL with an SSL connection.
@@ -535,18 +536,10 @@ def connect
535536
configure_connection
536537
end
537538

539+
# Many Rails applications monkey-patch a replacement of the configure_connection method
540+
# and don't call 'super', so leave this here even though it looks superfluous.
538541
def configure_connection
539-
encoding = @config[:encoding]
540-
execute("SET NAMES '#{encoding}'", :skip_logging) if encoding
541-
542-
# By default, MySQL 'where id is null' selects the last inserted id.
543-
# Turn this off. http://dev.rubyonrails.org/ticket/6778
544-
execute("SET SQL_AUTO_IS_NULL=0", :skip_logging)
545-
546-
# Make MySQL reject illegal values rather than truncating or
547-
# blanking them. See
548-
# http://dev.mysql.com/doc/refman/5.5/en/server-sql-mode.html#sqlmode_strict_all_tables
549-
execute("SET SQL_MODE='STRICT_ALL_TABLES'", :skip_logging) if strict_mode?
542+
super
550543
end
551544

552545
def select(sql, name = nil, binds = [])

activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ def postgresql_connection(config) # :nodoc:
2424
# Forward any unused config params to PGconn.connect.
2525
[:statement_limit, :encoding, :min_messages, :schema_search_path,
2626
:schema_order, :adapter, :pool, :checkout_timeout, :template,
27-
:reaping_frequency, :insert_returning].each do |key|
27+
:reaping_frequency, :insert_returning, :variables].each do |key|
2828
conn_params.delete key
2929
end
3030
conn_params.delete_if { |k,v| v.nil? }
@@ -238,6 +238,8 @@ def simplified_type(field_type)
238238
# <encoding></tt> call on the connection.
239239
# * <tt>:min_messages</tt> - An optional client min messages that is used in a
240240
# <tt>SET client_min_messages TO <min_messages></tt> call on the connection.
241+
# * <tt>:variables</tt> - An optional hash of additional parameters that
242+
# will be used in <tt>SET SESSION key = val</tt> calls on the connection.
241243
# * <tt>:insert_returning</tt> - An optional boolean to control the use or <tt>RETURNING</tt> for <tt>INSERT</tt> statements
242244
# defaults to true.
243245
#
@@ -706,11 +708,24 @@ def configure_connection
706708

707709
# If using Active Record's time zone support configure the connection to return
708710
# TIMESTAMP WITH ZONE types in UTC.
711+
# (SET TIME ZONE does not use an equals sign like other SET variables)
709712
if ActiveRecord::Base.default_timezone == :utc
710713
execute("SET time zone 'UTC'", 'SCHEMA')
711714
elsif @local_tz
712715
execute("SET time zone '#{@local_tz}'", 'SCHEMA')
713716
end
717+
718+
# SET statements from :variables config hash
719+
# http://www.postgresql.org/docs/8.3/static/sql-set.html
720+
variables = @config[:variables] || {}
721+
variables.map do |k, v|
722+
if v == ':default' || v == :default
723+
# Sets the value to the global or compile default
724+
execute("SET SESSION #{k.to_s} TO DEFAULT", 'SCHEMA')
725+
elsif !v.nil?
726+
execute("SET SESSION #{k.to_s} TO #{quote(v)}", 'SCHEMA')
727+
end
728+
end
714729
end
715730

716731
# Returns the current ID of a table's sequence.

activerecord/test/cases/adapters/mysql/connection_test.rb

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,23 @@ def test_mysql_strict_mode_disabled_dont_override_global_sql_mode
137137
end
138138
end
139139

140+
def test_mysql_set_session_variable
141+
run_without_connection do |orig_connection|
142+
ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:default_week_format => 3}}))
143+
session_mode = ActiveRecord::Base.connection.exec_query "SELECT @@SESSION.DEFAULT_WEEK_FORMAT"
144+
assert_equal 3, session_mode.rows.first.first.to_i
145+
end
146+
end
147+
148+
def test_mysql_set_session_variable_to_default
149+
run_without_connection do |orig_connection|
150+
ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:default_week_format => :default}}))
151+
global_mode = ActiveRecord::Base.connection.exec_query "SELECT @@GLOBAL.DEFAULT_WEEK_FORMAT"
152+
session_mode = ActiveRecord::Base.connection.exec_query "SELECT @@SESSION.DEFAULT_WEEK_FORMAT"
153+
assert_equal global_mode.rows, session_mode.rows
154+
end
155+
end
156+
140157
private
141158

142159
def run_without_connection

activerecord/test/cases/adapters/mysql2/connection_test.rb

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,23 @@ def test_mysql_strict_mode_disabled_dont_override_global_sql_mode
5353
end
5454
end
5555

56+
def test_mysql_set_session_variable
57+
run_without_connection do |orig_connection|
58+
ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:default_week_format => 3}}))
59+
session_mode = ActiveRecord::Base.connection.exec_query "SELECT @@SESSION.DEFAULT_WEEK_FORMAT"
60+
assert_equal 3, session_mode.rows.first.first.to_i
61+
end
62+
end
63+
64+
def test_mysql_set_session_variable_to_default
65+
run_without_connection do |orig_connection|
66+
ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:default_week_format => :default}}))
67+
global_mode = ActiveRecord::Base.connection.exec_query "SELECT @@GLOBAL.DEFAULT_WEEK_FORMAT"
68+
session_mode = ActiveRecord::Base.connection.exec_query "SELECT @@SESSION.DEFAULT_WEEK_FORMAT"
69+
assert_equal global_mode.rows, session_mode.rows
70+
end
71+
end
72+
5673
def test_logs_name_structure_dump
5774
@connection.structure_dump
5875
assert_equal "SCHEMA", @connection.logged[0][1]

activerecord/test/cases/adapters/postgresql/connection_test.rb

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,5 +154,46 @@ def test_reconnection_after_actual_disconnection_with_verify
154154
end
155155
end
156156

157+
def test_set_session_variable_true
158+
run_without_connection do |orig_connection|
159+
ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:debug_print_plan => true}}))
160+
set_true = ActiveRecord::Base.connection.exec_query "SHOW DEBUG_PRINT_PLAN"
161+
assert_equal set_true.rows, [["on"]]
162+
end
163+
end
164+
165+
def test_set_session_variable_false
166+
run_without_connection do |orig_connection|
167+
ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:debug_print_plan => false}}))
168+
set_false = ActiveRecord::Base.connection.exec_query "SHOW DEBUG_PRINT_PLAN"
169+
assert_equal set_false.rows, [["off"]]
170+
end
171+
end
172+
173+
def test_set_session_variable_nil
174+
run_without_connection do |orig_connection|
175+
# This should be a no-op that does not raise an error
176+
ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:debug_print_plan => nil}}))
177+
end
178+
end
179+
180+
def test_set_session_variable_default
181+
run_without_connection do |orig_connection|
182+
# This should execute a query that does not raise an error
183+
ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:debug_print_plan => :default}}))
184+
end
185+
end
186+
187+
private
188+
189+
def run_without_connection
190+
original_connection = ActiveRecord::Base.remove_connection
191+
begin
192+
yield original_connection
193+
ensure
194+
ActiveRecord::Base.establish_connection(original_connection)
195+
end
196+
end
197+
157198
end
158199
end

0 commit comments

Comments
 (0)