ruby-mysql 2.10.0 → 2.11.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 +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
|