(六)Go-编写MySQL数据库驱动之MySQL协议解析Result Set消息结构

· 1243字 · 3分钟 · 阅读量

准备:

结构 说明
[Result Set Header] 列数量
[Field] 列信息(多个)
[EOF] 列结束
[Row Data] 行数据(多个)
[EOF] 数据结束

Result Set Header 结构 🔗

字节 说明
1-9 Field结构计数(Length Coded Binary)用于标识Field结构的数量,取值范围0x00-0xFA。LengthEncodeInteger表示结果中含有列数量。Number of fields显示该返回字段列数量。
1-9 额外信息(Length Coded Binary)可选字段,一般情况下不应该出现。只有像SHOW COLUMNS这种语句的执行结果才会用到额外信息(标识表格的列数量)。

Img

Field 结构 🔗

该包前四个字节表示的包的字节长度以及包的序列号。后四字节为以下内容。

MySQL 4.1 及之后的版本:

相对包内容的位置 长度(字节) 名称 描述
0 不定长 catalog 目录,通常为def
不定长 不定长 schema 操作的数据库
不定长 不定长 table 操作的虚拟表名
不定长 不定长 org_table 操作的物理表名
不定长 不定长 name 虚拟列字段名
不定长 不定长 org_name 物理列字段名
不定长 0x0c length of fixed-length fields 以下字段长度
不定长 2 character 列字符集
不定长 4 length 字段最大长度
不定长 1 type 字段类型
不定长 2 flags 标志
不定长 1 decimals ??
不定长 2 预留字节数 预留字节数

Img

Go代码实现:

type ResultField struct {
	DefLen       uint16
	Def          string
	PreFixLen    uint16
	Database     string
	TableLen     uint16
	TableName    string //操作的虚拟表名
	OrgTableLen  uint16
	OrgTableName string //操作的物理表名
	FieldLen     uint16
	FieldName    string //操作的虚拟字段名
	OrgFieldLen  uint16
	OrgFieldName string //操作的物理字段名
}

func (s *SelectInfo) ResultSetField(data []byte, fieldsInfo *FieldsInfo) {
	var idx uint16 = 1
	defLen := make([]byte, 2)
	copy(defLen, data[:idx])

	s.ResultField.DefLen = binary.LittleEndian.Uint16(defLen)

	idx = idx + s.ResultField.DefLen
	s.ResultField.Def = string(data[1:idx])

	//------------------------------------------
	preFixLen := make([]byte, 2)

	copy(preFixLen, data[idx:idx+1])
	s.ResultField.PreFixLen = binary.LittleEndian.Uint16(preFixLen)
	idx++
	s.ResultField.Database = string(data[idx : idx+s.ResultField.PreFixLen])

	//------------------------------------------
	tableLen := make([]byte, 2)
	idx = idx + s.ResultField.PreFixLen
	copy(tableLen, data[idx:idx+1])

	s.ResultField.TableLen = binary.LittleEndian.Uint16(tableLen)
	idx++
	s.ResultField.TableName = string(data[idx : idx+s.ResultField.TableLen])

	//------------------------------------------
	orgTableLen := make([]byte, 2)
	idx = idx + s.ResultField.TableLen
	copy(orgTableLen, data[idx:idx+1])

	s.ResultField.OrgTableLen = binary.LittleEndian.Uint16(orgTableLen)
	idx++
	s.ResultField.OrgTableName = string(data[idx : idx+s.ResultField.OrgTableLen])

	//------------------------------------------
	fieldLen := make([]byte, 2)
	idx = idx + s.ResultField.OrgTableLen
	copy(fieldLen, data[idx:idx+1])

	s.ResultField.FieldLen = binary.LittleEndian.Uint16(fieldLen)
	idx++
	s.ResultField.FieldName = string(data[idx : idx+s.ResultField.FieldLen])

	//------------------------------------------
	orgFieldLen := make([]byte, 2)
	idx = idx + s.ResultField.FieldLen
	copy(orgFieldLen, data[idx:idx+1])

	s.ResultField.OrgFieldLen = binary.LittleEndian.Uint16(orgFieldLen)
	idx++
	s.ResultField.OrgFieldName = string(data[idx : idx+s.ResultField.OrgFieldLen])

	fieldsInfo.FieldMap = append(fieldsInfo.FieldMap, s.ResultField.OrgFieldName)
	//fmt.Println(s.ResultField.OrgFieldName)
}

EOF 结构 🔗

MySQL 4.1 及之后的版本:

相对包内容的位置 长度(字节) 名称 描述
0 1 包头标识 0xFE 代表这是一个EOF 包
1 2 警告数 上次命令引起的警告数
3 2 服务器状态 服务器状态

告警计数:服务器告警数量,在所有数据都发送给客户端后该值才有效。

状态标志位:包含类似SERVER_MORE_RESULTS_EXISTS这样的标志位。

注: 由于EOF值与其它Result Set结构共用1字节,所以在收到报文后需要对EOF包的真实性进行校验,校验条件为:

第1字节值为0xFE 包长度小于9字节 附:EOF结构的相关处理函数:

服务器:protocol.cc源文件中的send_eof函数

Img

Go代码实现:

type Eof struct {
	ResponseCode []byte
	EofMarker    uint16
	ServerStatus uint16
	Payload      uint16
}

func NewEof() *Eof {
	return &Eof{}
}
func (e *Eof) Eof(packet []byte) {
	e.ResponseCode = packet[0:1]

	eofMarker := make([]byte, 2)
	copy(eofMarker, e.ResponseCode)
	e.EofMarker = binary.LittleEndian.Uint16(eofMarker)

	e.ServerStatus = binary.LittleEndian.Uint16(packet[1:3])

	e.Payload = binary.LittleEndian.Uint16(packet[3:5])
	//fmt.Println(e)
}

Row Data 结构 🔗

Row Data含着的是我们需要获取的数据,一个Result Set包里面包含着多个Row Data结构。每一个值前面有长度字节值,可以帮助我们区分开不同的列。

相对包内容的位置 长度(字节) 名称 描述
0 不定 length 这个字段的数据长度
不定 不定 text 这个字段的值

Img

Go代码实现:

func NewRowPacket() *Row {
	return &Row{RowList: nil}
}

type Row struct {
	RowList []string
}

func (r *Row) RowPacket(mysql *server.Mysql) {
	for {
		packetData := mysql.Payload()

		packetType := hex.EncodeToString(packetData[:1])
		if packetType == "fe" { //需要做一个eof判断是否结束
			return
		}

		rowIdx = 0 //初始化一下
		r.row(packetData)
	}
}

var rowIdx uint16

func (r *Row) row(packetData []byte) {

	if int(rowIdx) >= len(packetData) {
		return
	}

	lengthBytes := make([]byte, 2)
	copy(lengthBytes, packetData[rowIdx:rowIdx+1])
	length := binary.LittleEndian.Uint16(lengthBytes)

	if int(rowIdx+1+length) > len(packetData) {
		return
	}

	text := packetData[rowIdx+1 : rowIdx+1+length]

	rowIdx = rowIdx + 1 + length

	r.RowList = append(r.RowList, fmt.Sprintf("%s", text))
	r.row(packetData)
}