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 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