SQLite克隆|第七步:键排序和键重复

在上一节中,我们实现了单节点的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;
        }
    }
}

你可能感兴趣的:(SQLite克隆|第七步:键排序和键重复)