ruby-mysql 2.9.0 → 2.9.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of ruby-mysql might be problematic. Click here for more details.
- data/ChangeLog +9 -0
- data/README.rdoc +2 -2
- data/lib/mysql.rb +6 -13
- data/lib/mysql/charset.rb +10 -0
- data/lib/mysql/protocol.rb +122 -53
- metadata +2 -2
data/ChangeLog
CHANGED
@@ -1,3 +1,12 @@
|
|
1
|
+
2010-01-16 TOMITA Masahiro <tommy@tmtm.org>
|
2
|
+
|
3
|
+
* lib/mysql.rb (Mysql#escape_string): raise error for unsafe
|
4
|
+
multibyte charset
|
5
|
+
|
6
|
+
2010-01-15 TOMITA Masahiro <tommy@tmtm.org>
|
7
|
+
|
8
|
+
* Fix: When GC destroy Mysql::Stmt object, protocol error occurred
|
9
|
+
|
1
10
|
2010-01-10 TOMITA Masahiro <tommy@tmtm.org>
|
2
11
|
|
3
12
|
* Version 2.9.0-beta
|
data/README.rdoc
CHANGED
@@ -33,11 +33,11 @@ MySQL connector for Ruby.
|
|
33
33
|
|
34
34
|
== Incompatible with MySQL/Ruby 2.8.x
|
35
35
|
|
36
|
-
* Ruby 1.8.x
|
36
|
+
* Ruby 1.8.x ではシフトJISのような安全でないマルチバイト文字セットに対して Mysql#escape_string を使用すると例外が発生します。
|
37
37
|
|
38
38
|
* いくつかのメソッドがありません: Mysql#debug, Mysql#change_user,
|
39
39
|
Mysql#create_db, Mysql#drop_db, Mysql#dump_debug_info,
|
40
|
-
Mysql#ssl_set, Mysql#reconnect
|
40
|
+
Mysql#ssl_set, Mysql#reconnect
|
41
41
|
|
42
42
|
* Mysql#options でサポートしているオプションは次のものだけです:
|
43
43
|
Mysql::INIT_COMMAND, Mysql::OPT_CONNECT_TIMEOUT,
|
data/lib/mysql.rb
CHANGED
@@ -39,7 +39,7 @@ class Mysql
|
|
39
39
|
# Arguments are same as Mysql#connect.
|
40
40
|
def new(*args)
|
41
41
|
my = self.init
|
42
|
-
my.connect
|
42
|
+
my.connect(*args)
|
43
43
|
end
|
44
44
|
|
45
45
|
alias real_connect new
|
@@ -175,15 +175,10 @@ class Mysql
|
|
175
175
|
# In Ruby 1.8, this is not safe for multibyte charset such as 'SJIS'.
|
176
176
|
# You should use place-holder in prepared-statement.
|
177
177
|
def escape_string(str)
|
178
|
-
|
179
|
-
|
180
|
-
when "\0" then "\\0"
|
181
|
-
when "\n" then "\\n"
|
182
|
-
when "\r" then "\\r"
|
183
|
-
when "\x1a" then "\\Z"
|
184
|
-
else "\\#{s}"
|
185
|
-
end
|
178
|
+
if not defined? Encoding and @charset.unsafe
|
179
|
+
raise ClientError, 'Mysql#escape_string is called for unsafe multibyte charset'
|
186
180
|
end
|
181
|
+
self.class.escape_string str
|
187
182
|
end
|
188
183
|
alias quote escape_string
|
189
184
|
|
@@ -805,9 +800,7 @@ class Mysql
|
|
805
800
|
|
806
801
|
def self.finalizer(protocol, statement_id)
|
807
802
|
proc do
|
808
|
-
|
809
|
-
protocol.stmt_close_command statement_id
|
810
|
-
end
|
803
|
+
protocol.gc_stmt statement_id
|
811
804
|
end
|
812
805
|
end
|
813
806
|
|
@@ -843,7 +836,7 @@ class Mysql
|
|
843
836
|
# === Argument
|
844
837
|
# values passed to query
|
845
838
|
# === Return
|
846
|
-
#
|
839
|
+
# self
|
847
840
|
def execute(*values)
|
848
841
|
raise ClientError, "not prepared" unless @param_count
|
849
842
|
raise ClientError, "parameter count mismatch" if values.length != @param_count
|
data/lib/mysql/charset.rb
CHANGED
@@ -7,8 +7,10 @@ class Mysql
|
|
7
7
|
class Charset
|
8
8
|
def initialize(number, name, csname)
|
9
9
|
@number, @name, @csname = number, name, csname
|
10
|
+
@unsafe = false
|
10
11
|
end
|
11
12
|
attr_reader :number, :name, :csname
|
13
|
+
attr_accessor :unsafe
|
12
14
|
|
13
15
|
# [[charset_number, charset_name, collation_name, default], ...]
|
14
16
|
CHARSETS = [
|
@@ -28,6 +30,7 @@ class Mysql
|
|
28
30
|
[ 14, "cp1251", "cp1251_bulgarian_ci", false],
|
29
31
|
[ 15, "latin1", "latin1_danish_ci", false],
|
30
32
|
[ 16, "hebrew", "hebrew_general_ci", true ],
|
33
|
+
[ 17, "filename", "filename", true ],
|
31
34
|
[ 18, "tis620", "tis620_thai_ci", true ],
|
32
35
|
[ 19, "euckr", "euckr_korean_ci", true ],
|
33
36
|
[ 20, "latin7", "latin7_estonian_cs", false],
|
@@ -100,6 +103,7 @@ class Mysql
|
|
100
103
|
[ 96, "cp932", "cp932_bin" , false],
|
101
104
|
[ 97, "eucjpms", "eucjpms_japanese_ci", true ],
|
102
105
|
[ 98, "eucjpms", "eucjpms_bin", false],
|
106
|
+
[ 99, "cp1250", "cp1250_polish_ci", false],
|
103
107
|
[128, "ucs2", "ucs2_unicode_ci", false],
|
104
108
|
[129, "ucs2", "ucs2_icelandic_ci", false],
|
105
109
|
[130, "ucs2", "ucs2_latvian_ci", false],
|
@@ -138,6 +142,11 @@ class Mysql
|
|
138
142
|
[208, "utf8", "utf8_persian_ci", false],
|
139
143
|
[209, "utf8", "utf8_esperanto_ci", false],
|
140
144
|
[210, "utf8", "utf8_hungarian_ci", false],
|
145
|
+
[254, "utf8", "utf8_general_cs", false],
|
146
|
+
]
|
147
|
+
|
148
|
+
UNSAFE_CHARSET = [
|
149
|
+
"big5", "sjis", "filename", "gbk", "ucs2", "cp932",
|
141
150
|
]
|
142
151
|
|
143
152
|
NUMBER_TO_CHARSET = {}
|
@@ -145,6 +154,7 @@ class Mysql
|
|
145
154
|
CHARSET_DEFAULT = {}
|
146
155
|
CHARSETS.each do |number, csname, clname, default|
|
147
156
|
cs = Charset.new number, csname, clname
|
157
|
+
cs.unsafe = true if UNSAFE_CHARSET.include? csname
|
148
158
|
NUMBER_TO_CHARSET[number] = cs
|
149
159
|
COLLATION_TO_CHARSET[clname] = cs
|
150
160
|
CHARSET_DEFAULT[csname] = cs if default
|
data/lib/mysql/protocol.rb
CHANGED
@@ -1,10 +1,9 @@
|
|
1
|
-
# Copyright (C) 2008-
|
1
|
+
# Copyright (C) 2008-2010 TOMITA Masahiro
|
2
2
|
# mailto:tommy@tmtm.org
|
3
3
|
|
4
4
|
require "socket"
|
5
5
|
require "timeout"
|
6
6
|
require "digest/sha1"
|
7
|
-
require "thread"
|
8
7
|
require "stringio"
|
9
8
|
|
10
9
|
class Mysql
|
@@ -185,6 +184,12 @@ class Mysql
|
|
185
184
|
attr_reader :message
|
186
185
|
attr_accessor :charset
|
187
186
|
|
187
|
+
# @state variable keep state for connection.
|
188
|
+
# :INIT :: Initial state.
|
189
|
+
# :READY :: Ready for command.
|
190
|
+
# :FIELD :: After query(). retr_fields() is needed.
|
191
|
+
# :RESULT :: After retr_fields(), retr_all_records() or stmt_retr_all_records() is needed.
|
192
|
+
|
188
193
|
# make socket connection to server.
|
189
194
|
# === Argument
|
190
195
|
# host :: [String] if "localhost" or "" nil then use UNIXSocket. Otherwise use TCPSocket
|
@@ -196,7 +201,8 @@ class Mysql
|
|
196
201
|
# === Exception
|
197
202
|
# [ClientError] :: connection timeout
|
198
203
|
def initialize(host, port, socket, conn_timeout, read_timeout, write_timeout)
|
199
|
-
@
|
204
|
+
@gc_stmt_queue = [] # stmt id list which GC destroy.
|
205
|
+
set_state :INIT
|
200
206
|
@read_timeout = read_timeout
|
201
207
|
@write_timeout = write_timeout
|
202
208
|
begin
|
@@ -226,25 +232,25 @@ class Mysql
|
|
226
232
|
# flag :: [Integer] client flag
|
227
233
|
# charset :: [Mysql::Charset / nil] charset for connection. nil: use server's charset
|
228
234
|
def authenticate(user, passwd, db, flag, charset)
|
235
|
+
check_state :INIT
|
229
236
|
@authinfo = [user, passwd, db, flag, charset]
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
end
|
237
|
+
reset
|
238
|
+
init_packet = InitialPacket.parse read
|
239
|
+
@server_info = init_packet.server_version
|
240
|
+
@server_version = init_packet.server_version.split(/\D/)[0,3].inject{|a,b|a.to_i*100+b.to_i}
|
241
|
+
@thread_id = init_packet.thread_id
|
242
|
+
client_flags = CLIENT_LONG_PASSWORD | CLIENT_LONG_FLAG | CLIENT_TRANSACTIONS | CLIENT_PROTOCOL_41 | CLIENT_SECURE_CONNECTION
|
243
|
+
client_flags |= CLIENT_CONNECT_WITH_DB if db
|
244
|
+
client_flags |= flag
|
245
|
+
@charset = charset
|
246
|
+
unless @charset
|
247
|
+
@charset = Charset.by_number(init_packet.server_charset)
|
248
|
+
@charset.encoding # raise error if unsupported charset
|
249
|
+
end
|
250
|
+
netpw = encrypt_password passwd, init_packet.scramble_buff
|
251
|
+
write AuthenticationPacket.serialize(client_flags, 1024**3, @charset.number, user, netpw, db)
|
252
|
+
read # skip OK packet
|
253
|
+
set_state :READY
|
248
254
|
end
|
249
255
|
|
250
256
|
# Quit command
|
@@ -262,10 +268,14 @@ class Mysql
|
|
262
268
|
# === Return
|
263
269
|
# [Integer / nil] number of fields of results. nil if no results.
|
264
270
|
def query_command(query)
|
265
|
-
|
271
|
+
check_state :READY
|
272
|
+
begin
|
266
273
|
reset
|
267
274
|
write [COM_QUERY, @charset.convert(query)].pack("Ca*")
|
268
275
|
get_result
|
276
|
+
rescue
|
277
|
+
set_state :READY
|
278
|
+
raise
|
269
279
|
end
|
270
280
|
end
|
271
281
|
|
@@ -273,19 +283,26 @@ class Mysql
|
|
273
283
|
# === Return
|
274
284
|
# [integer / nil] number of fields of results. nil if no results.
|
275
285
|
def get_result
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
286
|
+
begin
|
287
|
+
res_packet = ResultPacket.parse read
|
288
|
+
if res_packet.field_count.to_i > 0 # result data exists
|
289
|
+
set_state :FIELD
|
290
|
+
return res_packet.field_count
|
291
|
+
end
|
292
|
+
if res_packet.field_count.nil? # LOAD DATA LOCAL INFILE
|
293
|
+
filename = res_packet.message
|
294
|
+
File.open(filename){|f| write f}
|
295
|
+
write nil # EOF mark
|
296
|
+
read
|
297
|
+
end
|
298
|
+
@affected_rows, @insert_id, @server_status, @warning_count, @message =
|
299
|
+
res_packet.affected_rows, res_packet.insert_id, res_packet.server_status, res_packet.warning_count, res_packet.message
|
300
|
+
set_state :READY
|
301
|
+
return nil
|
302
|
+
rescue
|
303
|
+
set_state :READY
|
304
|
+
raise
|
285
305
|
end
|
286
|
-
@affected_rows, @insert_id, @server_status, @warning_count, @message =
|
287
|
-
res_packet.affected_rows, res_packet.insert_id, res_packet.server_status, res_packet.warning_count, res_packet.message
|
288
|
-
return nil
|
289
306
|
end
|
290
307
|
|
291
308
|
# Retrieve n fields
|
@@ -294,9 +311,16 @@ class Mysql
|
|
294
311
|
# === Return
|
295
312
|
# [Array of Mysql::Field] field list
|
296
313
|
def retr_fields(n)
|
297
|
-
|
298
|
-
|
299
|
-
|
314
|
+
check_state :FIELD
|
315
|
+
begin
|
316
|
+
fields = n.times.map{Field.new FieldPacket.parse(read)}
|
317
|
+
read_eof_packet
|
318
|
+
set_state :RESULT
|
319
|
+
fields
|
320
|
+
rescue
|
321
|
+
set_state :READY
|
322
|
+
raise
|
323
|
+
end
|
300
324
|
end
|
301
325
|
|
302
326
|
# Retrieve all records for simple query
|
@@ -305,16 +329,21 @@ class Mysql
|
|
305
329
|
# === Return
|
306
330
|
# [Array of Array of String] all records
|
307
331
|
def retr_all_records(fields)
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
332
|
+
check_state :RESULT
|
333
|
+
begin
|
334
|
+
all_recs = []
|
335
|
+
until self.class.eof_packet?(data = read)
|
336
|
+
rec = fields.map do
|
337
|
+
s = self.class.lcs2str!(data)
|
338
|
+
s && charset.force_encoding(s)
|
339
|
+
end
|
340
|
+
all_recs.push rec
|
313
341
|
end
|
314
|
-
|
342
|
+
@server_status = data[3].ord
|
343
|
+
all_recs
|
344
|
+
ensure
|
345
|
+
set_state :READY
|
315
346
|
end
|
316
|
-
@server_status = data[3].ord
|
317
|
-
all_recs
|
318
347
|
end
|
319
348
|
|
320
349
|
# Field list command
|
@@ -339,13 +368,18 @@ class Mysql
|
|
339
368
|
# === Return
|
340
369
|
# [Array of Field] field list
|
341
370
|
def process_info_command
|
342
|
-
|
371
|
+
check_state :READY
|
372
|
+
begin
|
343
373
|
reset
|
344
374
|
write [COM_PROCESS_INFO].pack("C")
|
345
375
|
field_count = self.class.lcb2int!(read)
|
346
376
|
fields = field_count.times.map{Field.new FieldPacket.parse(read)}
|
347
377
|
read_eof_packet
|
378
|
+
set_state :RESULT
|
348
379
|
return fields
|
380
|
+
rescue
|
381
|
+
set_state :READY
|
382
|
+
raise
|
349
383
|
end
|
350
384
|
end
|
351
385
|
|
@@ -412,10 +446,14 @@ class Mysql
|
|
412
446
|
# === Return
|
413
447
|
# [Integer] number of fields
|
414
448
|
def stmt_execute_command(stmt_id, values)
|
415
|
-
|
449
|
+
check_state :READY
|
450
|
+
begin
|
416
451
|
reset
|
417
452
|
write ExecutePacket.serialize(stmt_id, Mysql::Stmt::CURSOR_TYPE_NO_CURSOR, values)
|
418
|
-
|
453
|
+
get_result
|
454
|
+
rescue
|
455
|
+
set_state :READY
|
456
|
+
raise
|
419
457
|
end
|
420
458
|
end
|
421
459
|
|
@@ -426,11 +464,16 @@ class Mysql
|
|
426
464
|
# === Return
|
427
465
|
# [Array of Array of Object] all records
|
428
466
|
def stmt_retr_all_records(fields, charset)
|
429
|
-
|
430
|
-
|
431
|
-
all_recs
|
467
|
+
check_state :RESULT
|
468
|
+
begin
|
469
|
+
all_recs = []
|
470
|
+
until self.class.eof_packet?(data = read)
|
471
|
+
all_recs.push stmt_parse_record_packet(data, fields, charset)
|
472
|
+
end
|
473
|
+
all_recs
|
474
|
+
ensure
|
475
|
+
set_state :READY
|
432
476
|
end
|
433
|
-
all_recs
|
434
477
|
end
|
435
478
|
|
436
479
|
# Stmt close command
|
@@ -443,6 +486,10 @@ class Mysql
|
|
443
486
|
end
|
444
487
|
end
|
445
488
|
|
489
|
+
def gc_stmt(stmt_id)
|
490
|
+
@gc_stmt_queue.push stmt_id
|
491
|
+
end
|
492
|
+
|
446
493
|
private
|
447
494
|
|
448
495
|
# Parse statement result packet
|
@@ -473,9 +520,31 @@ class Mysql
|
|
473
520
|
rec
|
474
521
|
end
|
475
522
|
|
523
|
+
def check_state(st)
|
524
|
+
raise 'command out of sync' unless @state == st
|
525
|
+
end
|
526
|
+
|
527
|
+
def set_state(st)
|
528
|
+
@state = st
|
529
|
+
if st == :READY
|
530
|
+
gc_disabled = GC.disable
|
531
|
+
begin
|
532
|
+
while st = @gc_stmt_queue.shift
|
533
|
+
reset
|
534
|
+
write [COM_STMT_CLOSE, st].pack("CV")
|
535
|
+
end
|
536
|
+
ensure
|
537
|
+
GC.enable unless gc_disabled
|
538
|
+
end
|
539
|
+
end
|
540
|
+
end
|
541
|
+
|
476
542
|
def synchronize
|
477
|
-
|
543
|
+
begin
|
544
|
+
check_state :READY
|
478
545
|
return yield
|
546
|
+
ensure
|
547
|
+
set_state :READY
|
479
548
|
end
|
480
549
|
end
|
481
550
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby-mysql
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.9.
|
4
|
+
version: 2.9.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- tommy
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date:
|
12
|
+
date: 2010-01-16 00:00:00 +09:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|