Mysql Resultset 解析记录

Mysql Resultset 解析记录

结果集消息头

消息头由消息体长度+消息序列号+消息体组成;消息头长度为3字节,消息序列号长度为1字节。

结果集的消息头消息体内容为结果集的列数。

结果集消息头的spicy^1^格式如下:

type header = unit {
    osize : uint8[3];
    seq : uint8;

    on %done {
        self.size = self.osize[2];
        self.size = self.size << 8;
        self.size = self.size + self.osize[1];
        self.size = self.size << 8;
        self.size = self.size + self.osize[0];

    }
    var size : uint32;
};

消息体的内容是结果集的列数,是一个整数;但是为了适配整数的范围,该参数采用了INT_ENC的表现形式,其定义格式如下:

type INT_ENC = unit {
    osize : uint8;
    i2 : uint16  &byte-order=spicy::ByteOrder::Little if ((self.osize & 0xff) == 252);
    i3 : uint8[3] if ((self.osize & 0xff) == 253); 
    i8 : uint64  &byte-order=spicy::ByteOrder::Little if ((self.osize & 0xff) == 254);
    inull : uint8[0] if ((self.osize & 0xff) == 251); 

    on osize {
        self.value = self.osize;
    }
    on i2 {
        self.value = self.i2;
    }

    on i3 {
        self.value = self.i3[2];
        self.value = self.value << 8;
        self.value = self.value +  self.i3[1];
        self.value = self.value << 8;
        self.value = self.value +  self.i3[0];
    }

    on i8 {
        self.value = self.i8;
    }
    
    on inull {
        self.value = 0;
    }

    var value : uint64;
};

从以上定义可知,当列数小于251时,该类型数据占用的字节即为1字节;但是更大后,会采用大于一个字节的方式进行处理;当中有一个特殊情况,当暂用一个字节,且值为251时,表示的是一个无效值;(后面再定义结果集内容时会用到这个值

对于消息头的读取可以进行组合如下:

type COLUMN_SIZE  = unit(inout rs: mysql_rs) {
    size : INT_ENC { rs.column_size = self.size.value; }
};

public type mysql_rs = unit {
    head : header;
    hdata : bytes &size=self.head.size { self.s_col_size.write($$); }
	
	on %init {
        self.s_col_size.connect(new COLUMN_SIZE(self));
    }
    var column_size : uint64;
	sink s_col_size;

在组合中,用到了unit的参数传递了mysql_rs,同时采用了sink的方式对数据进行了一次传递;如此做主要是为了适配消息体后续可能得扩展;整体的格式不需要变化太大,只需要针对消息体进行更改即可;同时兼容性也会更强。

紧接着的是字段定义。

字段定义

每个字段的定义包括,字段头+字段体,字段头的定义与前面的header定义相同,而后定义的是字段的各个内容,包括catalog、database_name,table_name,orig_table_name,column_name, orig_column_name,字符集索引,字符集长度,列类型,列标识及列精度;其中catalog、database_name,table_name,orig_table_name,column_name, orig_column_name都是数据长度+数据内容的方式进行存储。所以字段的读取定义如下:

type column = unit {
    catalog_len : INT_ENC;
        :skip bytes &size=self.catalog_len.value;
    db_len : INT_ENC;
    db_name : bytes &size = self.db_len.value;
    tbl_len : INT_ENC;
    tbl_name : bytes &size = self.tbl_len.value;
    otbl_len : INT_ENC;
    otbl_name : bytes &size = self.otbl_len.value;
    col_len : INT_ENC;
    col_name : bytes &size = self.col_len.value;
    ocol_len : INT_ENC;
    ocol_name : bytes &size = self.ocol_len.value;
           : skip int8;
    collation_idx : int16;
    coll_len  : int32;
    col_type : int8;
    col_flag : int16;
    col_decimals : int8;
    :   skip bytes &eod;

    on %done {
        print "{database:%s, tbl_name:%s, otbl_name:%s, col_name:%s, ocol_name:%s, collation_idx:%x, col_type:%x, col_flag:%x, col_decimals:%x}" %
                (self.db_name, self.tbl_name, self.otbl_name, self.col_name, self.ocol_name, self.collation_idx, self.col_type, self.col_flag, self.col_decimals);
    }
};

其中因为catalog的内容定义恒为def,所以通过skip方式进行了忽略。同时其中col_flag的读取字段可能会是1字节也可能是2字节(会根据认证过程中包含的客户端的参数进行变换、此处为了简化直接定义成了2字节);

包含文件头的定义为:

type column_with_header = unit {

head :header;

data : bytes &size=self.head.size { self.b.write($$); }

on %init {
    self.b.connect(new column);
}

sink b;

};

因为在前面的解析总,已经获取了字段数,所以需要将该结构定义成数组的形式

public type mysql_rs = unit {
    head : header;
    hdata : bytes &size=self.head.size { self.s_col_size.write($$); }
    columns : column_with_header[self.column_size ];
	on %init {
        self.s_col_size.connect(new COLUMN_SIZE(self));
    }
    var column_size : uint64;
	sink s_col_size;

定义完字段后,接下来接收的就是实际的结果数据了

结果数据

resultset的结果数据以每行的形式进行传输。

每行的开头是header结构体,后面的数据内容即为一行数据,由N(N为结果集的列数)个数据单元组成,每个数据单元的组成形式为INT_ENC+数据实体组成。其定义如下:

type element_value = unit(inout r: row) {
    size : INT_ENC;
    data : bytes &size = self.size.value;
    on %done {
        print "idx: (%d, %d), size:%d, values:%s" % (r.row_idx, r.col_idx, self.size.value, self.data);
    }
};

此处为了方便的识别当前元素所处的位置,将行列索引进行了输出。

此处对element_value实际的值为NULL、空字符串的差异进行简要的说明;如果为空字符串,则INT_ENC内容为0,表示长度为0;而如果实际值为NULL,正常内容长度也为0,但是不能区分是否为NULL,所以mysql使用了251这个特殊的数字,将元素定义为了NULL。所以为INT_ENC的中,如果返现第一个字节的内容为251,则会将最终的size置为0,同时其结果也是NULL,此处未做特殊处理,实际应用时,可以继续这个条件进行修正。

行数据定义如下:

type row = unit(r_idx: uint32, column_size : uint64) {
    eles : element_value(self)[column_size] foreach { self.col_idx = self.col_idx + 1; }

    on %init {
        self.row_idx = r_idx;
        self.col_idx = 0;
    }
    var row_idx : uint32;
    var col_idx : uint32;
};

行数据头+行数据的定义如下:

type row_with_head = unit(inout rs: mysql_rs, column_size :uint64) {
    head : header;
    data : bytes &size=self.head.size { 
        if ( *self.data.at(0) == 0xfe) {
            rs.is_done = True;
        }
    
        if (!rs.is_done)
            self.b.write($$); 
    }
    on head {
        print "head size: %d" % self.head.size;
    }


    on %init {
        self.b.connect(new row(rs.row_idx, column_size));
    }
    sink b;
};

因为行数据传输的时候,未包含实际的行数信息;所以需要有标识定义何时结束结果集的传输;此处演示我们采用了相对比较简单的方式,即判断数据开始的值为0xfe则认为数据传输截止了(实际上还有数据大小的判断进行组合判断对结果集是否已经完成得判断)。

所以最终结果集的定义如下:

public type mysql_rs = unit {

head : header;

hdata : bytes &size=self.head.size { self.s_col_size.write($$); }

columns : column_with_header[self.column_size ];

rows : row_with_head(self, self.column_size)[] foreach {

if (self.is_done == True) {

stop;

}

self.row_idx = self.row_idx + 1;

}

on %init {

self.is_done = False;

self.row_idx = 0;

self.s_col_size.connect(new COLUMN_SIZE(self));

}

var column_size : uint64;
var is_done : bool;
var row_idx : uint32;
sink s_col_size;

};

完整spicy文件

完整spicy文件内容如下:

module mysql;
import spicy;



type INT_ENC = unit {
    osize : uint8;
    i2 : uint16  &byte-order=spicy::ByteOrder::Little if ((self.osize & 0xff) == 252);
    i3 : uint8[3] if ((self.osize & 0xff) == 253); 
    i8 : uint64  &byte-order=spicy::ByteOrder::Little if ((self.osize & 0xff) == 254);
    inull : uint8[0] if ((self.osize & 0xff) == 251); 

    on osize {
        self.value = self.osize;
    }
    on i2 {
        self.value = self.i2;
    }

    on i3 {
        self.value = self.i3[2];
        self.value = self.value << 8;
        self.value = self.value +  self.i3[1];
        self.value = self.value << 8;
        self.value = self.value +  self.i3[0];
    }

    on i8 {
        self.value = self.i8;
    }
    
    on inull {
        self.value = 0;
    }

    var value : uint64;
};

type header = unit {
    osize : uint8[3];
    seq : uint8;

    on %done {
        self.size = self.osize[2];
        self.size = self.size << 8;
        self.size = self.size + self.osize[1];
        self.size = self.size << 8;
        self.size = self.size + self.osize[0];

    }
    var size : uint32;
};


type column = unit {
    catalog_len : INT_ENC;
        :skip bytes &size=self.catalog_len.value;
    db_len : INT_ENC;
    db_name : bytes &size = self.db_len.value;
    tbl_len : INT_ENC;
    tbl_name : bytes &size = self.tbl_len.value;
    otbl_len : INT_ENC;
    otbl_name : bytes &size = self.otbl_len.value;
    col_len : INT_ENC;
    col_name : bytes &size = self.col_len.value;
    ocol_len : INT_ENC;
    ocol_name : bytes &size = self.ocol_len.value;
           : skip int8;
    collation_idx : int16;
    coll_len  : int32;
    col_type : int8;
    col_flag : int16;
    col_decimals : int8;
    :   skip bytes &eod;

    on %done {
        print "{database:%s, tbl_name:%s, otbl_name:%s, col_name:%s, ocol_name:%s, collation_idx:%x, col_type:%x, col_flag:%x, col_decimals:%x}" %
                (self.db_name, self.tbl_name, self.otbl_name, self.col_name, self.ocol_name, self.collation_idx, self.col_type, self.col_flag, self.col_decimals);
    }
};


type column_with_header = unit {
    head :header;
    data : bytes &size=self.head.size { self.b.write($$); }
    
    on %init {
        self.b.connect(new column);
    }

    sink b;
};

type element_value = unit(inout r: row) {
    size : INT_ENC;
    data : bytes &size = self.size.value;
    on %done {
        print "idx: (%d, %d), size:%d, values:%s" % (r.row_idx, r.col_idx, self.size.value, self.data);
    }
};

type row = unit(r_idx: uint32, column_size : uint64) {
    eles : element_value(self)[column_size] foreach { self.col_idx = self.col_idx + 1; }

    on %init {
        self.row_idx = r_idx;
        self.col_idx = 0;
    }
    var row_idx : uint32;
    var col_idx : uint32;

};

type row_with_head = unit(inout rs: mysql_rs, column_size :uint64) {
    head : header;
    data : bytes &size=self.head.size { 
        if ( *self.data.at(0) == 0xfe) {
            rs.is_done = True;
        }
    
        if (!rs.is_done)
            self.b.write($$); 
    }
    on head {
        print "head size: %d" % self.head.size;
    }


    on %init {
        self.b.connect(new row(rs.row_idx, column_size));
    }
    sink b;
};

type COLUMN_SIZE  = unit(inout rs: mysql_rs) {
    size : INT_ENC { rs.column_size = self.size.value; }
};

public type mysql_rs = unit {
    head : header;
    hdata : bytes &size=self.head.size { self.s_col_size.write($$); }
    columns : column_with_header[self.column_size ];
    rows : row_with_head(self, self.column_size)[] foreach {
        if (self.is_done == True) {
            stop;
        }
        self.row_idx = self.row_idx + 1;
    }
    on %init {
        self.is_done = False;
        self.row_idx = 0;
        self.s_col_size.connect(new COLUMN_SIZE(self));
    }

    var column_size : uint64;
    var is_done : bool;
    var row_idx : uint32;
    sink s_col_size;

};

假设文件存储名为mysql_rs.spicy,则可通过spicy-driver mysql_rs.spicy进行语法校验及调测。调测运行可以采用

printf "0x070x000x00..." | xxd -r -p | spicy-driver mysql_rs.spicy

进行调测输出。

其中xxd命令,主要是将16进制的字符串转换为二进制数。


  1. 1:spicy是zeek用于定义协议解析的语言,可参考https://zeek.org ↩︎
相关推荐
Mr_Xuhhh3 小时前
进程间通信
android·java·服务器·开发语言·数据库
ThisIsClark3 小时前
【后端面试总结】mysql的group by怎么用
mysql·面试·职场和发展
2501_903238654 小时前
Spring Boot与H2数据库:快速搭建内存数据库应用
数据库·spring boot·oracle·个人开发
扎量丙不要犟6 小时前
rust操作pgsql、mysql和sqlite
数据库·mysql·rust·sqlite·sqlx
不一样的信息安全6 小时前
深入探索SQL中修改表字段属性的技巧与策略
数据库·sql·oracle
努力的小T8 小时前
Linux MySQL离线安装
linux·运维·服务器·数据库·mysql·adb·云计算
2的n次方_9 小时前
【Redis】List 类型的介绍和常用命令
数据库·redis·缓存·list
J.Kuchiki10 小时前
【PostgreSQL内核学习 —— (WindowAgg(一))】
数据库·学习·postgresql
恩爸编程10 小时前
MySQL中的读锁与写锁:概念与作用深度剖析
数据库·mysql·mysql锁机制·mysql读锁·mysql写锁·mysql中的读锁啥意思·mysql中的写锁啥意思
程序研11 小时前
SQL99之内连接查询
数据库