Class: Mysql::Protocol

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

Overview

MySQL network protocol

Defined Under Namespace

Classes: AuthenticationPacket, ExecutePacket, FetchPacket, FieldListPacket, FieldPacket, InitialPacket, PreparePacket, PrepareResultPacket, QueryPacket, QuitPacket, ResultPacket, RxPacket, StmtClosePacket, TxPacket

Constant Summary collapse

VERSION =
10
MAX_PACKET_LENGTH =
2**24-1

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(host, port, socket, conn_timeout, read_timeout, write_timeout) ⇒ Protocol

make socket connection to server.

Argument

host
String

if “localhost” or “” nil then use UNIXSocket. Otherwise use TCPSocket

port
Integer

port number using by TCPSocket

socket
String

socket file name using by UNIXSocket

conn_timeout
Integer

connect timeout (sec).

read_timeout
Integer

read timeout (sec).

write_timeout
Integer

write timeout (sec).



186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
# File 'lib/mysql/protocol.rb', line 186

def initialize(host, port, socket, conn_timeout, read_timeout, write_timeout)
  begin
    Timeout.timeout conn_timeout do
      if host.nil? or host.empty? or host == "localhost"
        socket ||= ENV["MYSQL_UNIX_PORT"] || MYSQL_UNIX_PORT
        @sock = UNIXSocket.new socket
      else
        port ||= ENV["MYSQL_TCP_PORT"] || (Socket.getservbyname("mysql","tcp") rescue MYSQL_TCP_PORT)
        @sock = TCPSocket.new host, port
      end
    end
  rescue Timeout::Error
    raise ClientError, "connection timeout"
  end
  @read_timeout = read_timeout
  @write_timeout = write_timeout
  @seq = 0                # packet counter. reset by each command
  @mutex = Mutex.new
end

Instance Attribute Details

#sqlstateObject (readonly)

Returns the value of attribute sqlstate.



176
177
178
# File 'lib/mysql/protocol.rb', line 176

def sqlstate
  @sqlstate
end

Class Method Details

.eof_packet?(data) ⇒ Boolean

Returns:

  • (Boolean)


65
66
67
# File 'lib/mysql/protocol.rb', line 65

def self.eof_packet?(data)
  data[0] == ?\xfe && data.length == 5
end

.lcb(num) ⇒ Object

convert Numeric to LengthCodedBinary



18
19
20
21
22
23
24
# File 'lib/mysql/protocol.rb', line 18

def self.lcb(num)
  return "\xfb" if num.nil?
  return [num].pack("C") if num < 251
  return [252, num].pack("Cv") if num < 65536
  return [253, num&0xffff, num>>16].pack("CvC") if num < 16777216
  return [254, num&0xffffffff, num>>32].pack("CVV")
end

.lcb2int!(lcb) ⇒ Object

convert LengthCodedBinary to Integer

Argument

lcb
String

LengthCodedBinary. This value will be broken.

Return

Integer or nil



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/mysql/protocol.rb', line 37

def self.lcb2int!(lcb)
  return nil if lcb.empty?
  case v = lcb.slice!(0)
  when ?\xfb
    return nil
  when ?\xfc
    return lcb.slice!(0,2).unpack("v").first
  when ?\xfd
    c, v = lcb.slice!(0,3).unpack("Cv")
    return (v<<8)+c
  when ?\xfe
    v1, v2 = lcb.slice!(0,8).unpack("VV")
    return (v2<<32)+v1
  else
    return v.ord
  end
end

.lcs(str) ⇒ Object

convert String to LengthCodedString



27
28
29
30
# File 'lib/mysql/protocol.rb', line 27

def self.lcs(str)
  str = Charset.to_binary str
  lcb(str.length)+str
end

.lcs2str!(lcs) ⇒ Object

convert LengthCodedString to String

Argument

lcs
String

LengthCodedString. This value will be broken.

Return

String or nil



60
61
62
63
# File 'lib/mysql/protocol.rb', line 60

def self.lcs2str!(lcs)
  len = lcb2int! lcs
  return len && lcs.slice!(0, len)
end

.net2value(data, type, unsigned) ⇒ Object

Convert netdata to Ruby value

Argument

data
String

packet data. This will be broken.

type
Integer

field type

unsigned
true or false

true if value is unsigned

Return

Object

converted value.



76
77
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
# File 'lib/mysql/protocol.rb', line 76

def self.net2value(data, type, unsigned)
  case type
  when Field::TYPE_STRING, Field::TYPE_VAR_STRING, Field::TYPE_NEWDECIMAL, Field::TYPE_BLOB
    return Protocol.lcs2str!(data)
  when Field::TYPE_TINY
    v = data.slice!(0).ord
    return unsigned ? v : v < 128 ? v : v-256
  when Field::TYPE_SHORT
    v = data.slice!(0,2).unpack("v").first
    return unsigned ? v : v < 32768 ? v : v-65536
  when Field::TYPE_INT24, Field::TYPE_LONG
    v = data.slice!(0,4).unpack("V").first
    return unsigned ? v : v < 2**32/2 ? v : v-2**32
  when Field::TYPE_LONGLONG
    n1, n2 = data.slice!(0,8).unpack("VV")
    v = (n2<<32) | n1
    return unsigned ? v : v < 2**64/2 ? v : v-2**64
  when Field::TYPE_FLOAT
    return data.slice!(0,4).unpack("e").first
  when Field::TYPE_DOUBLE
    return data.slice!(0,8).unpack("E").first
  when Field::TYPE_DATE, Field::TYPE_DATETIME, Field::TYPE_TIMESTAMP
    len = data.slice!(0).ord
    y, m, d, h, mi, s, bs = data.slice!(0,len).unpack("vCCCCCV")
    return Mysql::Time.new(y, m, d, h, mi, s, bs)
  when Field::TYPE_TIME
    len = data.slice!(0).ord
    sign, d, h, mi, s, sp = data.slice!(0,len).unpack("CVCCCV")
    h = d.to_i * 24 + h.to_i
    return Mysql::Time.new(0, 0, 0, h, mi, s, sign!=0, sp)
  when Field::TYPE_YEAR
    return data.slice!(0,2).unpack("v").first
  when Field::TYPE_BIT
    return Protocol.lcs2str!(data)
  else
    raise "not implemented: type=#{type}"
  end
end

.value2net(v) ⇒ Object

convert Ruby value to netdata

Argument

v
Object

Ruby value.

Return

String

netdata

Exception

ProtocolError

value too large / value is not supported



122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/mysql/protocol.rb', line 122

def self.value2net(v)
  case v
  when nil
    type = Field::TYPE_NULL
    val = ""
  when Integer
    if v >= 0
      if v < 256
        type = Field::TYPE_TINY | 0x8000
        val = [v].pack("C")
      elsif v < 256**2
        type = Field::TYPE_SHORT | 0x8000
        val = [v].pack("v")
      elsif v < 256**4
        type = Field::TYPE_LONG | 0x8000
        val = [v].pack("V")
      elsif v < 256**8
        type = Field::TYPE_LONGLONG | 0x8000
        val = [v&0xffffffff, v>>32].pack("VV")
      else
        raise ProtocolError, "value too large: #{v}"
      end
    else
      if -v <= 256/2
        type = Field::TYPE_TINY
        val = [v].pack("C")
      elsif -v <= 256**2/2
        type = Field::TYPE_SHORT
        val = [v].pack("v")
      elsif -v <= 256**4/2
        type = Field::TYPE_LONG
        val = [v].pack("V")
      elsif -v <= 256**8/2
        type = Field::TYPE_LONGLONG
        val = [v&0xffffffff, v>>32].pack("VV")
      else
        raise ProtocolError, "value too large: #{v}"
      end
    end
  when Float
    type = Field::TYPE_DOUBLE
    val = [v].pack("E")
  when String
    type = Field::TYPE_STRING
    val = Protocol.lcs(v)
  when Mysql::Time, ::Time
    type = Field::TYPE_DATETIME
    val = [7, v.year, v.month, v.day, v.hour, v.min, v.sec].pack("CvCCCCC")
  else
    raise ProtocolError, "class #{v.class} is not supported"
  end
  return type, val
end

Instance Method Details

#closeObject



206
207
208
# File 'lib/mysql/protocol.rb', line 206

def close
  @sock.close
end

#readObject

Read one packet data

Return

String

Exception

ProtocolError

invalid packet sequence number



226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
# File 'lib/mysql/protocol.rb', line 226

def read
  ret = ""
  len = nil
  begin
    Timeout.timeout @read_timeout do
      header = @sock.read(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.concat @sock.read(len)
    end
  rescue Timeout::Error
    raise ClientError, "read timeout"
  end while len == MAX_PACKET_LENGTH

  @sqlstate = "00000"

  # Error packet
  if ret[0] == ?\xff
    f, errno, marker, @sqlstate, message = ret.unpack("Cvaa5a*")
    unless marker == "#"
      f, errno, message = ret.unpack("Cva*")    # Version 4.0 Error
      @sqlstate = ""
    end
    if Mysql::ServerError::ERROR_MAP.key? errno
      raise Mysql::ServerError::ERROR_MAP[errno].new(message, @sqlstate)
    end
    raise Mysql::ServerError.new(message, @sqlstate)
  end
  ret
end

#read_eof_packetObject

Read EOF packet

Exception

ProtocolError

packet is not EOF

Raises:



292
293
294
295
# File 'lib/mysql/protocol.rb', line 292

def read_eof_packet
  data = read
  raise ProtocolError, "packet is not EOF" unless Protocol.eof_packet? data
end

#read_field_packetObject

Read field packet

Return

FieldPacket

packet data

Exception

ProtocolError

invalid packet



318
319
320
# File 'lib/mysql/protocol.rb', line 318

def read_field_packet
  FieldPacket.parse read
end

#read_initial_packetObject

Read initial packet

Return

InitialPacket

Exception

ProtocolError

invalid packet



302
303
304
# File 'lib/mysql/protocol.rb', line 302

def read_initial_packet
  InitialPacket.parse read
end

#read_prepare_result_packetObject

Read prepare result packet

Return

PrepareResultPacket

Exception

ProtocolError

invalid packet



327
328
329
# File 'lib/mysql/protocol.rb', line 327

def read_prepare_result_packet
  PrepareResultPacket.parse read
end

#read_result_packetObject

Read result packet

Return

ResultPacket


309
310
311
# File 'lib/mysql/protocol.rb', line 309

def read_result_packet
  ResultPacket.parse read
end

#resetObject

Reset sequence number



217
218
219
# File 'lib/mysql/protocol.rb', line 217

def reset
  @seq = 0
end

#send_packet(packet) ⇒ Object

Send one packet

Argument

packet
*Packet


285
286
287
# File 'lib/mysql/protocol.rb', line 285

def send_packet(packet)
  write packet.serialize
end

#synchronizeObject



210
211
212
213
214
# File 'lib/mysql/protocol.rb', line 210

def synchronize
  @mutex.synchronize do
    return yield
  end
end

#write(data) ⇒ Object

Write one packet data

Argument

data [String / IO]


262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
# File 'lib/mysql/protocol.rb', line 262

def write(data)
  begin
    @sock.sync = false
    data = StringIO.new data if data.is_a? String
    while d = data.read(MAX_PACKET_LENGTH)
      Timeout.timeout @write_timeout do
        @sock.write [d.length%256, d.length/256, @seq].pack("CvC")
        @sock.write d
      end
      @seq = (@seq + 1) % 256
    end
    @sock.sync = true
    Timeout.timeout @write_timeout do
      @sock.flush
    end
  rescue Timeout::Error
    raise ClientError, "write timeout"
  end
end