From 630ddc707ec5f7221177f670ce55fb40b908b34f Mon Sep 17 00:00:00 2001 From: Adrianna Chang Date: Tue, 2 May 2023 09:43:30 -0400 Subject: [PATCH] Move shared database logic to MySQL::DatabaseStatements --- .../abstract_mysql_adapter.rb | 2 + .../mysql/database_statements.rb | 87 +++++++++++++++++++ .../mysql2/database_statements.rb | 77 ---------------- .../trilogy/database_statements.rb | 76 ++++++++-------- .../connection_adapters/trilogy_adapter.rb | 73 ---------------- activerecord/lib/active_record/explain.rb | 2 +- 6 files changed, 127 insertions(+), 190 deletions(-) create mode 100644 activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb index ccb5c583ce..b18c0be64a 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -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 diff --git a/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb b/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb new file mode 100644 index 0000000000..6f5a918349 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb @@ -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 diff --git a/activerecord/lib/active_record/connection_adapters/mysql2/database_statements.rb b/activerecord/lib/active_record/connection_adapters/mysql2/database_statements.rb index 96f92e1713..5b645e2e80 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2/database_statements.rb @@ -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 diff --git a/activerecord/lib/active_record/connection_adapters/trilogy/database_statements.rb b/activerecord/lib/active_record/connection_adapters/trilogy/database_statements.rb index 07de576208..388030ad55 100644 --- a/activerecord/lib/active_record/connection_adapters/trilogy/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/trilogy/database_statements.rb @@ -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 diff --git a/activerecord/lib/active_record/connection_adapters/trilogy_adapter.rb b/activerecord/lib/active_record/connection_adapters/trilogy_adapter.rb index ae543a61f8..1545af72c2 100644 --- a/activerecord/lib/active_record/connection_adapters/trilogy_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/trilogy_adapter.rb @@ -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 diff --git a/activerecord/lib/active_record/explain.rb b/activerecord/lib/active_record/explain.rb index 54c1952738..c607427dae 100644 --- a/activerecord/lib/active_record/explain.rb +++ b/activerecord/lib/active_record/explain.rb @@ -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:"