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