ruby-mysql 2.10.0 → 2.11.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.rdoc +4 -6
- data/lib/mysql/authenticator/caching_sha2_password.rb +62 -0
- data/lib/mysql/authenticator/mysql_native_password.rb +37 -0
- data/lib/mysql/authenticator/sha256_password.rb +40 -0
- data/lib/mysql/authenticator.rb +84 -0
- data/lib/mysql/charset.rb +379 -328
- data/lib/mysql/constants.rb +7 -0
- data/lib/mysql/error.rb +3 -6
- data/lib/mysql/protocol.rb +174 -88
- data/lib/mysql.rb +51 -31
- data/test/test_mysql.rb +28 -15
- metadata +6 -2
data/lib/mysql/constants.rb
CHANGED
@@ -116,6 +116,13 @@ class Mysql
|
|
116
116
|
OPT_ZSTD_COMPRESSION_LEVEL = 42
|
117
117
|
OPT_LOAD_DATA_LOCAL_DIR = 43
|
118
118
|
|
119
|
+
# SSL Mode
|
120
|
+
SSL_MODE_DISABLED = 1
|
121
|
+
SSL_MODE_PREFERRED = 2
|
122
|
+
SSL_MODE_REQUIRED = 3
|
123
|
+
SSL_MODE_VERIFY_CA = 4
|
124
|
+
SSL_MODE_VERIFY_IDENTITY = 5
|
125
|
+
|
119
126
|
# Server Option
|
120
127
|
OPTION_MULTI_STATEMENTS_ON = 0
|
121
128
|
OPTION_MULTI_STATEMENTS_OFF = 1
|
data/lib/mysql/error.rb
CHANGED
@@ -20,17 +20,14 @@ class Mysql
|
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
23
|
-
attr_reader :sqlstate, :error
|
23
|
+
attr_reader :sqlstate, :error, :errno
|
24
24
|
|
25
|
-
def initialize(message, sqlstate='HY000')
|
25
|
+
def initialize(message, sqlstate='HY000', errno=nil)
|
26
26
|
@sqlstate = sqlstate
|
27
27
|
@error = message
|
28
|
+
@errno = errno || self.class::ERRNO
|
28
29
|
super message
|
29
30
|
end
|
30
|
-
|
31
|
-
def errno
|
32
|
-
self.class::ERRNO
|
33
|
-
end
|
34
31
|
end
|
35
32
|
|
36
33
|
# server side error
|
data/lib/mysql/protocol.rb
CHANGED
@@ -3,9 +3,9 @@
|
|
3
3
|
# mailto:tommy@tmtm.org
|
4
4
|
|
5
5
|
require "socket"
|
6
|
-
require "timeout"
|
7
|
-
require "digest/sha1"
|
8
6
|
require "stringio"
|
7
|
+
require "openssl"
|
8
|
+
require_relative 'authenticator.rb'
|
9
9
|
|
10
10
|
class Mysql
|
11
11
|
# MySQL network protocol
|
@@ -112,12 +112,14 @@ class Mysql
|
|
112
112
|
attr_reader :server_info
|
113
113
|
attr_reader :server_version
|
114
114
|
attr_reader :thread_id
|
115
|
+
attr_reader :client_flags
|
115
116
|
attr_reader :sqlstate
|
116
117
|
attr_reader :affected_rows
|
117
118
|
attr_reader :insert_id
|
118
119
|
attr_reader :server_status
|
119
120
|
attr_reader :warning_count
|
120
121
|
attr_reader :message
|
122
|
+
attr_reader :get_server_public_key
|
121
123
|
attr_accessor :charset
|
122
124
|
|
123
125
|
# @state variable keep state for connection.
|
@@ -127,41 +129,38 @@ class Mysql
|
|
127
129
|
# :RESULT :: After retr_fields(), retr_all_records() or stmt_retr_all_records() is needed.
|
128
130
|
|
129
131
|
# make socket connection to server.
|
130
|
-
#
|
131
|
-
#
|
132
|
-
#
|
133
|
-
#
|
134
|
-
# conn_timeout
|
135
|
-
# read_timeout
|
136
|
-
# write_timeout
|
137
|
-
# local_infile
|
138
|
-
#
|
139
|
-
# [ClientError]
|
140
|
-
def initialize(host, port, socket,
|
132
|
+
# @param host [String] if "localhost" or "" or nil then use UNIX socket. Otherwise use TCP socket
|
133
|
+
# @param port [Integer] port number using by TCP socket
|
134
|
+
# @param socket [String] socket file name using by UNIX socket
|
135
|
+
# @param [Hash] opts
|
136
|
+
# @option opts :conn_timeout [Integer] connect timeout (sec).
|
137
|
+
# @option opts :read_timeout [Integer] read timeout (sec).
|
138
|
+
# @option opts :write_timeout [Integer] write timeout (sec).
|
139
|
+
# @option opts :local_infile [String] local infile path
|
140
|
+
# @option opts :get_server_public_key [Boolean]
|
141
|
+
# @raise [ClientError] connection timeout
|
142
|
+
def initialize(host, port, socket, opts)
|
143
|
+
@opts = opts
|
141
144
|
@insert_id = 0
|
142
145
|
@warning_count = 0
|
143
146
|
@gc_stmt_queue = [] # stmt id list which GC destroy.
|
144
147
|
set_state :INIT
|
145
|
-
@
|
146
|
-
@write_timeout = write_timeout
|
147
|
-
@local_infile = local_infile
|
148
|
+
@get_server_public_key = @opts[:get_server_public_key]
|
148
149
|
begin
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
@sock = TCPSocket.new host, port
|
156
|
-
end
|
150
|
+
if host.nil? or host.empty? or host == "localhost"
|
151
|
+
socket ||= ENV["MYSQL_UNIX_PORT"] || MYSQL_UNIX_PORT
|
152
|
+
@socket = Socket.unix(socket)
|
153
|
+
else
|
154
|
+
port ||= ENV["MYSQL_TCP_PORT"] || (Socket.getservbyname("mysql","tcp") rescue MYSQL_TCP_PORT)
|
155
|
+
@socket = Socket.tcp(host, port, connect_timeout: @opts[:connect_timeout])
|
157
156
|
end
|
158
|
-
rescue
|
157
|
+
rescue Errno::ETIMEDOUT
|
159
158
|
raise ClientError, "connection timeout"
|
160
159
|
end
|
161
160
|
end
|
162
161
|
|
163
162
|
def close
|
164
|
-
@
|
163
|
+
@socket.close
|
165
164
|
end
|
166
165
|
|
167
166
|
# initial negotiate and authenticate.
|
@@ -180,22 +179,49 @@ class Mysql
|
|
180
179
|
init_packet = InitialPacket.parse read
|
181
180
|
@server_info = init_packet.server_version
|
182
181
|
@server_version = init_packet.server_version.split(/\D/)[0,3].inject{|a,b|a.to_i*100+b.to_i}
|
182
|
+
@server_capabilities = init_packet.server_capabilities
|
183
183
|
@thread_id = init_packet.thread_id
|
184
|
-
client_flags = CLIENT_LONG_PASSWORD | CLIENT_LONG_FLAG | CLIENT_TRANSACTIONS | CLIENT_PROTOCOL_41 | CLIENT_SECURE_CONNECTION
|
185
|
-
client_flags |= CLIENT_LOCAL_FILES if @local_infile
|
186
|
-
client_flags |= CLIENT_CONNECT_WITH_DB if db
|
187
|
-
client_flags |= flag
|
184
|
+
@client_flags = CLIENT_LONG_PASSWORD | CLIENT_LONG_FLAG | CLIENT_TRANSACTIONS | CLIENT_PROTOCOL_41 | CLIENT_SECURE_CONNECTION | CLIENT_PLUGIN_AUTH
|
185
|
+
@client_flags |= CLIENT_LOCAL_FILES if @opts[:local_infile]
|
186
|
+
@client_flags |= CLIENT_CONNECT_WITH_DB if db
|
187
|
+
@client_flags |= flag
|
188
188
|
@charset = charset
|
189
189
|
unless @charset
|
190
190
|
@charset = Charset.by_number(init_packet.server_charset)
|
191
191
|
@charset.encoding # raise error if unsupported charset
|
192
192
|
end
|
193
|
-
|
194
|
-
|
195
|
-
raise ProtocolError, 'The old style password is not supported' if read.to_s == "\xfe"
|
193
|
+
enable_ssl
|
194
|
+
Authenticator.new(self).authenticate(user, passwd, db, init_packet.scramble_buff, init_packet.auth_plugin)
|
196
195
|
set_state :READY
|
197
196
|
end
|
198
197
|
|
198
|
+
def enable_ssl
|
199
|
+
case @opts[:ssl_mode]
|
200
|
+
when SSL_MODE_DISABLED
|
201
|
+
return
|
202
|
+
when SSL_MODE_PREFERRED
|
203
|
+
return if @socket.local_address.unix?
|
204
|
+
return if @server_capabilities & CLIENT_SSL == 0
|
205
|
+
when SSL_MODE_REQUIRED
|
206
|
+
if @server_capabilities & CLIENT_SSL == 0
|
207
|
+
raise ClientError::SslConnectionError, "SSL is required but the server doesn't support it"
|
208
|
+
end
|
209
|
+
else
|
210
|
+
raise ClientError, "ssl_mode #{@opts[:ssl_mode]} is not supported"
|
211
|
+
end
|
212
|
+
begin
|
213
|
+
@client_flags |= CLIENT_SSL
|
214
|
+
write Protocol::TlsAuthenticationPacket.serialize(@client_flags, 1024**3, @charset.number)
|
215
|
+
@socket = OpenSSL::SSL::SSLSocket.new(@socket)
|
216
|
+
@socket.sync_close = true
|
217
|
+
@socket.connect
|
218
|
+
rescue => e
|
219
|
+
@client_flags &= ~CLIENT_SSL
|
220
|
+
return if @opts[:ssl_mode] == SSL_MODE_PREFERRED
|
221
|
+
raise e
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
199
225
|
# Quit command
|
200
226
|
def quit_command
|
201
227
|
synchronize do
|
@@ -248,7 +274,7 @@ class Mysql
|
|
248
274
|
# send local file to server
|
249
275
|
def send_local_file(filename)
|
250
276
|
filename = File.absolute_path(filename)
|
251
|
-
if filename.start_with? @local_infile
|
277
|
+
if filename.start_with? @opts[:local_infile]
|
252
278
|
File.open(filename){|f| write f}
|
253
279
|
else
|
254
280
|
raise ClientError::LoadDataLocalInfileRejected, 'LOAD DATA LOCAL INFILE file request rejected due to restrictions on access.'
|
@@ -442,15 +468,13 @@ class Mysql
|
|
442
468
|
@gc_stmt_queue.push stmt_id
|
443
469
|
end
|
444
470
|
|
445
|
-
private
|
446
|
-
|
447
471
|
def check_state(st)
|
448
472
|
raise 'command out of sync' unless @state == st
|
449
473
|
end
|
450
474
|
|
451
475
|
def set_state(st)
|
452
476
|
@state = st
|
453
|
-
if st == :READY
|
477
|
+
if st == :READY && !@gc_stmt_queue.empty?
|
454
478
|
gc_disabled = GC.disable
|
455
479
|
begin
|
456
480
|
while st = @gc_stmt_queue.shift
|
@@ -486,20 +510,18 @@ class Mysql
|
|
486
510
|
data = ''
|
487
511
|
len = nil
|
488
512
|
begin
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
data.concat ret
|
499
|
-
end
|
513
|
+
header = read_timeout(4, @opts[:read_timeout])
|
514
|
+
raise EOFError unless header && header.length == 4
|
515
|
+
len1, len2, seq = header.unpack("CvC")
|
516
|
+
len = (len2 << 8) + len1
|
517
|
+
raise ProtocolError, "invalid packet: sequence number mismatch(#{seq} != #{@seq}(expected))" if @seq != seq
|
518
|
+
@seq = (@seq + 1) % 256
|
519
|
+
ret = read_timeout(len, @opts[:read_timeout])
|
520
|
+
raise EOFError unless ret && ret.length == len
|
521
|
+
data.concat ret
|
500
522
|
rescue EOFError
|
501
523
|
raise ClientError::ServerGoneError, 'MySQL server has gone away'
|
502
|
-
rescue
|
524
|
+
rescue Errno::ETIMEDOUT
|
503
525
|
raise ClientError, "read timeout"
|
504
526
|
end while len == MAX_PACKET_LENGTH
|
505
527
|
|
@@ -507,52 +529,87 @@ class Mysql
|
|
507
529
|
|
508
530
|
# Error packet
|
509
531
|
if data[0] == ?\xff
|
510
|
-
|
532
|
+
_, errno, marker, @sqlstate, message = data.unpack("Cvaa5a*")
|
511
533
|
unless marker == "#"
|
512
|
-
|
534
|
+
_, errno, message = data.unpack("Cva*") # Version 4.0 Error
|
513
535
|
@sqlstate = ""
|
514
536
|
end
|
515
537
|
message.force_encoding(@charset.encoding)
|
516
538
|
if Mysql::ServerError::ERROR_MAP.key? errno
|
517
539
|
raise Mysql::ServerError::ERROR_MAP[errno].new(message, @sqlstate)
|
518
540
|
end
|
519
|
-
raise Mysql::ServerError.new(message, @sqlstate)
|
541
|
+
raise Mysql::ServerError.new(message, @sqlstate, errno)
|
520
542
|
end
|
521
543
|
Packet.new(data)
|
522
544
|
end
|
523
545
|
|
546
|
+
def read_timeout(len, timeout)
|
547
|
+
return @socket.read(len) if timeout.nil? || timeout == 0
|
548
|
+
result = ''
|
549
|
+
e = ::Time.now + timeout
|
550
|
+
while result.size < len
|
551
|
+
now = ::Time.now
|
552
|
+
raise Errno::ETIMEDOUT if now > e
|
553
|
+
r = @socket.read_nonblock(len - result.size, exception: false)
|
554
|
+
case r
|
555
|
+
when :wait_readable
|
556
|
+
IO.select([@socket], nil, nil, e - now)
|
557
|
+
next
|
558
|
+
when :wait_writable
|
559
|
+
IO.select(nil, [@socket], nil, e - now)
|
560
|
+
next
|
561
|
+
else
|
562
|
+
result << r
|
563
|
+
end
|
564
|
+
end
|
565
|
+
return result
|
566
|
+
end
|
567
|
+
|
524
568
|
# Write one packet data
|
525
569
|
# === Argument
|
526
570
|
# data :: [String / IO] packet data. If data is nil, write empty packet.
|
527
571
|
def write(data)
|
528
572
|
begin
|
529
|
-
@
|
573
|
+
@socket.sync = false
|
530
574
|
if data.nil?
|
531
|
-
|
532
|
-
@sock.write [0, 0, @seq].pack("CvC")
|
533
|
-
end
|
575
|
+
write_timeout([0, 0, @seq].pack("CvC"), @opts[:write_timeout])
|
534
576
|
@seq = (@seq + 1) % 256
|
535
577
|
else
|
536
578
|
data = StringIO.new data if data.is_a? String
|
537
579
|
while d = data.read(MAX_PACKET_LENGTH)
|
538
|
-
|
539
|
-
@sock.write [d.length%256, d.length/256, @seq].pack("CvC")
|
540
|
-
@sock.write d
|
541
|
-
end
|
580
|
+
write_timeout([d.length%256, d.length/256, @seq].pack("CvC")+d, @opts[:write_timeout])
|
542
581
|
@seq = (@seq + 1) % 256
|
543
582
|
end
|
544
583
|
end
|
545
|
-
@
|
546
|
-
|
547
|
-
@sock.flush
|
548
|
-
end
|
584
|
+
@socket.sync = true
|
585
|
+
@socket.flush
|
549
586
|
rescue Errno::EPIPE
|
550
587
|
raise ClientError::ServerGoneError, 'MySQL server has gone away'
|
551
|
-
rescue
|
588
|
+
rescue Errno::ETIMEDOUT
|
552
589
|
raise ClientError, "write timeout"
|
553
590
|
end
|
554
591
|
end
|
555
592
|
|
593
|
+
def write_timeout(data, timeout)
|
594
|
+
return @socket.write(data) if timeout.nil? || timeout == 0
|
595
|
+
len = 0
|
596
|
+
e = ::Time.now + timeout
|
597
|
+
while len < data.size
|
598
|
+
now = ::Time.now
|
599
|
+
raise Errno::ETIMEDOUT if now > e
|
600
|
+
l = @socket.write_nonblock(data[len..-1], exception: false)
|
601
|
+
case l
|
602
|
+
when :wait_readable
|
603
|
+
IO.select([@socket], nil, nil, e - now)
|
604
|
+
when :wait_writable
|
605
|
+
IO.select(nil, [@socket], nil, e - now)
|
606
|
+
else
|
607
|
+
len += l
|
608
|
+
end
|
609
|
+
end
|
610
|
+
return len
|
611
|
+
end
|
612
|
+
|
556
613
|
# Read EOF packet
|
557
614
|
# === Exception
|
558
615
|
# [ProtocolError] packet is not EOF
|
@@ -573,19 +630,6 @@ class Mysql
|
|
573
630
|
end
|
574
631
|
end
|
575
632
|
|
576
|
-
# Encrypt password
|
577
|
-
# === Argument
|
578
|
-
# plain :: [String] plain password.
|
579
|
-
# scramble :: [String] scramble code from initial packet.
|
580
|
-
# === Return
|
581
|
-
# [String] encrypted password
|
582
|
-
def encrypt_password(plain, scramble)
|
583
|
-
return "" if plain.nil? or plain.empty?
|
584
|
-
hash_stage1 = Digest::SHA1.digest plain
|
585
|
-
hash_stage2 = Digest::SHA1.digest hash_stage1
|
586
|
-
return hash_stage1.unpack("C*").zip(Digest::SHA1.digest(scramble+hash_stage2).unpack("C*")).map{|a,b| a^b}.pack("C*")
|
587
|
-
end
|
588
|
-
|
589
633
|
# Initial packet
|
590
634
|
class InitialPacket
|
591
635
|
def self.parse(pkt)
|
@@ -597,18 +641,26 @@ class Mysql
|
|
597
641
|
server_capabilities = pkt.ushort
|
598
642
|
server_charset = pkt.utiny
|
599
643
|
server_status = pkt.ushort
|
600
|
-
|
644
|
+
server_capabilities2 = pkt.ushort
|
645
|
+
scramble_length = pkt.utiny
|
646
|
+
_f1 = pkt.read(10)
|
601
647
|
rest_scramble_buff = pkt.string
|
648
|
+
auth_plugin = pkt.string
|
649
|
+
|
650
|
+
server_capabilities |= server_capabilities2 << 16
|
651
|
+
scramble_buff.concat rest_scramble_buff
|
652
|
+
|
602
653
|
raise ProtocolError, "unsupported version: #{protocol_version}" unless protocol_version == VERSION
|
603
654
|
raise ProtocolError, "invalid packet: f0=#{f0}" unless f0 == 0
|
604
|
-
scramble_buff.
|
605
|
-
|
655
|
+
raise ProtocolError, "invalid packet: scramble_length(#{scramble_length}) != length of scramble(#{scramble_buff.size + 1})" unless scramble_length == scramble_buff.size + 1
|
656
|
+
|
657
|
+
self.new protocol_version, server_version, thread_id, server_capabilities, server_charset, server_status, scramble_buff, auth_plugin
|
606
658
|
end
|
607
659
|
|
608
|
-
attr_reader :protocol_version, :server_version, :thread_id, :server_capabilities, :server_charset, :server_status, :scramble_buff
|
660
|
+
attr_reader :protocol_version, :server_version, :thread_id, :server_capabilities, :server_charset, :server_status, :scramble_buff, :auth_plugin
|
609
661
|
|
610
662
|
def initialize(*args)
|
611
|
-
@protocol_version, @server_version, @thread_id, @server_capabilities, @server_charset, @server_status, @scramble_buff = args
|
663
|
+
@protocol_version, @server_version, @thread_id, @server_capabilities, @server_charset, @server_status, @scramble_buff, @auth_plugin = args
|
612
664
|
end
|
613
665
|
end
|
614
666
|
|
@@ -688,16 +740,35 @@ class Mysql
|
|
688
740
|
|
689
741
|
# Authentication packet
|
690
742
|
class AuthenticationPacket
|
691
|
-
def self.serialize(client_flags, max_packet_size, charset_number, username, scrambled_password, databasename)
|
692
|
-
[
|
743
|
+
def self.serialize(client_flags, max_packet_size, charset_number, username, scrambled_password, databasename, auth_plugin)
|
744
|
+
data = [
|
693
745
|
client_flags,
|
694
746
|
max_packet_size,
|
695
|
-
|
747
|
+
charset_number,
|
696
748
|
"", # always 0x00 * 23
|
697
749
|
username,
|
698
750
|
Packet.lcs(scrambled_password),
|
699
|
-
|
700
|
-
|
751
|
+
]
|
752
|
+
pack = "VVCa23Z*A*"
|
753
|
+
if databasename
|
754
|
+
data.push databasename
|
755
|
+
pack.concat "Z*"
|
756
|
+
end
|
757
|
+
data.push auth_plugin
|
758
|
+
pack.concat "Z*"
|
759
|
+
data.pack(pack)
|
760
|
+
end
|
761
|
+
end
|
762
|
+
|
763
|
+
# TLS Authentication packet
|
764
|
+
class TlsAuthenticationPacket
|
765
|
+
def self.serialize(client_flags, max_packet_size, charset_number)
|
766
|
+
[
|
767
|
+
client_flags,
|
768
|
+
max_packet_size,
|
769
|
+
charset_number,
|
770
|
+
"", # always 0x00 * 23
|
771
|
+
].pack("VVCa23")
|
701
772
|
end
|
702
773
|
end
|
703
774
|
|
@@ -725,6 +796,21 @@ class Mysql
|
|
725
796
|
end
|
726
797
|
|
727
798
|
end
|
799
|
+
|
800
|
+
class AuthenticationResultPacket
|
801
|
+
def self.parse(pkt)
|
802
|
+
result = pkt.utiny
|
803
|
+
auth_plugin = pkt.string
|
804
|
+
scramble = pkt.string
|
805
|
+
self.new(result, auth_plugin, scramble)
|
806
|
+
end
|
807
|
+
|
808
|
+
attr_reader :result, :auth_plugin, :scramble
|
809
|
+
|
810
|
+
def initialize(*args)
|
811
|
+
@result, @auth_plugin, @scramble = args
|
812
|
+
end
|
813
|
+
end
|
728
814
|
end
|
729
815
|
|
730
816
|
class RawRecord
|
data/lib/mysql.rb
CHANGED
@@ -16,12 +16,8 @@ class Mysql
|
|
16
16
|
require "mysql/charset"
|
17
17
|
require "mysql/protocol"
|
18
18
|
require "mysql/packet.rb"
|
19
|
-
begin
|
20
|
-
require "mysql/ext.so"
|
21
|
-
rescue LoadError
|
22
|
-
end
|
23
19
|
|
24
|
-
VERSION =
|
20
|
+
VERSION = 21100 # Version number of this library
|
25
21
|
MYSQL_UNIX_PORT = "/tmp/mysql.sock" # UNIX domain socket filename
|
26
22
|
MYSQL_TCP_PORT = 3306 # TCP socket port number
|
27
23
|
|
@@ -86,16 +82,20 @@ class Mysql
|
|
86
82
|
@fields = nil
|
87
83
|
@protocol = nil
|
88
84
|
@charset = nil
|
89
|
-
@connect_timeout = nil
|
90
|
-
@read_timeout = nil
|
91
|
-
@write_timeout = nil
|
92
85
|
@init_command = nil
|
93
86
|
@sqlstate = "00000"
|
94
87
|
@query_with_result = true
|
95
88
|
@host_info = nil
|
96
89
|
@last_error = nil
|
97
90
|
@result_exist = false
|
98
|
-
@
|
91
|
+
@opts = {
|
92
|
+
connect_timeout: nil,
|
93
|
+
read_timeout: nil,
|
94
|
+
write_timeout: nil,
|
95
|
+
local_infile: nil,
|
96
|
+
ssl_mode: SSL_MODE_PREFERRED,
|
97
|
+
get_server_public_key: false,
|
98
|
+
}
|
99
99
|
end
|
100
100
|
|
101
101
|
# Connect to mysqld.
|
@@ -112,7 +112,7 @@ class Mysql
|
|
112
112
|
warn 'unsupported flag: CLIENT_COMPRESS' if $VERBOSE
|
113
113
|
flag &= ~CLIENT_COMPRESS
|
114
114
|
end
|
115
|
-
@protocol = Protocol.new
|
115
|
+
@protocol = Protocol.new(host, port, socket, @opts)
|
116
116
|
@protocol.authenticate user, passwd, db, flag, @charset
|
117
117
|
@charset ||= @protocol.charset
|
118
118
|
@host_info = (host.nil? || host == "localhost") ? 'Localhost via UNIX socket' : "#{host} via TCP/IP"
|
@@ -144,38 +144,64 @@ class Mysql
|
|
144
144
|
# Set option for connection.
|
145
145
|
#
|
146
146
|
# Available options:
|
147
|
-
# Mysql::INIT_COMMAND, Mysql::OPT_CONNECT_TIMEOUT, Mysql::
|
148
|
-
# Mysql::
|
147
|
+
# Mysql::INIT_COMMAND, Mysql::OPT_CONNECT_TIMEOUT, Mysql::OPT_GET_SERVER_PUBLIC_KEY,
|
148
|
+
# Mysql::OPT_LOAD_DATA_LOCAL_DIR, Mysql::OPT_LOCAL_INFILE, Mysql::OPT_READ_TIMEOUT,
|
149
|
+
# Mysql::OPT_SSL_MODE, Mysql::OPT_WRITE_TIMEOUT, Mysql::SET_CHARSET_NAME
|
149
150
|
# @param [Integer] opt option
|
150
151
|
# @param [Integer] value option value that is depend on opt
|
151
152
|
# @return [Mysql] self
|
152
153
|
def options(opt, value=nil)
|
153
154
|
case opt
|
155
|
+
# when Mysql::DEFAULT_AUTH
|
156
|
+
# when Mysql::ENABLE_CLEARTEXT_PLUGIN
|
154
157
|
when Mysql::INIT_COMMAND
|
155
158
|
@init_command = value.to_s
|
159
|
+
# when Mysql::OPT_BIND
|
160
|
+
# when Mysql::OPT_CAN_HANDLE_EXPIRED_PASSWORDS
|
156
161
|
# when Mysql::OPT_COMPRESS
|
162
|
+
# when Mysql::OPT_COMPRESSION_ALGORITHMS
|
163
|
+
# when Mysql::OPT_CONNECT_ATTR_ADD
|
164
|
+
# when Mysql::OPT_CONNECT_ATTR_DELETE
|
165
|
+
# when Mysql::OPT_CONNECT_ATTR_RESET
|
157
166
|
when Mysql::OPT_CONNECT_TIMEOUT
|
158
|
-
@connect_timeout = value
|
159
|
-
|
160
|
-
|
161
|
-
@local_infile = value ? '' : nil
|
167
|
+
@opts[:connect_timeout] = value
|
168
|
+
when Mysql::OPT_GET_SERVER_PUBLIC_KEY
|
169
|
+
@opts[:get_server_public_key] = value
|
162
170
|
when Mysql::OPT_LOAD_DATA_LOCAL_DIR
|
163
|
-
@local_infile = value
|
171
|
+
@opts[:local_infile] = value
|
172
|
+
when Mysql::OPT_LOCAL_INFILE
|
173
|
+
@opts[:local_infile] = value ? '' : nil
|
174
|
+
# when Mysql::OPT_MAX_ALLOWED_PACKET
|
164
175
|
# when Mysql::OPT_NAMED_PIPE
|
176
|
+
# when Mysql::OPT_NET_BUFFER_LENGTH
|
177
|
+
# when Mysql::OPT_OPTIONAL_RESULTSET_METADATA
|
165
178
|
# when Mysql::OPT_PROTOCOL
|
166
179
|
when Mysql::OPT_READ_TIMEOUT
|
167
|
-
@read_timeout = value
|
180
|
+
@opts[:read_timeout] = value
|
168
181
|
# when Mysql::OPT_RECONNECT
|
182
|
+
# when Mysql::OPT_RETRY_COUNT
|
169
183
|
# when Mysql::SET_CLIENT_IP
|
170
|
-
# when Mysql::
|
171
|
-
# when Mysql::
|
172
|
-
# when Mysql::
|
184
|
+
# when Mysql::OPT_SSL_CA
|
185
|
+
# when Mysql::OPT_SSL_CAPATH
|
186
|
+
# when Mysql::OPT_SSL_CERT
|
187
|
+
# when Mysql::OPT_SSL_CIPHER
|
188
|
+
# when Mysql::OPT_SSL_CRL
|
189
|
+
# when Mysql::OPT_SSL_CRLPATH
|
190
|
+
# when Mysql::OPT_SSL_FIPS_MODE
|
191
|
+
# when Mysql::OPT_SSL_KEY
|
192
|
+
when Mysql::OPT_SSL_MODE
|
193
|
+
@opts[:ssl_mode] = value
|
194
|
+
# when Mysql::OPT_TLS_CIPHERSUITES
|
195
|
+
# when Mysql::OPT_TLS_VERSION
|
196
|
+
# when Mysql::OPT_USE_RESULT
|
173
197
|
when Mysql::OPT_WRITE_TIMEOUT
|
174
|
-
@write_timeout = value
|
198
|
+
@opts[:write_timeout] = value
|
199
|
+
# when Mysql::OPT_ZSTD_COMPRESSION_LEVEL
|
200
|
+
# when Mysql::PLUGIN_DIR
|
175
201
|
# when Mysql::READ_DEFAULT_FILE
|
176
202
|
# when Mysql::READ_DEFAULT_GROUP
|
177
203
|
# when Mysql::REPORT_DATA_TRUNCATION
|
178
|
-
# when Mysql::
|
204
|
+
# when Mysql::SERVER_PUBLIC_KEY
|
179
205
|
# when Mysql::SET_CHARSET_DIR
|
180
206
|
when Mysql::SET_CHARSET_NAME
|
181
207
|
@charset = Charset.by_name value.to_s
|
@@ -188,14 +214,9 @@ class Mysql
|
|
188
214
|
|
189
215
|
# Escape special character in MySQL.
|
190
216
|
#
|
191
|
-
# In Ruby 1.8, this is not safe for multibyte charset such as 'SJIS'.
|
192
|
-
# You should use place-holder in prepared-statement.
|
193
217
|
# @param [String] str
|
194
218
|
# return [String]
|
195
219
|
def escape_string(str)
|
196
|
-
if not defined? Encoding and @charset.unsafe
|
197
|
-
raise ClientError, 'Mysql#escape_string is called for unsafe multibyte charset'
|
198
|
-
end
|
199
220
|
self.class.escape_string str
|
200
221
|
end
|
201
222
|
alias quote escape_string
|
@@ -441,7 +462,6 @@ class Mysql
|
|
441
462
|
store_result
|
442
463
|
end
|
443
464
|
|
444
|
-
# @note for Ruby 1.8: This is not multi-byte safe. Don't use for multi-byte charset such as cp932.
|
445
465
|
# @param [String] table database name that may contain wild card.
|
446
466
|
# @return [Array<String>] list of table name.
|
447
467
|
def list_tables(table=nil)
|
@@ -902,7 +922,7 @@ class Mysql
|
|
902
922
|
row.zip(@bind_result).map do |col, type|
|
903
923
|
if col.nil?
|
904
924
|
nil
|
905
|
-
elsif [Numeric, Integer
|
925
|
+
elsif [Numeric, Integer].include? type
|
906
926
|
col.to_i
|
907
927
|
elsif type == String
|
908
928
|
col.to_s
|
@@ -955,7 +975,7 @@ class Mysql
|
|
955
975
|
raise ClientError, "bind_result: result value count(#{@fields.length}) != number of argument(#{args.length})"
|
956
976
|
end
|
957
977
|
args.each do |a|
|
958
|
-
raise TypeError unless [Numeric,
|
978
|
+
raise TypeError unless [Numeric, Integer, Float, String, Mysql::Time, nil].include? a
|
959
979
|
end
|
960
980
|
@bind_result = args
|
961
981
|
self
|