ruby-mysql-ext 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.
@@ -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