Move shared database logic to MySQL::DatabaseStatements
This commit is contained in:
parent
93b5fc1f95
commit
630ddc707e
@ -3,6 +3,7 @@
|
||||
require "active_record/connection_adapters/abstract_adapter"
|
||||
require "active_record/connection_adapters/statement_pool"
|
||||
require "active_record/connection_adapters/mysql/column"
|
||||
require "active_record/connection_adapters/mysql/database_statements"
|
||||
require "active_record/connection_adapters/mysql/explain_pretty_printer"
|
||||
require "active_record/connection_adapters/mysql/quoting"
|
||||
require "active_record/connection_adapters/mysql/schema_creation"
|
||||
@ -14,6 +15,7 @@
|
||||
module ActiveRecord
|
||||
module ConnectionAdapters
|
||||
class AbstractMysqlAdapter < AbstractAdapter
|
||||
include MySQL::DatabaseStatements
|
||||
include MySQL::Quoting
|
||||
include MySQL::SchemaStatements
|
||||
|
||||
|
@ -0,0 +1,87 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module ActiveRecord
|
||||
module ConnectionAdapters
|
||||
module MySQL
|
||||
module DatabaseStatements
|
||||
READ_QUERY = AbstractAdapter.build_read_query_regexp(
|
||||
:desc, :describe, :set, :show, :use
|
||||
) # :nodoc:
|
||||
private_constant :READ_QUERY
|
||||
|
||||
# https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_current-timestamp
|
||||
# https://dev.mysql.com/doc/refman/5.7/en/date-and-time-type-syntax.html
|
||||
HIGH_PRECISION_CURRENT_TIMESTAMP = Arel.sql("CURRENT_TIMESTAMP(6)").freeze # :nodoc:
|
||||
private_constant :HIGH_PRECISION_CURRENT_TIMESTAMP
|
||||
|
||||
def write_query?(sql) # :nodoc:
|
||||
!READ_QUERY.match?(sql)
|
||||
rescue ArgumentError # Invalid encoding
|
||||
!READ_QUERY.match?(sql.b)
|
||||
end
|
||||
|
||||
def high_precision_current_timestamp
|
||||
HIGH_PRECISION_CURRENT_TIMESTAMP
|
||||
end
|
||||
|
||||
def explain(arel, binds = [], options = [])
|
||||
sql = build_explain_clause(options) + " " + to_sql(arel, binds)
|
||||
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
||||
result = internal_exec_query(sql, "EXPLAIN", binds)
|
||||
elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
|
||||
|
||||
MySQL::ExplainPrettyPrinter.new.pp(result, elapsed)
|
||||
end
|
||||
|
||||
def build_explain_clause(options = [])
|
||||
return "EXPLAIN" if options.empty?
|
||||
|
||||
explain_clause = "EXPLAIN #{options.join(" ").upcase}"
|
||||
|
||||
if analyze_without_explain? && explain_clause.include?("ANALYZE")
|
||||
explain_clause.sub("EXPLAIN ", "")
|
||||
else
|
||||
explain_clause
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
# https://mariadb.com/kb/en/analyze-statement/
|
||||
def analyze_without_explain?
|
||||
mariadb? && database_version >= "10.1.0"
|
||||
end
|
||||
|
||||
def default_insert_value(column)
|
||||
super unless column.auto_increment?
|
||||
end
|
||||
|
||||
def combine_multi_statements(total_sql)
|
||||
total_sql.each_with_object([]) do |sql, total_sql_chunks|
|
||||
previous_packet = total_sql_chunks.last
|
||||
if max_allowed_packet_reached?(sql, previous_packet)
|
||||
total_sql_chunks << +sql
|
||||
else
|
||||
previous_packet << ";\n"
|
||||
previous_packet << sql
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def max_allowed_packet_reached?(current_packet, previous_packet)
|
||||
if current_packet.bytesize > max_allowed_packet
|
||||
raise ActiveRecordError,
|
||||
"Fixtures set is too large #{current_packet.bytesize}. Consider increasing the max_allowed_packet variable."
|
||||
elsif previous_packet.nil?
|
||||
true
|
||||
else
|
||||
(current_packet.bytesize + previous_packet.bytesize + 2) > max_allowed_packet
|
||||
end
|
||||
end
|
||||
|
||||
def max_allowed_packet
|
||||
@max_allowed_packet ||= show_variable("max_allowed_packet")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -18,26 +18,6 @@ def select_all(*, **) # :nodoc:
|
||||
result
|
||||
end
|
||||
|
||||
READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(
|
||||
:desc, :describe, :set, :show, :use
|
||||
) # :nodoc:
|
||||
private_constant :READ_QUERY
|
||||
|
||||
def write_query?(sql) # :nodoc:
|
||||
!READ_QUERY.match?(sql)
|
||||
rescue ArgumentError # Invalid encoding
|
||||
!READ_QUERY.match?(sql.b)
|
||||
end
|
||||
|
||||
def explain(arel, binds = [], options = [])
|
||||
sql = build_explain_clause(options) + " " + to_sql(arel, binds)
|
||||
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
||||
result = internal_exec_query(sql, "EXPLAIN", binds)
|
||||
elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
|
||||
|
||||
MySQL::ExplainPrettyPrinter.new.pp(result, elapsed)
|
||||
end
|
||||
|
||||
def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false) # :nodoc:
|
||||
if without_prepared_statement?(binds)
|
||||
execute_and_free(sql, name, async: async) do |result|
|
||||
@ -70,27 +50,6 @@ def exec_delete(sql, name = nil, binds = []) # :nodoc:
|
||||
end
|
||||
alias :exec_update :exec_delete
|
||||
|
||||
# https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_current-timestamp
|
||||
# https://dev.mysql.com/doc/refman/5.7/en/date-and-time-type-syntax.html
|
||||
HIGH_PRECISION_CURRENT_TIMESTAMP = Arel.sql("CURRENT_TIMESTAMP(6)").freeze # :nodoc:
|
||||
private_constant :HIGH_PRECISION_CURRENT_TIMESTAMP
|
||||
|
||||
def high_precision_current_timestamp
|
||||
HIGH_PRECISION_CURRENT_TIMESTAMP
|
||||
end
|
||||
|
||||
def build_explain_clause(options = [])
|
||||
return "EXPLAIN" if options.empty?
|
||||
|
||||
explain_clause = "EXPLAIN #{options.join(" ").upcase}"
|
||||
|
||||
if analyze_without_explain? && explain_clause.include?("ANALYZE")
|
||||
explain_clause.sub("EXPLAIN ", "")
|
||||
else
|
||||
explain_clause
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def sync_timezone_changes(raw_connection)
|
||||
raw_connection.query_options[:database_timezone] = default_timezone
|
||||
@ -106,10 +65,6 @@ def execute_batch(statements, name = nil)
|
||||
end
|
||||
end
|
||||
|
||||
def default_insert_value(column)
|
||||
super unless column.auto_increment?
|
||||
end
|
||||
|
||||
def last_inserted_id(result)
|
||||
@raw_connection&.last_id
|
||||
end
|
||||
@ -138,33 +93,6 @@ def with_multi_statements
|
||||
end
|
||||
end
|
||||
|
||||
def combine_multi_statements(total_sql)
|
||||
total_sql.each_with_object([]) do |sql, total_sql_chunks|
|
||||
previous_packet = total_sql_chunks.last
|
||||
if max_allowed_packet_reached?(sql, previous_packet)
|
||||
total_sql_chunks << +sql
|
||||
else
|
||||
previous_packet << ";\n"
|
||||
previous_packet << sql
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def max_allowed_packet_reached?(current_packet, previous_packet)
|
||||
if current_packet.bytesize > max_allowed_packet
|
||||
raise ActiveRecordError,
|
||||
"Fixtures set is too large #{current_packet.bytesize}. Consider increasing the max_allowed_packet variable."
|
||||
elsif previous_packet.nil?
|
||||
true
|
||||
else
|
||||
(current_packet.bytesize + previous_packet.bytesize + 2) > max_allowed_packet
|
||||
end
|
||||
end
|
||||
|
||||
def max_allowed_packet
|
||||
@max_allowed_packet ||= show_variable("max_allowed_packet")
|
||||
end
|
||||
|
||||
def raw_execute(sql, name, async: false, allow_retry: false, materialize_transactions: true)
|
||||
log(sql, name, async: async) do
|
||||
with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
|
||||
@ -214,11 +142,6 @@ def exec_stmt_and_free(sql, name, binds, cache_stmt: false, async: false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# https://mariadb.com/kb/en/analyze-statement/
|
||||
def analyze_without_explain?
|
||||
mariadb? && database_version >= "10.1.0"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -4,14 +4,6 @@ module ActiveRecord
|
||||
module ConnectionAdapters
|
||||
module Trilogy
|
||||
module DatabaseStatements
|
||||
READ_QUERY = AbstractAdapter.build_read_query_regexp(
|
||||
:desc, :describe, :set, :show, :use
|
||||
) # :nodoc:
|
||||
private_constant :READ_QUERY
|
||||
|
||||
HIGH_PRECISION_CURRENT_TIMESTAMP = Arel.sql("CURRENT_TIMESTAMP(6)").freeze # :nodoc:
|
||||
private_constant :HIGH_PRECISION_CURRENT_TIMESTAMP
|
||||
|
||||
def select_all(*, **) # :nodoc:
|
||||
result = nil
|
||||
with_raw_connection do |conn|
|
||||
@ -21,21 +13,6 @@ def select_all(*, **) # :nodoc:
|
||||
result
|
||||
end
|
||||
|
||||
def write_query?(sql) # :nodoc:
|
||||
!READ_QUERY.match?(sql)
|
||||
rescue ArgumentError # Invalid encoding
|
||||
!READ_QUERY.match?(sql.b)
|
||||
end
|
||||
|
||||
def explain(arel, binds = [], options = [])
|
||||
sql = build_explain_clause(options) + " " + to_sql(arel, binds)
|
||||
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
||||
result = internal_exec_query(sql, "EXPLAIN", binds)
|
||||
elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
|
||||
|
||||
MySQL::ExplainPrettyPrinter.new.pp(result, elapsed)
|
||||
end
|
||||
|
||||
def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false) # :nodoc:
|
||||
sql = transform_query(sql)
|
||||
check_if_write_query(sql)
|
||||
@ -64,22 +41,6 @@ def exec_delete(sql, name = nil, binds = []) # :nodoc:
|
||||
|
||||
alias :exec_update :exec_delete # :nodoc:
|
||||
|
||||
def high_precision_current_timestamp
|
||||
HIGH_PRECISION_CURRENT_TIMESTAMP
|
||||
end
|
||||
|
||||
def build_explain_clause(options = [])
|
||||
return "EXPLAIN" if options.empty?
|
||||
|
||||
explain_clause = "EXPLAIN #{options.join(" ").upcase}"
|
||||
|
||||
if analyze_without_explain? && explain_clause.include?("ANALYZE")
|
||||
explain_clause.sub("EXPLAIN ", "")
|
||||
else
|
||||
explain_clause
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def raw_execute(sql, name, async: false, allow_retry: false, materialize_transactions: true)
|
||||
log(sql, name, async: async) do
|
||||
@ -95,6 +56,43 @@ def raw_execute(sql, name, async: false, allow_retry: false, materialize_transac
|
||||
def last_inserted_id(result)
|
||||
result.last_insert_id
|
||||
end
|
||||
|
||||
def sync_timezone_changes(conn)
|
||||
# Sync any changes since connection last established.
|
||||
if default_timezone == :local
|
||||
conn.query_flags |= ::Trilogy::QUERY_FLAGS_LOCAL_TIMEZONE
|
||||
else
|
||||
conn.query_flags &= ~::Trilogy::QUERY_FLAGS_LOCAL_TIMEZONE
|
||||
end
|
||||
end
|
||||
|
||||
def execute_batch(statements, name = nil)
|
||||
statements = statements.map { |sql| transform_query(sql) }
|
||||
combine_multi_statements(statements).each do |statement|
|
||||
with_raw_connection do |conn|
|
||||
raw_execute(statement, name)
|
||||
conn.next_result while conn.more_results_exist?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def multi_statements_enabled?
|
||||
!!@config[:multi_statement]
|
||||
end
|
||||
|
||||
def with_multi_statements
|
||||
if multi_statements_enabled?
|
||||
return yield
|
||||
end
|
||||
|
||||
with_raw_connection do |conn|
|
||||
conn.set_server_option(::Trilogy::SET_SERVER_MULTI_STATEMENTS_ON)
|
||||
|
||||
yield
|
||||
ensure
|
||||
conn.set_server_option(::Trilogy::SET_SERVER_MULTI_STATEMENTS_OFF)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -182,70 +182,6 @@ def reconnect
|
||||
connect
|
||||
end
|
||||
|
||||
def sync_timezone_changes(conn)
|
||||
# Sync any changes since connection last established.
|
||||
if default_timezone == :local
|
||||
conn.query_flags |= ::Trilogy::QUERY_FLAGS_LOCAL_TIMEZONE
|
||||
else
|
||||
conn.query_flags &= ~::Trilogy::QUERY_FLAGS_LOCAL_TIMEZONE
|
||||
end
|
||||
end
|
||||
|
||||
def execute_batch(statements, name = nil)
|
||||
statements = statements.map { |sql| transform_query(sql) }
|
||||
combine_multi_statements(statements).each do |statement|
|
||||
with_raw_connection do |conn|
|
||||
raw_execute(statement, name)
|
||||
conn.next_result while conn.more_results_exist?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def multi_statements_enabled?
|
||||
!!@config[:multi_statement]
|
||||
end
|
||||
|
||||
def with_multi_statements
|
||||
if multi_statements_enabled?
|
||||
return yield
|
||||
end
|
||||
|
||||
with_raw_connection do |conn|
|
||||
conn.set_server_option(::Trilogy::SET_SERVER_MULTI_STATEMENTS_ON)
|
||||
|
||||
yield
|
||||
ensure
|
||||
conn.set_server_option(::Trilogy::SET_SERVER_MULTI_STATEMENTS_OFF)
|
||||
end
|
||||
end
|
||||
|
||||
def combine_multi_statements(total_sql)
|
||||
total_sql.each_with_object([]) do |sql, total_sql_chunks|
|
||||
previous_packet = total_sql_chunks.last
|
||||
if max_allowed_packet_reached?(sql, previous_packet)
|
||||
total_sql_chunks << +sql
|
||||
else
|
||||
previous_packet << ";\n"
|
||||
previous_packet << sql
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def max_allowed_packet_reached?(current_packet, previous_packet)
|
||||
if current_packet.bytesize > max_allowed_packet
|
||||
raise ActiveRecordError,
|
||||
"Fixtures set is too large #{current_packet.bytesize}. Consider increasing the max_allowed_packet variable."
|
||||
elsif previous_packet.nil?
|
||||
true
|
||||
else
|
||||
(current_packet.bytesize + previous_packet.bytesize + 2) > max_allowed_packet
|
||||
end
|
||||
end
|
||||
|
||||
def max_allowed_packet
|
||||
@max_allowed_packet ||= show_variable("max_allowed_packet")
|
||||
end
|
||||
|
||||
def full_version
|
||||
schema_cache.database_version.full_version_string
|
||||
end
|
||||
@ -265,15 +201,6 @@ def translate_exception(exception, message:, sql:, binds:)
|
||||
def default_prepared_statements
|
||||
false
|
||||
end
|
||||
|
||||
def default_insert_value(column)
|
||||
super unless column.auto_increment?
|
||||
end
|
||||
|
||||
# https://mariadb.com/kb/en/analyze-statement/
|
||||
def analyze_without_explain?
|
||||
mariadb? && database_version >= "10.1.0"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -52,7 +52,7 @@ def render_bind(attr)
|
||||
end
|
||||
|
||||
def build_explain_clause(options = [])
|
||||
if connection.respond_to?(:build_explain_clause)
|
||||
if connection.respond_to?(:build_explain_clause, true)
|
||||
connection.build_explain_clause(options)
|
||||
else
|
||||
"EXPLAIN for:"
|
||||
|
Loading…
Reference in New Issue
Block a user