ruby-mysql 2.11.0 → 3.0.1
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/CHANGELOG.md +58 -0
- data/README.md +28 -0
- data/lib/mysql/protocol.rb +86 -149
- data/lib/mysql.rb +182 -359
- data/test/test_mysql.rb +255 -551
- metadata +10 -7
- data/README.rdoc +0 -69
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 37cc4956fc000b612b24fe104acf96abf27e0a7016cbd05c2c5e7b4ae5e0bade
|
4
|
+
data.tar.gz: b2c4ded73325a5c4aadce1d81af507e2a7d4144764150fde183f0e541e25e8b8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c81163849e312cb8cb861a1c5add93fba0973d1fbf01b9296513914931134230dbf7d2d6437e72097c8426d24daab316c2756621d3302d5ee91efb72ab951cc3
|
7
|
+
data.tar.gz: c4e13cea550e4659db986772663b04364c8c18f51abc9442516058cbe5ebc420dd19a95e324e149ae0966edefa1259fff9274677295849834029f827667547c2
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
## [3.0.1] - 2022-06-18
|
2
|
+
|
3
|
+
- LICENSE: correct author
|
4
|
+
- FIX: correct LOAD DATA LOCAL INFILE result information.
|
5
|
+
- FIX: reset SERVER_MORE_RESULTS_EXISTS when error packet is received.
|
6
|
+
- FIX: close the socket when the connection is disconnected.
|
7
|
+
- FIX: allow multiple results by default.
|
8
|
+
|
9
|
+
## [3.0.0] - 2021-11-16
|
10
|
+
|
11
|
+
- `Mysql.new` no longer connect. use `Mysql.connect` or `Mysql#connect`.
|
12
|
+
|
13
|
+
- `Mysql.init` is removed. use `Mysql.new` instead.
|
14
|
+
|
15
|
+
- `Mysql.new`, `Mysql.conncet` and `Mysql#connect` takes URI object or URI string or Hash object.
|
16
|
+
example:
|
17
|
+
Mysql.connect('mysql://user:password@hostname:port/dbname?charset=ascii')
|
18
|
+
Mysql.connect('mysql://user:password@%2Ftmp%2Fmysql.sock/dbname?charset=ascii') # for UNIX socket
|
19
|
+
Mysql.connect('hostname', 'user', 'password', 'dbname')
|
20
|
+
Mysql.connect(host: 'hostname', username: 'user', password: 'password', database: 'dbname')
|
21
|
+
|
22
|
+
- `Mysql.options` is removed. use `Mysql#param = value` instead.
|
23
|
+
For example:
|
24
|
+
m = Mysql.init
|
25
|
+
m.options(Mysql::OPT_LOCAL_INFILE, true)
|
26
|
+
m.connect(host, user, passwd)
|
27
|
+
change to
|
28
|
+
m = Mysql.new
|
29
|
+
m.local_infile = true
|
30
|
+
m.connect(host, user, passwd)
|
31
|
+
or
|
32
|
+
m = Mysql.connect(host, user, passwd, local_infile: true)
|
33
|
+
|
34
|
+
- `Mysql::Time` is removed.
|
35
|
+
Instead, `Time` object is returned for the DATE, DATETIME, TIMESTAMP data,
|
36
|
+
and `Integer` object is returned for the TIME data.
|
37
|
+
If DATE, DATETIME, TIMESTAMP are invalid values for Time, nil is returned.
|
38
|
+
|
39
|
+
- meaningless methods are removed:
|
40
|
+
* `bind_result`
|
41
|
+
* `client_info`
|
42
|
+
* `client_version`
|
43
|
+
* `get_proto_info`
|
44
|
+
* `get_server_info`
|
45
|
+
* `get_server_version`
|
46
|
+
* `proto_info`
|
47
|
+
* `query_with_result`
|
48
|
+
|
49
|
+
- alias method are removed:
|
50
|
+
* `get_host_info`: use `host_info`
|
51
|
+
* `real_connect`: use `connect`
|
52
|
+
* `real_query`: use `query`
|
53
|
+
|
54
|
+
- methods corresponding to deprecated APIs in MySQL are removed:
|
55
|
+
* `list_dbs`: use `SHOW DATABASES`
|
56
|
+
* `list_fields`: use `SHOW COLUMNS`
|
57
|
+
* `list_processes`: use `SHOW PROCESSLIST`
|
58
|
+
* `list_tables`: use `SHOW TABLES`
|
data/README.md
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# ruby-mysql
|
2
|
+
|
3
|
+
## Description
|
4
|
+
|
5
|
+
MySQL connector for Ruby.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
```
|
10
|
+
gem install ruby-mysql
|
11
|
+
```
|
12
|
+
|
13
|
+
## Synopsis
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
my = Mysql.connect('mysql://username:password@hostname:port/dbname?charset=utf8mb4')
|
17
|
+
my.query("select col1, col2 from tblname").each do |col1, col2|
|
18
|
+
p col1, col2
|
19
|
+
end
|
20
|
+
stmt = my.prepare('insert into tblname (col1,col2) values (?,?)')
|
21
|
+
stmt.execute 123, 'abc'
|
22
|
+
```
|
23
|
+
|
24
|
+
## Copyright
|
25
|
+
|
26
|
+
* Author: TOMITA Masahiro <tommy@tmtm.org>
|
27
|
+
* Copyright: Copyright 2008 TOMITA Masahiro
|
28
|
+
* License: MIT
|
data/lib/mysql/protocol.rb
CHANGED
@@ -15,12 +15,10 @@ class Mysql
|
|
15
15
|
MAX_PACKET_LENGTH = 2**24-1
|
16
16
|
|
17
17
|
# Convert netdata to Ruby value
|
18
|
-
#
|
19
|
-
#
|
20
|
-
#
|
21
|
-
#
|
22
|
-
# === Return
|
23
|
-
# Object :: converted value.
|
18
|
+
# @param data [Packet] packet data
|
19
|
+
# @param type [Integer] field type
|
20
|
+
# @param unsigned [true or false] true if value is unsigned
|
21
|
+
# @return [Object] converted value.
|
24
22
|
def self.net2value(pkt, type, unsigned)
|
25
23
|
case type
|
26
24
|
when Field::TYPE_STRING, Field::TYPE_VAR_STRING, Field::TYPE_NEWDECIMAL, Field::TYPE_BLOB, Field::TYPE_JSON
|
@@ -45,17 +43,18 @@ class Mysql
|
|
45
43
|
when Field::TYPE_DATE
|
46
44
|
len = pkt.utiny
|
47
45
|
y, m, d = pkt.read(len).unpack("vCC")
|
48
|
-
t =
|
46
|
+
t = Time.new(y, m, d) rescue nil
|
49
47
|
return t
|
50
48
|
when Field::TYPE_DATETIME, Field::TYPE_TIMESTAMP
|
51
49
|
len = pkt.utiny
|
52
50
|
y, m, d, h, mi, s, sp = pkt.read(len).unpack("vCCCCCV")
|
53
|
-
return
|
51
|
+
return Time.new(y, m, d, h, mi, Rational((s.to_i*1000000+sp.to_i)/1000000)) rescue nil
|
54
52
|
when Field::TYPE_TIME
|
55
53
|
len = pkt.utiny
|
56
54
|
sign, d, h, mi, s, sp = pkt.read(len).unpack("CVCCCV")
|
57
|
-
|
58
|
-
|
55
|
+
r = d.to_i*86400 + h.to_i*3600 + mi.to_i*60 + s.to_i + sp.to_f/1000000
|
56
|
+
r *= -1 if sign != 0
|
57
|
+
return r
|
59
58
|
when Field::TYPE_YEAR
|
60
59
|
return pkt.ushort
|
61
60
|
when Field::TYPE_BIT
|
@@ -66,13 +65,10 @@ class Mysql
|
|
66
65
|
end
|
67
66
|
|
68
67
|
# convert Ruby value to netdata
|
69
|
-
#
|
70
|
-
#
|
71
|
-
#
|
72
|
-
#
|
73
|
-
# String :: netdata
|
74
|
-
# === Exception
|
75
|
-
# ProtocolError :: value too large / value is not supported
|
68
|
+
# @param v [Object] Ruby value.
|
69
|
+
# @return [Integer] type of column. Field::TYPE_*
|
70
|
+
# @return [String] netdata
|
71
|
+
# @raise [ProtocolError] value too large / value is not supported
|
76
72
|
def self.value2net(v)
|
77
73
|
case v
|
78
74
|
when nil
|
@@ -97,12 +93,9 @@ class Mysql
|
|
97
93
|
when String
|
98
94
|
type = Field::TYPE_STRING
|
99
95
|
val = Packet.lcs(v)
|
100
|
-
when
|
96
|
+
when Time
|
101
97
|
type = Field::TYPE_DATETIME
|
102
98
|
val = [11, v.year, v.month, v.day, v.hour, v.min, v.sec, v.usec].pack("CvCCCCCV")
|
103
|
-
when Mysql::Time
|
104
|
-
type = Field::TYPE_DATETIME
|
105
|
-
val = [11, v.year, v.month, v.day, v.hour, v.min, v.sec, v.second_part].pack("CvCCCCCV")
|
106
99
|
else
|
107
100
|
raise ProtocolError, "class #{v.class} is not supported"
|
108
101
|
end
|
@@ -129,30 +122,38 @@ class Mysql
|
|
129
122
|
# :RESULT :: After retr_fields(), retr_all_records() or stmt_retr_all_records() is needed.
|
130
123
|
|
131
124
|
# make socket connection to server.
|
132
|
-
# @param
|
133
|
-
# @
|
134
|
-
# @
|
135
|
-
# @
|
136
|
-
# @option
|
137
|
-
# @option
|
138
|
-
# @option
|
139
|
-
# @option
|
140
|
-
# @option
|
125
|
+
# @param opts [Hash]
|
126
|
+
# @option :host [String] hostname mysqld running
|
127
|
+
# @option :username [String] username to connect to mysqld
|
128
|
+
# @option :password [String] password to connect to mysqld
|
129
|
+
# @option :database [String] initial database name
|
130
|
+
# @option :port [String] port number (used if host is not 'localhost' or nil)
|
131
|
+
# @option :socket [String] socket filename (used if host is 'localhost' or nil)
|
132
|
+
# @option :flags [Integer] connection flag. Mysql::CLIENT_* ORed
|
133
|
+
# @option :charset [Mysql::Charset] character set
|
134
|
+
# @option :connect_timeout [Numeric, nil]
|
135
|
+
# @option :read_timeout [Numeric, nil]
|
136
|
+
# @option :write_timeout [Numeric, nil]
|
137
|
+
# @option :local_infile [Boolean]
|
138
|
+
# @option :load_data_local_dir [String]
|
139
|
+
# @option :ssl_mode [Integer]
|
140
|
+
# @option :get_server_public_key [Boolean]
|
141
141
|
# @raise [ClientError] connection timeout
|
142
|
-
def initialize(
|
142
|
+
def initialize(opts)
|
143
143
|
@opts = opts
|
144
|
+
@charset = Mysql::Charset.by_name("utf8mb4")
|
144
145
|
@insert_id = 0
|
145
146
|
@warning_count = 0
|
146
147
|
@gc_stmt_queue = [] # stmt id list which GC destroy.
|
147
148
|
set_state :INIT
|
148
149
|
@get_server_public_key = @opts[:get_server_public_key]
|
149
150
|
begin
|
150
|
-
if host.nil? or host.empty? or host == "localhost"
|
151
|
-
socket
|
151
|
+
if @opts[:host].nil? or @opts[:host].empty? or @opts[:host] == "localhost"
|
152
|
+
socket = @opts[:socket] || ENV["MYSQL_UNIX_PORT"] || MYSQL_UNIX_PORT
|
152
153
|
@socket = Socket.unix(socket)
|
153
154
|
else
|
154
|
-
port
|
155
|
-
@socket = Socket.tcp(host, port, connect_timeout: @opts[:connect_timeout])
|
155
|
+
port = @opts[:port] || ENV["MYSQL_TCP_PORT"] || (Socket.getservbyname("mysql","tcp") rescue MYSQL_TCP_PORT)
|
156
|
+
@socket = Socket.tcp(@opts[:host], port, connect_timeout: @opts[:connect_timeout])
|
156
157
|
end
|
157
158
|
rescue Errno::ETIMEDOUT
|
158
159
|
raise ClientError, "connection timeout"
|
@@ -164,45 +165,39 @@ class Mysql
|
|
164
165
|
end
|
165
166
|
|
166
167
|
# initial negotiate and authenticate.
|
167
|
-
#
|
168
|
-
#
|
169
|
-
|
170
|
-
# db :: [String / nil] default database name. nil: no default.
|
171
|
-
# flag :: [Integer] client flag
|
172
|
-
# charset :: [Mysql::Charset / nil] charset for connection. nil: use server's charset
|
173
|
-
# === Exception
|
174
|
-
# ProtocolError :: The old style password is not supported
|
175
|
-
def authenticate(user, passwd, db, flag, charset)
|
168
|
+
# @param charset [Mysql::Charset, nil] charset for connection. nil: use server's charset
|
169
|
+
# @raise [ProtocolError] The old style password is not supported
|
170
|
+
def authenticate
|
176
171
|
check_state :INIT
|
177
|
-
@authinfo = [user, passwd, db, flag, charset]
|
178
172
|
reset
|
179
173
|
init_packet = InitialPacket.parse read
|
180
174
|
@server_info = init_packet.server_version
|
181
175
|
@server_version = init_packet.server_version.split(/\D/)[0,3].inject{|a,b|a.to_i*100+b.to_i}
|
182
176
|
@server_capabilities = init_packet.server_capabilities
|
183
177
|
@thread_id = init_packet.thread_id
|
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
|
187
|
-
@client_flags |=
|
188
|
-
@charset
|
189
|
-
|
178
|
+
@client_flags = CLIENT_LONG_PASSWORD | CLIENT_LONG_FLAG | CLIENT_TRANSACTIONS | CLIENT_PROTOCOL_41 | CLIENT_SECURE_CONNECTION | CLIENT_MULTI_RESULTS | CLIENT_PS_MULTI_RESULTS | CLIENT_PLUGIN_AUTH
|
179
|
+
@client_flags |= CLIENT_LOCAL_FILES if @opts[:local_infile] || @opts[:load_data_local_dir]
|
180
|
+
@client_flags |= CLIENT_CONNECT_WITH_DB if @opts[:database]
|
181
|
+
@client_flags |= @opts[:flags]
|
182
|
+
if @opts[:charset]
|
183
|
+
@charset = @opts[:charset].is_a?(Charset) ? @opts[:charset] : Charset.by_name(@opts[:charset])
|
184
|
+
else
|
190
185
|
@charset = Charset.by_number(init_packet.server_charset)
|
191
186
|
@charset.encoding # raise error if unsupported charset
|
192
187
|
end
|
193
188
|
enable_ssl
|
194
|
-
Authenticator.new(self).authenticate(
|
189
|
+
Authenticator.new(self).authenticate(@opts[:username], @opts[:password].to_s, @opts[:database], init_packet.scramble_buff, init_packet.auth_plugin)
|
195
190
|
set_state :READY
|
196
191
|
end
|
197
192
|
|
198
193
|
def enable_ssl
|
199
194
|
case @opts[:ssl_mode]
|
200
|
-
when SSL_MODE_DISABLED
|
195
|
+
when SSL_MODE_DISABLED, '1', 'disabled'
|
201
196
|
return
|
202
|
-
when SSL_MODE_PREFERRED
|
197
|
+
when SSL_MODE_PREFERRED, '2', 'preferred'
|
203
198
|
return if @socket.local_address.unix?
|
204
199
|
return if @server_capabilities & CLIENT_SSL == 0
|
205
|
-
when SSL_MODE_REQUIRED
|
200
|
+
when SSL_MODE_REQUIRED, '3', 'required'
|
206
201
|
if @server_capabilities & CLIENT_SSL == 0
|
207
202
|
raise ClientError::SslConnectionError, "SSL is required but the server doesn't support it"
|
208
203
|
end
|
@@ -232,10 +227,8 @@ class Mysql
|
|
232
227
|
end
|
233
228
|
|
234
229
|
# Query command
|
235
|
-
#
|
236
|
-
#
|
237
|
-
# === Return
|
238
|
-
# [Integer / nil] number of fields of results. nil if no results.
|
230
|
+
# @param query [String] query string
|
231
|
+
# @return [Integer, nil] number of fields of results. nil if no results.
|
239
232
|
def query_command(query)
|
240
233
|
check_state :READY
|
241
234
|
begin
|
@@ -249,8 +242,7 @@ class Mysql
|
|
249
242
|
end
|
250
243
|
|
251
244
|
# get result of query.
|
252
|
-
#
|
253
|
-
# [integer / nil] number of fields of results. nil if no results.
|
245
|
+
# @return [integer, nil] number of fields of results. nil if no results.
|
254
246
|
def get_result
|
255
247
|
begin
|
256
248
|
res_packet = ResultPacket.parse read
|
@@ -260,6 +252,7 @@ class Mysql
|
|
260
252
|
end
|
261
253
|
if res_packet.field_count.nil? # LOAD DATA LOCAL INFILE
|
262
254
|
send_local_file(res_packet.message)
|
255
|
+
res_packet = ResultPacket.parse read
|
263
256
|
end
|
264
257
|
@affected_rows, @insert_id, @server_status, @warning_count, @message =
|
265
258
|
res_packet.affected_rows, res_packet.insert_id, res_packet.server_status, res_packet.warning_count, res_packet.message
|
@@ -274,21 +267,18 @@ class Mysql
|
|
274
267
|
# send local file to server
|
275
268
|
def send_local_file(filename)
|
276
269
|
filename = File.absolute_path(filename)
|
277
|
-
if filename.start_with?
|
270
|
+
if @opts[:local_infile] || @opts[:load_data_local_dir] && filename.start_with?(@opts[:load_data_local_dir])
|
278
271
|
File.open(filename){|f| write f}
|
279
272
|
else
|
280
273
|
raise ClientError::LoadDataLocalInfileRejected, 'LOAD DATA LOCAL INFILE file request rejected due to restrictions on access.'
|
281
274
|
end
|
282
275
|
ensure
|
283
276
|
write nil # EOF mark
|
284
|
-
read
|
285
277
|
end
|
286
278
|
|
287
279
|
# Retrieve n fields
|
288
|
-
#
|
289
|
-
#
|
290
|
-
# === Return
|
291
|
-
# [Array of Mysql::Field] field list
|
280
|
+
# @param n [Integer] number of fields
|
281
|
+
# @return [Array<Mysql::Field>] field list
|
292
282
|
def retr_fields(n)
|
293
283
|
check_state :FIELD
|
294
284
|
begin
|
@@ -303,10 +293,8 @@ class Mysql
|
|
303
293
|
end
|
304
294
|
|
305
295
|
# Retrieve all records for simple query
|
306
|
-
#
|
307
|
-
#
|
308
|
-
# === Return
|
309
|
-
# [Array of Array of String] all records
|
296
|
+
# @param fields [Array<Mysql::Field>] number of fields
|
297
|
+
# @return [Array<Array<String>>] all records
|
310
298
|
def retr_all_records(fields)
|
311
299
|
check_state :RESULT
|
312
300
|
enc = charset.encoding
|
@@ -323,43 +311,6 @@ class Mysql
|
|
323
311
|
end
|
324
312
|
end
|
325
313
|
|
326
|
-
# Field list command
|
327
|
-
# === Argument
|
328
|
-
# table :: [String] table name.
|
329
|
-
# field :: [String / nil] field name that may contain wild card.
|
330
|
-
# === Return
|
331
|
-
# [Array of Field] field list
|
332
|
-
def field_list_command(table, field)
|
333
|
-
synchronize do
|
334
|
-
reset
|
335
|
-
write [COM_FIELD_LIST, table, 0, field].pack("Ca*Ca*")
|
336
|
-
fields = []
|
337
|
-
until (data = read).eof?
|
338
|
-
fields.push Field.new(FieldPacket.parse(data))
|
339
|
-
end
|
340
|
-
return fields
|
341
|
-
end
|
342
|
-
end
|
343
|
-
|
344
|
-
# Process info command
|
345
|
-
# === Return
|
346
|
-
# [Array of Field] field list
|
347
|
-
def process_info_command
|
348
|
-
check_state :READY
|
349
|
-
begin
|
350
|
-
reset
|
351
|
-
write [COM_PROCESS_INFO].pack("C")
|
352
|
-
field_count = read.lcb
|
353
|
-
fields = field_count.times.map{Field.new FieldPacket.parse(read)}
|
354
|
-
read_eof_packet
|
355
|
-
set_state :RESULT
|
356
|
-
return fields
|
357
|
-
rescue
|
358
|
-
set_state :READY
|
359
|
-
raise
|
360
|
-
end
|
361
|
-
end
|
362
|
-
|
363
314
|
# Ping command
|
364
315
|
def ping_command
|
365
316
|
simple_command [COM_PING].pack("C")
|
@@ -391,12 +342,8 @@ class Mysql
|
|
391
342
|
end
|
392
343
|
|
393
344
|
# Stmt prepare command
|
394
|
-
#
|
395
|
-
#
|
396
|
-
# === Return
|
397
|
-
# [Integer] statement id
|
398
|
-
# [Integer] number of parameters
|
399
|
-
# [Array of Field] field list
|
345
|
+
# @param stmt [String] prepared statement
|
346
|
+
# @return [Array<Integer, Integer, Array<Field>>] statement id, number of parameters, field list
|
400
347
|
def stmt_prepare_command(stmt)
|
401
348
|
synchronize do
|
402
349
|
reset
|
@@ -417,11 +364,9 @@ class Mysql
|
|
417
364
|
end
|
418
365
|
|
419
366
|
# Stmt execute command
|
420
|
-
#
|
421
|
-
#
|
422
|
-
#
|
423
|
-
# === Return
|
424
|
-
# [Integer] number of fields
|
367
|
+
# @param stmt_id [Integer] statement id
|
368
|
+
# @param values [Array] parameters
|
369
|
+
# @return [Integer] number of fields
|
425
370
|
def stmt_execute_command(stmt_id, values)
|
426
371
|
check_state :READY
|
427
372
|
begin
|
@@ -435,11 +380,9 @@ class Mysql
|
|
435
380
|
end
|
436
381
|
|
437
382
|
# Retrieve all records for prepared statement
|
438
|
-
#
|
439
|
-
#
|
440
|
-
#
|
441
|
-
# === Return
|
442
|
-
# [Array of Array of Object] all records
|
383
|
+
# @param fields [Array of Mysql::Fields] field list
|
384
|
+
# @param charset [Mysql::Charset]
|
385
|
+
# @return [Array<Array<Object>>] all records
|
443
386
|
def stmt_retr_all_records(fields, charset)
|
444
387
|
check_state :RESULT
|
445
388
|
enc = charset.encoding
|
@@ -455,8 +398,7 @@ class Mysql
|
|
455
398
|
end
|
456
399
|
|
457
400
|
# Stmt close command
|
458
|
-
#
|
459
|
-
# stmt_id :: [Integer] statement id
|
401
|
+
# @param stmt_id [Integer] statement id
|
460
402
|
def stmt_close_command(stmt_id)
|
461
403
|
synchronize do
|
462
404
|
reset
|
@@ -502,10 +444,8 @@ class Mysql
|
|
502
444
|
end
|
503
445
|
|
504
446
|
# Read one packet data
|
505
|
-
#
|
506
|
-
# [
|
507
|
-
# === Exception
|
508
|
-
# [ProtocolError] invalid packet sequence number
|
447
|
+
# @return [Packet] packet data
|
448
|
+
# @rails [ProtocolError] invalid packet sequence number
|
509
449
|
def read
|
510
450
|
data = ''
|
511
451
|
len = nil
|
@@ -520,6 +460,7 @@ class Mysql
|
|
520
460
|
raise EOFError unless ret && ret.length == len
|
521
461
|
data.concat ret
|
522
462
|
rescue EOFError
|
463
|
+
@socket.close rescue nil
|
523
464
|
raise ClientError::ServerGoneError, 'MySQL server has gone away'
|
524
465
|
rescue Errno::ETIMEDOUT
|
525
466
|
raise ClientError, "read timeout"
|
@@ -534,6 +475,7 @@ class Mysql
|
|
534
475
|
_, errno, message = data.unpack("Cva*") # Version 4.0 Error
|
535
476
|
@sqlstate = ""
|
536
477
|
end
|
478
|
+
@server_status &= ~SERVER_MORE_RESULTS_EXISTS
|
537
479
|
message.force_encoding(@charset.encoding)
|
538
480
|
if Mysql::ServerError::ERROR_MAP.key? errno
|
539
481
|
raise Mysql::ServerError::ERROR_MAP[errno].new(message, @sqlstate)
|
@@ -546,9 +488,9 @@ class Mysql
|
|
546
488
|
def read_timeout(len, timeout)
|
547
489
|
return @socket.read(len) if timeout.nil? || timeout == 0
|
548
490
|
result = ''
|
549
|
-
e =
|
491
|
+
e = Time.now + timeout
|
550
492
|
while result.size < len
|
551
|
-
now =
|
493
|
+
now = Time.now
|
552
494
|
raise Errno::ETIMEDOUT if now > e
|
553
495
|
r = @socket.read_nonblock(len - result.size, exception: false)
|
554
496
|
case r
|
@@ -566,8 +508,7 @@ class Mysql
|
|
566
508
|
end
|
567
509
|
|
568
510
|
# Write one packet data
|
569
|
-
#
|
570
|
-
# data :: [String / IO] packet data. If data is nil, write empty packet.
|
511
|
+
# @param data [String, IO, nil] packet data. If data is nil, write empty packet.
|
571
512
|
def write(data)
|
572
513
|
begin
|
573
514
|
@socket.sync = false
|
@@ -584,6 +525,7 @@ class Mysql
|
|
584
525
|
@socket.sync = true
|
585
526
|
@socket.flush
|
586
527
|
rescue Errno::EPIPE
|
528
|
+
@socket.close rescue nil
|
587
529
|
raise ClientError::ServerGoneError, 'MySQL server has gone away'
|
588
530
|
rescue Errno::ETIMEDOUT
|
589
531
|
raise ClientError, "write timeout"
|
@@ -593,9 +535,9 @@ class Mysql
|
|
593
535
|
def write_timeout(data, timeout)
|
594
536
|
return @socket.write(data) if timeout.nil? || timeout == 0
|
595
537
|
len = 0
|
596
|
-
e =
|
538
|
+
e = Time.now + timeout
|
597
539
|
while len < data.size
|
598
|
-
now =
|
540
|
+
now = Time.now
|
599
541
|
raise Errno::ETIMEDOUT if now > e
|
600
542
|
l = @socket.write_nonblock(data[len..-1], exception: false)
|
601
543
|
case l
|
@@ -611,17 +553,14 @@ class Mysql
|
|
611
553
|
end
|
612
554
|
|
613
555
|
# Read EOF packet
|
614
|
-
#
|
615
|
-
# [ProtocolError] packet is not EOF
|
556
|
+
# @raise [ProtocolError] packet is not EOF
|
616
557
|
def read_eof_packet
|
617
558
|
raise ProtocolError, "packet is not EOF" unless read.eof?
|
618
559
|
end
|
619
560
|
|
620
561
|
# Send simple command
|
621
|
-
#
|
622
|
-
#
|
623
|
-
# === Return
|
624
|
-
# [String] received data
|
562
|
+
# @param packet :: [String] packet data
|
563
|
+
# @return [String] received data
|
625
564
|
def simple_command(packet)
|
626
565
|
synchronize do
|
627
566
|
reset
|
@@ -831,17 +770,15 @@ class Mysql
|
|
831
770
|
end
|
832
771
|
|
833
772
|
class StmtRawRecord
|
834
|
-
#
|
835
|
-
#
|
836
|
-
#
|
837
|
-
# encoding:: [Encoding]
|
773
|
+
# @param pkt [Packet]
|
774
|
+
# @param fields [Array of Fields]
|
775
|
+
# @param encoding [Encoding]
|
838
776
|
def initialize(packet, fields, encoding)
|
839
777
|
@packet, @fields, @encoding = packet, fields, encoding
|
840
778
|
end
|
841
779
|
|
842
780
|
# Parse statement result packet
|
843
|
-
#
|
844
|
-
# [Array of Object] one record
|
781
|
+
# @return [Array<Object>] one record
|
845
782
|
def parse_record_packet
|
846
783
|
@packet.utiny # skip first byte
|
847
784
|
null_bit_map = @packet.read((@fields.length+7+2)/8).unpack("b*").first
|
@@ -851,7 +788,7 @@ class Mysql
|
|
851
788
|
else
|
852
789
|
unsigned = f.flags & Field::UNSIGNED_FLAG != 0
|
853
790
|
v = Protocol.net2value(@packet, f.type, unsigned)
|
854
|
-
if v.is_a? Numeric or v.is_a?
|
791
|
+
if v.nil? or v.is_a? Numeric or v.is_a? Time
|
855
792
|
v
|
856
793
|
elsif f.type == Field::TYPE_BIT or f.charsetnr == Charset::BINARY_CHARSET_NUMBER
|
857
794
|
Charset.to_binary(v)
|