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 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 では Mysql#escape_string は、マルチバイト文字の一部として特殊記号を含むシフトJISのようなマルチバイト文字セットに対して安全ではありません。
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, Mysql::Stmt#attr_set
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 *args
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
- str.gsub(/[\0\n\r\\\'\"\x1a]/) do |s|
179
- case s
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
- Thread.new do
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
- # Mysql::Result
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
@@ -1,10 +1,9 @@
1
- # Copyright (C) 2008-2009 TOMITA Masahiro
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
- @mutex = Mutex.new
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
- synchronize do
231
- reset
232
- init_packet = InitialPacket.parse read
233
- @server_info = init_packet.server_version
234
- @server_version = init_packet.server_version.split(/\D/)[0,3].inject{|a,b|a.to_i*100+b.to_i}
235
- @thread_id = init_packet.thread_id
236
- client_flags = CLIENT_LONG_PASSWORD | CLIENT_LONG_FLAG | CLIENT_TRANSACTIONS | CLIENT_PROTOCOL_41 | CLIENT_SECURE_CONNECTION
237
- client_flags |= CLIENT_CONNECT_WITH_DB if db
238
- client_flags |= flag
239
- @charset = charset
240
- unless @charset
241
- @charset = Charset.by_number(init_packet.server_charset)
242
- @charset.encoding # raise error if unsupported charset
243
- end
244
- netpw = encrypt_password passwd, init_packet.scramble_buff
245
- write AuthenticationPacket.serialize(client_flags, 1024**3, @charset.number, user, netpw, db)
246
- read # skip OK packet
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
- synchronize do
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
- res_packet = ResultPacket.parse read
277
- if res_packet.field_count.to_i > 0 # result data exists
278
- return res_packet.field_count
279
- end
280
- if res_packet.field_count.nil? # LOAD DATA LOCAL INFILE
281
- filename = res_packet.message
282
- File.open(filename){|f| write f}
283
- write nil # EOF mark
284
- read
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
- fields = n.times.map{Field.new FieldPacket.parse(read)}
298
- read_eof_packet
299
- fields
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
- all_recs = []
309
- until self.class.eof_packet?(data = read)
310
- rec = fields.map do
311
- s = self.class.lcs2str!(data)
312
- s && charset.force_encoding(s)
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
- all_recs.push rec
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
- synchronize do
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
- synchronize do
449
+ check_state :READY
450
+ begin
416
451
  reset
417
452
  write ExecutePacket.serialize(stmt_id, Mysql::Stmt::CURSOR_TYPE_NO_CURSOR, values)
418
- return get_result
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
- all_recs = []
430
- until self.class.eof_packet?(data = read)
431
- all_recs.push stmt_parse_record_packet(data, fields, charset)
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
- @mutex.synchronize do
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.0
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: 2009-07-29 00:00:00 +09:00
12
+ date: 2010-01-16 00:00:00 +09:00
13
13
  default_executable:
14
14
  dependencies: []
15
15