ruby-mysql-ext 2.9.7

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