From 38746d085b2c3344f6d26d38d48afb3a356a3e40 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Wed, 16 Sep 2015 13:37:04 +0900 Subject: Add prepared statements support for `Mysql2Adapter` --- .../connection_adapters/mysql2_adapter.rb | 77 +++++++++++++++------- 1 file changed, 52 insertions(+), 25 deletions(-) (limited to 'activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb') diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index 3944698910..273a9ee5fa 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -1,6 +1,6 @@ require 'active_record/connection_adapters/abstract_mysql_adapter' -gem 'mysql2', '>= 0.3.18', '< 0.5' +gem 'mysql2', '>= 0.4.2', '< 0.5' require 'mysql2' module ActiveRecord @@ -33,7 +33,6 @@ module ActiveRecord def initialize(connection, logger, connection_options, config) super - @prepared_statements = false configure_connection end @@ -126,9 +125,13 @@ module ActiveRecord # Returns an array of arrays containing the field values. # Order is the same as that returned by +columns+. def select_rows(sql, name = nil, binds = []) - result = execute(sql, name) + rows = if without_prepared_statement?(binds) + execute_and_free(sql, name) { |result| result.to_a } + else + exec_stmt_and_free(sql, name, binds) { |stmt, result| result.to_a } + end @connection.next_result while @connection.more_results? - result.to_a + rows end # Executes the SQL statement in the context of this connection. @@ -143,34 +146,58 @@ module ActiveRecord end def exec_query(sql, name = 'SQL', binds = [], prepare: false) - result = execute(sql, name) - @connection.next_result while @connection.more_results? - ActiveRecord::Result.new(result.fields, result.to_a) + if without_prepared_statement?(binds) + execute_and_free(sql, name) do |result| + ActiveRecord::Result.new(result.fields, result.to_a) if result + end + else + exec_stmt_and_free(sql, name, binds, cache_stmt: prepare) do |stmt, result| + ActiveRecord::Result.new(result.fields, result.to_a) if result + end + end end - alias exec_without_stmt exec_query - - def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) - super - id_value || @connection.last_id + def last_inserted_id(result = nil) + @connection.last_id end - alias :create :insert_sql - def exec_insert(sql, name, binds, pk = nil, sequence_name = nil) - execute to_sql(sql, binds), name - end + private - def exec_delete(sql, name, binds) - execute to_sql(sql, binds), name - @connection.affected_rows - end - alias :exec_update :exec_delete + def exec_stmt_and_free(sql, name, binds, cache_stmt: false) + if @connection + # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been + # made since we established the connection + @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone + end - def last_inserted_id(result) - @connection.last_id - end + type_casted_binds = binds.map { |attr| type_cast(attr.value_for_database) } - private + log(sql, name, binds) do + if !cache_stmt + stmt = @connection.prepare(sql) + else + cache = @statements[sql] ||= { + stmt: @connection.prepare(sql) + } + stmt = cache[:stmt] + end + + begin + result = stmt.execute(*type_casted_binds) + rescue Mysql2::Error => e + if !cache_stmt + stmt.close + else + @statements.delete(sql) + end + raise e + end + + ret = yield stmt, result + stmt.close if !cache_stmt + ret + end + end def connect @connection = Mysql2::Client.new(@config) -- cgit v1.2.3