ruby-mysql 2.9.0 → 2.9.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.
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
|
|