activerecord-trilogy-adapter 3.1.0 → 3.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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