ruby-mysql 2.9.6 → 2.9.7

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