MySQL Server和Client之间的交互有一套定义得很明确的协议,称为MySQL Client/Server Protocol。 写数据库的人,只需要遵循这套协议来写程序,就能让自己的数据库被各种MySQL客户端连接,如mysql命令行,php mysql,JDBC等等。这是一个非常诱人的设计选择(Design Choice)!如果自己实现一套协议,写完数据库后,还需要给各种语言写客户端库,写各种客户端软件,完全就是噩梦。
MySQL源码中,哪里负责实现这个协议呢?这里:
sql/protocol.cc
对于一个select语句,他会有很多行结果,每一行结果都是调用
bool Protocol::send_result_set_row(List<Item> *row_items)
来发送,它的详细实现如下:
/**
Send one result set row.
@param row_items a collection of column values for that row
@return Error status.
@retval TRUE Error.
@retval FALSE Success.
*/
bool Protocol::send_result_set_row(List<Item> *row_items)
{
char buffer[MAX_FIELD_WIDTH];
String str_buffer(buffer, sizeof (buffer), &my_charset_bin);
List_iterator_fast<Item> it(*row_items);
DBUG_ENTER("Protocol::send_result_set_row");
for (Item *item= it++; item; item= it++)
{
if (item->send(this, &str_buffer))
{
// If we're out of memory, reclaim some, to help us recover.
this->free();
DBUG_RETURN(TRUE);
}
/* Item::send() may generate an error. If so, abort the loop. */
if (thd->is_error())
DBUG_RETURN(TRUE);
/*
Reset str_buffer to its original state, as it may have been altered in
Item::send().
*/
str_buffer.set(buffer, sizeof(buffer), &my_charset_bin);
}
DBUG_RETURN(FALSE);
}
一行数据中有很多列,每一列都是一个Item对象,它有一个send方法,负责将Item中的数据按照MySQL Client/Server协议“序列化”到发送缓冲区内:
item->send(this, &str_buffer)
Item是一个基类,它下面有很多子类,子类下面还有子类。如下图1,显示了第一层子类 (图片由Doxgen自动生成), 图2、3是部分细节展开。
如有必要,任何子类都可以去实现send方法。MySQL中,如下一个类实现了send,其中Item::send是兜底方案。
virtual bool Item::send(Protocol *protocol, String *str);
inline bool Item_sp_variable::send(Protocol *protocol, String *str);
bool Item_name_const::send(Protocol *protocol, String *str)
bool Item_field::send(Protocol *protocol, String *str_arg);
bool Item_null::send(Protocol *protocol, String *str);
bool Item_ref::send(Protocol *prot, String *tmp);
bool Item_func_set_user_var::send(Protocol *protocol, String *str_arg);
Field和Item之间如何建立联系的呢?Item_field类!
Item_field(Field *field); // 会将field保存到Item_field::result_field
它将Field封装为一个Item,然后通过下面的代码实现结果的桥接:
bool Item_field::send(Protocol *protocol, String *buffer)
{
return protocol->store(result_field);
}
进而调用
bool Protocol_text::store(Field *field)
{
if (field->is_null())
return store_null();
char buff[MAX_FIELD_WIDTH];
String str(buff,sizeof(buff), &my_charset_bin);
const CHARSET_INFO *tocs= this->thd->variables.character_set_results;
field->val_str(&str); /// bridge point
return store_string_aux(str.ptr(), str.length(), str.charset(), tocs);
}
例如,Field是一个Field_medium,则调用下面的代码:
String *Field_medium::val_str(String *val_buffer,
String *val_ptr __attribute__((unused)))
{
ASSERT_COLUMN_MARKED_FOR_READ;
const CHARSET_INFO *cs= &my_charset_numeric;
uint length;
uint mlength=max(field_length+1,10*cs->mbmaxlen);
val_buffer->alloc(mlength);
char *to=(char*) val_buffer->ptr();
long j= unsigned_flag ? (long) uint3korr(ptr) : sint3korr(ptr);
length=(uint) cs->cset->long10_to_str(cs,to,mlength,-10,j);
val_buffer->length(length);
if (zerofill)
prepend_zeros(val_buffer); /* 他们这个补0做得比较挫,val_buffer是个临时缓冲区,并且还会有memmove操作在里面 */
val_buffer->set_charset(cs);
return val_buffer;
}
这里会将数值转化成字符串,如果有zerofill标记,还会根据需要在字符串前面补0。
[TBC/未完]