mirror of https://github.com/rails/rails
Revert "Add prepared statements support for `Mysql2Adapter`"
This commit is contained in:
parent
14b20ce9b3
commit
b5bbdbd3bc
2
Gemfile
2
Gemfile
|
@ -92,7 +92,7 @@ platforms :ruby do
|
|||
group :db do
|
||||
gem 'pg', '>= 0.18.0'
|
||||
gem 'mysql', '>= 2.9.0'
|
||||
gem 'mysql2', '>= 0.4.2'
|
||||
gem 'mysql2', '>= 0.4.0'
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -238,7 +238,7 @@ GEM
|
|||
multi_json (1.11.2)
|
||||
mustache (1.0.2)
|
||||
mysql (2.9.1)
|
||||
mysql2 (0.4.2)
|
||||
mysql2 (0.4.1)
|
||||
nokogiri (1.6.7.rc3)
|
||||
mini_portile (~> 0.7.0.rc4)
|
||||
pg (0.18.3)
|
||||
|
@ -347,7 +347,7 @@ DEPENDENCIES
|
|||
minitest (< 5.3.4)
|
||||
mocha (~> 0.14)
|
||||
mysql (>= 2.9.0)
|
||||
mysql2 (>= 0.4.2)
|
||||
mysql2 (>= 0.4.0)
|
||||
nokogiri (>= 1.6.7.rc3)
|
||||
pg (>= 0.18.0)
|
||||
psych (~> 2.0)
|
||||
|
|
|
@ -1,7 +1,3 @@
|
|||
* Add prepared statements support for `Mysql2Adapter`.
|
||||
|
||||
*Ryuta Kamizono*
|
||||
|
||||
* Add schema dumping support for PostgreSQL geometric data types.
|
||||
|
||||
*Ryuta Kamizono*
|
||||
|
|
|
@ -2,7 +2,6 @@ require 'active_record/connection_adapters/abstract_adapter'
|
|||
require 'active_record/connection_adapters/mysql/schema_creation'
|
||||
require 'active_record/connection_adapters/mysql/schema_definitions'
|
||||
require 'active_record/connection_adapters/mysql/schema_dumper'
|
||||
require 'active_record/connection_adapters/statement_pool'
|
||||
|
||||
require 'active_support/core_ext/string/strip'
|
||||
|
||||
|
@ -142,14 +141,6 @@ module ActiveRecord
|
|||
INDEX_TYPES = [:fulltext, :spatial]
|
||||
INDEX_USINGS = [:btree, :hash]
|
||||
|
||||
class StatementPool < ConnectionAdapters::StatementPool
|
||||
private
|
||||
|
||||
def dealloc(stmt)
|
||||
stmt[:stmt].close
|
||||
end
|
||||
end
|
||||
|
||||
# FIXME: Make the first parameter more similar for the two adapters
|
||||
def initialize(connection, logger, connection_options, config)
|
||||
super(connection, logger)
|
||||
|
@ -157,7 +148,6 @@ module ActiveRecord
|
|||
@quoted_column_names, @quoted_table_names = {}, {}
|
||||
|
||||
@visitor = Arel::Visitors::MySQL.new self
|
||||
@statements = StatementPool.new(self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 }))
|
||||
|
||||
if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
|
||||
@prepared_statements = true
|
||||
|
@ -194,12 +184,6 @@ module ActiveRecord
|
|||
true
|
||||
end
|
||||
|
||||
# Returns true, since this connection adapter supports prepared statement
|
||||
# caching.
|
||||
def supports_statement_cache?
|
||||
true
|
||||
end
|
||||
|
||||
# Technically MySQL allows to create indexes with the sort order syntax
|
||||
# but at the moment (5.5) it doesn't yet implement them
|
||||
def supports_index_sort_order?
|
||||
|
@ -407,20 +391,9 @@ module ActiveRecord
|
|||
end
|
||||
end
|
||||
|
||||
def select_all(arel, name = nil, binds = [])
|
||||
rows = if ExplainRegistry.collect? && prepared_statements
|
||||
unprepared_statement { super }
|
||||
else
|
||||
super
|
||||
end
|
||||
@connection.next_result while @connection.more_results?
|
||||
rows
|
||||
end
|
||||
|
||||
# Clears the prepared statements cache.
|
||||
def clear_cache!
|
||||
super
|
||||
reload_type_map
|
||||
@statements.clear
|
||||
end
|
||||
|
||||
# Executes the SQL statement in the context of this connection.
|
||||
|
@ -431,26 +404,11 @@ module ActiveRecord
|
|||
# MysqlAdapter has to free a result after using it, so we use this method to write
|
||||
# stuff in an abstract way without concerning ourselves about whether it needs to be
|
||||
# explicitly freed or not.
|
||||
def execute_and_free(sql, name = nil) # :nodoc:
|
||||
def execute_and_free(sql, name = nil) #:nodoc:
|
||||
yield execute(sql, name)
|
||||
end
|
||||
|
||||
def exec_delete(sql, name, binds) # :nodoc:
|
||||
if without_prepared_statement?(binds)
|
||||
execute_and_free(sql, name) { @connection.affected_rows }
|
||||
else
|
||||
exec_stmt_and_free(sql, name, binds) { |stmt| stmt.affected_rows }
|
||||
end
|
||||
end
|
||||
alias :exec_update :exec_delete
|
||||
|
||||
def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) # :nodoc:
|
||||
super
|
||||
id_value || last_inserted_id
|
||||
end
|
||||
alias :create :insert_sql
|
||||
|
||||
def update_sql(sql, name = nil) # :nodoc:
|
||||
def update_sql(sql, name = nil) #:nodoc:
|
||||
super
|
||||
@connection.affected_rows
|
||||
end
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
require 'active_record/connection_adapters/abstract_mysql_adapter'
|
||||
|
||||
gem 'mysql2', '>= 0.4.2', '< 0.5'
|
||||
gem 'mysql2', '>= 0.3.18', '< 0.5'
|
||||
require 'mysql2'
|
||||
|
||||
module ActiveRecord
|
||||
|
@ -33,6 +33,7 @@ module ActiveRecord
|
|||
|
||||
def initialize(connection, logger, connection_options, config)
|
||||
super
|
||||
@prepared_statements = false
|
||||
configure_connection
|
||||
end
|
||||
|
||||
|
@ -125,13 +126,9 @@ 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 = [])
|
||||
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
|
||||
result = execute(sql, name)
|
||||
@connection.next_result while @connection.more_results?
|
||||
rows
|
||||
result.to_a
|
||||
end
|
||||
|
||||
# Executes the SQL statement in the context of this connection.
|
||||
|
@ -146,59 +143,35 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
def exec_query(sql, name = 'SQL', binds = [], prepare: false)
|
||||
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
|
||||
result = execute(sql, name)
|
||||
@connection.next_result while @connection.more_results?
|
||||
ActiveRecord::Result.new(result.fields, result.to_a)
|
||||
end
|
||||
|
||||
def last_inserted_id(result = nil)
|
||||
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
|
||||
end
|
||||
alias :create :insert_sql
|
||||
|
||||
def exec_insert(sql, name, binds, pk = nil, sequence_name = nil)
|
||||
execute to_sql(sql, binds), name
|
||||
end
|
||||
|
||||
def exec_delete(sql, name, binds)
|
||||
execute to_sql(sql, binds), name
|
||||
@connection.affected_rows
|
||||
end
|
||||
alias :exec_update :exec_delete
|
||||
|
||||
def last_inserted_id(result)
|
||||
@connection.last_id
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
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
|
||||
|
||||
type_casted_binds = binds.map { |attr| type_cast(attr.value_for_database) }
|
||||
|
||||
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)
|
||||
configure_connection
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
require 'active_record/connection_adapters/abstract_mysql_adapter'
|
||||
require 'active_record/connection_adapters/statement_pool'
|
||||
require 'active_support/core_ext/hash/keys'
|
||||
|
||||
gem 'mysql', '~> 2.9'
|
||||
|
@ -69,12 +70,27 @@ module ActiveRecord
|
|||
class MysqlAdapter < AbstractMysqlAdapter
|
||||
ADAPTER_NAME = 'MySQL'.freeze
|
||||
|
||||
class StatementPool < ConnectionAdapters::StatementPool
|
||||
private
|
||||
|
||||
def dealloc(stmt)
|
||||
stmt[:stmt].close
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(connection, logger, connection_options, config)
|
||||
super
|
||||
@statements = StatementPool.new(self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 }))
|
||||
@client_encoding = nil
|
||||
connect
|
||||
end
|
||||
|
||||
# Returns true, since this connection adapter supports prepared statement
|
||||
# caching.
|
||||
def supports_statement_cache?
|
||||
true
|
||||
end
|
||||
|
||||
# HELPER METHODS ===========================================
|
||||
|
||||
def each_hash(result) # :nodoc:
|
||||
|
@ -150,6 +166,27 @@ module ActiveRecord
|
|||
# DATABASE STATEMENTS ======================================
|
||||
#++
|
||||
|
||||
def select_all(arel, name = nil, binds = [])
|
||||
if ExplainRegistry.collect? && prepared_statements
|
||||
unprepared_statement { super }
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
def select_rows(sql, name = nil, binds = [])
|
||||
@connection.query_with_result = true
|
||||
rows = exec_query(sql, name, binds).rows
|
||||
@connection.more_results && @connection.next_result # invoking stored procedures with CLIENT_MULTI_RESULTS requires this to tidy up else connection will be dropped
|
||||
rows
|
||||
end
|
||||
|
||||
# Clears the prepared statements cache.
|
||||
def clear_cache!
|
||||
super
|
||||
@statements.clear
|
||||
end
|
||||
|
||||
# Taken from here:
|
||||
# https://github.com/tmtm/ruby-mysql/blob/master/lib/mysql/charset.rb
|
||||
# Author: TOMITA Masahiro <tommy@tmtm.org>
|
||||
|
@ -195,55 +232,27 @@ module ActiveRecord
|
|||
|
||||
# Get the client encoding for this database
|
||||
def client_encoding
|
||||
@client_encoding ||= ENCODINGS[select_value("SELECT @@character_set_client", 'SCHEMA')]
|
||||
end
|
||||
return @client_encoding if @client_encoding
|
||||
|
||||
def select_all(arel, name = nil, binds = [])
|
||||
@connection.query_with_result = true
|
||||
super
|
||||
end
|
||||
|
||||
def select_rows(sql, name = nil, binds = [])
|
||||
@connection.query_with_result = true
|
||||
rows = if without_prepared_statement?(binds)
|
||||
execute_and_free(sql, name) { |result| result.to_a }
|
||||
else
|
||||
exec_stmt_and_free(sql, name, binds) { |stmt| stmt.to_a }
|
||||
end
|
||||
@connection.next_result while @connection.more_results?
|
||||
rows
|
||||
result = exec_query(
|
||||
"select @@character_set_client",
|
||||
'SCHEMA')
|
||||
@client_encoding = ENCODINGS[result.rows.last.last]
|
||||
end
|
||||
|
||||
def exec_query(sql, name = 'SQL', binds = [], prepare: false)
|
||||
if without_prepared_statement?(binds)
|
||||
execute_and_free(sql, name) do |result|
|
||||
if result
|
||||
types = {}
|
||||
fields = []
|
||||
result.fetch_fields.each { |field|
|
||||
field_name = field.name
|
||||
fields << field_name
|
||||
|
||||
if field.decimals > 0
|
||||
types[field_name] = Type::Decimal.new
|
||||
else
|
||||
types[field_name] = Fields.find_type field
|
||||
end
|
||||
}
|
||||
ActiveRecord::Result.new(fields, result.to_a, types)
|
||||
end
|
||||
end
|
||||
result_set, affected_rows = exec_without_stmt(sql, name)
|
||||
else
|
||||
exec_stmt_and_free(sql, name, binds, cache_stmt: prepare) do |stmt, result|
|
||||
if result
|
||||
fields = result.fetch_fields.map(&:name)
|
||||
ActiveRecord::Result.new(fields, stmt.to_a)
|
||||
end
|
||||
end
|
||||
result_set, affected_rows = exec_stmt(sql, name, binds, cache_stmt: prepare)
|
||||
end
|
||||
|
||||
yield affected_rows if block_given?
|
||||
|
||||
result_set
|
||||
end
|
||||
|
||||
def last_inserted_id(result = nil)
|
||||
def last_inserted_id(result)
|
||||
@connection.insert_id
|
||||
end
|
||||
|
||||
|
@ -313,16 +322,69 @@ module ActiveRecord
|
|||
register_class_with_precision m, %r(time)i, Fields::Time
|
||||
end
|
||||
|
||||
def exec_without_stmt(sql, name = 'SQL') # :nodoc:
|
||||
# Some queries, like SHOW CREATE TABLE don't work through the prepared
|
||||
# statement API. For those queries, we need to use this method. :'(
|
||||
log(sql, name) do
|
||||
result = @connection.query(sql)
|
||||
affected_rows = @connection.affected_rows
|
||||
|
||||
if result
|
||||
types = {}
|
||||
fields = []
|
||||
result.fetch_fields.each { |field|
|
||||
field_name = field.name
|
||||
fields << field_name
|
||||
|
||||
if field.decimals > 0
|
||||
types[field_name] = Type::Decimal.new
|
||||
else
|
||||
types[field_name] = Fields.find_type field
|
||||
end
|
||||
}
|
||||
|
||||
result_set = ActiveRecord::Result.new(fields, result.to_a, types)
|
||||
result.free
|
||||
else
|
||||
result_set = ActiveRecord::Result.new([], [])
|
||||
end
|
||||
|
||||
[result_set, affected_rows]
|
||||
end
|
||||
end
|
||||
|
||||
def execute_and_free(sql, name = nil) # :nodoc:
|
||||
result = execute(sql, name)
|
||||
ret = yield result
|
||||
result.free if result
|
||||
result.free
|
||||
ret
|
||||
end
|
||||
|
||||
def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
|
||||
super sql, name
|
||||
id_value || @connection.insert_id
|
||||
end
|
||||
alias :create :insert_sql
|
||||
|
||||
def exec_delete(sql, name, binds) # :nodoc:
|
||||
affected_rows = 0
|
||||
|
||||
exec_query(sql, name, binds) do |n|
|
||||
affected_rows = n
|
||||
end
|
||||
|
||||
affected_rows
|
||||
end
|
||||
alias :exec_update :exec_delete
|
||||
|
||||
def begin_db_transaction #:nodoc:
|
||||
exec_query "BEGIN"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def exec_stmt_and_free(sql, name, binds, cache_stmt: false)
|
||||
def exec_stmt(sql, name, binds, cache_stmt: false)
|
||||
cache = {}
|
||||
type_casted_binds = binds.map { |attr| type_cast(attr.value_for_database) }
|
||||
|
||||
log(sql, name, binds) do
|
||||
|
@ -330,7 +392,7 @@ module ActiveRecord
|
|||
stmt = @connection.prepare(sql)
|
||||
else
|
||||
cache = @statements[sql] ||= {
|
||||
stmt: @connection.prepare(sql)
|
||||
:stmt => @connection.prepare(sql)
|
||||
}
|
||||
stmt = cache[:stmt]
|
||||
end
|
||||
|
@ -345,18 +407,24 @@ module ActiveRecord
|
|||
if !cache_stmt
|
||||
stmt.close
|
||||
else
|
||||
@statements.delete(sql)
|
||||
@statements.delete sql
|
||||
end
|
||||
raise e
|
||||
end
|
||||
|
||||
result = stmt.result_metadata
|
||||
ret = yield stmt, result
|
||||
result.free if result
|
||||
cols = nil
|
||||
if metadata = stmt.result_metadata
|
||||
cols = cache[:cols] ||= metadata.fetch_fields.map(&:name)
|
||||
metadata.free
|
||||
end
|
||||
|
||||
result_set = ActiveRecord::Result.new(cols, stmt.to_a) if cols
|
||||
affected_rows = stmt.affected_rows
|
||||
|
||||
stmt.free_result
|
||||
stmt.close if !cache_stmt
|
||||
ret
|
||||
|
||||
[result_set, affected_rows]
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -388,6 +456,13 @@ module ActiveRecord
|
|||
super
|
||||
end
|
||||
|
||||
def select(sql, name = nil, binds = [])
|
||||
@connection.query_with_result = true
|
||||
rows = super
|
||||
@connection.more_results && @connection.next_result # invoking stored procedures with CLIENT_MULTI_RESULTS requires this to tidy up else connection will be dropped
|
||||
rows
|
||||
end
|
||||
|
||||
# Returns the full version of the connected MySQL server.
|
||||
def full_version
|
||||
@full_version ||= @connection.server_info
|
||||
|
|
Loading…
Reference in New Issue