在上一节中,我们实现了单节点的B-Tree,即根节点为叶节点的情况。在这一节中,我们仍然在这种单节点的情况下继续设计。我们想要实现两个功能:键排序和防止键重复。
首先我们先定义两个函数用于设置和读取节点类型:
//返回节点类型
NodeType get_node_type(void *node){
uint8_t value=*((uint8_t*)(node+NODE_TYPE_OFFSET));
return (NodeType)value;
}
//设置节点类型
void set_node_type(void *node,NodeType type){
uint8_t value=type;
*((uint8_t*)(node+NODE_TYPE_OFFSET))=value;
}
//初始化一个叶节点并使得节点胞元数量为0
void initialize_leaf_node(void *node){
set_node_type(node,NODE_LEAF);
*leaf_node_num_cells(node)=0;
}
在之前我们的execute_insert()函数是将数据元胞插入到数据表表尾,而在这里我们希望他能够按照键序来插入,这就要求我们对已插入的数据元胞进行操作。为此,我们首先删除了table_end()函数。此外我们使用集体后移的策略来插入新的元胞。
//计算插入元素的对应元胞编号
Cursor *leaf_node_find(Table* table,uint32_t page_num,uint32_t key){
void* node=get_page(table->pager,page_num);//读取对应页数编号的页内容到node
uint32_t num_cells=*leaf_node_num_cells(node);//读取节点中的元胞数量
Cursor *cursor=malloc(sizeof(Cursor));//初始化一个光标
cursor->table=table;
cursor->page_num=page_num;
uint32_t min_index=0;
uint32_t one_past_max_index=num_cells;//之前的最大元胞数
//计算出的位置特征:插入key所对应的元胞编号
while(one_past_max_index!=min_index){
uint32_t index=(min_index+one_past_max_index)/2;
uint32_t key_at_index=*leaf_node_key(node,index);
if(key<key_at_index){
one_past_max_index=index;
}else{
min_index=index+1;
}
}
cursor->cell_num=min_index;
return cursor;
}
//如果是叶子结点返回胞元插入位置,否则报错退出
Cursor *table_find(Table* table,uint32_t key){
uint32_t root_page_num=table->root_page_num;//初始化根节点编号
void* root_node=get_page(table->pager,root_page_num);//初始化根节点
if(get_node_type(root_node)==NODE_LEAF){
return leaf_node_find(table,root_page_num,key);//返回元胞插入位置
}else{
printf("Need to implement searching an internal node.\n");
exit(EXIT_FAILURE);
}
}
//执行insert指令将输入输出到内存并返回执行状态码
ExecuteResult execute_insert(Statement* statement, Table* table) {
void* node=get_page(table->pager,table->root_page_num);//读节点
uint32_t num_cells=(*leaf_node_num_cells(node));
//如果当前元胞数大于叶节点最大元胞数,返回数据表满
if (num_cells>=LEAF_NODE_MAX_CELLS){
return EXECUTE_TABLE_FULL;
}
Row* row_to_insert = &(statement->row_to_insert);
uint32_t key_to_insert=row_to_insert->id;
Cursor *cursor=table_find(table,key_to_insert);//返回胞元插入位置
if (cursor->cell_num<num_cells){
uint32_t key_at_index=*leaf_node_key(node,cursor->cell_num);
//防止键重复
if (key_at_index==key_to_insert){
return EXECUTE_DUPLICATE_KEY;
}
}
leaf_node_insert(cursor,row_to_insert->id,row_to_insert);//将数据输出到内存
free(cursor);
return EXECUTE_SUCCESS;
}
此外,我们定义了键重复的错误响应码在ExecuteResult中,加入了EXECUTE_DUPLICATE_KEY枚举项。代表重复键情况,当出现这种类型的错误时,我们需要进行相应的输出,为此,我们还需要调整主函数中execute_statement()的响应码输出函数:
int main(int argc, char* argv[]) {
if (argc<2) {
printf("Must supply a database filename.\n");
exit(EXIT_FAILURE);
}
char* filename=argv[1];
Table* table=db_open(filename);
InputBuffer* input_buffer = new_input_buffer();//创建一个初始化输入空间
//循环REPL
while (true) {
.......省略......
switch (execute_statement(&statement, table))
{
case(EXECUTE_SUCCESS):
printf("Executed.\n");
break;
case (EXECUTE_TABLE_FULL):
printf("Error:Table full.\n");
break;
case(EXECUTE_DUPLICATE_KEY):
printf("Error: Duplicate key.\n");
break;
}
}
}