mysql-pr 2.9.11

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