Class: Mysql::Protocol

Inherits:
Object
  • Object
show all
Defined in:
lib/mysql/protocol.rb

Overview

MySQL network protocol

Defined Under Namespace

Classes: AuthenticationPacket, AuthenticationResultPacket, ExecutePacket, FieldPacket, InitialPacket, PrepareResultPacket, ResultPacket, TlsAuthenticationPacket

Constant Summary collapse

VERSION =
10
MAX_PACKET_LENGTH =
2**24-1
SSL_MODE_KEY =
{
  SSL_MODE_DISABLED        => 1,
  SSL_MODE_PREFERRED       => 2,
  SSL_MODE_REQUIRED        => 3,
  SSL_MODE_VERIFY_CA       => 4,
  SSL_MODE_VERIFY_IDENTITY => 5,
  '1'                      => 1,
  '2'                      => 2,
  '3'                      => 3,
  '4'                      => 4,
  '5'                      => 5,
  'disabled'               => 1,
  'preferred'              => 2,
  'required'               => 3,
  'verify_ca'              => 4,
  'verify_identity'        => 5,
  :disabled                => 1,
  :preferred               => 2,
  :required                => 3,
  :verify_ca               => 4,
  :verify_identity         => 5,
}.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(opts) ⇒ Protocol

make socket connection to server.

Parameters:

  • a customizable set of options

  • a customizable set of options

  • a customizable set of options

  • a customizable set of options

  • a customizable set of options

  • a customizable set of options

  • a customizable set of options

  • a customizable set of options

  • a customizable set of options

  • a customizable set of options

  • a customizable set of options

  • a customizable set of options

  • a customizable set of options

  • a customizable set of options

  • a customizable set of options

  • a customizable set of options

  • a customizable set of options

Raises:

  • connection timeout



166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# File 'lib/mysql/protocol.rb', line 166

def initialize(opts)
  @mutex = Mutex.new
  @opts = opts
  @charset = Mysql::Charset.by_name("utf8mb4")
  @insert_id = 0
  @warning_count = 0
  @session_track = {}
  @gc_stmt_queue = []   # stmt id list which GC destroy.
  set_state :INIT
  @get_server_public_key = @opts[:get_server_public_key]
  begin
    if @opts[:io]
      @socket = @opts[:io]
    elsif @opts[:host].nil? or @opts[:host].empty? or @opts[:host] == "localhost"
      socket = @opts[:socket] || ENV["MYSQL_UNIX_PORT"] || MYSQL_UNIX_PORT
      @socket = Socket.unix(socket)
    else
      port = @opts[:port] || ENV["MYSQL_TCP_PORT"] || (Socket.getservbyname("mysql", "tcp") rescue MYSQL_TCP_PORT)
      @socket = Socket.tcp(@opts[:host], port, connect_timeout: @opts[:connect_timeout])
    end
  rescue Errno::ETIMEDOUT
    raise ClientError, "connection timeout"
  end
end

Instance Attribute Details

#affected_rowsObject (readonly)

Returns the value of attribute affected_rows.



128
129
130
# File 'lib/mysql/protocol.rb', line 128

def affected_rows
  @affected_rows
end

#charsetObject

Returns the value of attribute charset.



136
137
138
# File 'lib/mysql/protocol.rb', line 136

def charset
  @charset
end

#client_flagsObject (readonly)

Returns the value of attribute client_flags.



126
127
128
# File 'lib/mysql/protocol.rb', line 126

def client_flags
  @client_flags
end

#field_countObject (readonly)

Returns the value of attribute field_count.



135
136
137
# File 'lib/mysql/protocol.rb', line 135

def field_count
  @field_count
end

#get_server_public_keyObject (readonly)

Returns the value of attribute get_server_public_key.



134
135
136
# File 'lib/mysql/protocol.rb', line 134

def get_server_public_key
  @get_server_public_key
end

#insert_idObject (readonly)

Returns the value of attribute insert_id.



129
130
131
# File 'lib/mysql/protocol.rb', line 129

def insert_id
  @insert_id
end

#messageObject (readonly)

Returns the value of attribute message.



132
133
134
# File 'lib/mysql/protocol.rb', line 132

def message
  @message
end

#server_infoObject (readonly)

Returns the value of attribute server_info.



123
124
125
# File 'lib/mysql/protocol.rb', line 123

def server_info
  @server_info
end

#server_statusObject (readonly)

Returns the value of attribute server_status.



130
131
132
# File 'lib/mysql/protocol.rb', line 130

def server_status
  @server_status
end

#server_versionObject (readonly)

Returns the value of attribute server_version.



124
125
126
# File 'lib/mysql/protocol.rb', line 124

def server_version
  @server_version
end

#session_trackObject (readonly)

Returns the value of attribute session_track.



133
134
135
# File 'lib/mysql/protocol.rb', line 133

def session_track
  @session_track
end

#socketObject (readonly)

Returns the value of attribute socket.



122
123
124
# File 'lib/mysql/protocol.rb', line 122

def socket
  @socket
end

#sqlstateObject (readonly)

Returns the value of attribute sqlstate.



127
128
129
# File 'lib/mysql/protocol.rb', line 127

def sqlstate
  @sqlstate
end

#thread_idObject (readonly)

Returns the value of attribute thread_id.



125
126
127
# File 'lib/mysql/protocol.rb', line 125

def thread_id
  @thread_id
end

#warning_countObject (readonly)

Returns the value of attribute warning_count.



131
132
133
# File 'lib/mysql/protocol.rb', line 131

def warning_count
  @warning_count
end

Class Method Details

.net2value(pkt, type, unsigned) ⇒ Object

Convert netdata to Ruby value

Parameters:

  • packet data

  • field type

  • true if value is unsigned

Returns:

  • converted value.



25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/mysql/protocol.rb', line 25

def self.net2value(pkt, type, unsigned)
  case type
  when Field::TYPE_STRING, Field::TYPE_VAR_STRING, Field::TYPE_BLOB, Field::TYPE_JSON, Field::TYPE_GEOMETRY
    return pkt.lcs
  when Field::TYPE_NEWDECIMAL
    s = pkt.lcs
    return s =~ /\./ && s !~ /\.0*\z/ ? BigDecimal(s) : s.to_i
  when Field::TYPE_TINY
    v = pkt.utiny
    return unsigned ? v : v < 128 ? v : v-256
  when Field::TYPE_SHORT
    v = pkt.ushort
    return unsigned ? v : v < 32768 ? v : v-65536
  when Field::TYPE_INT24, Field::TYPE_LONG
    v = pkt.ulong
    return unsigned ? v : v < 0x8000_0000 ? v : v-0x10000_0000
  when Field::TYPE_LONGLONG
    n1, n2 = pkt.ulong, pkt.ulong
    v = (n2 << 32) | n1
    return unsigned ? v : v < 0x8000_0000_0000_0000 ? v : v-0x10000_0000_0000_0000
  when Field::TYPE_FLOAT
    return pkt.read(4).unpack1('e')
  when Field::TYPE_DOUBLE
    return pkt.read(8).unpack1('E')
  when Field::TYPE_DATE
    len = pkt.utiny
    y, m, d = pkt.read(len).unpack("vCC")
    t = Date.new(y, m, d) rescue nil
    return t
  when Field::TYPE_DATETIME, Field::TYPE_TIMESTAMP
    len = pkt.utiny
    y, m, d, h, mi, s, sp = pkt.read(len).unpack("vCCCCCV")
    return Time.new(y, m, d, h, mi, Rational(s.to_i*1000000+sp.to_i, 1000000)) rescue nil
  when Field::TYPE_TIME
    len = pkt.utiny
    sign, d, h, mi, s, sp = pkt.read(len).unpack("CVCCCV")
    r = d.to_i*86400 + h.to_i*3600 + mi.to_i*60 + s.to_i + sp.to_f/1000000
    r *= -1 if sign != 0
    return r
  when Field::TYPE_YEAR
    return pkt.ushort
  when Field::TYPE_BIT
    return pkt.lcs
  else
    raise "not implemented: type=#{type}"
  end
end

.value2net(v) ⇒ Integer, String

convert Ruby value to netdata

Parameters:

  • Ruby value.

Returns:

  • type of column. Field::TYPE_*

  • netdata

Raises:

  • value too large / value is not supported



78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/mysql/protocol.rb', line 78

def self.value2net(v)
  v = v == true ? 1 : v == false ? 0 : v
  case v
  when nil
    type = Field::TYPE_NULL
    val = ""
  when Integer
    if -0x8000_0000 <= v && v < 0x8000_0000
      type = Field::TYPE_LONG
      val = [v].pack('V')
    elsif -0x8000_0000_0000_0000 <= v && v < 0x8000_0000_0000_0000
      type = Field::TYPE_LONGLONG
      val = [v&0xffffffff, v>>32].pack("VV")
    elsif 0x8000_0000_0000_0000 <= v && v <= 0xffff_ffff_ffff_ffff
      type = Field::TYPE_LONGLONG | 0x8000
      val = [v&0xffffffff, v>>32].pack("VV")
    else
      type =Field::TYPE_NEWDECIMAL
      val = Packet.lcs(v.to_s)
    end
  when BigDecimal
    type = Field::TYPE_NEWDECIMAL
    val = Packet.lcs(v.to_s)
  when Float
    type = Field::TYPE_DOUBLE
    val = [v].pack("E")
  when String
    type = Field::TYPE_STRING
    val = Packet.lcs(v)
  when Time
    type = Field::TYPE_DATETIME
    val = [11, v.year, v.month, v.day, v.hour, v.min, v.sec, v.usec].pack("CvCCCCCV")
  when DateTime
    type = Field::TYPE_DATETIME
    val = [11, v.year, v.month, v.day, v.hour, v.min, v.sec, (v.sec_fraction*1000000).to_i].pack("CvCCCCCV")
  when Date
    type = Field::TYPE_DATE
    val = [11, v.year, v.month, v.day, 0, 0, 0, 0].pack("CvCCCCCV")
  else
    raise ProtocolError, "class #{v.class} is not supported"
  end
  return type, val
end

Instance Method Details

#authenticateObject

initial negotiate and authenticate.

Parameters:

  • charset for connection. nil: use server's charset

Raises:

  • The old style password is not supported



198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
# File 'lib/mysql/protocol.rb', line 198

def authenticate
  synchronize(before: :INIT, after: :READY) do
    reset
    init_packet = InitialPacket.parse read
    @server_info = init_packet.server_version
    @server_version = init_packet.server_version.split(/\D/)[0, 3].inject{|a, b| a.to_i*100+b.to_i}
    @server_capabilities = init_packet.server_capabilities
    @thread_id = init_packet.thread_id
    @client_flags = CLIENT_LONG_PASSWORD | CLIENT_LONG_FLAG | CLIENT_TRANSACTIONS | CLIENT_PROTOCOL_41 | CLIENT_SECURE_CONNECTION | CLIENT_MULTI_RESULTS | CLIENT_PS_MULTI_RESULTS | CLIENT_PLUGIN_AUTH | CLIENT_CONNECT_ATTRS | CLIENT_SESSION_TRACK | CLIENT_LOCAL_FILES
    @client_flags |= CLIENT_CONNECT_WITH_DB if @opts[:database]
    @client_flags |= @opts[:flags]
    if @opts[:charset]
      @charset = @opts[:charset].is_a?(Charset) ? @opts[:charset] : Charset.by_name(@opts[:charset])
    else
      @charset = Charset.by_number(init_packet.server_charset)
      @charset.encoding       # raise error if unsupported charset
    end
    enable_ssl
    Authenticator.new(self).authenticate(@opts[:username], @opts[:password].to_s, @opts[:database], init_packet.scramble_buff, init_packet.auth_plugin, @opts[:connect_attrs])
  end
end

#check_state(st) ⇒ Object

Raises:



475
476
477
# File 'lib/mysql/protocol.rb', line 475

def check_state(st)
  raise Mysql::ClientError::CommandsOutOfSync, 'command out of sync' unless @state == st
end

#closeObject



191
192
193
# File 'lib/mysql/protocol.rb', line 191

def close
  @socket.close rescue nil
end

#enable_sslObject



243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
# File 'lib/mysql/protocol.rb', line 243

def enable_ssl
  ssl_mode = SSL_MODE_KEY[@opts[:ssl_mode]]
  raise ClientError, "ssl_mode #{@opts[:ssl_mode]} is not supported" unless ssl_mode

  return if ssl_mode == SSL_MODE_DISABLED
  if ssl_mode == SSL_MODE_PREFERRED
    return if @socket.local_address.unix?
    return if @server_capabilities & CLIENT_SSL == 0
  end
  if ssl_mode >= SSL_MODE_REQUIRED && @server_capabilities & CLIENT_SSL == 0
    raise ClientError::SslConnectionError, "SSL is required but the server doesn't support it"
  end

  context = OpenSSL::SSL::SSLContext.new
  context.set_params(@opts[:ssl_context_params])
  context.verify_mode = OpenSSL::SSL::VERIFY_NONE if ssl_mode < SSL_MODE_VERIFY_CA
  context.verify_hostname = false if ssl_mode < SSL_MODE_VERIFY_IDENTITY

  ssl_socket = OpenSSL::SSL::SSLSocket.new(@socket, context)
  ssl_socket.sync_close = true
  ssl_socket.hostname = @opts[:host] if ssl_mode >= SSL_MODE_VERIFY_IDENTITY

  @client_flags |= CLIENT_SSL
  write Protocol::TlsAuthenticationPacket.serialize(@client_flags, 1024**3, @charset.number)

  ssl_socket.connect
  @socket = ssl_socket
rescue OpenSSL::SSL::SSLError => e
  @client_flags &= ~CLIENT_SSL
  return if @opts[:ssl_mode] < SSL_MODE_REQUIRED
  raise e
end

#gc_stmt(stmt_id) ⇒ Object



471
472
473
# File 'lib/mysql/protocol.rb', line 471

def gc_stmt(stmt_id)
  @gc_stmt_queue.push stmt_id
end

#get_resultinteger?

get result of query.

Returns:

  • number of fields of results. nil if no results.



304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
# File 'lib/mysql/protocol.rb', line 304

def get_result
  synchronize(before: :WAIT_RESULT, error: :READY) do
    res_packet = ResultPacket.parse read
    @field_count = res_packet.field_count
    if @field_count.to_i > 0  # result data exists
      set_state :FIELD
      return @field_count
    end
    if @field_count.nil?      # LOAD DATA LOCAL INFILE
      send_local_file(res_packet.message)
      res_packet = ResultPacket.parse read
    end
    @affected_rows, @insert_id, @server_status, @warning_count, @message, @session_track =
      res_packet.affected_rows, res_packet.insert_id, res_packet.server_status, res_packet.warning_count, res_packet.message, res_packet.session_track
    set_state :READY unless more_results?
    return nil
  end
end

#kill_command(pid) ⇒ Object

Kill command



401
402
403
# File 'lib/mysql/protocol.rb', line 401

def kill_command(pid)
  simple_command [COM_PROCESS_KILL, pid].pack("CV")
end

#more_results?Boolean

Returns:



323
324
325
# File 'lib/mysql/protocol.rb', line 323

def more_results?
  @server_status & SERVER_MORE_RESULTS_EXISTS != 0
end

#ping_commandObject

Ping command



396
397
398
# File 'lib/mysql/protocol.rb', line 396

def ping_command
  simple_command [COM_PING].pack("C")
end

#query_command(query) ⇒ Object

Query command

Parameters:

  • query string



295
296
297
298
299
300
# File 'lib/mysql/protocol.rb', line 295

def query_command(query)
  synchronize(before: :READY, after: :WAIT_RESULT, error: :READY) do
    reset
    write [COM_QUERY, @charset.convert(query)].pack("Ca*")
  end
end

#quit_commandObject

Quit command



281
282
283
284
285
286
287
288
289
290
291
# File 'lib/mysql/protocol.rb', line 281

def quit_command
  get_result if @state == :WAIT_RESULT
  retr_fields if @state == :FIELD
  retr_all_records(RawRecord) if @state == :RESULT
  synchronize(before: :READY, after: :CLOSED) do
    reset
    write [COM_QUIT].pack("C")
    close
    @gc_stmt_queue.clear
  end
end

#readPacket

Read one packet data

Returns:

  • packet data



516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
# File 'lib/mysql/protocol.rb', line 516

def read
  data = +''
  len = nil
  begin
    timeout = @state == :INIT ? @opts[:connect_timeout] : @opts[:read_timeout]
    header = read_timeout(4, timeout)
    raise EOFError unless header && header.length == 4
    len1, len2, seq = header.unpack("CvC")
    len = (len2 << 8) + len1
    raise ProtocolError, "invalid packet: sequence number mismatch(#{seq} != #{@seq}(expected))" if @seq != seq
    @seq = (@seq + 1) % 256
    ret = read_timeout(len, timeout)
    raise EOFError unless ret && ret.length == len
    data.concat ret
  rescue EOFError, OpenSSL::SSL::SSLError
    close
    raise ClientError::ServerLost, 'Lost connection to server during query'
  rescue Errno::ETIMEDOUT
    raise ClientError, "read timeout"
  end while len == MAX_PACKET_LENGTH

  @sqlstate = "00000"

  # Error packet
  if data[0] == ?\xff
    _, errno, marker, @sqlstate, message = data.unpack("Cvaa5a*")
    unless marker == "#"
      _, errno, message = data.unpack("Cva*")    # Version 4.0 Error
      @sqlstate = ""
    end
    @server_status &= ~SERVER_MORE_RESULTS_EXISTS
    message.force_encoding(@charset.encoding)
    if Mysql::ServerError::ERROR_MAP.key? errno
      raise Mysql::ServerError::ERROR_MAP[errno].new(message, @sqlstate)
    end
    raise Mysql::ServerError.new(message, @sqlstate, errno)
  end
  Packet.new(data)
end

#read_eof_packetObject

Read EOF packet

Raises:

  • packet is not EOF



624
625
626
627
628
629
630
# File 'lib/mysql/protocol.rb', line 624

def read_eof_packet
  pkt = read
  raise ProtocolError, "packet is not EOF" unless pkt.eof?
  pkt.utiny  # 0xFE
  _warnings = pkt.ushort
  @server_status = pkt.ushort
end

#read_timeout(len, timeout) ⇒ Object



556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
# File 'lib/mysql/protocol.rb', line 556

def read_timeout(len, timeout)
  return @socket.read(len) if timeout.nil? || timeout == 0
  result = +''
  e = Time.now + timeout
  while result.size < len
    now = Time.now
    raise Errno::ETIMEDOUT if now > e
    r = @socket.read_nonblock(len - result.size, exception: false)
    case r
    when :wait_readable
      IO.select([@socket], nil, nil, e - now)  # rubocop:disable Lint/IncompatibleIoSelectWithFiberScheduler
      next
    when :wait_writable
      IO.select(nil, [@socket], nil, e - now)  # rubocop:disable Lint/IncompatibleIoSelectWithFiberScheduler
      next
    else
      result << r
    end
  end
  return result
end

#refresh_command(op) ⇒ Object

Refresh command



406
407
408
# File 'lib/mysql/protocol.rb', line 406

def refresh_command(op)
  simple_command [COM_REFRESH, op].pack("CC")
end

#resetObject

Reset sequence number



509
510
511
# File 'lib/mysql/protocol.rb', line 509

def reset
  @seq = 0    # packet counter. reset by each command
end

#retr_all_records(record_class) ⇒ Array<record_class>

Retrieve all records for simple query or prepared statement

Parameters:

Returns:

  • all records



376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
# File 'lib/mysql/protocol.rb', line 376

def retr_all_records(record_class)
  synchronize(before: :RESULT) do
    enc = charset.encoding
    begin
      all_recs = []
      until (pkt = read).eof?
        all_recs.push record_class.new(pkt, @fields, enc)
      end
      pkt.utiny  # 0xFE
      _warnings = pkt.ushort
      @server_status = pkt.ushort
      @no_more_records = true
      all_recs
    ensure
      set_state(more_results? ? :WAIT_RESULT : :READY)
    end
  end
end

#retr_fieldsArray<Mysql::Field>

Retrieve n fields

Returns:

  • field list



342
343
344
345
346
347
348
349
# File 'lib/mysql/protocol.rb', line 342

def retr_fields
  synchronize(before: :FIELD, after: :RESULT, error: :READY) do
    @fields = @field_count.times.map{Field.new FieldPacket.parse(read)}
    read_eof_packet
    @no_more_records = false
    @fields
  end
end

#retr_record(record_class) ⇒ <record_class>?

Retrieve one record for simple query or prepared statement

Parameters:

Returns:

  • record

  • no more record



355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
# File 'lib/mysql/protocol.rb', line 355

def retr_record(record_class)
  return nil if @no_more_records
  synchronize(before: :RESULT) do
    enc = charset.encoding
    begin
      unless (pkt = read).eof?
        return record_class.new(pkt, @fields, enc)
      end
      pkt.utiny
      pkt.ushort
      @server_status = pkt.ushort
      set_state(more_results? ? :WAIT_RESULT : :READY)
      @no_more_records = true
      return nil
    end
  end
end

#send_local_file(filename) ⇒ Object

send local file to server



328
329
330
331
332
333
334
335
336
337
338
# File 'lib/mysql/protocol.rb', line 328

def send_local_file(filename)
  filename = File.absolute_path(filename)
  if @opts[:local_infile] || @opts[:load_data_local_dir] && filename.start_with?(@opts[:load_data_local_dir])
    File.open(filename){|f| write f}
    write nil  # EOF
  else
    write nil  # send empty data instead of file contents
    read       # result packet
    raise ClientError::LoadDataLocalInfileRejected, 'LOAD DATA LOCAL INFILE file request rejected due to restrictions on access.'
  end
end

#set_option_command(opt) ⇒ Object

Set option command



411
412
413
# File 'lib/mysql/protocol.rb', line 411

def set_option_command(opt)
  simple_command [COM_SET_OPTION, opt].pack("Cv")
end

#set_state(st) ⇒ Object



479
480
481
482
483
484
485
486
487
488
489
490
491
# File 'lib/mysql/protocol.rb', line 479

def set_state(st)
  @state = st
  return if st != :READY || @gc_stmt_queue.empty? || @socket&.closed?
  gc_disabled = GC.disable
  begin
    while (st = @gc_stmt_queue.shift)
      reset
      write [COM_STMT_CLOSE, st].pack("CV")
    end
  ensure
    GC.enable unless gc_disabled
  end
end

#shutdown_command(level) ⇒ Object

Shutdown command



416
417
418
# File 'lib/mysql/protocol.rb', line 416

def shutdown_command(level)
  simple_command [COM_SHUTDOWN, level].pack("CC")
end

#simple_command(packet) ⇒ String

Send simple command

Parameters:

  • String

    packet data

Returns:

  • received data



635
636
637
638
639
640
641
# File 'lib/mysql/protocol.rb', line 635

def simple_command(packet)
  synchronize(before: :READY, after: :READY) do
    reset
    write packet
    read.to_s
  end
end

#ssl_cipherObject



276
277
278
# File 'lib/mysql/protocol.rb', line 276

def ssl_cipher
  @client_flags.allbits?(CLIENT_SSL) ? @socket.cipher : nil
end

#statistics_commandObject

Statistics command



421
422
423
# File 'lib/mysql/protocol.rb', line 421

def statistics_command
  simple_command [COM_STATISTICS].pack("C")
end

#stmt_close_command(stmt_id) ⇒ Object

Stmt close command

Parameters:

  • statement id



460
461
462
463
464
465
466
467
468
469
# File 'lib/mysql/protocol.rb', line 460

def stmt_close_command(stmt_id)
  get_result if @state == :WAIT_RESULT
  retr_fields if @state == :FIELD
  retr_all_records(StmtRawRecord) if @state == :RESULT
  synchronize(before: :READY, after: :READY) do
    reset
    write [COM_STMT_CLOSE, stmt_id].pack("CV")
    @gc_stmt_queue.delete stmt_id
  end
end

#stmt_execute_command(stmt_id, values) ⇒ Integer

Stmt execute command

Parameters:

  • statement id

  • parameters

Returns:

  • number of fields



451
452
453
454
455
456
# File 'lib/mysql/protocol.rb', line 451

def stmt_execute_command(stmt_id, values)
  synchronize(before: :READY, after: :WAIT_RESULT, error: :READY) do
    reset
    write ExecutePacket.serialize(stmt_id, Mysql::Stmt::CURSOR_TYPE_NO_CURSOR, values)
  end
end

#stmt_prepare_command(stmt) ⇒ Array<Integer, Integer, Array<Field>>

Stmt prepare command

Parameters:

  • prepared statement

Returns:

  • statement id, number of parameters, field list



428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
# File 'lib/mysql/protocol.rb', line 428

def stmt_prepare_command(stmt)
  synchronize(before: :READY, after: :READY) do
    reset
    write [COM_STMT_PREPARE, charset.convert(stmt)].pack("Ca*")
    res_packet = PrepareResultPacket.parse read
    if res_packet.param_count > 0
      res_packet.param_count.times{read}    # skip parameter packet
      read_eof_packet
    end
    if res_packet.field_count > 0
      fields = res_packet.field_count.times.map{Field.new FieldPacket.parse(read)}
      read_eof_packet
    else
      fields = []
    end
    return res_packet.statement_id, res_packet.param_count, fields
  end
end

#synchronize(before: nil, after: nil, error: nil) ⇒ Object



493
494
495
496
497
498
499
500
501
502
503
504
505
506
# File 'lib/mysql/protocol.rb', line 493

def synchronize(before: nil, after: nil, error: nil)
  @mutex.synchronize do
    check_state before if before
    begin
      return yield
    rescue
      set_state error if error
      raised = true
      raise
    ensure
      set_state after if after && !raised
    end
  end
end

#write(data) ⇒ Object

Write one packet data

Parameters:

  • packet data. If data is nil, write empty packet.



580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
# File 'lib/mysql/protocol.rb', line 580

def write(data)
  timeout = @state == :INIT ? @opts[:connect_timeout] : @opts[:write_timeout]
  @socket.sync = false
  if data.nil?
    write_timeout([0, 0, @seq].pack("CvC"), timeout)
    @seq = (@seq + 1) % 256
  else
    data = StringIO.new data if data.is_a? String
    while (d = data.read(MAX_PACKET_LENGTH))
      write_timeout([d.length%256, d.length/256, @seq].pack("CvC")+d, timeout)
      @seq = (@seq + 1) % 256
    end
  end
  @socket.sync = true
  @socket.flush
rescue Errno::EPIPE, OpenSSL::SSL::SSLError
  close
  raise ClientError::ServerGoneError, 'MySQL server has gone away'
rescue Errno::ETIMEDOUT
  raise ClientError, "write timeout"
end

#write_timeout(data, timeout) ⇒ Object



602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
# File 'lib/mysql/protocol.rb', line 602

def write_timeout(data, timeout)
  return @socket.write(data) if timeout.nil? || timeout == 0
  len = 0
  e = Time.now + timeout
  while len < data.size
    now = Time.now
    raise Errno::ETIMEDOUT if now > e
    l = @socket.write_nonblock(data[len..], exception: false)
    case l
    when :wait_readable
      IO.select([@socket], nil, nil, e - now)  # rubocop:disable Lint/IncompatibleIoSelectWithFiberScheduler
    when :wait_writable
      IO.select(nil, [@socket], nil, e - now)  # rubocop:disable Lint/IncompatibleIoSelectWithFiberScheduler
    else
      len += l
    end
  end
  return len
end