aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib/active_record/connection_adapters/mysql_adapter.rb')
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb171
1 files changed, 123 insertions, 48 deletions
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index 7f1710b99c..f2d7b54105 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -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