activerecord-mysql-reconnect-before-retry 0.5.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e74e264eb347ad48e9f32999a598468ff82ef673beeeceb007dc16774faeece4
4
+ data.tar.gz: 8cb24217590520df18e4cb630a3fd410f086f72d59ea8c138a55f8b984105fc2
5
+ SHA512:
6
+ metadata.gz: e660f5073b7863071bd6722392957b863e2f6df9e301c64b12f78bd349802d799a92f76cf909f29aee7182f3fc355c5171b7c3ef56596254228bbea0f2aef231
7
+ data.tar.gz: 2aeadc0cb5148069ab8b61f367d5793c0f2879e0c9b5880908f56cbfb65bc34d3d0b26ebb0cc6cee0bedf05bd3b3ff11ede24aa74667d3650ffcea97b7afade5
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ test.rb
19
+ *.bak
20
+ *.swp
21
+ /gemfiles/*.lock
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --require spec_helper
2
+ --colour
3
+ --format documentation
data/.travis.yml ADDED
@@ -0,0 +1,45 @@
1
+ dist: trusty
2
+ sudo: required
3
+ language: ruby
4
+ rvm:
5
+ - 2.3.8
6
+ - 2.4.6
7
+ - 2.5.5
8
+ - 2.6.3
9
+ - 2.7.0
10
+ before_install:
11
+ - docker-compose up -d
12
+ - function mysql_ping { mysqladmin -u root -h 127.0.0.1 -ppassword ping > /dev/null 2> /dev/null; }
13
+ - for i in {1..60}; do mysql_ping && break; sleep 1; done
14
+ - gem install bundler
15
+ - docker-compose stop
16
+ script:
17
+ - bundle exec rake
18
+ gemfile:
19
+ - gemfiles/activerecord_4.2.gemfile
20
+ - gemfiles/activerecord_5.0.gemfile
21
+ - gemfiles/activerecord_5.1.gemfile
22
+ - gemfiles/activerecord_5.2.gemfile
23
+ - gemfiles/activerecord_6.0.gemfile
24
+ - gemfiles/activerecord_master.gemfile
25
+ env:
26
+ matrix:
27
+ - ACTIVERECORD_MYSQL_RECONNECT_ENGINE=InnoDB
28
+ - ACTIVERECORD_MYSQL_RECONNECT_ENGINE=MyISAM
29
+ jobs:
30
+ exclude:
31
+ - rvm: 2.3.8
32
+ gemfile: gemfiles/activerecord_6.0.gemfile
33
+ - rvm: 2.3.8
34
+ gemfile: gemfiles/activerecord_master.gemfile
35
+ - rvm: 2.4.6
36
+ gemfile: gemfiles/activerecord_6.0.gemfile
37
+ - rvm: 2.4.6
38
+ gemfile: gemfiles/activerecord_master.gemfile
39
+ - rvm: 2.7.0
40
+ gemfile: gemfiles/activerecord_4.2.gemfile
41
+ addons:
42
+ apt:
43
+ packages:
44
+ - mysql-client-core-5.6
45
+ - mysql-client-5.6
data/Appraisals ADDED
@@ -0,0 +1,24 @@
1
+ appraise "activerecord-4.2" do
2
+ gem "activerecord", "~> 4.2.0"
3
+ gem "mysql2", "< 0.5"
4
+ end
5
+
6
+ appraise "activerecord-5.0" do
7
+ gem "activerecord", "~> 5.0.0"
8
+ end
9
+
10
+ appraise "activerecord-5.1" do
11
+ gem "activerecord", "~> 5.1.0"
12
+ end
13
+
14
+ appraise "activerecord-5.2" do
15
+ gem "activerecord", "~> 5.2.0"
16
+ end
17
+
18
+ appraise "activerecord-6.0" do
19
+ gem "activerecord", "~> 6.0.2"
20
+ end
21
+
22
+ appraise "activerecord-master" do
23
+ gem "activerecord", git: "https://github.com/rails/rails.git"
24
+ end
data/ChangeLog ADDED
@@ -0,0 +1,31 @@
1
+ activerecord-mysql-reconnect 0.4.1 (Aug 8, 2016)
2
+
3
+ * Support AR 5.0 (RP#5 @ssig33)
4
+ * Fix test (use docker-compose)
5
+
6
+ activerecord-mysql-reconnect 0.4.0 (Mar 22, 2016)
7
+
8
+ * Remove `retryable_transaction`
9
+ * Disable support AR 3.x 4.0
10
+
11
+ activerecord-mysql-reconnect 0.3.3 (Jan 18, 2014)
12
+
13
+ * use BigDecimal for sleep
14
+ * add handling error ('The MySQL server is running with the --read-only...')
15
+
16
+ activerecord-mysql-reconnect 0.3.1 (Jan 9, 2014)
17
+
18
+ * Retry mode is added
19
+
20
+ activerecord-mysql-reconnect 0.3.0 (Jan 9, 2014)
21
+
22
+ * Retry is disabled by default
23
+ * Read-only mode is added
24
+
25
+ activerecord-mysql-reconnect 0.2.0 (Jan 4, 2014)
26
+
27
+ * Retry transaction is supported
28
+
29
+ activerecord-mysql-reconnect 0.1.0 (Oct 11, 2013)
30
+
31
+ * activerecord-mysql-reconnect is released
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in activerecord-mysql-reconnect.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Genki Sugawara
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,124 @@
1
+ # activerecord-mysql-reconnect
2
+
3
+ It is the library to reconnect automatically when ActiveRecord is disconnected from MySQL.
4
+
5
+ [![Gem Version](https://badge.fury.io/rb/activerecord-mysql-reconnect.svg)](http://badge.fury.io/rb/activerecord-mysql-reconnect)
6
+ [![Build Status](https://travis-ci.org/winebarrel/activerecord-mysql-reconnect.svg?branch=master)](https://travis-ci.org/winebarrel/activerecord-mysql-reconnect)
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ gem 'activerecord-mysql-reconnect'
13
+
14
+ And then execute:
15
+
16
+ $ bundle
17
+
18
+ Or install it yourself as:
19
+
20
+ $ gem install activerecord-mysql-reconnect
21
+
22
+ ## Usage
23
+
24
+ ```ruby
25
+ require 'active_record'
26
+ require 'activerecord-mysql-reconnect'
27
+ require 'logger'
28
+
29
+ ActiveRecord::Base.establish_connection(
30
+ adapter: 'mysql2',
31
+ host: '127.0.0.1',
32
+ username: 'root',
33
+ database: 'employees',
34
+ )
35
+
36
+ ActiveRecord::Base.logger = Logger.new($stdout)
37
+ ActiveRecord::Base.logger.formatter = proc {|_, _, _, message| "#{message}\n" }
38
+ ActiveRecord::Base.enable_retry = true
39
+ ActiveRecord::Base.execution_tries = 3
40
+
41
+ class Employee < ActiveRecord::Base; end
42
+
43
+ p Employee.count
44
+ system('sudo /etc/init.d/mysqld restart')
45
+ p Employee.count
46
+ ```
47
+
48
+ ```
49
+ shell> ruby test.rb
50
+ (64.1ms) SELECT COUNT(*) FROM `employees`
51
+ 300024
52
+ Stopping mysqld: [ OK ]
53
+ Starting mysqld: [ OK ]
54
+ (0.4ms) SELECT COUNT(*) FROM `employees`
55
+ Mysql2::Error: MySQL server has gone away: SELECT COUNT(*) FROM `employees`
56
+ MySQL server has gone away. Trying to reconnect in 0.5 seconds. (cause: Mysql2::Error: MySQL server has gone away: SELECT COUNT(*) FROM `employees` [ActiveRecord::StatementInvalid], connection: host=127.0.0.1;database=employees;username=root)
57
+ (101.5ms) SELECT COUNT(*) FROM `employees`
58
+ 300024
59
+ ```
60
+
61
+ ### without_retry
62
+
63
+ ```ruby
64
+ ActiveRecord::Base.without_retry do
65
+ Employee.count
66
+ end
67
+ ```
68
+
69
+ ### Add a retry error message
70
+
71
+ ```ruby
72
+ Activerecord::Mysql::Reconnect.handle_rw_error_messages.update(
73
+ zapzapzap: 'ZapZapZap'
74
+ )
75
+ # or `Activerecord::Mysql::Reconnect.handle_r_error_messages...`
76
+ ```
77
+
78
+ ## Use on rails
79
+
80
+ ### Gemfile
81
+
82
+ ```ruby
83
+ gem 'activerecord-mysql-reconnect'
84
+ ```
85
+
86
+ ### environment file
87
+
88
+ ```ruby
89
+ MyApp::Application.configure do
90
+ ...
91
+ config.active_record.enable_retry = true
92
+ #config.active_record.retry_databases = :employees
93
+ # e.g. [:employees]
94
+ # ['employees', 'localhost:test', '192.168.1.1:users']
95
+ # ['192.168.%:emp\_all']
96
+ # ['emp%']
97
+ # retry_databases -> nil: retry all databases (default)
98
+ config.active_record.execution_tries = 10 # times
99
+ # execution_tries -> 0: retry indefinitely
100
+ config.active_record.execution_retry_wait = 1.5 # sec
101
+ config.active_record.retry_mode = :rw # default: `:r`, valid values: `:r`, `:rw`, `:force`
102
+ ...
103
+ ene
104
+ ```
105
+
106
+ ## Retry mode
107
+
108
+ * `:r` Retry only SELECT / SHOW / SET
109
+ * `:rw` Retry in all SQL, but does not retry if `Lost connection` has happened in write SQL
110
+ * `:force` Retry in all SQL
111
+
112
+ ## Run tests
113
+
114
+ It requires the following:
115
+
116
+ * Docker
117
+ * Docker Compose
118
+
119
+ ```sh
120
+ bundle install
121
+ bundle exec appraisal install
122
+ bundle exec appraisal activerecord-4.2 rake
123
+ bundle exec appraisal activerecord-5.0 rake
124
+ ```
data/Rakefile ADDED
@@ -0,0 +1,5 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new('spec')
5
+ task :default => :spec
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'activerecord/mysql/reconnect/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'activerecord-mysql-reconnect-before-retry'
8
+ spec.version = Activerecord::Mysql::Reconnect::VERSION
9
+ spec.authors = ['Genki Sugawara']
10
+ spec.email = ['sugawara@cookpad.com']
11
+ spec.description = %q{It is the library to reconnect automatically when ActiveRecord is disconnected from MySQL.}
12
+ spec.summary = spec.description
13
+ spec.homepage = 'https://github.com/winebarrel/activerecord-mysql-reconnect'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ['lib']
20
+
21
+ # '~> 4.2.6'
22
+ spec.add_dependency 'activerecord'
23
+ spec.add_dependency 'mysql2'
24
+ spec.add_development_dependency 'bundler'
25
+ spec.add_development_dependency 'rake'
26
+ spec.add_development_dependency 'rspec', '>= 3.0.0'
27
+ spec.add_development_dependency "appraisal"
28
+ end
@@ -0,0 +1,6 @@
1
+ mysql_for_ar_mysql_reconn:
2
+ image: "mysql:5.6"
3
+ ports:
4
+ - "14407:3306"
5
+ environment:
6
+ MYSQL_ALLOW_EMPTY_PASSWORD: "yes"
@@ -0,0 +1,8 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activerecord", "~> 4.2.0"
6
+ gem "mysql2", "< 0.5"
7
+
8
+ gemspec path: "../"
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activerecord", "~> 5.0.0"
6
+
7
+ gemspec path: "../"
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activerecord", "~> 5.1.0"
6
+
7
+ gemspec path: "../"
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activerecord", "~> 5.2.0"
6
+
7
+ gemspec path: "../"
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activerecord", "~> 6.0.2"
6
+
7
+ gemspec path: "../"
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activerecord", git: "https://github.com/rails/rails.git"
6
+
7
+ gemspec path: "../"
@@ -0,0 +1,5 @@
1
+ require 'active_support'
2
+
3
+ ActiveSupport.on_load :active_record do
4
+ require_relative 'activerecord/mysql/reconnect'
5
+ end
@@ -0,0 +1,282 @@
1
+ require 'mysql2'
2
+ require 'logger'
3
+ require 'bigdecimal'
4
+ require 'strscan'
5
+
6
+ require 'active_record'
7
+ require 'active_record/connection_adapters/abstract_adapter'
8
+ require 'active_record/connection_adapters/abstract_mysql_adapter'
9
+ require 'active_record/connection_adapters/mysql2_adapter'
10
+ require 'active_record/connection_adapters/abstract/connection_pool'
11
+ require 'active_record/connection_adapters/abstract/transaction'
12
+
13
+ require 'activerecord/mysql/reconnect/version'
14
+ require 'activerecord/mysql/reconnect/base_ext'
15
+
16
+ require 'activerecord/mysql/reconnect/abstract_mysql_adapter_ext'
17
+ require 'activerecord/mysql/reconnect/mysql2_adapter_ext'
18
+ require 'activerecord/mysql/reconnect/connection_pool_ext'
19
+ require 'activerecord/mysql/reconnect/null_transaction_ext'
20
+
21
+ module Activerecord::Mysql::Reconnect
22
+ DEFAULT_EXECUTION_TRIES = 3
23
+ DEFAULT_EXECUTION_RETRY_WAIT = 0.5
24
+
25
+ WITHOUT_RETRY_KEY = 'activerecord-mysql-reconnect-without-retry'
26
+
27
+ HANDLE_ERROR = [
28
+ ActiveRecord::StatementInvalid,
29
+ Mysql2::Error,
30
+ ]
31
+
32
+ @@handle_r_error_messages = {
33
+ lost_connection: 'Lost connection to MySQL server during query',
34
+ }
35
+
36
+ @@handle_rw_error_messages = {
37
+ gone_away: 'MySQL server has gone away',
38
+ server_shutdown: 'Server shutdown in progress',
39
+ closed_connection: 'closed MySQL connection',
40
+ cannot_connect: "Can't connect to MySQL server",
41
+ interrupted: 'Query execution was interrupted',
42
+ access_denied: 'Access denied for user',
43
+ read_only: 'The MySQL server is running with the --read-only option',
44
+ cannot_connect_to_local: "Can't connect to local MySQL server", # When running in local sandbox, or using a socket file
45
+ unknown_host: 'Unknown MySQL server host', # For DNS blips
46
+ lost_connection: "Lost connection to MySQL server at 'reading initial communication packet'",
47
+ not_connected: "MySQL client is not connected",
48
+ killed: 'Connection was killed',
49
+ }
50
+
51
+ READ_SQL_REGEXP = /\A\s*(?:SELECT|SHOW|SET)\b/i
52
+
53
+ RETRY_MODES = [:r, :rw, :force]
54
+ DEFAULT_RETRY_MODE = :r
55
+
56
+ class << self
57
+ def handle_r_error_messages
58
+ @@handle_r_error_messages
59
+ end
60
+
61
+ def handle_rw_error_messages
62
+ @@handle_rw_error_messages
63
+ end
64
+
65
+ def execution_tries
66
+ ActiveRecord::Base.execution_tries || DEFAULT_EXECUTION_TRIES
67
+ end
68
+
69
+ def execution_retry_wait
70
+ wait = ActiveRecord::Base.execution_retry_wait || DEFAULT_EXECUTION_RETRY_WAIT
71
+ wait.kind_of?(BigDecimal) ? wait : BigDecimal(wait.to_s)
72
+ end
73
+
74
+ def enable_retry
75
+ !!ActiveRecord::Base.enable_retry
76
+ end
77
+
78
+ def retry_mode=(v)
79
+ unless RETRY_MODES.include?(v)
80
+ raise "Invalid retry_mode. Please set one of the following: #{RETRY_MODES.map {|i| i.inspect }.join(', ')}"
81
+ end
82
+
83
+ @activerecord_mysql_reconnect_retry_mode = v
84
+ end
85
+
86
+ def retry_mode
87
+ @activerecord_mysql_reconnect_retry_mode || DEFAULT_RETRY_MODE
88
+ end
89
+
90
+ def before_retry=(v)
91
+ @activerecord_mysql_reconnect_before_retry = v
92
+ end
93
+
94
+ def before_retry
95
+ @activerecord_mysql_reconnect_before_retry
96
+ end
97
+
98
+ def retry_databases=(v)
99
+ v ||= []
100
+
101
+ unless v.kind_of?(Array)
102
+ v = [v]
103
+ end
104
+
105
+ @activerecord_mysql_reconnect_retry_databases = v.map do |database|
106
+ if database.instance_of?(Symbol)
107
+ database = Regexp.escape(database.to_s)
108
+ [/.*/, /\A#{database}\z/]
109
+ else
110
+ host = '%'
111
+ database = database.to_s
112
+
113
+ if database =~ /:/
114
+ host, database = database.split(':', 2)
115
+ end
116
+
117
+ [create_pattern_match_regex(host), create_pattern_match_regex(database)]
118
+ end
119
+ end
120
+ end
121
+
122
+ def retry_databases
123
+ @activerecord_mysql_reconnect_retry_databases || []
124
+ end
125
+
126
+ def retryable(opts)
127
+ block = opts.fetch(:proc)
128
+ on_error = opts[:on_error]
129
+ conn = opts[:connection]
130
+ sql = opts[:sql]
131
+ tries = self.execution_tries
132
+ before_retry = self.before_retry
133
+ retval = nil
134
+
135
+ retryable_loop(tries) do |n|
136
+ begin
137
+ retval = block.call
138
+ break
139
+ rescue => e
140
+ if enable_retry and (tries.zero? or n < tries) and should_handle?(e, opts)
141
+ on_error.call if on_error
142
+
143
+ if before_retry.is_a?(Proc)
144
+ logger.warn("MySQL server has gone away. Running before retry proc")
145
+ before_retry.call(e, sql, conn)
146
+ end
147
+
148
+ wait = self.execution_retry_wait * n
149
+
150
+ logger.warn("MySQL server has gone away. Trying to reconnect in #{wait.to_f} seconds. (#{build_error_message(e, sql, conn)})")
151
+ sleep(wait)
152
+ next
153
+ else
154
+ if enable_retry and n > 1
155
+ logger.warn("Query retry failed. (#{build_error_message(e, sql, conn)})")
156
+ end
157
+
158
+ raise e
159
+ end
160
+ end
161
+ end
162
+
163
+ return retval
164
+ end
165
+
166
+ def logger
167
+ if defined?(Rails)
168
+ Rails.logger || ActiveRecord::Base.logger || Logger.new($stderr)
169
+ else
170
+ ActiveRecord::Base.logger || Logger.new($stderr)
171
+ end
172
+ end
173
+
174
+ def without_retry
175
+ begin
176
+ Thread.current[WITHOUT_RETRY_KEY] = true
177
+ yield
178
+ ensure
179
+ Thread.current[WITHOUT_RETRY_KEY] = nil
180
+ end
181
+ end
182
+
183
+ def without_retry?
184
+ !!Thread.current[WITHOUT_RETRY_KEY]
185
+ end
186
+
187
+ private
188
+
189
+ def retryable_loop(n)
190
+ if n.zero?
191
+ loop { n += 1 ; yield(n) }
192
+ else
193
+ n.times {|i| yield(i + 1) }
194
+ end
195
+ end
196
+
197
+ def should_handle?(e, opts = {})
198
+ sql = opts[:sql]
199
+ retry_mode = opts[:retry_mode]
200
+ conn = opts[:connection]
201
+
202
+ if without_retry?
203
+ return false
204
+ end
205
+
206
+ if conn and not retry_databases.empty?
207
+ conn_info = connection_info(conn)
208
+
209
+ included = retry_databases.any? do |host, database|
210
+ host =~ conn_info[:host] and database =~ conn_info[:database]
211
+ end
212
+
213
+ return false unless included
214
+ end
215
+
216
+ unless HANDLE_ERROR.any? {|i| e.kind_of?(i) }
217
+ return false
218
+ end
219
+
220
+ unless Regexp.union(@@handle_r_error_messages.values + @@handle_rw_error_messages.values) =~ e.message
221
+ return false
222
+ end
223
+
224
+ if sql and READ_SQL_REGEXP !~ sql
225
+ if retry_mode == :r
226
+ return false
227
+ end
228
+
229
+ if retry_mode != :force and Regexp.union(@@handle_r_error_messages.values) =~ e.message
230
+ return false
231
+ end
232
+ end
233
+
234
+ return true
235
+ end
236
+
237
+ def connection_info(conn)
238
+ conn_info = {}
239
+
240
+ if conn.kind_of?(Mysql2::Client)
241
+ [:host, :database, :username].each {|k| conn_info[k] = conn.query_options[k] }
242
+ elsif conn.kind_of?(Hash)
243
+ conn_info = conn.dup
244
+ end
245
+
246
+ return conn_info
247
+ end
248
+
249
+ def create_pattern_match_regex(str)
250
+ ss = StringScanner.new(str)
251
+ buf = []
252
+
253
+ until ss.eos?
254
+ if (tok = ss.scan(/[^\\%_]+/))
255
+ buf << Regexp.escape(tok)
256
+ elsif (tok = ss.scan(/\\/))
257
+ buf << Regexp.escape(ss.getch)
258
+ elsif (tok = ss.scan(/%/))
259
+ buf << '.*'
260
+ elsif (tok = ss.scan(/_/))
261
+ buf << '.'
262
+ else
263
+ raise 'must not happen'
264
+ end
265
+ end
266
+
267
+ /\A#{buf.join}\z/
268
+ end
269
+
270
+ def build_error_message(e, sql, conn)
271
+ msgs = {cause: "#{e.message} [#{e.class}]"}
272
+ msgs[:sql] = sql if sql
273
+
274
+ if conn
275
+ conn_info = connection_info(conn)
276
+ msgs[:connection] = [:host, :database, :username].map {|k| "#{k}=#{conn_info[k]}" }.join(";")
277
+ end
278
+
279
+ msgs.map {|k, v| "#{k}: #{v}" }.join(", ")
280
+ end
281
+ end # end of class methods
282
+ end