ruby-mysql 2.11.0 → 3.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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)
|