Skip to content

Commit 5947ec2

Browse files
authored
Merge pull request rails#48112 from adrianna-chang-shopify/ac-shared-mysql-db-statements
Clean up shared DB statements code between Mysql2 and Trilogy
2 parents d7d595b + 630ddc7 commit 5947ec2

File tree

7 files changed

+208
-271
lines changed

7 files changed

+208
-271
lines changed

activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
require "active_record/connection_adapters/abstract_adapter"
44
require "active_record/connection_adapters/statement_pool"
55
require "active_record/connection_adapters/mysql/column"
6+
require "active_record/connection_adapters/mysql/database_statements"
67
require "active_record/connection_adapters/mysql/explain_pretty_printer"
78
require "active_record/connection_adapters/mysql/quoting"
89
require "active_record/connection_adapters/mysql/schema_creation"
@@ -14,6 +15,7 @@
1415
module ActiveRecord
1516
module ConnectionAdapters
1617
class AbstractMysqlAdapter < AbstractAdapter
18+
include MySQL::DatabaseStatements
1719
include MySQL::Quoting
1820
include MySQL::SchemaStatements
1921

activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb

Lines changed: 13 additions & 151 deletions
Original file line numberDiff line numberDiff line change
@@ -4,31 +4,26 @@ module ActiveRecord
44
module ConnectionAdapters
55
module MySQL
66
module DatabaseStatements
7-
# Returns an ActiveRecord::Result instance.
8-
def select_all(*, **) # :nodoc:
9-
result = nil
10-
with_raw_connection do |conn|
11-
result = if ExplainRegistry.collect? && prepared_statements
12-
unprepared_statement { super }
13-
else
14-
super
15-
end
16-
conn.abandon_results!
17-
end
18-
result
19-
end
20-
21-
READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(
7+
READ_QUERY = AbstractAdapter.build_read_query_regexp(
228
:desc, :describe, :set, :show, :use
239
) # :nodoc:
2410
private_constant :READ_QUERY
2511

12+
# https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_current-timestamp
13+
# https://dev.mysql.com/doc/refman/5.7/en/date-and-time-type-syntax.html
14+
HIGH_PRECISION_CURRENT_TIMESTAMP = Arel.sql("CURRENT_TIMESTAMP(6)").freeze # :nodoc:
15+
private_constant :HIGH_PRECISION_CURRENT_TIMESTAMP
16+
2617
def write_query?(sql) # :nodoc:
2718
!READ_QUERY.match?(sql)
2819
rescue ArgumentError # Invalid encoding
2920
!READ_QUERY.match?(sql.b)
3021
end
3122

23+
def high_precision_current_timestamp
24+
HIGH_PRECISION_CURRENT_TIMESTAMP
25+
end
26+
3227
def explain(arel, binds = [], options = [])
3328
sql = build_explain_clause(options) + " " + to_sql(arel, binds)
3429
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
@@ -38,47 +33,6 @@ def explain(arel, binds = [], options = [])
3833
MySQL::ExplainPrettyPrinter.new.pp(result, elapsed)
3934
end
4035

41-
def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false) # :nodoc:
42-
if without_prepared_statement?(binds)
43-
execute_and_free(sql, name, async: async) do |result|
44-
if result
45-
build_result(columns: result.fields, rows: result.to_a)
46-
else
47-
build_result(columns: [], rows: [])
48-
end
49-
end
50-
else
51-
exec_stmt_and_free(sql, name, binds, cache_stmt: prepare, async: async) do |_, result|
52-
if result
53-
build_result(columns: result.fields, rows: result.to_a)
54-
else
55-
build_result(columns: [], rows: [])
56-
end
57-
end
58-
end
59-
end
60-
61-
def exec_delete(sql, name = nil, binds = []) # :nodoc:
62-
if without_prepared_statement?(binds)
63-
with_raw_connection do |conn|
64-
@affected_rows_before_warnings = nil
65-
execute_and_free(sql, name) { @affected_rows_before_warnings || conn.affected_rows }
66-
end
67-
else
68-
exec_stmt_and_free(sql, name, binds) { |stmt| stmt.affected_rows }
69-
end
70-
end
71-
alias :exec_update :exec_delete
72-
73-
# https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_current-timestamp
74-
# https://dev.mysql.com/doc/refman/5.7/en/date-and-time-type-syntax.html
75-
HIGH_PRECISION_CURRENT_TIMESTAMP = Arel.sql("CURRENT_TIMESTAMP(6)").freeze # :nodoc:
76-
private_constant :HIGH_PRECISION_CURRENT_TIMESTAMP
77-
78-
def high_precision_current_timestamp
79-
HIGH_PRECISION_CURRENT_TIMESTAMP
80-
end
81-
8236
def build_explain_clause(options = [])
8337
return "EXPLAIN" if options.empty?
8438

@@ -92,52 +46,15 @@ def build_explain_clause(options = [])
9246
end
9347

9448
private
95-
def sync_timezone_changes(raw_connection)
96-
raw_connection.query_options[:database_timezone] = default_timezone
97-
end
98-
99-
def execute_batch(statements, name = nil)
100-
statements = statements.map { |sql| transform_query(sql) }
101-
combine_multi_statements(statements).each do |statement|
102-
with_raw_connection do |conn|
103-
raw_execute(statement, name)
104-
conn.abandon_results!
105-
end
106-
end
49+
# https://mariadb.com/kb/en/analyze-statement/
50+
def analyze_without_explain?
51+
mariadb? && database_version >= "10.1.0"
10752
end
10853

10954
def default_insert_value(column)
11055
super unless column.auto_increment?
11156
end
11257

113-
def last_inserted_id(result)
114-
@raw_connection&.last_id
115-
end
116-
117-
def multi_statements_enabled?
118-
flags = @config[:flags]
119-
120-
if flags.is_a?(Array)
121-
flags.include?("MULTI_STATEMENTS")
122-
else
123-
flags.anybits?(Mysql2::Client::MULTI_STATEMENTS)
124-
end
125-
end
126-
127-
def with_multi_statements
128-
if multi_statements_enabled?
129-
return yield
130-
end
131-
132-
with_raw_connection do |conn|
133-
conn.set_server_option(Mysql2::Client::OPTION_MULTI_STATEMENTS_ON)
134-
135-
yield
136-
ensure
137-
conn.set_server_option(Mysql2::Client::OPTION_MULTI_STATEMENTS_OFF)
138-
end
139-
end
140-
14158
def combine_multi_statements(total_sql)
14259
total_sql.each_with_object([]) do |sql, total_sql_chunks|
14360
previous_packet = total_sql_chunks.last
@@ -164,61 +81,6 @@ def max_allowed_packet_reached?(current_packet, previous_packet)
16481
def max_allowed_packet
16582
@max_allowed_packet ||= show_variable("max_allowed_packet")
16683
end
167-
168-
def raw_execute(sql, name, async: false, allow_retry: false, materialize_transactions: true)
169-
log(sql, name, async: async) do
170-
with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
171-
sync_timezone_changes(conn)
172-
result = conn.query(sql)
173-
handle_warnings(sql)
174-
result
175-
end
176-
end
177-
end
178-
179-
def exec_stmt_and_free(sql, name, binds, cache_stmt: false, async: false)
180-
sql = transform_query(sql)
181-
check_if_write_query(sql)
182-
183-
mark_transaction_written_if_write(sql)
184-
185-
type_casted_binds = type_casted_binds(binds)
186-
187-
log(sql, name, binds, type_casted_binds, async: async) do
188-
with_raw_connection do |conn|
189-
sync_timezone_changes(conn)
190-
191-
if cache_stmt
192-
stmt = @statements[sql] ||= conn.prepare(sql)
193-
else
194-
stmt = conn.prepare(sql)
195-
end
196-
197-
begin
198-
result = ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
199-
stmt.execute(*type_casted_binds)
200-
end
201-
rescue Mysql2::Error => e
202-
if cache_stmt
203-
@statements.delete(sql)
204-
else
205-
stmt.close
206-
end
207-
raise e
208-
end
209-
210-
ret = yield stmt, result
211-
result.free if result
212-
stmt.close unless cache_stmt
213-
ret
214-
end
215-
end
216-
end
217-
218-
# https://mariadb.com/kb/en/analyze-statement/
219-
def analyze_without_explain?
220-
mariadb? && database_version >= "10.1.0"
221-
end
22284
end
22385
end
22486
end
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
# frozen_string_literal: true
2+
3+
module ActiveRecord
4+
module ConnectionAdapters
5+
module Mysql2
6+
module DatabaseStatements
7+
# Returns an ActiveRecord::Result instance.
8+
def select_all(*, **) # :nodoc:
9+
result = nil
10+
with_raw_connection do |conn|
11+
result = if ExplainRegistry.collect? && prepared_statements
12+
unprepared_statement { super }
13+
else
14+
super
15+
end
16+
conn.abandon_results!
17+
end
18+
result
19+
end
20+
21+
def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false) # :nodoc:
22+
if without_prepared_statement?(binds)
23+
execute_and_free(sql, name, async: async) do |result|
24+
if result
25+
build_result(columns: result.fields, rows: result.to_a)
26+
else
27+
build_result(columns: [], rows: [])
28+
end
29+
end
30+
else
31+
exec_stmt_and_free(sql, name, binds, cache_stmt: prepare, async: async) do |_, result|
32+
if result
33+
build_result(columns: result.fields, rows: result.to_a)
34+
else
35+
build_result(columns: [], rows: [])
36+
end
37+
end
38+
end
39+
end
40+
41+
def exec_delete(sql, name = nil, binds = []) # :nodoc:
42+
if without_prepared_statement?(binds)
43+
with_raw_connection do |conn|
44+
@affected_rows_before_warnings = nil
45+
execute_and_free(sql, name) { @affected_rows_before_warnings || conn.affected_rows }
46+
end
47+
else
48+
exec_stmt_and_free(sql, name, binds) { |stmt| stmt.affected_rows }
49+
end
50+
end
51+
alias :exec_update :exec_delete
52+
53+
private
54+
def sync_timezone_changes(raw_connection)
55+
raw_connection.query_options[:database_timezone] = default_timezone
56+
end
57+
58+
def execute_batch(statements, name = nil)
59+
statements = statements.map { |sql| transform_query(sql) }
60+
combine_multi_statements(statements).each do |statement|
61+
with_raw_connection do |conn|
62+
raw_execute(statement, name)
63+
conn.abandon_results!
64+
end
65+
end
66+
end
67+
68+
def last_inserted_id(result)
69+
@raw_connection&.last_id
70+
end
71+
72+
def multi_statements_enabled?
73+
flags = @config[:flags]
74+
75+
if flags.is_a?(Array)
76+
flags.include?("MULTI_STATEMENTS")
77+
else
78+
flags.anybits?(::Mysql2::Client::MULTI_STATEMENTS)
79+
end
80+
end
81+
82+
def with_multi_statements
83+
if multi_statements_enabled?
84+
return yield
85+
end
86+
87+
with_raw_connection do |conn|
88+
conn.set_server_option(::Mysql2::Client::OPTION_MULTI_STATEMENTS_ON)
89+
90+
yield
91+
ensure
92+
conn.set_server_option(::Mysql2::Client::OPTION_MULTI_STATEMENTS_OFF)
93+
end
94+
end
95+
96+
def raw_execute(sql, name, async: false, allow_retry: false, materialize_transactions: true)
97+
log(sql, name, async: async) do
98+
with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
99+
sync_timezone_changes(conn)
100+
result = conn.query(sql)
101+
handle_warnings(sql)
102+
result
103+
end
104+
end
105+
end
106+
107+
def exec_stmt_and_free(sql, name, binds, cache_stmt: false, async: false)
108+
sql = transform_query(sql)
109+
check_if_write_query(sql)
110+
111+
mark_transaction_written_if_write(sql)
112+
113+
type_casted_binds = type_casted_binds(binds)
114+
115+
log(sql, name, binds, type_casted_binds, async: async) do
116+
with_raw_connection do |conn|
117+
sync_timezone_changes(conn)
118+
119+
if cache_stmt
120+
stmt = @statements[sql] ||= conn.prepare(sql)
121+
else
122+
stmt = conn.prepare(sql)
123+
end
124+
125+
begin
126+
result = ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
127+
stmt.execute(*type_casted_binds)
128+
end
129+
rescue Mysql2::Error => e
130+
if cache_stmt
131+
@statements.delete(sql)
132+
else
133+
stmt.close
134+
end
135+
raise e
136+
end
137+
138+
ret = yield stmt, result
139+
result.free if result
140+
stmt.close unless cache_stmt
141+
ret
142+
end
143+
end
144+
end
145+
end
146+
end
147+
end
148+
end

0 commit comments

Comments
 (0)