mysql-pr 2.9.11

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,77 @@
1
+ class MysqlPR
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
@@ -0,0 +1,783 @@
1
+ # Copyright (C) 2008-2012 TOMITA Masahiro
2
+ # mailto:tommy@tmtm.org
3
+
4
+ require "socket"
5
+ require "timeout"
6
+ require "digest/sha1"
7
+ require "stringio"
8
+
9
+ class MysqlPR
10
+ # MySQL network protocol
11
+ class Protocol
12
+
13
+ VERSION = 10
14
+ MAX_PACKET_LENGTH = 2**24-1
15
+
16
+ # Convert netdata to Ruby value
17
+ # === Argument
18
+ # data :: [Packet] packet data
19
+ # type :: [Integer] field type
20
+ # unsigned :: [true or false] true if value is unsigned
21
+ # === Return
22
+ # Object :: converted value.
23
+ def self.net2value(pkt, type, unsigned)
24
+ case type
25
+ when Field::TYPE_STRING, Field::TYPE_VAR_STRING, Field::TYPE_NEWDECIMAL, Field::TYPE_BLOB
26
+ return pkt.lcs
27
+ when Field::TYPE_TINY
28
+ v = pkt.utiny
29
+ return unsigned ? v : v < 128 ? v : v-256
30
+ when Field::TYPE_SHORT
31
+ v = pkt.ushort
32
+ return unsigned ? v : v < 32768 ? v : v-65536
33
+ when Field::TYPE_INT24, Field::TYPE_LONG
34
+ v = pkt.ulong
35
+ return unsigned ? v : v < 2**32/2 ? v : v-2**32
36
+ when Field::TYPE_LONGLONG
37
+ n1, n2 = pkt.ulong, pkt.ulong
38
+ v = (n2 << 32) | n1
39
+ return unsigned ? v : v < 2**64/2 ? v : v-2**64
40
+ when Field::TYPE_FLOAT
41
+ return pkt.read(4).unpack('e').first
42
+ when Field::TYPE_DOUBLE
43
+ return pkt.read(8).unpack('E').first
44
+ when Field::TYPE_DATE
45
+ len = pkt.utiny
46
+ y, m, d = pkt.read(len).unpack("vCC")
47
+ t = MysqlPR::Time.new(y, m, d, nil, nil, nil)
48
+ return t
49
+ when Field::TYPE_DATETIME, Field::TYPE_TIMESTAMP
50
+ len = pkt.utiny
51
+ y, m, d, h, mi, s, sp = pkt.read(len).unpack("vCCCCCV")
52
+ return MysqlPR::Time.new(y, m, d, h, mi, s, false, sp)
53
+ when Field::TYPE_TIME
54
+ len = pkt.utiny
55
+ sign, d, h, mi, s, sp = pkt.read(len).unpack("CVCCCV")
56
+ h = d.to_i * 24 + h.to_i
57
+ return MysqlPR::Time.new(0, 0, 0, h, mi, s, sign!=0, sp)
58
+ when Field::TYPE_YEAR
59
+ return pkt.ushort
60
+ when Field::TYPE_BIT
61
+ return pkt.lcs
62
+ else
63
+ raise "not implemented: type=#{type}"
64
+ end
65
+ end
66
+
67
+ # convert Ruby value to netdata
68
+ # === Argument
69
+ # v :: [Object] Ruby value.
70
+ # === Return
71
+ # Integer :: type of column. Field::TYPE_*
72
+ # String :: netdata
73
+ # === Exception
74
+ # ProtocolError :: value too large / value is not supported
75
+ def self.value2net(v)
76
+ case v
77
+ when nil
78
+ type = Field::TYPE_NULL
79
+ val = ""
80
+ when Integer
81
+ if v >= 0
82
+ if v < 256
83
+ type = Field::TYPE_TINY | 0x8000
84
+ val = [v].pack("C")
85
+ elsif v < 256**2
86
+ type = Field::TYPE_SHORT | 0x8000
87
+ val = [v].pack("v")
88
+ elsif v < 256**4
89
+ type = Field::TYPE_LONG | 0x8000
90
+ val = [v].pack("V")
91
+ elsif v < 256**8
92
+ type = Field::TYPE_LONGLONG | 0x8000
93
+ val = [v&0xffffffff, v>>32].pack("VV")
94
+ else
95
+ raise ProtocolError, "value too large: #{v}"
96
+ end
97
+ else
98
+ if -v <= 256/2
99
+ type = Field::TYPE_TINY
100
+ val = [v].pack("C")
101
+ elsif -v <= 256**2/2
102
+ type = Field::TYPE_SHORT
103
+ val = [v].pack("v")
104
+ elsif -v <= 256**4/2
105
+ type = Field::TYPE_LONG
106
+ val = [v].pack("V")
107
+ elsif -v <= 256**8/2
108
+ type = Field::TYPE_LONGLONG
109
+ val = [v&0xffffffff, v>>32].pack("VV")
110
+ else
111
+ raise ProtocolError, "value too large: #{v}"
112
+ end
113
+ end
114
+ when Float
115
+ type = Field::TYPE_DOUBLE
116
+ val = [v].pack("E")
117
+ when String
118
+ type = Field::TYPE_STRING
119
+ val = Packet.lcs(v)
120
+ when MysqlPR::Time, ::Time
121
+ type = Field::TYPE_DATETIME
122
+ val = [7, v.year, v.month, v.day, v.hour, v.min, v.sec].pack("CvCCCCC")
123
+ else
124
+ raise ProtocolError, "class #{v.class} is not supported"
125
+ end
126
+ return type, val
127
+ end
128
+
129
+ attr_reader :server_info
130
+ attr_reader :server_version
131
+ attr_reader :thread_id
132
+ attr_reader :sqlstate
133
+ attr_reader :affected_rows
134
+ attr_reader :insert_id
135
+ attr_reader :server_status
136
+ attr_reader :warning_count
137
+ attr_reader :message
138
+ attr_accessor :charset
139
+
140
+ # @state variable keep state for connection.
141
+ # :INIT :: Initial state.
142
+ # :READY :: Ready for command.
143
+ # :FIELD :: After query(). retr_fields() is needed.
144
+ # :RESULT :: After retr_fields(), retr_all_records() or stmt_retr_all_records() is needed.
145
+
146
+ # make socket connection to server.
147
+ # === Argument
148
+ # host :: [String] if "localhost" or "" nil then use UNIXSocket. Otherwise use TCPSocket
149
+ # port :: [Integer] port number using by TCPSocket
150
+ # socket :: [String] socket file name using by UNIXSocket
151
+ # conn_timeout :: [Integer] connect timeout (sec).
152
+ # read_timeout :: [Integer] read timeout (sec).
153
+ # write_timeout :: [Integer] write timeout (sec).
154
+ # === Exception
155
+ # [ClientError] :: connection timeout
156
+ def initialize(host, port, socket, conn_timeout, read_timeout, write_timeout)
157
+ @insert_id = 0
158
+ @warning_count = 0
159
+ @gc_stmt_queue = [] # stmt id list which GC destroy.
160
+ set_state :INIT
161
+ @read_timeout = read_timeout
162
+ @write_timeout = write_timeout
163
+ begin
164
+ Timeout.timeout conn_timeout do
165
+ if host.nil? or host.empty? or host == "localhost"
166
+ socket ||= ENV["MYSQL_UNIX_PORT"] || MYSQL_UNIX_PORT
167
+ @sock = UNIXSocket.new socket
168
+ else
169
+ port ||= ENV["MYSQL_TCP_PORT"] || (Socket.getservbyname("mysql","tcp") rescue MYSQL_TCP_PORT)
170
+ @sock = TCPSocket.new host, port
171
+ end
172
+ end
173
+ rescue Timeout::Error
174
+ raise ClientError, "connection timeout"
175
+ end
176
+ end
177
+
178
+ def close
179
+ @sock.close
180
+ end
181
+
182
+ # initial negotiate and authenticate.
183
+ # === Argument
184
+ # user :: [String / nil] username
185
+ # passwd :: [String / nil] password
186
+ # db :: [String / nil] default database name. nil: no default.
187
+ # flag :: [Integer] client flag
188
+ # charset :: [MysqlPR::Charset / nil] charset for connection. nil: use server's charset
189
+ # === Exception
190
+ # ProtocolError :: The old style password is not supported
191
+ def authenticate(user, passwd, db, flag, charset)
192
+ check_state :INIT
193
+ @authinfo = [user, passwd, db, flag, charset]
194
+ reset
195
+ init_packet = InitialPacket.parse read
196
+ @server_info = init_packet.server_version
197
+ @server_version = init_packet.server_version.split(/\D/)[0,3].inject{|a,b|a.to_i*100+b.to_i}
198
+ @thread_id = init_packet.thread_id
199
+ client_flags = CLIENT_LONG_PASSWORD | CLIENT_LONG_FLAG | CLIENT_TRANSACTIONS | CLIENT_PROTOCOL_41 | CLIENT_SECURE_CONNECTION
200
+ client_flags |= CLIENT_CONNECT_WITH_DB if db
201
+ client_flags |= flag
202
+ @charset = charset
203
+ unless @charset
204
+ @charset = Charset.by_number(init_packet.server_charset)
205
+ @charset.encoding # raise error if unsupported charset
206
+ end
207
+ netpw = encrypt_password passwd, init_packet.scramble_buff
208
+ write AuthenticationPacket.serialize(client_flags, 1024**3, @charset.number, user, netpw, db)
209
+ raise ProtocolError, 'The old style password is not supported' if read.to_s == "\xfe"
210
+ set_state :READY
211
+ end
212
+
213
+ # Quit command
214
+ def quit_command
215
+ synchronize do
216
+ reset
217
+ write [COM_QUIT].pack("C")
218
+ close
219
+ end
220
+ end
221
+
222
+ # Query command
223
+ # === Argument
224
+ # query :: [String] query string
225
+ # === Return
226
+ # [Integer / nil] number of fields of results. nil if no results.
227
+ def query_command(query)
228
+ check_state :READY
229
+ begin
230
+ reset
231
+ write [COM_QUERY, @charset.convert(query)].pack("Ca*")
232
+ get_result
233
+ rescue
234
+ set_state :READY
235
+ raise
236
+ end
237
+ end
238
+
239
+ # get result of query.
240
+ # === Return
241
+ # [integer / nil] number of fields of results. nil if no results.
242
+ def get_result
243
+ begin
244
+ res_packet = ResultPacket.parse read
245
+ if res_packet.field_count.to_i > 0 # result data exists
246
+ set_state :FIELD
247
+ return res_packet.field_count
248
+ end
249
+ if res_packet.field_count.nil? # LOAD DATA LOCAL INFILE
250
+ filename = res_packet.message
251
+ File.open(filename){|f| write f}
252
+ write nil # EOF mark
253
+ read
254
+ end
255
+ @affected_rows, @insert_id, @server_status, @warning_count, @message =
256
+ res_packet.affected_rows, res_packet.insert_id, res_packet.server_status, res_packet.warning_count, res_packet.message
257
+ set_state :READY
258
+ return nil
259
+ rescue
260
+ set_state :READY
261
+ raise
262
+ end
263
+ end
264
+
265
+ # Retrieve n fields
266
+ # === Argument
267
+ # n :: [Integer] number of fields
268
+ # === Return
269
+ # [Array of MysqlPR::Field] field list
270
+ def retr_fields(n)
271
+ check_state :FIELD
272
+ begin
273
+ fields = n.times.map{Field.new FieldPacket.parse(read)}
274
+ read_eof_packet
275
+ set_state :RESULT
276
+ fields
277
+ rescue
278
+ set_state :READY
279
+ raise
280
+ end
281
+ end
282
+
283
+ # Retrieve all records for simple query
284
+ # === Argument
285
+ # nfields :: [Integer] number of fields
286
+ # === Return
287
+ # [Array of Array of String] all records
288
+ def retr_all_records(nfields)
289
+ check_state :RESULT
290
+ enc = charset.encoding
291
+ begin
292
+ all_recs = []
293
+ until (pkt = read).eof?
294
+ all_recs.push RawRecord.new(pkt, nfields, enc)
295
+ end
296
+ pkt.read(3)
297
+ @server_status = pkt.utiny
298
+ all_recs
299
+ ensure
300
+ set_state :READY
301
+ end
302
+ end
303
+
304
+ # Field list command
305
+ # === Argument
306
+ # table :: [String] table name.
307
+ # field :: [String / nil] field name that may contain wild card.
308
+ # === Return
309
+ # [Array of Field] field list
310
+ def field_list_command(table, field)
311
+ synchronize do
312
+ reset
313
+ write [COM_FIELD_LIST, table, 0, field].pack("Ca*Ca*")
314
+ fields = []
315
+ until (data = read).eof?
316
+ fields.push Field.new(FieldPacket.parse(data))
317
+ end
318
+ return fields
319
+ end
320
+ end
321
+
322
+ # Process info command
323
+ # === Return
324
+ # [Array of Field] field list
325
+ def process_info_command
326
+ check_state :READY
327
+ begin
328
+ reset
329
+ write [COM_PROCESS_INFO].pack("C")
330
+ field_count = read.lcb
331
+ fields = field_count.times.map{Field.new FieldPacket.parse(read)}
332
+ read_eof_packet
333
+ set_state :RESULT
334
+ return fields
335
+ rescue
336
+ set_state :READY
337
+ raise
338
+ end
339
+ end
340
+
341
+ # Ping command
342
+ def ping_command
343
+ simple_command [COM_PING].pack("C")
344
+ end
345
+
346
+ # Kill command
347
+ def kill_command(pid)
348
+ simple_command [COM_PROCESS_KILL, pid].pack("CV")
349
+ end
350
+
351
+ # Refresh command
352
+ def refresh_command(op)
353
+ simple_command [COM_REFRESH, op].pack("CC")
354
+ end
355
+
356
+ # Set option command
357
+ def set_option_command(opt)
358
+ simple_command [COM_SET_OPTION, opt].pack("Cv")
359
+ end
360
+
361
+ # Shutdown command
362
+ def shutdown_command(level)
363
+ simple_command [COM_SHUTDOWN, level].pack("CC")
364
+ end
365
+
366
+ # Statistics command
367
+ def statistics_command
368
+ simple_command [COM_STATISTICS].pack("C")
369
+ end
370
+
371
+ # Stmt prepare command
372
+ # === Argument
373
+ # stmt :: [String] prepared statement
374
+ # === Return
375
+ # [Integer] statement id
376
+ # [Integer] number of parameters
377
+ # [Array of Field] field list
378
+ def stmt_prepare_command(stmt)
379
+ synchronize do
380
+ reset
381
+ write [COM_STMT_PREPARE, charset.convert(stmt)].pack("Ca*")
382
+ res_packet = PrepareResultPacket.parse read
383
+ if res_packet.param_count > 0
384
+ res_packet.param_count.times{read} # skip parameter packet
385
+ read_eof_packet
386
+ end
387
+ if res_packet.field_count > 0
388
+ fields = res_packet.field_count.times.map{Field.new FieldPacket.parse(read)}
389
+ read_eof_packet
390
+ else
391
+ fields = []
392
+ end
393
+ return res_packet.statement_id, res_packet.param_count, fields
394
+ end
395
+ end
396
+
397
+ # Stmt execute command
398
+ # === Argument
399
+ # stmt_id :: [Integer] statement id
400
+ # values :: [Array] parameters
401
+ # === Return
402
+ # [Integer] number of fields
403
+ def stmt_execute_command(stmt_id, values)
404
+ check_state :READY
405
+ begin
406
+ reset
407
+ write ExecutePacket.serialize(stmt_id, MysqlPR::Stmt::CURSOR_TYPE_NO_CURSOR, values)
408
+ get_result
409
+ rescue
410
+ set_state :READY
411
+ raise
412
+ end
413
+ end
414
+
415
+ # Retrieve all records for prepared statement
416
+ # === Argument
417
+ # fields :: [Array of MysqlPR::Fields] field list
418
+ # charset :: [MysqlPR::Charset]
419
+ # === Return
420
+ # [Array of Array of Object] all records
421
+ def stmt_retr_all_records(fields, charset)
422
+ check_state :RESULT
423
+ enc = charset.encoding
424
+ begin
425
+ all_recs = []
426
+ until (pkt = read).eof?
427
+ all_recs.push StmtRawRecord.new(pkt, fields, enc)
428
+ end
429
+ all_recs
430
+ ensure
431
+ set_state :READY
432
+ end
433
+ end
434
+
435
+ # Stmt close command
436
+ # === Argument
437
+ # stmt_id :: [Integer] statement id
438
+ def stmt_close_command(stmt_id)
439
+ synchronize do
440
+ reset
441
+ write [COM_STMT_CLOSE, stmt_id].pack("CV")
442
+ end
443
+ end
444
+
445
+ def gc_stmt(stmt_id)
446
+ @gc_stmt_queue.push stmt_id
447
+ end
448
+
449
+ private
450
+
451
+ def check_state(st)
452
+ raise 'command out of sync' unless @state == st
453
+ end
454
+
455
+ def set_state(st)
456
+ @state = st
457
+ if st == :READY
458
+ gc_disabled = GC.disable unless RUBY_PLATFORM == 'java'
459
+ begin
460
+ while st = @gc_stmt_queue.shift
461
+ reset
462
+ write [COM_STMT_CLOSE, st].pack("CV")
463
+ end
464
+ ensure
465
+ GC.enable unless gc_disabled unless RUBY_PLATFORM == 'java'
466
+ end
467
+ end
468
+ end
469
+
470
+ def synchronize
471
+ begin
472
+ check_state :READY
473
+ return yield
474
+ ensure
475
+ set_state :READY
476
+ end
477
+ end
478
+
479
+ # Reset sequence number
480
+ def reset
481
+ @seq = 0 # packet counter. reset by each command
482
+ end
483
+
484
+ # Read one packet data
485
+ # === Return
486
+ # [Packet] packet data
487
+ # === Exception
488
+ # [ProtocolError] invalid packet sequence number
489
+ def read
490
+ ret = ""
491
+ len = nil
492
+ begin
493
+ Timeout.timeout @read_timeout do
494
+ header = @sock.read(4)
495
+ raise EOFError unless header && header.length == 4
496
+ len1, len2, seq = header.unpack("CvC")
497
+ len = (len2 << 8) + len1
498
+ raise ProtocolError, "invalid packet: sequence number mismatch(#{seq} != #{@seq}(expected))" if @seq != seq
499
+ @seq = (@seq + 1) % 256
500
+ ret = @sock.read(len)
501
+ raise EOFError unless ret && ret.length == len
502
+ end
503
+ rescue EOFError
504
+ raise ClientError::ServerGoneError, 'The MySQL server has gone away'
505
+ rescue Timeout::Error
506
+ raise ClientError, "read timeout"
507
+ end while len == MAX_PACKET_LENGTH
508
+
509
+ @sqlstate = "00000"
510
+
511
+ # Error packet
512
+ if ret[0] == ?\xff
513
+ f, errno, marker, @sqlstate, message = ret.unpack("Cvaa5a*")
514
+ unless marker == "#"
515
+ f, errno, message = ret.unpack("Cva*") # Version 4.0 Error
516
+ @sqlstate = ""
517
+ end
518
+ if MysqlPR::ServerError::ERROR_MAP.key? errno
519
+ raise MysqlPR::ServerError::ERROR_MAP[errno].new(message, @sqlstate)
520
+ end
521
+ raise MysqlPR::ServerError.new(message, @sqlstate)
522
+ end
523
+ Packet.new(ret)
524
+ end
525
+
526
+ # Write one packet data
527
+ # === Argument
528
+ # data :: [String / IO] packet data. If data is nil, write empty packet.
529
+ def write(data)
530
+ begin
531
+ @sock.sync = false
532
+ if data.nil?
533
+ Timeout.timeout @write_timeout do
534
+ @sock.write [0, 0, @seq].pack("CvC")
535
+ end
536
+ @seq = (@seq + 1) % 256
537
+ else
538
+ data = StringIO.new data if data.is_a? String
539
+ while d = data.read(MAX_PACKET_LENGTH)
540
+ Timeout.timeout @write_timeout do
541
+ @sock.write [d.length%256, d.length/256, @seq].pack("CvC")
542
+ @sock.write d
543
+ end
544
+ @seq = (@seq + 1) % 256
545
+ end
546
+ end
547
+ @sock.sync = true
548
+ Timeout.timeout @write_timeout do
549
+ @sock.flush
550
+ end
551
+ rescue Errno::EPIPE
552
+ raise ClientError::ServerGoneError, 'The MySQL server has gone away'
553
+ rescue Timeout::Error
554
+ raise ClientError, "write timeout"
555
+ end
556
+ end
557
+
558
+ # Read EOF packet
559
+ # === Exception
560
+ # [ProtocolError] packet is not EOF
561
+ def read_eof_packet
562
+ raise ProtocolError, "packet is not EOF" unless read.eof?
563
+ end
564
+
565
+ # Send simple command
566
+ # === Argument
567
+ # packet :: [String] packet data
568
+ # === Return
569
+ # [String] received data
570
+ def simple_command(packet)
571
+ synchronize do
572
+ reset
573
+ write packet
574
+ read.to_s
575
+ end
576
+ end
577
+
578
+ # Encrypt password
579
+ # === Argument
580
+ # plain :: [String] plain password.
581
+ # scramble :: [String] scramble code from initial packet.
582
+ # === Return
583
+ # [String] encrypted password
584
+ def encrypt_password(plain, scramble)
585
+ return "" if plain.nil? or plain.empty?
586
+ hash_stage1 = Digest::SHA1.digest plain
587
+ hash_stage2 = Digest::SHA1.digest hash_stage1
588
+ return hash_stage1.unpack("C*").zip(Digest::SHA1.digest(scramble+hash_stage2).unpack("C*")).map{|a,b| a^b}.pack("C*")
589
+ end
590
+
591
+ # Initial packet
592
+ class InitialPacket
593
+ def self.parse(pkt)
594
+ protocol_version = pkt.utiny
595
+ server_version = pkt.string
596
+ thread_id = pkt.ulong
597
+ scramble_buff = pkt.read(8)
598
+ f0 = pkt.utiny
599
+ server_capabilities = pkt.ushort
600
+ server_charset = pkt.utiny
601
+ server_status = pkt.ushort
602
+ f1 = pkt.read(13)
603
+ rest_scramble_buff = pkt.string
604
+ raise ProtocolError, "unsupported version: #{protocol_version}" unless protocol_version == VERSION
605
+ raise ProtocolError, "invalid packet: f0=#{f0}" unless f0 == 0
606
+ scramble_buff.concat rest_scramble_buff
607
+ self.new protocol_version, server_version, thread_id, server_capabilities, server_charset, server_status, scramble_buff
608
+ end
609
+
610
+ attr_reader :protocol_version, :server_version, :thread_id, :server_capabilities, :server_charset, :server_status, :scramble_buff
611
+
612
+ def initialize(*args)
613
+ @protocol_version, @server_version, @thread_id, @server_capabilities, @server_charset, @server_status, @scramble_buff = args
614
+ end
615
+ end
616
+
617
+ # Result packet
618
+ class ResultPacket
619
+ def self.parse(pkt)
620
+ field_count = pkt.lcb
621
+ if field_count == 0
622
+ affected_rows = pkt.lcb
623
+ insert_id = pkt.lcb
624
+ server_status = pkt.ushort
625
+ warning_count = pkt.ushort
626
+ message = pkt.lcs
627
+ return self.new(field_count, affected_rows, insert_id, server_status, warning_count, message)
628
+ elsif field_count.nil? # LOAD DATA LOCAL INFILE
629
+ return self.new(nil, nil, nil, nil, nil, pkt.to_s)
630
+ else
631
+ return self.new(field_count)
632
+ end
633
+ end
634
+
635
+ attr_reader :field_count, :affected_rows, :insert_id, :server_status, :warning_count, :message
636
+
637
+ def initialize(*args)
638
+ @field_count, @affected_rows, @insert_id, @server_status, @warning_count, @message = args
639
+ end
640
+ end
641
+
642
+ # Field packet
643
+ class FieldPacket
644
+ def self.parse(pkt)
645
+ first = pkt.lcs
646
+ db = pkt.lcs
647
+ table = pkt.lcs
648
+ org_table = pkt.lcs
649
+ name = pkt.lcs
650
+ org_name = pkt.lcs
651
+ f0 = pkt.utiny
652
+ charsetnr = pkt.ushort
653
+ length = pkt.ulong
654
+ type = pkt.utiny
655
+ flags = pkt.ushort
656
+ decimals = pkt.utiny
657
+ f1 = pkt.ushort
658
+
659
+ raise ProtocolError, "invalid packet: f1=#{f1}" unless f1 == 0
660
+ default = pkt.lcs
661
+ return self.new(db, table, org_table, name, org_name, charsetnr, length, type, flags, decimals, default)
662
+ end
663
+
664
+ attr_reader :db, :table, :org_table, :name, :org_name, :charsetnr, :length, :type, :flags, :decimals, :default
665
+
666
+ def initialize(*args)
667
+ @db, @table, @org_table, @name, @org_name, @charsetnr, @length, @type, @flags, @decimals, @default = args
668
+ end
669
+ end
670
+
671
+ # Prepare result packet
672
+ class PrepareResultPacket
673
+ def self.parse(pkt)
674
+ raise ProtocolError, "invalid packet" unless pkt.utiny == 0
675
+ statement_id = pkt.ulong
676
+ field_count = pkt.ushort
677
+ param_count = pkt.ushort
678
+ f = pkt.utiny
679
+ warning_count = pkt.ushort
680
+ raise ProtocolError, "invalid packet" unless f == 0x00
681
+ self.new statement_id, field_count, param_count, warning_count
682
+ end
683
+
684
+ attr_reader :statement_id, :field_count, :param_count, :warning_count
685
+
686
+ def initialize(*args)
687
+ @statement_id, @field_count, @param_count, @warning_count = args
688
+ end
689
+ end
690
+
691
+ # Authentication packet
692
+ class AuthenticationPacket
693
+ def self.serialize(client_flags, max_packet_size, charset_number, username, scrambled_password, databasename)
694
+ [
695
+ client_flags,
696
+ max_packet_size,
697
+ Packet.lcb(charset_number),
698
+ "", # always 0x00 * 23
699
+ username,
700
+ Packet.lcs(scrambled_password),
701
+ databasename
702
+ ].pack("VVa*a23Z*A*Z*")
703
+ end
704
+ end
705
+
706
+ # Execute packet
707
+ class ExecutePacket
708
+ def self.serialize(statement_id, cursor_type, values)
709
+ nbm = null_bitmap values
710
+ netvalues = ""
711
+ types = values.map do |v|
712
+ t, n = Protocol.value2net v
713
+ netvalues.concat n if v
714
+ t
715
+ end
716
+ [MysqlPR::COM_STMT_EXECUTE, statement_id, cursor_type, 1, nbm, 1, types.pack("v*"), netvalues].pack("CVCVa*Ca*a*")
717
+ end
718
+
719
+ # make null bitmap
720
+ #
721
+ # If values is [1, nil, 2, 3, nil] then returns "\x12"(0b10010).
722
+ def self.null_bitmap(values)
723
+ bitmap = values.enum_for(:each_slice,8).map do |vals|
724
+ vals.reverse.inject(0){|b, v|(b << 1 | (v ? 0 : 1))}
725
+ end
726
+ return bitmap.pack("C*")
727
+ end
728
+
729
+ end
730
+ end
731
+
732
+ class RawRecord
733
+ def initialize(packet, nfields, encoding)
734
+ @packet, @nfields, @encoding = packet, nfields, encoding
735
+ end
736
+
737
+ def to_a
738
+ @nfields.times.map do
739
+ if s = @packet.lcs
740
+ s = Charset.convert_encoding(s, @encoding)
741
+ end
742
+ s
743
+ end
744
+ end
745
+ end
746
+
747
+ class StmtRawRecord
748
+ # === Argument
749
+ # pkt :: [Packet]
750
+ # fields :: [Array of Fields]
751
+ # encoding:: [Encoding]
752
+ def initialize(packet, fields, encoding)
753
+ @packet, @fields, @encoding = packet, fields, encoding
754
+ end
755
+
756
+ # Parse statement result packet
757
+ # === Return
758
+ # [Array of Object] one record
759
+ def parse_record_packet
760
+ @packet.utiny # skip first byte
761
+ null_bit_map = @packet.read((@fields.length+7+2)/8).unpack("b*").first
762
+ rec = @fields.each_with_index.map do |f, i|
763
+ if null_bit_map[i+2] == ?1
764
+ nil
765
+ else
766
+ unsigned = f.flags & Field::UNSIGNED_FLAG != 0
767
+ v = Protocol.net2value(@packet, f.type, unsigned)
768
+ if v.is_a? Numeric or v.is_a? MysqlPR::Time
769
+ v
770
+ elsif f.type == Field::TYPE_BIT or f.charsetnr == Charset::BINARY_CHARSET_NUMBER
771
+ Charset.to_binary(v)
772
+ else
773
+ Charset.convert_encoding(v, @encoding)
774
+ end
775
+ end
776
+ end
777
+ rec
778
+ end
779
+
780
+ alias to_a parse_record_packet
781
+
782
+ end
783
+ end