Revert "Add prepared statements support for Mysql2Adapter"

This commit is contained in:
Sean Griffin 2015-11-26 11:53:10 -07:00
parent 14b20ce9b3
commit b5bbdbd3bc
6 changed files with 155 additions and 153 deletions

@ -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/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 @@ def attributes_for_hash
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 @@ def initialize(connection, logger, connection_options, config)
@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 @@ def supports_bulk_alter? #:nodoc:
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 @@ def build_footer(nrows, elapsed)
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 @@ def execute(sql, name = nil)
# 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 @@ class Mysql2Adapter < AbstractMysqlAdapter
def initialize(connection, logger, connection_options, config)
super
@prepared_statements = false
configure_connection
end
@ -125,13 +126,9 @@ def disconnect!
# 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 @@ def execute(sql, name = nil)
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 ConnectionAdapters
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 @@ def reset!
# 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 @@ def reset!
# 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 @@ def initialize_type_map(m) # :nodoc:
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 @@ def exec_stmt_and_free(sql, name, binds, cache_stmt: false)
stmt = @connection.prepare(sql)
else
cache = @statements[sql] ||= {
stmt: @connection.prepare(sql)
:stmt => @connection.prepare(sql)
}
stmt = cache[:stmt]
end
@ -345,18 +407,24 @@ def exec_stmt_and_free(sql, name, binds, cache_stmt: false)
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 @@ def configure_connection
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