activerecord-trilogy-adapter 3.1.0 → 3.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 065e502503fb93e0e951b03e67c43b8ea257a01c681c077b5b904a95ddcb0c90
4
- data.tar.gz: 2a85e79e0654196e0dba708f7bbc4146c413bf0f0b135894357bc174374c76fd
3
+ metadata.gz: 0a3a2190bc8818bad95cf334d7e4b0c9108a9520f37a899ff356f36b2c067ef1
4
+ data.tar.gz: bf58ccfed24d5a7ecd17f6670d4a6fb623e27f862cbcbb87b408470b46fa44c0
5
5
  SHA512:
6
- metadata.gz: 7ed2509f2e8593d3d1b73c56b403875a1751f50c5e5ae99fc405fead7020ec43c5b9e0510e196b3818a5cc1335480062a8c75e2cfebb757ca19c46fa9d1404be
7
- data.tar.gz: 45d9e001bee0aa227b2e7a7ac2f3b009d009f312cc2af128d5be0275c5f9ff2ce7b496ebde2ca7df1fedfd7a9a6b235df9fef1cfaf2309ca374357ba14d0a485
6
+ metadata.gz: 0b0991df8962bdfbe7d917aac03f224cbe919a3cef9113c39f1eab150d207c04b4a7db457404d492d3bb0e5b0e459d353f7511a1e82d7ff92c804d02353a8c34
7
+ data.tar.gz: 44a43a09ad1230250f29da21e941bc59a8f2d606f0716c7fc045ffba0b457e029fe01b34fade384b33277f06f2667cb8498c9b895fc7fce3461f7f07afacf332
@@ -12,6 +12,15 @@ module ActiveRecord
12
12
  HIGH_PRECISION_CURRENT_TIMESTAMP = Arel.sql("CURRENT_TIMESTAMP(6)").freeze # :nodoc:
13
13
  private_constant :HIGH_PRECISION_CURRENT_TIMESTAMP
14
14
 
15
+ def execute(sql, name = nil, **kwargs)
16
+ sql = transform_query(sql)
17
+ check_if_write_query(sql)
18
+ mark_transaction_written_if_write(sql)
19
+
20
+ result = raw_execute(sql, name, **kwargs)
21
+ ActiveRecord::Result.new(result.fields, result.to_a)
22
+ end
23
+
15
24
  def write_query?(sql) # :nodoc:
16
25
  !READ_QUERY.match?(sql)
17
26
  rescue ArgumentError # Invalid encoding
@@ -35,6 +44,21 @@ module ActiveRecord
35
44
  result
36
45
  end
37
46
 
47
+ def write_query?(sql) # :nodoc:
48
+ !READ_QUERY.match?(sql)
49
+ rescue ArgumentError # Invalid encoding
50
+ !READ_QUERY.match?(sql.b)
51
+ end
52
+
53
+ def explain(arel, binds = [], options = [])
54
+ sql = build_explain_clause(options) + " " + to_sql(arel, binds)
55
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
56
+ result = internal_exec_query(sql, "EXPLAIN", binds)
57
+ elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
58
+
59
+ MySQL::ExplainPrettyPrinter.new.pp(result, elapsed)
60
+ end
61
+
38
62
  def exec_query(sql, name = "SQL", binds = [], prepare: false, **kwargs)
39
63
  internal_exec_query(sql, name, binds, prepare: prepare, **kwargs)
40
64
  end
@@ -71,41 +95,44 @@ module ActiveRecord
71
95
  HIGH_PRECISION_CURRENT_TIMESTAMP
72
96
  end
73
97
 
74
- private
75
- if ActiveRecord.version < ::Gem::Version.new('7.0.a') # ActiveRecord <= 6.1 support
76
- def raw_execute(sql, name, uses_transaction: true, **_kwargs)
77
- # Same as mark_transaction_written_if_write(sql)
78
- transaction = current_transaction
79
- if transaction.respond_to?(:written) && transaction.open?
80
- transaction.written ||= write_query?(sql)
81
- end
98
+ def build_explain_clause(options = [])
99
+ return "EXPLAIN" if options.empty?
82
100
 
83
- log(sql, name) do
84
- with_trilogy_connection(uses_transaction: uses_transaction) do |conn|
85
- sync_timezone_changes(conn)
86
- conn.query(sql)
87
- end
88
- end
89
- end
101
+ explain_clause = "EXPLAIN #{options.join(" ").upcase}"
90
102
 
103
+ if analyze_without_explain? && explain_clause.include?("ANALYZE")
104
+ explain_clause.sub("EXPLAIN ", "")
105
+ else
106
+ explain_clause
107
+ end
108
+ end
109
+
110
+ private
111
+ if ActiveRecord.version < ::Gem::Version.new('7.0.a') # ActiveRecord <= 6.1 support
91
112
  def transform_query(sql); sql; end
92
113
  def check_if_write_query(*args); end
93
114
 
94
115
  if ActiveRecord.version < ::Gem::Version.new('6.1.a') # ActiveRecord <= 6.0 support
95
- def mark_transaction_written_if_write(*args); end
96
- end
97
- else # ActiveRecord 7.0 support
98
- def raw_execute(sql, name, uses_transaction: true, async: false, allow_retry: false)
99
- mark_transaction_written_if_write(sql)
100
- log(sql, name, async: async) do
101
- with_trilogy_connection(uses_transaction: uses_transaction, allow_retry: allow_retry) do |conn|
102
- sync_timezone_changes(conn)
103
- conn.query(sql)
116
+ def mark_transaction_written_if_write(sql)
117
+ transaction = current_transaction
118
+ if transaction.respond_to?(:written) && transaction.open?
119
+ transaction.written ||= write_query?(sql)
104
120
  end
105
121
  end
106
122
  end
107
123
  end
108
124
 
125
+ def raw_execute(sql, name, async: false, materialize_transactions: true)
126
+ log_kwargs = {}
127
+ log_kwargs[:async] = async if ActiveRecord.version >= ::Gem::Version.new('7.0.a')
128
+ log(sql, name, **log_kwargs) do
129
+ with_trilogy_connection(materialize_transactions: materialize_transactions) do |conn|
130
+ sync_timezone_changes(conn)
131
+ conn.query(sql)
132
+ end
133
+ end
134
+ end
135
+
109
136
  def last_inserted_id(result)
110
137
  result.last_insert_id
111
138
  end
@@ -118,6 +145,46 @@ module ActiveRecord
118
145
  conn.query_flags &= ~::Trilogy::QUERY_FLAGS_LOCAL_TIMEZONE
119
146
  end
120
147
  end
148
+
149
+ def execute_batch(statements, name = nil)
150
+ statements = statements.map { |sql| transform_query(sql) } if respond_to?(:transform_query)
151
+ combine_multi_statements(statements).each do |statement|
152
+ with_trilogy_connection do |conn|
153
+ raw_execute(statement, name)
154
+ conn.next_result while conn.more_results_exist?
155
+ end
156
+ end
157
+ end
158
+
159
+ def multi_statements_enabled?
160
+ !!@config[:multi_statement]
161
+ end
162
+
163
+ def with_multi_statements
164
+ if multi_statements_enabled?
165
+ return yield
166
+ end
167
+
168
+ with_trilogy_connection do |conn|
169
+ conn.set_server_option(::Trilogy::SET_SERVER_MULTI_STATEMENTS_ON)
170
+
171
+ yield
172
+ ensure
173
+ conn.set_server_option(::Trilogy::SET_SERVER_MULTI_STATEMENTS_OFF)
174
+ end
175
+ end
176
+
177
+ def combine_multi_statements(total_sql)
178
+ total_sql.each_with_object([]) do |sql, total_sql_chunks|
179
+ previous_packet = total_sql_chunks.last
180
+ if max_allowed_packet_reached?(sql, previous_packet)
181
+ total_sql_chunks << +sql
182
+ else
183
+ previous_packet << ";\n"
184
+ previous_packet << sql
185
+ end
186
+ end
187
+ end
121
188
  end
122
189
  end
123
190
  end
@@ -5,7 +5,6 @@ require "active_record/connection_adapters/abstract_mysql_adapter"
5
5
 
6
6
  require "active_record/tasks/trilogy_database_tasks"
7
7
  require "active_record/connection_adapters/trilogy/database_statements"
8
- require "trilogy_adapter/lost_connection_exception_translator"
9
8
 
10
9
  module ActiveRecord
11
10
  # ActiveRecord <= 6.1 support
@@ -61,7 +60,9 @@ module ActiveRecord
61
60
  module ConnectionAdapters
62
61
  class TrilogyAdapter < ::ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter
63
62
  ER_BAD_DB_ERROR = 1049
63
+ ER_DBACCESS_DENIED_ERROR = 1044
64
64
  ER_ACCESS_DENIED_ERROR = 1045
65
+ ER_SERVER_SHUTDOWN = 1053
65
66
 
66
67
  ADAPTER_NAME = "Trilogy"
67
68
 
@@ -107,6 +108,38 @@ module ActiveRecord
107
108
  end
108
109
  end
109
110
 
111
+ def dbconsole(config, options = {})
112
+ mysql_config = if ActiveRecord.version < ::Gem::Version.new('6.1.a')
113
+ config.config
114
+ else
115
+ config.configuration_hash
116
+ end
117
+
118
+ args = {
119
+ host: "--host",
120
+ port: "--port",
121
+ socket: "--socket",
122
+ username: "--user",
123
+ encoding: "--default-character-set",
124
+ sslca: "--ssl-ca",
125
+ sslcert: "--ssl-cert",
126
+ sslcapath: "--ssl-capath",
127
+ sslcipher: "--ssl-cipher",
128
+ sslkey: "--ssl-key",
129
+ ssl_mode: "--ssl-mode"
130
+ }.filter_map { |opt, arg| "#{arg}=#{mysql_config[opt]}" if mysql_config[opt] }
131
+
132
+ if mysql_config[:password] && options[:include_password]
133
+ args << "--password=#{mysql_config[:password]}"
134
+ elsif mysql_config[:password] && !mysql_config[:password].to_s.empty?
135
+ args << "-p"
136
+ end
137
+
138
+ args << mysql_config[:database]
139
+
140
+ find_cmd_and_exec(["mysql", "mysql5"], *args)
141
+ end
142
+
110
143
  private
111
144
  def initialize_type_map(m)
112
145
  super if ActiveRecord.version >= ::Gem::Version.new('7.0.a')
@@ -156,7 +189,7 @@ module ActiveRecord
156
189
  end
157
190
 
158
191
  def quote_string(string)
159
- with_trilogy_connection(allow_retry: true, uses_transaction: false) do |conn|
192
+ with_trilogy_connection(allow_retry: true, materialize_transactions: false) do |conn|
160
193
  conn.escape(string)
161
194
  end
162
195
  end
@@ -173,21 +206,14 @@ module ActiveRecord
173
206
  end
174
207
  end
175
208
 
176
- def with_trilogy_connection(uses_transaction: true, **_kwargs)
209
+ def with_trilogy_connection(materialize_transactions: true, **_kwargs)
177
210
  @lock.synchronize do
178
211
  verify!
179
- materialize_transactions if uses_transaction
212
+ materialize_transactions if materialize_transactions
180
213
  yield connection
181
214
  end
182
215
  end
183
216
 
184
- def execute(sql, name = nil, allow_retry: false, **kwargs)
185
- sql = transform_query(sql)
186
- check_if_write_query(sql)
187
-
188
- raw_execute(sql, name, allow_retry: allow_retry, **kwargs)
189
- end
190
-
191
217
  def active?
192
218
  return false if connection&.closed?
193
219
 
@@ -214,6 +240,34 @@ module ActiveRecord
214
240
  end
215
241
  end
216
242
 
243
+ def self.find_cmd_and_exec(commands, *args) # :doc:
244
+ commands = Array(commands)
245
+
246
+ dirs_on_path = ENV["PATH"].to_s.split(File::PATH_SEPARATOR)
247
+ unless (ext = RbConfig::CONFIG["EXEEXT"]).empty?
248
+ commands = commands.map { |cmd| "#{cmd}#{ext}" }
249
+ end
250
+
251
+ full_path_command = nil
252
+ found = commands.detect do |cmd|
253
+ dirs_on_path.detect do |path|
254
+ full_path_command = File.join(path, cmd)
255
+ begin
256
+ stat = File.stat(full_path_command)
257
+ rescue SystemCallError
258
+ else
259
+ stat.file? && stat.executable?
260
+ end
261
+ end
262
+ end
263
+
264
+ if found
265
+ exec full_path_command, *args
266
+ else
267
+ abort("Couldn't find database client: #{commands.join(', ')}. Check your $PATH and try again.")
268
+ end
269
+ end
270
+
217
271
  private
218
272
  def text_type?(type)
219
273
  TYPE_MAP.lookup(type).is_a?(Type::String) || TYPE_MAP.lookup(type).is_a?(Type::Text)
@@ -222,7 +276,7 @@ module ActiveRecord
222
276
  def each_hash(result)
223
277
  return to_enum(:each_hash, result) unless block_given?
224
278
 
225
- keys = result.fields.map(&:to_sym)
279
+ keys = result.columns.map(&:to_sym)
226
280
  result.rows.each do |row|
227
281
  hash = {}
228
282
  idx = 0
@@ -244,6 +298,7 @@ module ActiveRecord
244
298
 
245
299
  def connect
246
300
  self.connection = self.class.new_client(@config)
301
+ configure_connection
247
302
  end
248
303
 
249
304
  def reconnect
@@ -252,46 +307,6 @@ module ActiveRecord
252
307
  connect
253
308
  end
254
309
 
255
- def execute_batch(statements, name = nil)
256
- statements = statements.map { |sql| transform_query(sql) } if respond_to?(:transform_query)
257
- combine_multi_statements(statements).each do |statement|
258
- with_trilogy_connection do |conn|
259
- raw_execute(statement, name)
260
- conn.next_result while conn.more_results_exist?
261
- end
262
- end
263
- end
264
-
265
- def multi_statements_enabled?
266
- !!@config[:multi_statement]
267
- end
268
-
269
- def with_multi_statements
270
- if multi_statements_enabled?
271
- return yield
272
- end
273
-
274
- with_trilogy_connection do |conn|
275
- conn.set_server_option(::Trilogy::SET_SERVER_MULTI_STATEMENTS_ON)
276
-
277
- yield
278
- ensure
279
- conn.set_server_option(::Trilogy::SET_SERVER_MULTI_STATEMENTS_OFF)
280
- end
281
- end
282
-
283
- def combine_multi_statements(total_sql)
284
- total_sql.each_with_object([]) do |sql, total_sql_chunks|
285
- previous_packet = total_sql_chunks.last
286
- if max_allowed_packet_reached?(sql, previous_packet)
287
- total_sql_chunks << +sql
288
- else
289
- previous_packet << ";\n"
290
- previous_packet << sql
291
- end
292
- end
293
- end
294
-
295
310
  def max_allowed_packet_reached?(current_packet, previous_packet)
296
311
  if current_packet.bytesize > max_allowed_packet
297
312
  raise ActiveRecordError,
@@ -312,7 +327,7 @@ module ActiveRecord
312
327
  end
313
328
 
314
329
  def get_full_version
315
- with_trilogy_connection(allow_retry: true, uses_transaction: false) do |conn|
330
+ with_trilogy_connection(allow_retry: true, materialize_transactions: false) do |conn|
316
331
  conn.server_info[:version]
317
332
  end
318
333
  end
@@ -327,16 +342,6 @@ module ActiveRecord
327
342
  end
328
343
  end
329
344
 
330
- def translate_exception(exception, message:, sql:, binds:)
331
- if exception.is_a?(::Trilogy::TimeoutError) && !exception.error_code
332
- return ActiveRecord::AdapterTimeout.new(message, sql: sql, binds: binds)
333
- end
334
- error_code = exception.error_code if exception.respond_to?(:error_code)
335
-
336
- ::TrilogyAdapter::LostConnectionExceptionTranslator.
337
- new(exception, message, error_code).translate || super
338
- end
339
-
340
345
  def default_prepared_statements
341
346
  false
342
347
  end
@@ -347,6 +352,15 @@ module ActiveRecord
347
352
  end
348
353
  end
349
354
 
355
+ def default_insert_value(column)
356
+ super unless column.auto_increment?
357
+ end
358
+
359
+ # https://mariadb.com/kb/en/analyze-statement/
360
+ def analyze_without_explain?
361
+ mariadb? && database_version >= "10.1.0"
362
+ end
363
+
350
364
  ActiveRecord::Type.register(:immutable_string, adapter: :trilogy) do |_, **args|
351
365
  Type::ImmutableString.new(true: "1", false: "0", **args)
352
366
  end
@@ -1,5 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_record"
4
- require "trilogy_adapter/errors"
5
4
  require "trilogy_adapter/railtie"
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TrilogyAdapter
4
+ module Rails
5
+ module DBConsole
6
+ class AdapterAdapter < SimpleDelegator
7
+ def adapter
8
+ "mysql"
9
+ end
10
+ end
11
+
12
+ def db_config
13
+ if super.adapter == "trilogy"
14
+ AdapterAdapter.new(super)
15
+ else
16
+ super
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ module Rails
24
+ class DBConsole
25
+ # require "rails/commands/dbconsole/dbconsole_command"
26
+ if ActiveRecord.version < ::Gem::Version.new('6.1.a')
27
+ alias _brick_start start
28
+ def start
29
+ ENV["RAILS_ENV"] ||= @options[:environment] || environment
30
+
31
+ if config["adapter"] == "trilogy"
32
+ begin
33
+ ::ActiveRecord::ConnectionAdapters::TrilogyAdapter.dbconsole(config, @options)
34
+ rescue NotImplementedError
35
+ abort "Unknown command-line client for #{db_config.database}."
36
+ end
37
+ else
38
+ _brick_start
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TrilogyAdapter
4
- VERSION = "3.1.0"
4
+ VERSION = "3.1.1"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord-trilogy-adapter
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.0
4
+ version: 3.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - GitHub Engineering
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-07-07 00:00:00.000000000 Z
11
+ date: 2023-08-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: trilogy
@@ -116,8 +116,7 @@ files:
116
116
  - lib/active_record/tasks/trilogy_database_tasks.rb
117
117
  - lib/activerecord-trilogy-adapter.rb
118
118
  - lib/trilogy_adapter/connection.rb
119
- - lib/trilogy_adapter/errors.rb
120
- - lib/trilogy_adapter/lost_connection_exception_translator.rb
119
+ - lib/trilogy_adapter/rails/dbconsole.rb
121
120
  - lib/trilogy_adapter/railtie.rb
122
121
  - lib/trilogy_adapter/version.rb
123
122
  homepage: https://github.com/trilogy-libraries/activerecord-trilogy-adapter
@@ -1,54 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- if ActiveRecord.version < ::Gem::Version.new('6.1.a') # ActiveRecord <= 6.0 support
4
- module ::ActiveRecord
5
- class QueryAborted < ::ActiveRecord::StatementInvalid
6
- end
7
- class AdapterTimeout < ::ActiveRecord::QueryAborted
8
- end
9
- end
10
- end
11
-
12
- module TrilogyAdapter
13
- module Errors
14
- # ServerShutdown will be raised when the database server was shutdown.
15
- class ServerShutdown < ::ActiveRecord::QueryAborted
16
- end
17
-
18
- # ServerLost will be raised when the database connection was lost.
19
- class ServerLost < ::ActiveRecord::QueryAborted
20
- end
21
-
22
- # ServerGone will be raised when the database connection is gone.
23
- class ServerGone < ::ActiveRecord::QueryAborted
24
- end
25
-
26
- # BrokenPipe will be raised when a system process connection fails.
27
- class BrokenPipe < ::ActiveRecord::QueryAborted
28
- end
29
-
30
- # SocketError will be raised when Ruby encounters a network error.
31
- class SocketError < ::ActiveRecord::QueryAborted
32
- end
33
-
34
- # ConnectionResetByPeer will be raised when a network connection is closed
35
- # outside the sytstem process.
36
- class ConnectionResetByPeer < ::ActiveRecord::QueryAborted
37
- end
38
-
39
- # ClosedConnection will be raised when the Trilogy encounters a closed
40
- # connection.
41
- class ClosedConnection < ::ActiveRecord::QueryAborted
42
- end
43
-
44
- # InvalidSequenceId will be raised when Trilogy ecounters an invalid sequence
45
- # id.
46
- class InvalidSequenceId < ::ActiveRecord::QueryAborted
47
- end
48
-
49
- # UnexpectedPacket will be raised when Trilogy ecounters an unexpected
50
- # response packet.
51
- class UnexpectedPacket < ::ActiveRecord::QueryAborted
52
- end
53
- end
54
- end
@@ -1,60 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module TrilogyAdapter
4
- class LostConnectionExceptionTranslator
5
- attr_reader :exception, :message, :error_number
6
-
7
- def initialize(exception, message, error_number)
8
- @exception = exception
9
- @message = message
10
- @error_number = error_number
11
- end
12
-
13
- def translate
14
- translate_database_exception || translate_ruby_exception || translate_trilogy_exception
15
- end
16
-
17
- private
18
- ER_SERVER_SHUTDOWN = 1053
19
- CR_SERVER_LOST = 2013
20
- CR_SERVER_LOST_EXTENDED = 2055
21
- CR_SERVER_GONE_ERROR = 2006
22
-
23
- def translate_database_exception
24
- case error_number
25
- when ER_SERVER_SHUTDOWN
26
- Errors::ServerShutdown.new(message)
27
- when CR_SERVER_LOST, CR_SERVER_LOST_EXTENDED
28
- Errors::ServerLost.new(message)
29
- when CR_SERVER_GONE_ERROR
30
- Errors::ServerGone.new(message)
31
- end
32
- end
33
-
34
- def translate_ruby_exception
35
- case exception
36
- when Errno::EPIPE
37
- Errors::BrokenPipe.new(message)
38
- when SocketError, IOError
39
- Errors::SocketError.new(message)
40
- when Trilogy::ConnectionError
41
- if message.match?(/Connection reset by peer/)
42
- Errors::ConnectionResetByPeer.new(message)
43
- end
44
- end
45
- end
46
-
47
- def translate_trilogy_exception
48
- return unless exception.is_a?(Trilogy::Error)
49
-
50
- case message
51
- when /TRILOGY_CLOSED_CONNECTION/
52
- Errors::ClosedConnection.new(message)
53
- when /TRILOGY_INVALID_SEQUENCE_ID/
54
- Errors::InvalidSequenceId.new(message)
55
- when /TRILOGY_UNEXPECTED_PACKET/
56
- Errors::UnexpectedPacket.new(message)
57
- end
58
- end
59
- end
60
- end