class Mysql::Protocol
MySQL network protocol
Constants
- MAX_PACKET_LENGTH
- VERSION
Attributes
Public Class Methods
Convert netdata to Ruby value
Argument¶ ↑
- data
Packet
-
packet data
- type
- Integer
-
field type
- unsigned
- true or false
-
true if value is unsigned
Return¶ ↑
Object
-
converted value.
# File lib/vendor/mysql/protocol.rb, line 24 def self.net2value(pkt, type, unsigned) case type when Field::TYPE_STRING, Field::TYPE_VAR_STRING, Field::TYPE_NEWDECIMAL, Field::TYPE_BLOB return pkt.lcs 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).unpack('e').first when Field::TYPE_DOUBLE return pkt.read(8).unpack('E').first when Field::TYPE_DATE len = pkt.utiny y, m, d = pkt.read(len).unpack("vCC") t = Mysql::Time.new(y, m, d, nil, nil, 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 Mysql::Time.new(y, m, d, h, mi, s, false, sp) when Field::TYPE_TIME len = pkt.utiny sign, d, h, mi, s, sp = pkt.read(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 pkt.ushort when Field::TYPE_BIT return pkt.lcs else raise "not implemented: type=#{type}" end end
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).
Exception¶ ↑
ClientError
-
connection timeout
# File lib/vendor/mysql/protocol.rb, line 139 def initialize(host, port, socket, conn_timeout, read_timeout, write_timeout) @insert_id = 0 @warning_count = 0 @gc_stmt_queue = [] # stmt id list which GC destroy. set_state :INIT @read_timeout = read_timeout @write_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 end
convert Ruby value to netdata
Argument¶ ↑
- v
Object
-
Ruby value.
Return¶ ↑
- Integer
-
type of column. Field::TYPE_*
- String
-
netdata
Exception¶ ↑
ProtocolError
-
value too large / value is not supported
# File lib/vendor/mysql/protocol.rb, line 76 def self.value2net(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 raise ProtocolError, "value too large: #{v}" end 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 Mysql::Time type = Field::TYPE_DATETIME val = [11, v.year, v.month, v.day, v.hour, v.min, v.sec, v.second_part].pack("CvCCCCCV") else raise ProtocolError, "class #{v.class} is not supported" end return type, val end
Public Instance Methods
initial negotiate and authenticate.
Argument¶ ↑
- user
- String / nil
-
username
- passwd
- String / nil
-
password
- db
- String / nil
-
default database name. nil: no default.
- flag
- Integer
-
client flag
- charset
Mysql::Charset
/ nil-
charset for connection. nil: use server's charset
Exception¶ ↑
ProtocolError
-
The old style password is not supported
# File lib/vendor/mysql/protocol.rb, line 174 def authenticate(user, passwd, db, flag, charset) check_state :INIT @authinfo = [user, passwd, db, flag, charset] 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} @thread_id = init_packet.thread_id client_flags = CLIENT_LONG_PASSWORD | CLIENT_LONG_FLAG | CLIENT_TRANSACTIONS | CLIENT_PROTOCOL_41 | CLIENT_SECURE_CONNECTION client_flags |= CLIENT_CONNECT_WITH_DB if db client_flags |= flag @charset = charset unless @charset @charset = Charset.by_number(init_packet.server_charset) @charset.encoding # raise error if unsupported charset end netpw = encrypt_password passwd, init_packet.scramble_buff write AuthenticationPacket.serialize(client_flags, 1024**3, @charset.number, user, netpw, db) raise ProtocolError, 'The old style password is not supported' if read.to_s == "\xfe" set_state :READY end
# File lib/vendor/mysql/protocol.rb, line 161 def close @sock.close end
Field
list command
Argument¶ ↑
- table
- String
-
table name.
- field
- String / nil
-
field name that may contain wild card.
Return¶ ↑
- Array of
Field
-
field list
# File lib/vendor/mysql/protocol.rb, line 293 def field_list_command(table, field) synchronize do reset write [COM_FIELD_LIST, table, 0, field].pack("Ca*Ca*") fields = [] until (data = read).eof? fields.push Field.new(FieldPacket.parse(data)) end return fields end end
# File lib/vendor/mysql/protocol.rb, line 428 def gc_stmt(stmt_id) @gc_stmt_queue.push stmt_id end
get result of query.
Return¶ ↑
- integer / nil
-
number of fields of results. nil if no results.
# File lib/vendor/mysql/protocol.rb, line 225 def get_result begin res_packet = ResultPacket.parse read if res_packet.field_count.to_i > 0 # result data exists set_state :FIELD return res_packet.field_count end if res_packet.field_count.nil? # LOAD DATA LOCAL INFILE filename = res_packet.message File.open(filename){|f| write f} write nil # EOF mark read end @affected_rows, @insert_id, @server_status, @warning_count, @message = res_packet.affected_rows, res_packet.insert_id, res_packet.server_status, res_packet.warning_count, res_packet.message set_state :READY return nil rescue set_state :READY raise end end
Kill command
# File lib/vendor/mysql/protocol.rb, line 330 def kill_command(pid) simple_command [COM_PROCESS_KILL, pid].pack("CV") end
Ping command
# File lib/vendor/mysql/protocol.rb, line 325 def ping_command simple_command [COM_PING].pack("C") end
Process info command
Return¶ ↑
- Array of
Field
-
field list
# File lib/vendor/mysql/protocol.rb, line 308 def process_info_command check_state :READY begin reset write [COM_PROCESS_INFO].pack("C") field_count = read.lcb fields = field_count.times.map{Field.new FieldPacket.parse(read)} read_eof_packet set_state :RESULT return fields rescue set_state :READY raise end end
Query command
Argument¶ ↑
- query
- String
-
query string
Return¶ ↑
- Integer / nil
-
number of fields of results. nil if no results.
# File lib/vendor/mysql/protocol.rb, line 210 def query_command(query) check_state :READY begin reset write [COM_QUERY, @charset.convert(query)].pack("Ca*") get_result rescue set_state :READY raise end end
Quit command
# File lib/vendor/mysql/protocol.rb, line 197 def quit_command synchronize do reset write [COM_QUIT].pack("C") close end end
Refresh command
# File lib/vendor/mysql/protocol.rb, line 335 def refresh_command(op) simple_command [COM_REFRESH, op].pack("CC") end
Retrieve all records for simple query
Argument¶ ↑
- fields
- Array<Mysql::Field>
-
number of fields
Return¶ ↑
- Array of Array of String
-
all records
# File lib/vendor/mysql/protocol.rb, line 271 def retr_all_records(fields) check_state :RESULT enc = charset.encoding begin all_recs = [] until (pkt = read).eof? all_recs.push RawRecord.new(pkt, fields, enc) end pkt.read(3) @server_status = pkt.utiny all_recs ensure set_state :READY end end
Retrieve n fields
Argument¶ ↑
- n
- Integer
-
number of fields
Return¶ ↑
- Array of
Mysql::Field
-
field list
# File lib/vendor/mysql/protocol.rb, line 253 def retr_fields(n) check_state :FIELD begin fields = n.times.map{Field.new FieldPacket.parse(read)} read_eof_packet set_state :RESULT fields rescue set_state :READY raise end end
Set option command
# File lib/vendor/mysql/protocol.rb, line 340 def set_option_command(opt) simple_command [COM_SET_OPTION, opt].pack("Cv") end
Shutdown command
# File lib/vendor/mysql/protocol.rb, line 345 def shutdown_command(level) simple_command [COM_SHUTDOWN, level].pack("CC") end
Statistics command
# File lib/vendor/mysql/protocol.rb, line 350 def statistics_command simple_command [COM_STATISTICS].pack("C") end
Stmt
execute command
Argument¶ ↑
- stmt_id
- Integer
-
statement id
- values
- Array
-
parameters
Return¶ ↑
- Integer
-
number of fields
# File lib/vendor/mysql/protocol.rb, line 386 def stmt_execute_command(stmt_id, values) check_state :READY begin reset write ExecutePacket.serialize(stmt_id, Mysql::Stmt::CURSOR_TYPE_NO_CURSOR, values) get_result rescue set_state :READY raise end end
Stmt
prepare command
Argument¶ ↑
- stmt
- String
-
prepared statement
Return¶ ↑
- Integer
-
statement id
- Integer
-
number of parameters
- Array of
Field
-
field list
# File lib/vendor/mysql/protocol.rb, line 361 def stmt_prepare_command(stmt) synchronize 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
Retrieve all records for prepared statement
Argument¶ ↑
- fields
- Array of Mysql::Fields
-
field list
- charset
Return¶ ↑
- Array of Array of
Object
-
all records
# File lib/vendor/mysql/protocol.rb, line 404 def stmt_retr_all_records(fields, charset) check_state :RESULT enc = charset.encoding begin all_recs = [] until (pkt = read).eof? all_recs.push StmtRawRecord.new(pkt, fields, enc) end all_recs ensure set_state :READY end end
Private Instance Methods
# File lib/vendor/mysql/protocol.rb, line 434 def check_state(st) raise 'command out of sync' unless @state == st end
Encrypt password
Argument¶ ↑
- plain
- String
-
plain password.
- scramble
- String
-
scramble code from initial packet.
Return¶ ↑
- String
-
encrypted password
# File lib/vendor/mysql/protocol.rb, line 569 def encrypt_password(plain, scramble) return "" if plain.nil? or plain.empty? hash_stage1 = Digest::SHA1.digest plain hash_stage2 = Digest::SHA1.digest hash_stage1 return hash_stage1.unpack("C*").zip(Digest::SHA1.digest(scramble+hash_stage2).unpack("C*")).map{|a,b| a^b}.pack("C*") end
Read one packet data
Return¶ ↑
Packet
-
packet data
Exception¶ ↑
ProtocolError
-
invalid packet sequence number
# File lib/vendor/mysql/protocol.rb, line 472 def read data = '' len = nil begin Timeout.timeout @read_timeout do header = @sock.read(4) 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 = @sock.read(len) raise EOFError unless ret && ret.length == len data.concat ret end rescue EOFError raise ClientError::ServerGoneError, 'MySQL server has gone away' rescue Timeout::Error raise ClientError, "read timeout" end while len == MAX_PACKET_LENGTH @sqlstate = "00000" # Error packet if data[0] == ?\xff f, errno, marker, @sqlstate, message = data.unpack("Cvaa5a*") unless marker == "#" f, errno, message = data.unpack("Cva*") # Version 4.0 Error @sqlstate = "" end 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) end Packet.new(data) end
Read EOF packet
Exception¶ ↑
ProtocolError
-
packet is not EOF
# File lib/vendor/mysql/protocol.rb, line 546 def read_eof_packet raise ProtocolError, "packet is not EOF" unless read.eof? end
Reset sequence number
# File lib/vendor/mysql/protocol.rb, line 463 def reset @seq = 0 # packet counter. reset by each command end
# File lib/vendor/mysql/protocol.rb, line 438 def set_state(st) @state = st if st == :READY 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 end
# File lib/vendor/mysql/protocol.rb, line 453 def synchronize begin check_state :READY return yield ensure set_state :READY end end
Write one packet data
Argument¶ ↑
- data
- String / IO
-
packet data. If data is nil, write empty packet.
# File lib/vendor/mysql/protocol.rb, line 514 def write(data) begin @sock.sync = false if data.nil? Timeout.timeout @write_timeout do @sock.write [0, 0, @seq].pack("CvC") end @seq = (@seq + 1) % 256 else 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 end @sock.sync = true Timeout.timeout @write_timeout do @sock.flush end rescue Errno::EPIPE raise ClientError::ServerGoneError, 'MySQL server has gone away' rescue Timeout::Error raise ClientError, "write timeout" end end