activerecord-mysql-reconnect-before-retry 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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