ruby-mysql2 0.5.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +1 -0
- data/LICENSE +21 -0
- data/README.md +20 -0
- data/README.org.md +676 -0
- data/lib/mysql2/client.rb +461 -0
- data/lib/mysql2/console.rb +5 -0
- data/lib/mysql2/error.rb +101 -0
- data/lib/mysql2/field.rb +3 -0
- data/lib/mysql2/result.rb +176 -0
- data/lib/mysql2/statement.rb +103 -0
- data/lib/mysql2/version.rb +3 -0
- data/lib/mysql2.rb +87 -0
- metadata +102 -0
@@ -0,0 +1,461 @@
|
|
1
|
+
module Mysql2
|
2
|
+
class Client
|
3
|
+
Mysql.constants.each do |c|
|
4
|
+
case c.to_s
|
5
|
+
when /\ACLIENT_/
|
6
|
+
self.const_set($', Mysql.const_get(c))
|
7
|
+
when /\ASSL_MODE_|\AOPTION_MULTI_STATEMENTS_/
|
8
|
+
self.const_set(c, Mysql.const_get(c))
|
9
|
+
when /\ASESSION_TRACK_/
|
10
|
+
self.const_set(c, Mysql.const_get(c))
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :query_options, :read_timeout
|
15
|
+
|
16
|
+
def self.default_query_options
|
17
|
+
@default_query_options ||= {
|
18
|
+
as: :hash, # the type of object you want each row back as; also supports :array (an array of values)
|
19
|
+
async: false, # don't wait for a result after sending the query, you'll have to monitor the socket yourself then eventually call Mysql2::Client#async_result
|
20
|
+
cast_booleans: false, # cast tinyint(1) fields as true/false in ruby
|
21
|
+
symbolize_keys: false, # return field names as symbols instead of strings
|
22
|
+
database_timezone: :local, # timezone Mysql2 will assume datetime objects are stored in
|
23
|
+
application_timezone: nil, # timezone Mysql2 will convert to before handing the object back to the caller
|
24
|
+
cache_rows: true, # tells Mysql2 to use its internal row cache for results
|
25
|
+
connect_flags: REMEMBER_OPTIONS | LONG_PASSWORD | LONG_FLAG | TRANSACTIONS | PROTOCOL_41 | SECURE_CONNECTION | CONNECT_ATTRS,
|
26
|
+
cast: true,
|
27
|
+
default_file: nil,
|
28
|
+
default_group: nil,
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.escape(s)
|
33
|
+
s2 = Mysql.quote(s)
|
34
|
+
s.size == s2.size ? s : s2
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.info
|
38
|
+
{
|
39
|
+
id: Mysql::VERSION.split('.').each_with_index.map{|v, i| v.to_i * 100**(2-i)}.sum,
|
40
|
+
version: Mysql::VERSION.encode('us-ascii'),
|
41
|
+
header_version: Mysql::VERSION.encode('us-ascii'),
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.finalizer(mysql, finalizer_opts)
|
46
|
+
proc do
|
47
|
+
if finalizer_opts[:automatic_close]
|
48
|
+
mysql.close rescue nil
|
49
|
+
else
|
50
|
+
mysql.close! rescue nil
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class ::Mysql
|
56
|
+
def async_query(str, **)
|
57
|
+
check_connection
|
58
|
+
@fields = nil
|
59
|
+
@protocol.query_command str
|
60
|
+
return self
|
61
|
+
rescue ServerError => e
|
62
|
+
@last_error = e
|
63
|
+
@sqlstate = e.sqlstate
|
64
|
+
raise
|
65
|
+
end
|
66
|
+
|
67
|
+
def async_query_result(**opts)
|
68
|
+
@protocol.get_result
|
69
|
+
store_result(**opts)
|
70
|
+
rescue ServerError => e
|
71
|
+
@last_error = e
|
72
|
+
@sqlstate = e.sqlstate
|
73
|
+
raise
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
attr_accessor :reconnect
|
78
|
+
|
79
|
+
def initialize(opts = {})
|
80
|
+
raise Mysql2::Error, "Options parameter must be a Hash" unless opts.is_a? Hash
|
81
|
+
opts = Mysql2::Util.key_hash_as_symbols(opts)
|
82
|
+
@read_timeout = nil
|
83
|
+
@query_options = self.class.default_query_options.dup
|
84
|
+
@query_options.merge! opts
|
85
|
+
@automatic_close = true
|
86
|
+
|
87
|
+
@mutex = Mutex.new
|
88
|
+
@mysql = Mysql.new
|
89
|
+
|
90
|
+
# Set default connect_timeout to avoid unlimited retries from signal interruption
|
91
|
+
opts[:connect_timeout] = 120 unless opts.key?(:connect_timeout)
|
92
|
+
|
93
|
+
# TODO: stricter validation rather than silent massaging
|
94
|
+
%i[reconnect connect_timeout local_infile read_timeout write_timeout default_file default_group secure_auth init_command automatic_close enable_cleartext_plugin default_auth].each do |key|
|
95
|
+
next unless opts.key?(key)
|
96
|
+
case key
|
97
|
+
when :reconnect
|
98
|
+
send(:"#{key}=", !!opts[key]) # rubocop:disable Style/DoubleNegation
|
99
|
+
when :automatic_close
|
100
|
+
@automatic_close = opts[key]
|
101
|
+
when :local_infile, :secure_auth, :enable_cleartext_plugin
|
102
|
+
@mysql.send(:"#{key}=", !!opts[key]) # rubocop:disable Style/DoubleNegation
|
103
|
+
when :connect_timeout, :read_timeout, :write_timeout
|
104
|
+
t = opts[key]
|
105
|
+
if t
|
106
|
+
raise Mysql2::Error, 'time interval must not be negative' if t < 0
|
107
|
+
t = 0.0000001 if t == 0
|
108
|
+
@mysql.send(:"#{key}=", t)
|
109
|
+
end
|
110
|
+
else
|
111
|
+
@mysql.send(:"#{key}=", opts[key])
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# force the encoding to utf8
|
116
|
+
self.charset_name = opts[:encoding] || 'utf8'
|
117
|
+
|
118
|
+
mode = parse_ssl_mode(opts[:ssl_mode]) if opts[:ssl_mode]
|
119
|
+
if (mode == SSL_MODE_VERIFY_CA || mode == SSL_MODE_VERIFY_IDENTITY) && !opts[:sslca]
|
120
|
+
opts[:sslca] = find_default_ca_path
|
121
|
+
end
|
122
|
+
|
123
|
+
ssl_options = {}
|
124
|
+
ssl_options[:key] = OpenSSL::PKey::RSA.new(File.read(opts[:sslkey])) if opts[:sslkey]
|
125
|
+
ssl_options[:cert] = OpenSSL::X509::Certificate.new(File.read(opts[:sslcert])) if opts[:sslcert]
|
126
|
+
ssl_options[:ca_file] = opts[:sslca] if opts[:sslca]
|
127
|
+
ssl_options[:ca_path] = opts[:sslcapath] if opts[:sslcapath]
|
128
|
+
ssl_options[:ciphers] = opts[:sslcipher] if opts[:sslcipher]
|
129
|
+
@mysql.ssl_context_params = ssl_options if ssl_options.any? || opts.key?(:sslverify)
|
130
|
+
@mysql.ssl_mode = mode if mode
|
131
|
+
|
132
|
+
flags = case opts[:flags]
|
133
|
+
when Array
|
134
|
+
parse_flags_array(opts[:flags], @query_options[:connect_flags])
|
135
|
+
when String
|
136
|
+
parse_flags_array(opts[:flags].split(' '), @query_options[:connect_flags])
|
137
|
+
when Integer
|
138
|
+
@query_options[:connect_flags] | opts[:flags]
|
139
|
+
else
|
140
|
+
@query_options[:connect_flags]
|
141
|
+
end
|
142
|
+
|
143
|
+
# SSL verify is a connection flag rather than a mysql_ssl_set option
|
144
|
+
flags |= SSL_VERIFY_SERVER_CERT if opts[:sslverify]
|
145
|
+
|
146
|
+
if %i[user pass hostname dbname db sock].any? { |k| @query_options.key?(k) }
|
147
|
+
warn "============= WARNING FROM mysql2 ============="
|
148
|
+
warn "The options :user, :pass, :hostname, :dbname, :db, and :sock are deprecated and will be removed at some point in the future."
|
149
|
+
warn "Instead, please use :username, :password, :host, :port, :database, :socket, :flags for the options."
|
150
|
+
warn "============= END WARNING FROM mysql2 ========="
|
151
|
+
end
|
152
|
+
|
153
|
+
user = opts[:username] || opts[:user]
|
154
|
+
pass = opts[:password] || opts[:pass]
|
155
|
+
host = opts[:host] || opts[:hostname]
|
156
|
+
port = opts[:port]
|
157
|
+
database = opts[:database] || opts[:dbname] || opts[:db]
|
158
|
+
socket = opts[:socket] || opts[:sock]
|
159
|
+
|
160
|
+
# Correct the data types before passing these values down to the C level
|
161
|
+
user = user.to_s unless user.nil?
|
162
|
+
pass = pass.to_s unless pass.nil?
|
163
|
+
host = host.to_s unless host.nil?
|
164
|
+
port = port.to_i unless port.nil?
|
165
|
+
database = database.to_s unless database.nil?
|
166
|
+
socket = socket.to_s unless socket.nil?
|
167
|
+
conn_attrs = parse_connect_attrs(opts[:connect_attrs])
|
168
|
+
|
169
|
+
connect user, pass, host, port, database, socket, flags, conn_attrs
|
170
|
+
@finalizer_opts = {automatic_close: @automatic_close}
|
171
|
+
ObjectSpace.define_finalizer(self, self.class.finalizer(@mysql, @finalizer_opts))
|
172
|
+
rescue Mysql::Error => e
|
173
|
+
raise Mysql2::Error.new(e.message, @mysql&.server_version, e.errno, e.sqlstate)
|
174
|
+
rescue Errno::ECONNREFUSED => e
|
175
|
+
raise Mysql2::Error::ConnectionError, e.message
|
176
|
+
end
|
177
|
+
|
178
|
+
def connect(user, pass, host, port, database, socket, flags, conn_attrs)
|
179
|
+
@conn_params ||= [ user, pass, host, port, database, socket, flags, conn_attrs ]
|
180
|
+
@mysql.connect(host, user, pass, database, port, socket, flags, connect_attrs: conn_attrs)
|
181
|
+
rescue Mysql::Error => e
|
182
|
+
raise Mysql2::Error::ConnectionError, e.message
|
183
|
+
end
|
184
|
+
|
185
|
+
def close
|
186
|
+
@mysql.close rescue nil
|
187
|
+
nil
|
188
|
+
end
|
189
|
+
|
190
|
+
def closed?
|
191
|
+
s = @mysql.protocol&.instance_variable_get(:@socket)
|
192
|
+
s ? s.closed? : true
|
193
|
+
end
|
194
|
+
|
195
|
+
def charset_name=(cs)
|
196
|
+
unless cs.is_a? String
|
197
|
+
raise TypeError, "wrong argument type Symbol (expected String)"
|
198
|
+
end
|
199
|
+
@mysql.charset = cs
|
200
|
+
rescue Mysql::Error => e
|
201
|
+
raise Mysql2::Error.new(e.message, @mysql&.server_version, e.errno, e.sqlstate)
|
202
|
+
end
|
203
|
+
|
204
|
+
def encoding
|
205
|
+
Mysql::Charset::CHARSET_ENCODING[@mysql.character_set_name]
|
206
|
+
end
|
207
|
+
|
208
|
+
def escape(s)
|
209
|
+
self.class.escape(s)
|
210
|
+
end
|
211
|
+
|
212
|
+
def more_results?
|
213
|
+
@mysql.more_results?
|
214
|
+
end
|
215
|
+
|
216
|
+
def next_result
|
217
|
+
!! @mysql.next_result(return_result: false)
|
218
|
+
rescue Mysql::Error => e
|
219
|
+
raise Mysql2::Error.new(e.message, @mysql&.server_version, e.errno, e.sqlstate)
|
220
|
+
end
|
221
|
+
|
222
|
+
def abandon_results!
|
223
|
+
while more_results?
|
224
|
+
next_result
|
225
|
+
store_result
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
def store_result
|
230
|
+
res = @mysql.store_result
|
231
|
+
reset_active_thread
|
232
|
+
res && Result.new(res, @current_query_options)
|
233
|
+
end
|
234
|
+
|
235
|
+
def async_result
|
236
|
+
current_thread_active?
|
237
|
+
res = @mysql.async_query_result
|
238
|
+
reset_active_thread
|
239
|
+
Result.new(res, @current_query_options)
|
240
|
+
end
|
241
|
+
|
242
|
+
def affected_rows
|
243
|
+
@mysql.affected_rows
|
244
|
+
end
|
245
|
+
|
246
|
+
def parse_ssl_mode(mode)
|
247
|
+
m = mode.to_s.upcase
|
248
|
+
if m.start_with?('SSL_MODE_')
|
249
|
+
return Mysql2::Client.const_get(m) if Mysql2::Client.const_defined?(m)
|
250
|
+
else
|
251
|
+
x = 'SSL_MODE_' + m
|
252
|
+
return Mysql2::Client.const_get(x) if Mysql2::Client.const_defined?(x)
|
253
|
+
end
|
254
|
+
warn "Unknown MySQL ssl_mode flag: #{mode}"
|
255
|
+
end
|
256
|
+
|
257
|
+
def ssl_cipher
|
258
|
+
@mysql.protocol&.ssl_cipher&.first
|
259
|
+
end
|
260
|
+
|
261
|
+
def parse_flags_array(flags, initial = 0)
|
262
|
+
flags.reduce(initial) do |memo, f|
|
263
|
+
fneg = f.start_with?('-') ? f[1..-1] : nil
|
264
|
+
if fneg && fneg =~ /^\w+$/ && Mysql2::Client.const_defined?(fneg)
|
265
|
+
memo & ~ Mysql2::Client.const_get(fneg)
|
266
|
+
elsif f && f =~ /^\w+$/ && Mysql2::Client.const_defined?(f)
|
267
|
+
memo | Mysql2::Client.const_get(f)
|
268
|
+
else
|
269
|
+
warn "Unknown MySQL connection flag: '#{f}'"
|
270
|
+
memo
|
271
|
+
end
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
# Find any default system CA paths to handle system roots
|
276
|
+
# by default if stricter validation is requested and no
|
277
|
+
# path is provide.
|
278
|
+
def find_default_ca_path
|
279
|
+
[
|
280
|
+
"/etc/ssl/certs/ca-certificates.crt",
|
281
|
+
"/etc/pki/tls/certs/ca-bundle.crt",
|
282
|
+
"/etc/ssl/ca-bundle.pem",
|
283
|
+
"/etc/ssl/cert.pem",
|
284
|
+
].find { |f| File.exist?(f) }
|
285
|
+
end
|
286
|
+
|
287
|
+
# Set default program_name in performance_schema.session_connect_attrs
|
288
|
+
# and performance_schema.session_account_connect_attrs
|
289
|
+
def parse_connect_attrs(conn_attrs)
|
290
|
+
return {} if Mysql2::Client::CONNECT_ATTRS.zero?
|
291
|
+
conn_attrs ||= {}
|
292
|
+
conn_attrs[:program_name] ||= $PROGRAM_NAME
|
293
|
+
conn_attrs.each_with_object({}) do |(key, value), hash|
|
294
|
+
hash[key.to_s] = value.to_s
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
def set_active_thread
|
299
|
+
@mutex.synchronize do
|
300
|
+
if @active_thread
|
301
|
+
if @active_thread == Thread.current
|
302
|
+
raise Mysql2::Error, 'This connection is still waiting for a result, try again once you have the result'
|
303
|
+
else
|
304
|
+
raise Mysql2::Error, "This connection is in use by: #{@active_thread.inspect}"
|
305
|
+
end
|
306
|
+
end
|
307
|
+
@active_thread = Thread.current
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
def reset_active_thread
|
312
|
+
@mutex.synchronize do
|
313
|
+
@active_thread = nil
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
def current_thread_active?
|
318
|
+
unless Thread.current == @active_thread
|
319
|
+
raise Mysql2::Error, 'This connection is still waiting for a result, try again once you have the result'
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
def query(sql, options = {})
|
324
|
+
if @reconnect && @mysql.protocol && closed?
|
325
|
+
connect(*@conn_params)
|
326
|
+
end
|
327
|
+
raise TypeError, "wrong argument type #{sql.class} (expected String)" unless sql.is_a? String
|
328
|
+
raise Mysql2::Error, 'MySQL client is not connected' if closed?
|
329
|
+
set_active_thread
|
330
|
+
@current_query_options = @query_options.merge(options)
|
331
|
+
Thread.handle_interrupt(::Mysql2::Util::TIMEOUT_ERROR_NEVER) do
|
332
|
+
if options[:async]
|
333
|
+
@mysql.async_query(sql, auto_store_result: !options[:stream], **options)
|
334
|
+
return nil
|
335
|
+
end
|
336
|
+
res = @mysql.query(sql, auto_store_result: !options[:stream], **options)
|
337
|
+
reset_active_thread
|
338
|
+
return res && Result.new(res, @current_query_options)
|
339
|
+
end
|
340
|
+
rescue Mysql::Error => e
|
341
|
+
reset_active_thread
|
342
|
+
if e.message =~ /timeout$/
|
343
|
+
raise Mysql2::Error::TimeoutError, e.message
|
344
|
+
else
|
345
|
+
raise Mysql2::Error.new(e.message, @mysql&.server_version, e.errno, e.sqlstate)
|
346
|
+
end
|
347
|
+
rescue Errno::ENOENT => e
|
348
|
+
reset_active_thread
|
349
|
+
raise Mysql2::Error, e.message
|
350
|
+
rescue
|
351
|
+
reset_active_thread
|
352
|
+
@mysql&.protocol&.close
|
353
|
+
raise
|
354
|
+
end
|
355
|
+
|
356
|
+
def row_to_hash(res, fields, &block)
|
357
|
+
res.each do |row|
|
358
|
+
h = {}
|
359
|
+
fields.each_with_index do |f, i|
|
360
|
+
key = @current_query_options[:symbolize_keys] ? f.name.intern : f.name
|
361
|
+
h[key] = convert_type(row[i], f.type)
|
362
|
+
end
|
363
|
+
block.call h
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
def query_info
|
368
|
+
info = query_info_string
|
369
|
+
return {} unless info
|
370
|
+
info_hash = {}
|
371
|
+
info.split.each_slice(2) { |s| info_hash[s[0].downcase.delete(':').to_sym] = s[1].to_i }
|
372
|
+
info_hash
|
373
|
+
end
|
374
|
+
|
375
|
+
def query_info_string
|
376
|
+
@mysql.info
|
377
|
+
end
|
378
|
+
|
379
|
+
def info
|
380
|
+
self.class.info
|
381
|
+
end
|
382
|
+
|
383
|
+
def server_info
|
384
|
+
{
|
385
|
+
id: @mysql.server_version,
|
386
|
+
version: @mysql.server_info.encode(Encoding.default_internal || encoding),
|
387
|
+
}
|
388
|
+
rescue => e
|
389
|
+
raise Mysql2::Error, e.message
|
390
|
+
end
|
391
|
+
|
392
|
+
def last_id
|
393
|
+
@mysql.insert_id
|
394
|
+
end
|
395
|
+
|
396
|
+
def ping
|
397
|
+
@mysql.ping
|
398
|
+
true
|
399
|
+
rescue
|
400
|
+
false
|
401
|
+
end
|
402
|
+
|
403
|
+
def thread_id
|
404
|
+
@mysql.thread_id
|
405
|
+
end
|
406
|
+
|
407
|
+
def select_db(db)
|
408
|
+
query("use `#{db}`")
|
409
|
+
db
|
410
|
+
end
|
411
|
+
|
412
|
+
def set_server_option(opt)
|
413
|
+
@mysql.set_server_option(opt)
|
414
|
+
true
|
415
|
+
rescue Mysql::Error => e
|
416
|
+
return false if e.message == 'Unknown command'
|
417
|
+
raise Mysql2::Error.new(e.message, @mysql&.server_version, e.errno, e.sqlstate)
|
418
|
+
end
|
419
|
+
|
420
|
+
def warning_count
|
421
|
+
@mysql.warning_count
|
422
|
+
end
|
423
|
+
|
424
|
+
def socket
|
425
|
+
raise Mysql2::Error, 'MySQL client is not connected' if closed?
|
426
|
+
@mysql.protocol.instance_variable_get(:@socket).fileno
|
427
|
+
end
|
428
|
+
|
429
|
+
def automatic_close
|
430
|
+
@automatic_close
|
431
|
+
end
|
432
|
+
|
433
|
+
def automatic_close=(f)
|
434
|
+
@automatic_close = f
|
435
|
+
@finalizer_opts[:automatic_close] = f
|
436
|
+
end
|
437
|
+
|
438
|
+
def automatic_close?
|
439
|
+
@automatic_close ? true : false
|
440
|
+
end
|
441
|
+
|
442
|
+
def prepare(*args)
|
443
|
+
st = @mysql.prepare(*args)
|
444
|
+
Statement.new(st, **@query_options)
|
445
|
+
rescue Mysql::Error => e
|
446
|
+
raise Mysql2::Error.new(e.message, @mysql&.server_version, e.errno, e.sqlstate)
|
447
|
+
end
|
448
|
+
|
449
|
+
def session_track(type)
|
450
|
+
@mysql.session_track[type]&.flatten
|
451
|
+
end
|
452
|
+
|
453
|
+
class << self
|
454
|
+
private
|
455
|
+
|
456
|
+
def local_offset
|
457
|
+
::Time.local(2010).utc_offset.to_r / 86400
|
458
|
+
end
|
459
|
+
end
|
460
|
+
end
|
461
|
+
end
|
data/lib/mysql2/error.rb
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
module Mysql2
|
2
|
+
class Error < StandardError
|
3
|
+
ENCODE_OPTS = {
|
4
|
+
undef: :replace,
|
5
|
+
invalid: :replace,
|
6
|
+
replace: '?'.freeze,
|
7
|
+
}.freeze
|
8
|
+
|
9
|
+
ConnectionError = Class.new(Error)
|
10
|
+
TimeoutError = Class.new(Error)
|
11
|
+
|
12
|
+
CODES = {
|
13
|
+
1205 => TimeoutError, # ER_LOCK_WAIT_TIMEOUT
|
14
|
+
|
15
|
+
1044 => ConnectionError, # ER_DBACCESS_DENIED_ERROR
|
16
|
+
1045 => ConnectionError, # ER_ACCESS_DENIED_ERROR
|
17
|
+
1152 => ConnectionError, # ER_ABORTING_CONNECTION
|
18
|
+
1153 => ConnectionError, # ER_NET_PACKET_TOO_LARGE
|
19
|
+
1154 => ConnectionError, # ER_NET_READ_ERROR_FROM_PIPE
|
20
|
+
1155 => ConnectionError, # ER_NET_FCNTL_ERROR
|
21
|
+
1156 => ConnectionError, # ER_NET_PACKETS_OUT_OF_ORDER
|
22
|
+
1157 => ConnectionError, # ER_NET_UNCOMPRESS_ERROR
|
23
|
+
1158 => ConnectionError, # ER_NET_READ_ERROR
|
24
|
+
1159 => ConnectionError, # ER_NET_READ_INTERRUPTED
|
25
|
+
1160 => ConnectionError, # ER_NET_ERROR_ON_WRITE
|
26
|
+
1161 => ConnectionError, # ER_NET_WRITE_INTERRUPTED
|
27
|
+
1927 => ConnectionError, # ER_CONNECTION_KILLED
|
28
|
+
|
29
|
+
2001 => ConnectionError, # CR_SOCKET_CREATE_ERROR
|
30
|
+
2002 => ConnectionError, # CR_CONNECTION_ERROR
|
31
|
+
2003 => ConnectionError, # CR_CONN_HOST_ERROR
|
32
|
+
2004 => ConnectionError, # CR_IPSOCK_ERROR
|
33
|
+
2005 => ConnectionError, # CR_UNKNOWN_HOST
|
34
|
+
2006 => ConnectionError, # CR_SERVER_GONE_ERROR
|
35
|
+
2007 => ConnectionError, # CR_VERSION_ERROR
|
36
|
+
2009 => ConnectionError, # CR_WRONG_HOST_INFO
|
37
|
+
2012 => ConnectionError, # CR_SERVER_HANDSHAKE_ERR
|
38
|
+
2013 => ConnectionError, # CR_SERVER_LOST
|
39
|
+
2020 => ConnectionError, # CR_NET_PACKET_TOO_LARGE
|
40
|
+
2026 => ConnectionError, # CR_SSL_CONNECTION_ERROR
|
41
|
+
2027 => ConnectionError, # CR_MALFORMED_PACKET
|
42
|
+
2047 => ConnectionError, # CR_CONN_UNKNOW_PROTOCOL
|
43
|
+
2048 => ConnectionError, # CR_INVALID_CONN_HANDLE
|
44
|
+
2049 => ConnectionError, # CR_UNUSED_1
|
45
|
+
}.freeze
|
46
|
+
|
47
|
+
attr_reader :error_number, :sql_state
|
48
|
+
|
49
|
+
# Mysql gem compatibility
|
50
|
+
alias errno error_number
|
51
|
+
alias error message
|
52
|
+
|
53
|
+
def initialize(msg, server_version = nil, error_number = nil, sql_state = nil)
|
54
|
+
@server_version = server_version
|
55
|
+
@error_number = error_number
|
56
|
+
@sql_state = sql_state ? sql_state.encode('ascii', **ENCODE_OPTS) : nil
|
57
|
+
|
58
|
+
super(clean_message(msg))
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.new_with_args(msg, server_version, error_number, sql_state)
|
62
|
+
error_class = CODES.fetch(error_number, self)
|
63
|
+
error_class.new(msg, server_version, error_number, sql_state)
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
# In MySQL 5.5+ error messages are always constructed server-side as UTF-8
|
69
|
+
# then returned in the encoding set by the `character_set_results` system
|
70
|
+
# variable.
|
71
|
+
#
|
72
|
+
# See http://dev.mysql.com/doc/refman/5.5/en/charset-errors.html for
|
73
|
+
# more context.
|
74
|
+
#
|
75
|
+
# Before MySQL 5.5 error message template strings are in whatever encoding
|
76
|
+
# is associated with the error message language.
|
77
|
+
# See http://dev.mysql.com/doc/refman/5.1/en/error-message-language.html
|
78
|
+
# for more information.
|
79
|
+
#
|
80
|
+
# The issue is that the user-data inserted in the message could potentially
|
81
|
+
# be in any encoding MySQL supports and is insert into the latin1, euckr or
|
82
|
+
# koi8r string raw. Meaning there's a high probability the string will be
|
83
|
+
# corrupt encoding-wise.
|
84
|
+
#
|
85
|
+
# See http://dev.mysql.com/doc/refman/5.1/en/charset-errors.html for
|
86
|
+
# more information.
|
87
|
+
#
|
88
|
+
# So in an attempt to make sure the error message string is always in a valid
|
89
|
+
# encoding, we'll assume UTF-8 and clean the string of anything that's not a
|
90
|
+
# valid UTF-8 character.
|
91
|
+
#
|
92
|
+
# Returns a valid UTF-8 string.
|
93
|
+
def clean_message(message)
|
94
|
+
if @server_version && @server_version > 50500
|
95
|
+
message.encode(**ENCODE_OPTS)
|
96
|
+
else
|
97
|
+
message.encode(Encoding::UTF_8, **ENCODE_OPTS)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
data/lib/mysql2/field.rb
ADDED