ruby-mysql 2.9.12 → 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 +7 -0
- data/README.rdoc +4 -3
- 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 -256
- data/lib/mysql/constants.rb +154 -70
- data/lib/mysql/error.rb +13 -7
- data/lib/mysql/protocol.rb +191 -92
- data/lib/mysql.rb +55 -33
- data/test/test_mysql.rb +1899 -0
- data/test/test_mysql_packet.rb +149 -0
- metadata +21 -20
- data/spec/mysql/packet_spec.rb +0 -120
- data/spec/mysql_spec.rb +0 -1789
data/lib/mysql/protocol.rb
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
# coding: ascii-8bit
|
2
|
-
# Copyright (C) 2008
|
2
|
+
# Copyright (C) 2008 TOMITA Masahiro
|
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
|
@@ -23,7 +23,7 @@ class Mysql
|
|
23
23
|
# Object :: converted value.
|
24
24
|
def self.net2value(pkt, type, unsigned)
|
25
25
|
case type
|
26
|
-
when Field::TYPE_STRING, Field::TYPE_VAR_STRING, Field::TYPE_NEWDECIMAL, Field::TYPE_BLOB
|
26
|
+
when Field::TYPE_STRING, Field::TYPE_VAR_STRING, Field::TYPE_NEWDECIMAL, Field::TYPE_BLOB, Field::TYPE_JSON
|
27
27
|
return pkt.lcs
|
28
28
|
when Field::TYPE_TINY
|
29
29
|
v = pkt.utiny
|
@@ -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,39 +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
|
-
#
|
138
|
-
#
|
139
|
-
|
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
|
140
144
|
@insert_id = 0
|
141
145
|
@warning_count = 0
|
142
146
|
@gc_stmt_queue = [] # stmt id list which GC destroy.
|
143
147
|
set_state :INIT
|
144
|
-
@
|
145
|
-
@write_timeout = write_timeout
|
148
|
+
@get_server_public_key = @opts[:get_server_public_key]
|
146
149
|
begin
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
@sock = TCPSocket.new host, port
|
154
|
-
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])
|
155
156
|
end
|
156
|
-
rescue
|
157
|
+
rescue Errno::ETIMEDOUT
|
157
158
|
raise ClientError, "connection timeout"
|
158
159
|
end
|
159
160
|
end
|
160
161
|
|
161
162
|
def close
|
162
|
-
@
|
163
|
+
@socket.close
|
163
164
|
end
|
164
165
|
|
165
166
|
# initial negotiate and authenticate.
|
@@ -178,21 +179,49 @@ class Mysql
|
|
178
179
|
init_packet = InitialPacket.parse read
|
179
180
|
@server_info = init_packet.server_version
|
180
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
|
181
183
|
@thread_id = init_packet.thread_id
|
182
|
-
client_flags = CLIENT_LONG_PASSWORD | CLIENT_LONG_FLAG | CLIENT_TRANSACTIONS | CLIENT_PROTOCOL_41 | CLIENT_SECURE_CONNECTION
|
183
|
-
client_flags |=
|
184
|
-
client_flags |=
|
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
|
185
188
|
@charset = charset
|
186
189
|
unless @charset
|
187
190
|
@charset = Charset.by_number(init_packet.server_charset)
|
188
191
|
@charset.encoding # raise error if unsupported charset
|
189
192
|
end
|
190
|
-
|
191
|
-
|
192
|
-
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)
|
193
195
|
set_state :READY
|
194
196
|
end
|
195
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
|
+
|
196
225
|
# Quit command
|
197
226
|
def quit_command
|
198
227
|
synchronize do
|
@@ -230,10 +259,7 @@ class Mysql
|
|
230
259
|
return res_packet.field_count
|
231
260
|
end
|
232
261
|
if res_packet.field_count.nil? # LOAD DATA LOCAL INFILE
|
233
|
-
|
234
|
-
File.open(filename){|f| write f}
|
235
|
-
write nil # EOF mark
|
236
|
-
read
|
262
|
+
send_local_file(res_packet.message)
|
237
263
|
end
|
238
264
|
@affected_rows, @insert_id, @server_status, @warning_count, @message =
|
239
265
|
res_packet.affected_rows, res_packet.insert_id, res_packet.server_status, res_packet.warning_count, res_packet.message
|
@@ -245,6 +271,19 @@ class Mysql
|
|
245
271
|
end
|
246
272
|
end
|
247
273
|
|
274
|
+
# send local file to server
|
275
|
+
def send_local_file(filename)
|
276
|
+
filename = File.absolute_path(filename)
|
277
|
+
if filename.start_with? @opts[:local_infile]
|
278
|
+
File.open(filename){|f| write f}
|
279
|
+
else
|
280
|
+
raise ClientError::LoadDataLocalInfileRejected, 'LOAD DATA LOCAL INFILE file request rejected due to restrictions on access.'
|
281
|
+
end
|
282
|
+
ensure
|
283
|
+
write nil # EOF mark
|
284
|
+
read
|
285
|
+
end
|
286
|
+
|
248
287
|
# Retrieve n fields
|
249
288
|
# === Argument
|
250
289
|
# n :: [Integer] number of fields
|
@@ -429,15 +468,13 @@ class Mysql
|
|
429
468
|
@gc_stmt_queue.push stmt_id
|
430
469
|
end
|
431
470
|
|
432
|
-
private
|
433
|
-
|
434
471
|
def check_state(st)
|
435
472
|
raise 'command out of sync' unless @state == st
|
436
473
|
end
|
437
474
|
|
438
475
|
def set_state(st)
|
439
476
|
@state = st
|
440
|
-
if st == :READY
|
477
|
+
if st == :READY && !@gc_stmt_queue.empty?
|
441
478
|
gc_disabled = GC.disable
|
442
479
|
begin
|
443
480
|
while st = @gc_stmt_queue.shift
|
@@ -473,20 +510,18 @@ class Mysql
|
|
473
510
|
data = ''
|
474
511
|
len = nil
|
475
512
|
begin
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
data.concat ret
|
486
|
-
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
|
487
522
|
rescue EOFError
|
488
523
|
raise ClientError::ServerGoneError, 'MySQL server has gone away'
|
489
|
-
rescue
|
524
|
+
rescue Errno::ETIMEDOUT
|
490
525
|
raise ClientError, "read timeout"
|
491
526
|
end while len == MAX_PACKET_LENGTH
|
492
527
|
|
@@ -494,52 +529,87 @@ class Mysql
|
|
494
529
|
|
495
530
|
# Error packet
|
496
531
|
if data[0] == ?\xff
|
497
|
-
|
532
|
+
_, errno, marker, @sqlstate, message = data.unpack("Cvaa5a*")
|
498
533
|
unless marker == "#"
|
499
|
-
|
534
|
+
_, errno, message = data.unpack("Cva*") # Version 4.0 Error
|
500
535
|
@sqlstate = ""
|
501
536
|
end
|
502
537
|
message.force_encoding(@charset.encoding)
|
503
538
|
if Mysql::ServerError::ERROR_MAP.key? errno
|
504
539
|
raise Mysql::ServerError::ERROR_MAP[errno].new(message, @sqlstate)
|
505
540
|
end
|
506
|
-
raise Mysql::ServerError.new(message, @sqlstate)
|
541
|
+
raise Mysql::ServerError.new(message, @sqlstate, errno)
|
507
542
|
end
|
508
543
|
Packet.new(data)
|
509
544
|
end
|
510
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
|
+
|
511
568
|
# Write one packet data
|
512
569
|
# === Argument
|
513
570
|
# data :: [String / IO] packet data. If data is nil, write empty packet.
|
514
571
|
def write(data)
|
515
572
|
begin
|
516
|
-
@
|
573
|
+
@socket.sync = false
|
517
574
|
if data.nil?
|
518
|
-
|
519
|
-
@sock.write [0, 0, @seq].pack("CvC")
|
520
|
-
end
|
575
|
+
write_timeout([0, 0, @seq].pack("CvC"), @opts[:write_timeout])
|
521
576
|
@seq = (@seq + 1) % 256
|
522
577
|
else
|
523
578
|
data = StringIO.new data if data.is_a? String
|
524
579
|
while d = data.read(MAX_PACKET_LENGTH)
|
525
|
-
|
526
|
-
@sock.write [d.length%256, d.length/256, @seq].pack("CvC")
|
527
|
-
@sock.write d
|
528
|
-
end
|
580
|
+
write_timeout([d.length%256, d.length/256, @seq].pack("CvC")+d, @opts[:write_timeout])
|
529
581
|
@seq = (@seq + 1) % 256
|
530
582
|
end
|
531
583
|
end
|
532
|
-
@
|
533
|
-
|
534
|
-
@sock.flush
|
535
|
-
end
|
584
|
+
@socket.sync = true
|
585
|
+
@socket.flush
|
536
586
|
rescue Errno::EPIPE
|
537
587
|
raise ClientError::ServerGoneError, 'MySQL server has gone away'
|
538
|
-
rescue
|
588
|
+
rescue Errno::ETIMEDOUT
|
539
589
|
raise ClientError, "write timeout"
|
540
590
|
end
|
541
591
|
end
|
542
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
|
+
|
543
613
|
# Read EOF packet
|
544
614
|
# === Exception
|
545
615
|
# [ProtocolError] packet is not EOF
|
@@ -560,19 +630,6 @@ class Mysql
|
|
560
630
|
end
|
561
631
|
end
|
562
632
|
|
563
|
-
# Encrypt password
|
564
|
-
# === Argument
|
565
|
-
# plain :: [String] plain password.
|
566
|
-
# scramble :: [String] scramble code from initial packet.
|
567
|
-
# === Return
|
568
|
-
# [String] encrypted password
|
569
|
-
def encrypt_password(plain, scramble)
|
570
|
-
return "" if plain.nil? or plain.empty?
|
571
|
-
hash_stage1 = Digest::SHA1.digest plain
|
572
|
-
hash_stage2 = Digest::SHA1.digest hash_stage1
|
573
|
-
return hash_stage1.unpack("C*").zip(Digest::SHA1.digest(scramble+hash_stage2).unpack("C*")).map{|a,b| a^b}.pack("C*")
|
574
|
-
end
|
575
|
-
|
576
633
|
# Initial packet
|
577
634
|
class InitialPacket
|
578
635
|
def self.parse(pkt)
|
@@ -584,18 +641,26 @@ class Mysql
|
|
584
641
|
server_capabilities = pkt.ushort
|
585
642
|
server_charset = pkt.utiny
|
586
643
|
server_status = pkt.ushort
|
587
|
-
|
644
|
+
server_capabilities2 = pkt.ushort
|
645
|
+
scramble_length = pkt.utiny
|
646
|
+
_f1 = pkt.read(10)
|
588
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
|
+
|
589
653
|
raise ProtocolError, "unsupported version: #{protocol_version}" unless protocol_version == VERSION
|
590
654
|
raise ProtocolError, "invalid packet: f0=#{f0}" unless f0 == 0
|
591
|
-
scramble_buff.
|
592
|
-
|
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
|
593
658
|
end
|
594
659
|
|
595
|
-
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
|
596
661
|
|
597
662
|
def initialize(*args)
|
598
|
-
@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
|
599
664
|
end
|
600
665
|
end
|
601
666
|
|
@@ -627,13 +692,13 @@ class Mysql
|
|
627
692
|
# Field packet
|
628
693
|
class FieldPacket
|
629
694
|
def self.parse(pkt)
|
630
|
-
|
695
|
+
_first = pkt.lcs
|
631
696
|
db = pkt.lcs
|
632
697
|
table = pkt.lcs
|
633
698
|
org_table = pkt.lcs
|
634
699
|
name = pkt.lcs
|
635
700
|
org_name = pkt.lcs
|
636
|
-
|
701
|
+
_f0 = pkt.utiny
|
637
702
|
charsetnr = pkt.ushort
|
638
703
|
length = pkt.ulong
|
639
704
|
type = pkt.utiny
|
@@ -675,16 +740,35 @@ class Mysql
|
|
675
740
|
|
676
741
|
# Authentication packet
|
677
742
|
class AuthenticationPacket
|
678
|
-
def self.serialize(client_flags, max_packet_size, charset_number, username, scrambled_password, databasename)
|
679
|
-
[
|
743
|
+
def self.serialize(client_flags, max_packet_size, charset_number, username, scrambled_password, databasename, auth_plugin)
|
744
|
+
data = [
|
680
745
|
client_flags,
|
681
746
|
max_packet_size,
|
682
|
-
|
747
|
+
charset_number,
|
683
748
|
"", # always 0x00 * 23
|
684
749
|
username,
|
685
750
|
Packet.lcs(scrambled_password),
|
686
|
-
|
687
|
-
|
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")
|
688
772
|
end
|
689
773
|
end
|
690
774
|
|
@@ -712,6 +796,21 @@ class Mysql
|
|
712
796
|
end
|
713
797
|
|
714
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
|
715
814
|
end
|
716
815
|
|
717
816
|
class RawRecord
|
data/lib/mysql.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# coding: ascii-8bit
|
2
|
-
# Copyright (C) 2008
|
2
|
+
# Copyright (C) 2008 TOMITA Masahiro
|
3
3
|
# mailto:tommy@tmtm.org
|
4
4
|
|
5
5
|
# MySQL connection class.
|
@@ -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,8 +112,8 @@ class Mysql
|
|
112
112
|
warn 'unsupported flag: CLIENT_COMPRESS' if $VERBOSE
|
113
113
|
flag &= ~CLIENT_COMPRESS
|
114
114
|
end
|
115
|
-
@protocol = Protocol.new
|
116
|
-
@protocol.authenticate user, passwd, db,
|
115
|
+
@protocol = Protocol.new(host, port, socket, @opts)
|
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"
|
119
119
|
query @init_command if @init_command
|
@@ -144,36 +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
|
-
|
167
|
+
@opts[:connect_timeout] = value
|
168
|
+
when Mysql::OPT_GET_SERVER_PUBLIC_KEY
|
169
|
+
@opts[:get_server_public_key] = value
|
170
|
+
when Mysql::OPT_LOAD_DATA_LOCAL_DIR
|
171
|
+
@opts[:local_infile] = value
|
160
172
|
when Mysql::OPT_LOCAL_INFILE
|
161
|
-
@local_infile = value
|
173
|
+
@opts[:local_infile] = value ? '' : nil
|
174
|
+
# when Mysql::OPT_MAX_ALLOWED_PACKET
|
162
175
|
# when Mysql::OPT_NAMED_PIPE
|
176
|
+
# when Mysql::OPT_NET_BUFFER_LENGTH
|
177
|
+
# when Mysql::OPT_OPTIONAL_RESULTSET_METADATA
|
163
178
|
# when Mysql::OPT_PROTOCOL
|
164
179
|
when Mysql::OPT_READ_TIMEOUT
|
165
|
-
@read_timeout = value
|
180
|
+
@opts[:read_timeout] = value
|
166
181
|
# when Mysql::OPT_RECONNECT
|
182
|
+
# when Mysql::OPT_RETRY_COUNT
|
167
183
|
# when Mysql::SET_CLIENT_IP
|
168
|
-
# when Mysql::
|
169
|
-
# when Mysql::
|
170
|
-
# 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
|
171
197
|
when Mysql::OPT_WRITE_TIMEOUT
|
172
|
-
@write_timeout = value
|
198
|
+
@opts[:write_timeout] = value
|
199
|
+
# when Mysql::OPT_ZSTD_COMPRESSION_LEVEL
|
200
|
+
# when Mysql::PLUGIN_DIR
|
173
201
|
# when Mysql::READ_DEFAULT_FILE
|
174
202
|
# when Mysql::READ_DEFAULT_GROUP
|
175
203
|
# when Mysql::REPORT_DATA_TRUNCATION
|
176
|
-
# when Mysql::
|
204
|
+
# when Mysql::SERVER_PUBLIC_KEY
|
177
205
|
# when Mysql::SET_CHARSET_DIR
|
178
206
|
when Mysql::SET_CHARSET_NAME
|
179
207
|
@charset = Charset.by_name value.to_s
|
@@ -186,14 +214,9 @@ class Mysql
|
|
186
214
|
|
187
215
|
# Escape special character in MySQL.
|
188
216
|
#
|
189
|
-
# In Ruby 1.8, this is not safe for multibyte charset such as 'SJIS'.
|
190
|
-
# You should use place-holder in prepared-statement.
|
191
217
|
# @param [String] str
|
192
218
|
# return [String]
|
193
219
|
def escape_string(str)
|
194
|
-
if not defined? Encoding and @charset.unsafe
|
195
|
-
raise ClientError, 'Mysql#escape_string is called for unsafe multibyte charset'
|
196
|
-
end
|
197
220
|
self.class.escape_string str
|
198
221
|
end
|
199
222
|
alias quote escape_string
|
@@ -439,7 +462,6 @@ class Mysql
|
|
439
462
|
store_result
|
440
463
|
end
|
441
464
|
|
442
|
-
# @note for Ruby 1.8: This is not multi-byte safe. Don't use for multi-byte charset such as cp932.
|
443
465
|
# @param [String] table database name that may contain wild card.
|
444
466
|
# @return [Array<String>] list of table name.
|
445
467
|
def list_tables(table=nil)
|
@@ -731,8 +753,8 @@ class Mysql
|
|
731
753
|
max_length = Array.new(@fields.size, 0)
|
732
754
|
@records.each_with_index do |rec, i|
|
733
755
|
rec = @records[i] = rec.to_a if rec.is_a? RawRecord
|
734
|
-
max_length.each_index do |
|
735
|
-
max_length[
|
756
|
+
max_length.each_index do |j|
|
757
|
+
max_length[j] = rec[j].length if rec[j] && rec[j].length > max_length[j]
|
736
758
|
end
|
737
759
|
end
|
738
760
|
max_length.each_with_index do |len, i|
|
@@ -900,7 +922,7 @@ class Mysql
|
|
900
922
|
row.zip(@bind_result).map do |col, type|
|
901
923
|
if col.nil?
|
902
924
|
nil
|
903
|
-
elsif [Numeric, Integer
|
925
|
+
elsif [Numeric, Integer].include? type
|
904
926
|
col.to_i
|
905
927
|
elsif type == String
|
906
928
|
col.to_s
|
@@ -953,7 +975,7 @@ class Mysql
|
|
953
975
|
raise ClientError, "bind_result: result value count(#{@fields.length}) != number of argument(#{args.length})"
|
954
976
|
end
|
955
977
|
args.each do |a|
|
956
|
-
raise TypeError unless [Numeric,
|
978
|
+
raise TypeError unless [Numeric, Integer, Float, String, Mysql::Time, nil].include? a
|
957
979
|
end
|
958
980
|
@bind_result = args
|
959
981
|
self
|