ruby-mysql 2.9.6 → 2.9.7

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/lib/mysql.rb CHANGED
@@ -10,13 +10,17 @@
10
10
  # end
11
11
  class Mysql
12
12
 
13
- dir = File.dirname __FILE__
14
- require "#{dir}/mysql/constants"
15
- require "#{dir}/mysql/error"
16
- require "#{dir}/mysql/charset"
17
- require "#{dir}/mysql/protocol"
13
+ require "mysql/constants"
14
+ require "mysql/error"
15
+ require "mysql/charset"
16
+ require "mysql/protocol"
17
+ begin
18
+ require "mysql/packet.so"
19
+ rescue LoadError
20
+ require "mysql/packet.rb"
21
+ end
18
22
 
19
- VERSION = 20906 # Version number of this library
23
+ VERSION = 20907 # Version number of this library
20
24
  MYSQL_UNIX_PORT = "/tmp/mysql.sock" # UNIX domain socket filename
21
25
  MYSQL_TCP_PORT = 3306 # TCP socket port number
22
26
 
@@ -577,7 +581,7 @@ class Mysql
577
581
  attr_reader :decimals # number of decimals
578
582
  attr_reader :default # defualt value
579
583
  alias :def :default
580
- attr_accessor :max_length # maximum width of the field for the result set
584
+ attr_accessor :result # :nodoc:
581
585
 
582
586
  # === Argument
583
587
  # [Protocol::FieldPacket]
@@ -585,6 +589,7 @@ class Mysql
585
589
  @db, @table, @org_table, @name, @org_name, @charsetnr, @length, @type, @flags, @decimals, @default =
586
590
  packet.db, packet.table, packet.org_table, packet.name, packet.org_name, packet.charsetnr, packet.length, packet.type, packet.flags, packet.decimals, packet.default
587
591
  @flags |= NUM_FLAG if is_num_type?
592
+ @max_length = nil
588
593
  end
589
594
 
590
595
  def hash
@@ -594,7 +599,7 @@ class Mysql
594
599
  "def" => @default,
595
600
  "type" => @type,
596
601
  "length" => @length,
597
- "max_length" => @max_length,
602
+ "max_length" => max_length,
598
603
  "flags" => @flags,
599
604
  "decimals" => @decimals
600
605
  }
@@ -619,6 +624,16 @@ class Mysql
619
624
  @flags & PRI_KEY_FLAG != 0
620
625
  end
621
626
 
627
+ # maximum width of the field for the result set
628
+ def max_length
629
+ return @max_length if @max_length
630
+ @max_length = 0
631
+ @result.calculate_field_max_length if @result
632
+ @max_length
633
+ end
634
+
635
+ attr_writer :max_length
636
+
622
637
  private
623
638
 
624
639
  def is_num_type?
@@ -661,10 +676,10 @@ class Mysql
661
676
  def fetch
662
677
  @fetched_record = nil
663
678
  return nil if @index >= @records.size
664
- rec = @records[@index]
679
+ @records[@index] = @records[@index].to_a unless @records[@index].is_a? Array
680
+ @fetched_record = @records[@index]
665
681
  @index += 1
666
- @fetched_record = rec
667
- return rec
682
+ return @fetched_record
668
683
  end
669
684
  alias fetch_row fetch
670
685
 
@@ -750,13 +765,22 @@ class Mysql
750
765
  def initialize(fields, protocol=nil)
751
766
  super fields
752
767
  return unless protocol
753
- @records = protocol.retr_all_records @fields
754
- # for Field#max_length
755
- @records.each do |rec|
756
- rec.zip(fields) do |v, f|
757
- f.max_length = [v ? v.length : 0, f.max_length || 0].max
768
+ @records = protocol.retr_all_records fields.size
769
+ fields.each{|f| f.result = self} # for calculating max_field
770
+ end
771
+
772
+ # calculate max_length of all fields
773
+ def calculate_field_max_length
774
+ max_length = Array.new(@fields.size, 0)
775
+ @records.each_with_index do |rec, i|
776
+ rec = @records[i] = rec.to_a if rec.is_a? RawRecord
777
+ max_length.each_index do |i|
778
+ max_length[i] = rec[i].length if rec[i] && rec[i].length > max_length[i]
758
779
  end
759
780
  end
781
+ max_length.each_with_index do |len, i|
782
+ @fields[i].max_length = len
783
+ end
760
784
  end
761
785
 
762
786
  # Return current field
data/lib/mysql/charset.rb CHANGED
@@ -1,8 +1,6 @@
1
1
  # Copyright (C) 2008-2010 TOMITA Masahiro
2
2
  # mailto:tommy@tmtm.org
3
3
 
4
- require "#{File.dirname __FILE__}/error"
5
-
6
4
  class Mysql
7
5
  class Charset
8
6
  def initialize(number, name, csname)
@@ -238,7 +236,7 @@ class Mysql
238
236
  }
239
237
 
240
238
  def self.to_binary(value)
241
- value.dup.force_encoding Encoding::ASCII_8BIT
239
+ value.force_encoding Encoding::ASCII_8BIT
242
240
  end
243
241
 
244
242
  # convert raw to encoding and convert to Encoding.default_internal
@@ -248,7 +246,7 @@ class Mysql
248
246
  # === Return
249
247
  # result [String]
250
248
  def self.convert_encoding(raw, encoding)
251
- raw.dup.force_encoding(encoding).encode
249
+ raw.force_encoding(encoding).encode
252
250
  end
253
251
 
254
252
  # retrun corresponding Ruby encoding
@@ -0,0 +1,77 @@
1
+ class Mysql
2
+ class Packet
3
+ # convert Numeric to LengthCodedBinary
4
+ def self.lcb(num)
5
+ return "\xfb" if num.nil?
6
+ return [num].pack("C") if num < 251
7
+ return [252, num].pack("Cv") if num < 65536
8
+ return [253, num&0xffff, num>>16].pack("CvC") if num < 16777216
9
+ return [254, num&0xffffffff, num>>32].pack("CVV")
10
+ end
11
+
12
+ # convert String to LengthCodedString
13
+ def self.lcs(str)
14
+ str = Charset.to_binary str
15
+ lcb(str.length)+str
16
+ end
17
+
18
+ def initialize(data)
19
+ @data = data
20
+ end
21
+
22
+ def lcb
23
+ return nil if @data.empty?
24
+ case v = utiny
25
+ when 0xfb
26
+ return nil
27
+ when 0xfc
28
+ return ushort
29
+ when 0xfd
30
+ c, v = utiny, ushort
31
+ return (v << 8)+c
32
+ when 0xfe
33
+ v1, v2 = ulong, ulong
34
+ return (v2 << 32)+v1
35
+ else
36
+ return v
37
+ end
38
+ end
39
+
40
+ def lcs
41
+ len = self.lcb
42
+ return nil unless len
43
+ @data.slice!(0, len)
44
+ end
45
+
46
+ def read(len)
47
+ @data.slice!(0, len)
48
+ end
49
+
50
+ def string
51
+ str = @data.unpack('Z*').first
52
+ @data.slice!(0, str.length+1)
53
+ str
54
+ end
55
+
56
+ def utiny
57
+ @data.slice!(0, 1).unpack('C').first
58
+ end
59
+
60
+ def ushort
61
+ @data.slice!(0, 2).unpack('v').first
62
+ end
63
+
64
+ def ulong
65
+ @data.slice!(0, 4).unpack('V').first
66
+ end
67
+
68
+ def eof?
69
+ @data[0] == ?\xfe && @data.length == 5
70
+ end
71
+
72
+ def to_s
73
+ @data
74
+ end
75
+
76
+ end
77
+ end
@@ -13,107 +13,55 @@ class Mysql
13
13
  VERSION = 10
14
14
  MAX_PACKET_LENGTH = 2**24-1
15
15
 
16
- # convert Numeric to LengthCodedBinary
17
- def self.lcb(num)
18
- return "\xfb" if num.nil?
19
- return [num].pack("C") if num < 251
20
- return [252, num].pack("Cv") if num < 65536
21
- return [253, num&0xffff, num>>16].pack("CvC") if num < 16777216
22
- return [254, num&0xffffffff, num>>32].pack("CVV")
23
- end
24
-
25
- # convert String to LengthCodedString
26
- def self.lcs(str)
27
- str = Charset.to_binary str
28
- lcb(str.length)+str
29
- end
30
-
31
- # convert LengthCodedBinary to Integer
32
- # === Argument
33
- # lcb :: [String] LengthCodedBinary. This value will be broken.
34
- # === Return
35
- # Integer or nil
36
- def self.lcb2int!(lcb)
37
- return nil if lcb.empty?
38
- case v = lcb.slice!(0)
39
- when ?\xfb
40
- return nil
41
- when ?\xfc
42
- return lcb.slice!(0,2).unpack("v").first
43
- when ?\xfd
44
- c, v = lcb.slice!(0,3).unpack("Cv")
45
- return (v << 8)+c
46
- when ?\xfe
47
- v1, v2 = lcb.slice!(0,8).unpack("VV")
48
- return (v2 << 32)+v1
49
- else
50
- return v.ord
51
- end
52
- end
53
-
54
- # convert LengthCodedString to String
55
- # === Argument
56
- # lcs :: [String] LengthCodedString. This value will be broken.
57
- # === Return
58
- # String or nil
59
- def self.lcs2str!(lcs)
60
- len = lcb2int! lcs
61
- return len && lcs.slice!(0, len)
62
- end
63
-
64
- def self.eof_packet?(data)
65
- data[0] == ?\xfe && data.length == 5
66
- end
67
-
68
16
  # Convert netdata to Ruby value
69
17
  # === Argument
70
- # data :: [String] packet data. This will be broken.
18
+ # data :: [Packet] packet data
71
19
  # type :: [Integer] field type
72
20
  # unsigned :: [true or false] true if value is unsigned
73
21
  # === Return
74
22
  # Object :: converted value.
75
- def self.net2value(data, type, unsigned)
23
+ def self.net2value(pkt, type, unsigned)
76
24
  case type
77
25
  when Field::TYPE_STRING, Field::TYPE_VAR_STRING, Field::TYPE_NEWDECIMAL, Field::TYPE_BLOB
78
- return lcs2str!(data)
26
+ return pkt.lcs
79
27
  when Field::TYPE_TINY
80
- v = data.slice!(0).ord
28
+ v = pkt.utiny
81
29
  return unsigned ? v : v < 128 ? v : v-256
82
30
  when Field::TYPE_SHORT
83
- v = data.slice!(0,2).unpack("v").first
31
+ v = pkt.ushort
84
32
  return unsigned ? v : v < 32768 ? v : v-65536
85
33
  when Field::TYPE_INT24, Field::TYPE_LONG
86
- v = data.slice!(0,4).unpack("V").first
34
+ v = pkt.ulong
87
35
  return unsigned ? v : v < 2**32/2 ? v : v-2**32
88
36
  when Field::TYPE_LONGLONG
89
- n1, n2 = data.slice!(0,8).unpack("VV")
37
+ n1, n2 = pkt.ulong, pkt.ulong
90
38
  v = (n2 << 32) | n1
91
39
  return unsigned ? v : v < 2**64/2 ? v : v-2**64
92
40
  when Field::TYPE_FLOAT
93
- return data.slice!(0,4).unpack("e").first
41
+ return pkt.read(4).unpack('e').first
94
42
  when Field::TYPE_DOUBLE
95
- return data.slice!(0,8).unpack("E").first
43
+ return pkt.read(8).unpack('E').first
96
44
  when Field::TYPE_DATE
97
- len = data.slice!(0).ord
98
- y, m, d = data.slice!(0,len).unpack("vCC")
45
+ len = pkt.utiny
46
+ y, m, d = pkt.read(len).unpack("vCC")
99
47
  t = Mysql::Time.new(y, m, d)
100
48
  def t.to_s
101
49
  sprintf "%04d-%02d-%02d", year, mon ,day
102
50
  end
103
51
  return t
104
52
  when Field::TYPE_DATETIME, Field::TYPE_TIMESTAMP
105
- len = data.slice!(0).ord
106
- y, m, d, h, mi, s, bs = data.slice!(0,len).unpack("vCCCCCV")
53
+ len = pkt.utiny
54
+ y, m, d, h, mi, s, bs = pkt.read(len).unpack("vCCCCCV")
107
55
  return Mysql::Time.new(y, m, d, h, mi, s, bs)
108
56
  when Field::TYPE_TIME
109
- len = data.slice!(0).ord
110
- sign, d, h, mi, s, sp = data.slice!(0,len).unpack("CVCCCV")
57
+ len = pkt.utiny
58
+ sign, d, h, mi, s, sp = pkt.read(len).unpack("CVCCCV")
111
59
  h = d.to_i * 24 + h.to_i
112
60
  return Mysql::Time.new(0, 0, 0, h, mi, s, sign!=0, sp)
113
61
  when Field::TYPE_YEAR
114
- return data.slice!(0,2).unpack("v").first
62
+ return pkt.ushort
115
63
  when Field::TYPE_BIT
116
- return lcs2str!(data)
64
+ return pkt.lcs
117
65
  else
118
66
  raise "not implemented: type=#{type}"
119
67
  end
@@ -171,7 +119,7 @@ class Mysql
171
119
  val = [v].pack("E")
172
120
  when String
173
121
  type = Field::TYPE_STRING
174
- val = lcs(v)
122
+ val = Packet.lcs(v)
175
123
  when Mysql::Time, ::Time
176
124
  type = Field::TYPE_DATETIME
177
125
  val = [7, v.year, v.month, v.day, v.hour, v.min, v.sec].pack("CvCCCCC")
@@ -261,7 +209,7 @@ class Mysql
261
209
  end
262
210
  netpw = encrypt_password passwd, init_packet.scramble_buff
263
211
  write AuthenticationPacket.serialize(client_flags, 1024**3, @charset.number, user, netpw, db)
264
- raise ProtocolError, 'The old style password is not supported' if read == "\xfe"
212
+ raise ProtocolError, 'The old style password is not supported' if read.to_s == "\xfe"
265
213
  set_state :READY
266
214
  end
267
215
 
@@ -337,21 +285,19 @@ class Mysql
337
285
 
338
286
  # Retrieve all records for simple query
339
287
  # === Argument
340
- # fields :: [Array of Mysql::Field] field list
288
+ # nfields :: [Integer] number of fields
341
289
  # === Return
342
290
  # [Array of Array of String] all records
343
- def retr_all_records(fields)
291
+ def retr_all_records(nfields)
344
292
  check_state :RESULT
293
+ enc = charset.encoding
345
294
  begin
346
295
  all_recs = []
347
- until self.class.eof_packet?(data = read)
348
- rec = fields.map do
349
- s = self.class.lcs2str!(data)
350
- s && Charset.convert_encoding(s, charset.encoding)
351
- end
352
- all_recs.push rec
296
+ until (pkt = read).eof?
297
+ all_recs.push RawRecord.new(pkt, nfields, enc)
353
298
  end
354
- @server_status = data[3].ord
299
+ pkt.read(3)
300
+ @server_status = pkt.utiny
355
301
  all_recs
356
302
  ensure
357
303
  set_state :READY
@@ -369,7 +315,7 @@ class Mysql
369
315
  reset
370
316
  write [COM_FIELD_LIST, table, 0, field].pack("Ca*Ca*")
371
317
  fields = []
372
- until self.class.eof_packet?(data = read)
318
+ until (data = read).eof?
373
319
  fields.push Field.new(FieldPacket.parse(data))
374
320
  end
375
321
  return fields
@@ -384,7 +330,7 @@ class Mysql
384
330
  begin
385
331
  reset
386
332
  write [COM_PROCESS_INFO].pack("C")
387
- field_count = self.class.lcb2int!(read)
333
+ field_count = read.lcb
388
334
  fields = field_count.times.map{Field.new FieldPacket.parse(read)}
389
335
  read_eof_packet
390
336
  set_state :RESULT
@@ -479,8 +425,8 @@ class Mysql
479
425
  check_state :RESULT
480
426
  begin
481
427
  all_recs = []
482
- until self.class.eof_packet?(data = read)
483
- all_recs.push stmt_parse_record_packet(data, fields, charset)
428
+ until (data = read).eof?
429
+ all_recs.push StmtRawRecord.new(data, fields, charset.encoding)
484
430
  end
485
431
  all_recs
486
432
  ensure
@@ -504,34 +450,6 @@ class Mysql
504
450
 
505
451
  private
506
452
 
507
- # Parse statement result packet
508
- # === Argument
509
- # data :: [String]
510
- # fields :: [Array of Fields]
511
- # charset :: [Mysql::Charset]
512
- # === Return
513
- # [Array of Object] one record
514
- def stmt_parse_record_packet(data, fields, charset)
515
- data.slice!(0) # skip first byte
516
- null_bit_map = data.slice!(0, (fields.length+7+2)/8).unpack("b*").first
517
- rec = fields.each_with_index.map do |f, i|
518
- if null_bit_map[i+2] == ?1
519
- nil
520
- else
521
- unsigned = f.flags & Field::UNSIGNED_FLAG != 0
522
- v = self.class.net2value(data, f.type, unsigned)
523
- if v.is_a? Numeric or v.is_a? Mysql::Time
524
- v
525
- elsif f.type == Field::TYPE_BIT or f.charsetnr == Charset::BINARY_CHARSET_NUMBER
526
- Charset.to_binary(v)
527
- else
528
- Charset.convert_encoding(v, charset.encoding)
529
- end
530
- end
531
- end
532
- rec
533
- end
534
-
535
453
  def check_state(st)
536
454
  raise 'command out of sync' unless @state == st
537
455
  end
@@ -567,7 +485,7 @@ class Mysql
567
485
 
568
486
  # Read one packet data
569
487
  # === Return
570
- # [String] packet data
488
+ # [Packet] packet data
571
489
  # === Exception
572
490
  # [ProtocolError] invalid packet sequence number
573
491
  def read
@@ -575,17 +493,14 @@ class Mysql
575
493
  len = nil
576
494
  begin
577
495
  Timeout.timeout @read_timeout do
578
- header = @sock.sysread(4)
496
+ header = @sock.read(4)
497
+ raise EOFError unless header.length == 4
579
498
  len1, len2, seq = header.unpack("CvC")
580
499
  len = (len2 << 8) + len1
581
500
  raise ProtocolError, "invalid packet: sequence number mismatch(#{seq} != #{@seq}(expected))" if @seq != seq
582
501
  @seq = (@seq + 1) % 256
583
- l = len
584
- while l > 0
585
- buf = @sock.sysread l
586
- ret.concat buf
587
- l -= buf.bytesize
588
- end
502
+ ret = @sock.read(len)
503
+ raise EOFError unless ret.length == len
589
504
  end
590
505
  rescue EOFError
591
506
  raise ClientError::ServerGoneError, 'The MySQL server has gone away'
@@ -607,7 +522,7 @@ class Mysql
607
522
  end
608
523
  raise Mysql::ServerError.new(message, @sqlstate)
609
524
  end
610
- ret
525
+ Packet.new(ret)
611
526
  end
612
527
 
613
528
  # Write one packet data
@@ -646,8 +561,7 @@ class Mysql
646
561
  # === Exception
647
562
  # [ProtocolError] packet is not EOF
648
563
  def read_eof_packet
649
- data = read
650
- raise ProtocolError, "packet is not EOF" unless self.class.eof_packet? data
564
+ raise ProtocolError, "packet is not EOF" unless read.eof?
651
565
  end
652
566
 
653
567
  # Send simple command
@@ -659,7 +573,7 @@ class Mysql
659
573
  synchronize do
660
574
  reset
661
575
  write packet
662
- read
576
+ read.to_s
663
577
  end
664
578
  end
665
579
 
@@ -678,10 +592,17 @@ class Mysql
678
592
 
679
593
  # Initial packet
680
594
  class InitialPacket
681
- def self.parse(data)
682
- protocol_version, server_version, thread_id, scramble_buff, f0,
683
- server_capabilities, server_charset, server_status, f1,
684
- rest_scramble_buff = data.unpack("CZ*Va8CvCva13Z13")
595
+ def self.parse(pkt)
596
+ protocol_version = pkt.utiny
597
+ server_version = pkt.string
598
+ thread_id = pkt.ulong
599
+ scramble_buff = pkt.read(8)
600
+ f0 = pkt.utiny
601
+ server_capabilities = pkt.ushort
602
+ server_charset = pkt.utiny
603
+ server_status = pkt.ushort
604
+ f1 = pkt.read(13)
605
+ rest_scramble_buff = pkt.string
685
606
  raise ProtocolError, "unsupported version: #{protocol_version}" unless protocol_version == VERSION
686
607
  raise ProtocolError, "invalid packet: f0=#{f0}" unless f0 == 0
687
608
  scramble_buff.concat rest_scramble_buff
@@ -697,15 +618,17 @@ class Mysql
697
618
 
698
619
  # Result packet
699
620
  class ResultPacket
700
- def self.parse(data)
701
- field_count = Protocol.lcb2int! data
621
+ def self.parse(pkt)
622
+ field_count = pkt.lcb
702
623
  if field_count == 0
703
- affected_rows = Protocol.lcb2int! data
704
- insert_id = Protocol.lcb2int!(data)
705
- server_status, warning_count, message = data.unpack("vva*")
706
- return self.new(field_count, affected_rows, insert_id, server_status, warning_count, Protocol.lcs2str!(message))
624
+ affected_rows = pkt.lcb
625
+ insert_id = pkt.lcb
626
+ server_status = pkt.ushort
627
+ warning_count = pkt.ushort
628
+ message = pkt.lcs
629
+ return self.new(field_count, affected_rows, insert_id, server_status, warning_count, message)
707
630
  elsif field_count.nil? # LOAD DATA LOCAL INFILE
708
- return self.new(nil, nil, nil, nil, nil, data)
631
+ return self.new(nil, nil, nil, nil, nil, pkt.to_s)
709
632
  else
710
633
  return self.new(field_count)
711
634
  end
@@ -720,16 +643,23 @@ class Mysql
720
643
 
721
644
  # Field packet
722
645
  class FieldPacket
723
- def self.parse(data)
724
- first = Protocol.lcs2str! data
725
- db = Protocol.lcs2str! data
726
- table = Protocol.lcs2str! data
727
- org_table = Protocol.lcs2str! data
728
- name = Protocol.lcs2str! data
729
- org_name = Protocol.lcs2str! data
730
- f0, charsetnr, length, type, flags, decimals, f1, data = data.unpack("CvVCvCva*")
646
+ def self.parse(pkt)
647
+ first = pkt.lcs
648
+ db = pkt.lcs
649
+ table = pkt.lcs
650
+ org_table = pkt.lcs
651
+ name = pkt.lcs
652
+ org_name = pkt.lcs
653
+ f0 = pkt.utiny
654
+ charsetnr = pkt.ushort
655
+ length = pkt.ulong
656
+ type = pkt.utiny
657
+ flags = pkt.ushort
658
+ decimals = pkt.utiny
659
+ f1 = pkt.ushort
660
+
731
661
  raise ProtocolError, "invalid packet: f1=#{f1}" unless f1 == 0
732
- default = Protocol.lcs2str! data
662
+ default = pkt.lcs
733
663
  return self.new(db, table, org_table, name, org_name, charsetnr, length, type, flags, decimals, default)
734
664
  end
735
665
 
@@ -742,9 +672,13 @@ class Mysql
742
672
 
743
673
  # Prepare result packet
744
674
  class PrepareResultPacket
745
- def self.parse(data)
746
- raise ProtocolError, "invalid packet" unless data.slice!(0) == ?\0
747
- statement_id, field_count, param_count, f, warning_count = data.unpack("VvvCv")
675
+ def self.parse(pkt)
676
+ raise ProtocolError, "invalid packet" unless pkt.utiny == 0
677
+ statement_id = pkt.ulong
678
+ field_count = pkt.ushort
679
+ param_count = pkt.ushort
680
+ f = pkt.utiny
681
+ warning_count = pkt.ushort
748
682
  raise ProtocolError, "invalid packet" unless f == 0x00
749
683
  self.new statement_id, field_count, param_count, warning_count
750
684
  end
@@ -762,10 +696,10 @@ class Mysql
762
696
  [
763
697
  client_flags,
764
698
  max_packet_size,
765
- Protocol.lcb(charset_number),
699
+ Packet.lcb(charset_number),
766
700
  "", # always 0x00 * 23
767
701
  username,
768
- Protocol.lcs(scrambled_password),
702
+ Packet.lcs(scrambled_password),
769
703
  databasename
770
704
  ].pack("VVa*a23Z*A*Z*")
771
705
  end
@@ -796,4 +730,56 @@ class Mysql
796
730
 
797
731
  end
798
732
  end
733
+
734
+ class RawRecord
735
+ def initialize(packet, nfields, encoding)
736
+ @packet, @nfields, @encoding = packet, nfields, encoding
737
+ end
738
+
739
+ def to_a
740
+ @nfields.times.map do
741
+ if s = @packet.lcs
742
+ s = Charset.convert_encoding(s, @encoding)
743
+ end
744
+ s
745
+ end
746
+ end
747
+ end
748
+
749
+ class StmtRawRecord
750
+ # === Argument
751
+ # pkt :: [Packet]
752
+ # fields :: [Array of Fields]
753
+ # encoding:: [Encoding]
754
+ def initialize(packet, fields, encoding)
755
+ @packet, @fields, @encoding = packet, fields, encoding
756
+ end
757
+
758
+ # Parse statement result packet
759
+ # === Return
760
+ # [Array of Object] one record
761
+ def parse_record_packet
762
+ @packet.utiny # skip first byte
763
+ null_bit_map = @packet.read((@fields.length+7+2)/8).unpack("b*").first
764
+ rec = @fields.each_with_index.map do |f, i|
765
+ if null_bit_map[i+2] == ?1
766
+ nil
767
+ else
768
+ unsigned = f.flags & Field::UNSIGNED_FLAG != 0
769
+ v = Protocol.net2value(@packet, f.type, unsigned)
770
+ if v.is_a? Numeric or v.is_a? Mysql::Time
771
+ v
772
+ elsif f.type == Field::TYPE_BIT or f.charsetnr == Charset::BINARY_CHARSET_NUMBER
773
+ Charset.to_binary(v)
774
+ else
775
+ Charset.convert_encoding(v, @encoding)
776
+ end
777
+ end
778
+ end
779
+ rec
780
+ end
781
+
782
+ alias to_a parse_record_packet
783
+
784
+ end
799
785
  end
@@ -0,0 +1,118 @@
1
+ # coding: binary
2
+ describe Mysql::Packet do
3
+ def self._(s)
4
+ s.unpack('H*').first
5
+ end
6
+ subject{Mysql::Packet.new(data)}
7
+ describe '#lcb' do
8
+ [
9
+ ["\xfb", nil],
10
+ ["\xfc\x01\x02", 0x0201],
11
+ ["\xfd\x01\x02\x03", 0x030201],
12
+ ["\xfe\x01\x02\x03\x04\x05\x06\x07\x08", 0x0807060504030201],
13
+ ["\x01", 0x01],
14
+ ].each do |data, result|
15
+ context "for '#{_ data}'" do
16
+ let(:data){data}
17
+ it{subject.lcb.should == result}
18
+ end
19
+ end
20
+ end
21
+
22
+ describe '#lcs' do
23
+ [
24
+ ["\x03\x41\x42\x43", 'ABC'],
25
+ ["\x01", ''],
26
+ ["", nil],
27
+ ].each do |data, result|
28
+ context "for '#{_ data}'" do
29
+ let(:data){data}
30
+ it{subject.lcs.should == result}
31
+ end
32
+ end
33
+ end
34
+
35
+ describe '#read' do
36
+ let(:data){'ABCDEFGHI'}
37
+ it{subject.read(7).should == 'ABCDEFG'}
38
+ end
39
+
40
+ describe '#string' do
41
+ let(:data){"ABC\0DEF"}
42
+ it 'should NUL terminated String' do
43
+ subject.string.should == 'ABC'
44
+ end
45
+ end
46
+
47
+ describe '#utiny' do
48
+ [
49
+ ["\x01", 0x01],
50
+ ["\xFF", 0xff],
51
+ ].each do |data, result|
52
+ context "for '#{_ data}'" do
53
+ let(:data){data}
54
+ it{subject.utiny.should == result}
55
+ end
56
+ end
57
+ end
58
+
59
+ describe '#ushort' do
60
+ [
61
+ ["\x01\x02", 0x0201],
62
+ ["\xFF\xFE", 0xfeff],
63
+ ].each do |data, result|
64
+ context "for '#{_ data}'" do
65
+ let(:data){data}
66
+ it{subject.ushort.should == result}
67
+ end
68
+ end
69
+ end
70
+
71
+ describe '#ulong' do
72
+ [
73
+ ["\x01\x02\x03\x04", 0x04030201],
74
+ ["\xFF\xFE\xFD\xFC", 0xfcfdfeff],
75
+ ].each do |data, result|
76
+ context "for '#{_ data}'" do
77
+ let(:data){data}
78
+ it{subject.ulong.should == result}
79
+ end
80
+ end
81
+ end
82
+
83
+ describe '#eof?' do
84
+ [
85
+ ["\xfe\x00\x00\x00\x00", true],
86
+ ["ABCDE", false],
87
+ ].each do |data, result|
88
+ context "for '#{_ data}'" do
89
+ let(:data){data}
90
+ it{subject.eof?.should == result}
91
+ end
92
+ end
93
+ end
94
+
95
+ end
96
+
97
+ describe 'Mysql::Packet.lcb' do
98
+ [
99
+ [nil, "\xfb"],
100
+ [1, "\x01"],
101
+ [250, "\xfa"],
102
+ [251, "\xfc\xfb\x00"],
103
+ [65535, "\xfc\xff\xff"],
104
+ [65536, "\xfd\x00\x00\x01"],
105
+ [16777215, "\xfd\xff\xff\xff"],
106
+ [16777216, "\xfe\x00\x00\x00\x01\x00\x00\x00\x00"],
107
+ [0xffffffffffffffff, "\xfe\xff\xff\xff\xff\xff\xff\xff\xff"],
108
+ ].each do |val, result|
109
+ context "with #{val}" do
110
+ it{Mysql::Packet.lcb(val).should == result}
111
+ end
112
+ end
113
+ end
114
+
115
+ describe 'Mysql::Packet.lcs' do
116
+ it{Mysql::Packet.lcs("hoge").should == "\x04hoge"}
117
+ it{Mysql::Packet.lcs("あいう".force_encoding("UTF-8")).should == "\x09\xe3\x81\x82\xe3\x81\x84\xe3\x81\x86"}
118
+ end
data/spec/mysql_spec.rb CHANGED
@@ -1,8 +1,6 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  require "tempfile"
3
3
 
4
- $LOAD_PATH.unshift "#{File.dirname __FILE__}/../lib"
5
-
6
4
  require "mysql"
7
5
 
8
6
  # MYSQL_USER must have ALL privilege for MYSQL_DATABASE.* and RELOAD privilege for *.*
@@ -15,7 +13,7 @@ MYSQL_SOCKET = ENV['MYSQL_SOCKET']
15
13
 
16
14
  describe 'Mysql::VERSION' do
17
15
  it 'returns client version' do
18
- Mysql::VERSION.should == 20906
16
+ Mysql::VERSION.should == 20907
19
17
  end
20
18
  end
21
19
 
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.6
4
+ version: 2.9.7
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,9 +9,9 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-04-19 00:00:00.000000000 Z
12
+ date: 2012-04-22 00:00:00.000000000 Z
13
13
  dependencies: []
14
- description: This is pure Ruby MySQL connector.
14
+ description: This is MySQL connector. pure Ruby version
15
15
  email: tommy@tmtm.org
16
16
  executables: []
17
17
  extensions: []
@@ -24,7 +24,9 @@ files:
24
24
  - lib/mysql/protocol.rb
25
25
  - lib/mysql/charset.rb
26
26
  - lib/mysql/error.rb
27
+ - lib/mysql/packet.rb
27
28
  - spec/mysql_spec.rb
29
+ - spec/mysql/packet_spec.rb
28
30
  homepage: http://github.com/tmtm/ruby-mysql
29
31
  licenses:
30
32
  - Ruby's
@@ -49,7 +51,8 @@ rubyforge_project:
49
51
  rubygems_version: 1.8.11
50
52
  signing_key:
51
53
  specification_version: 3
52
- summary: pure Ruby MySQL connector
54
+ summary: MySQL connector
53
55
  test_files:
54
56
  - spec/mysql_spec.rb
57
+ - spec/mysql/packet_spec.rb
55
58
  has_rdoc: true