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 +40 -16
- data/lib/mysql/charset.rb +2 -4
- data/lib/mysql/packet.rb +77 -0
- data/lib/mysql/protocol.rb +135 -149
- data/spec/mysql/packet_spec.rb +118 -0
- data/spec/mysql_spec.rb +1 -3
- metadata +7 -4
data/lib/mysql.rb
CHANGED
@@ -10,13 +10,17 @@
|
|
10
10
|
# end
|
11
11
|
class Mysql
|
12
12
|
|
13
|
-
|
14
|
-
require "
|
15
|
-
require "
|
16
|
-
require "
|
17
|
-
|
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 =
|
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 :
|
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" =>
|
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
|
-
|
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
|
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
|
754
|
-
# for
|
755
|
-
|
756
|
-
|
757
|
-
|
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.
|
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.
|
249
|
+
raw.force_encoding(encoding).encode
|
252
250
|
end
|
253
251
|
|
254
252
|
# retrun corresponding Ruby encoding
|
data/lib/mysql/packet.rb
ADDED
@@ -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
|
data/lib/mysql/protocol.rb
CHANGED
@@ -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 :: [
|
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(
|
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
|
26
|
+
return pkt.lcs
|
79
27
|
when Field::TYPE_TINY
|
80
|
-
v =
|
28
|
+
v = pkt.utiny
|
81
29
|
return unsigned ? v : v < 128 ? v : v-256
|
82
30
|
when Field::TYPE_SHORT
|
83
|
-
v =
|
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 =
|
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 =
|
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
|
41
|
+
return pkt.read(4).unpack('e').first
|
94
42
|
when Field::TYPE_DOUBLE
|
95
|
-
return
|
43
|
+
return pkt.read(8).unpack('E').first
|
96
44
|
when Field::TYPE_DATE
|
97
|
-
len =
|
98
|
-
y, m, d =
|
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 =
|
106
|
-
y, m, d, h, mi, s, bs =
|
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 =
|
110
|
-
sign, d, h, mi, s, sp =
|
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
|
62
|
+
return pkt.ushort
|
115
63
|
when Field::TYPE_BIT
|
116
|
-
return
|
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
|
-
#
|
288
|
+
# nfields :: [Integer] number of fields
|
341
289
|
# === Return
|
342
290
|
# [Array of Array of String] all records
|
343
|
-
def retr_all_records(
|
291
|
+
def retr_all_records(nfields)
|
344
292
|
check_state :RESULT
|
293
|
+
enc = charset.encoding
|
345
294
|
begin
|
346
295
|
all_recs = []
|
347
|
-
until
|
348
|
-
|
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
|
-
|
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
|
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 =
|
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
|
483
|
-
all_recs.push
|
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
|
-
# [
|
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.
|
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
|
-
|
584
|
-
|
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
|
-
|
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(
|
682
|
-
protocol_version
|
683
|
-
|
684
|
-
|
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(
|
701
|
-
field_count =
|
621
|
+
def self.parse(pkt)
|
622
|
+
field_count = pkt.lcb
|
702
623
|
if field_count == 0
|
703
|
-
affected_rows =
|
704
|
-
insert_id =
|
705
|
-
server_status
|
706
|
-
|
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,
|
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(
|
724
|
-
first =
|
725
|
-
db =
|
726
|
-
table =
|
727
|
-
org_table =
|
728
|
-
name =
|
729
|
-
org_name =
|
730
|
-
f0
|
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 =
|
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(
|
746
|
-
raise ProtocolError, "invalid packet" unless
|
747
|
-
statement_id
|
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
|
-
|
699
|
+
Packet.lcb(charset_number),
|
766
700
|
"", # always 0x00 * 23
|
767
701
|
username,
|
768
|
-
|
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 ==
|
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.
|
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-
|
12
|
+
date: 2012-04-22 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
|
-
description: This is pure Ruby
|
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:
|
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
|