目录
在本节中,我们将要实现一个光标(cursor)对象,用来指示表中的某个具体位置,以便我们后续实现B-tree。光标常见的使用方式有以下几种:
- 在表的开头处创建一个光标
- 在表的结尾处创建一个光标
- 访问光标所指向的行
- 将光标移动到下一行
上述功能在本节都会实现,后续我们还会新增以下功能:
- 删除光标所指向的行
- 修改光标所指向的行
- 基于给定的ID搜索整张表,并创建光标指向该ID的行
Cursor
话不多说,我们现在就给出光标类型的定义:
typedef struct {
Table *table;
uint32_t row_num;
bool end_of_table; // Indicates a position one past the last element
} Cursor;
结合我们当前的表数据结构,光标所指示的位置其实就是每一行的行号。
光标包含一个指针,指向它所属的那张表,所以我们的光标类函数可以只用光标对象作为参数。
最后,光标还包含一个布尔值——end_of_table。我们用这个值表示光标是否已经到达当前表的末尾,通常会是我们想要插入新数据的位置。
我们用table_start()和table_end()分别在表的开头和末尾创建新的光标:
Cursor* table_start(Table* table) {
Cursor* cursor = malloc(sizeof(Cursor));
cursor->table = table;
cursor->row_num = 0;
cursor->end_of_table = (table->num_rows == 0);
return cursor;
}
Cursor* table_end(Table* table) {
Cursor* cursor = malloc(sizeof(Cursor));
cursor->table = table;
cursor->row_num = table->num_rows;
cursor->end_of_table = true;
return cursor;
}
row_slot()函数也需要被改成cursor_value(),返回值是指向光标所描述位置的指针:
void* cursor_value(Cursor* cursor) {
uint32_t row_num = cursor->row_num;
uint32_t page_num = row_num / ROWS_PER_PAGE;
void* page = get_page(cursor->table->pager, page_num);
uint32_t row_offset = row_num % ROWS_PER_PAGE;
uint32_t byte_offset = row_offset * ROW_SIZE;
return page + byte_offset;
}
在当前的表数据结构中,向后移动光标实际上就是增加行号,等到了B-tree中移动光标操作会变得更加复杂。
void cursor_advance(Cursor* cursor) {
cursor->row_num += 1;
if (cursor->row_num >= cursor->table->num_rows) {
cursor->end_of_table = true;
}
}
最后,我们把“虚拟机”方法(即操作表的函数)都替换成光标类函数。比如,在插入行时,我们先在表的末尾创建一个光标,在光标处写入数据后释放光标。
ExecuteResult execute_insert(Statement* statement, Table* table) {
if (table->num_rows >= TABLE_MAX_ROWS) {
return EXECUTE_TABLE_FULL;
}
Row* row_to_insert = &(statement->row_to_insert);
Cursor* cursor = table_end(table);
serialize_row(row_to_insert, cursor_value(cursor));
table->num_rows += 1;
free(cursor);
return EXECUTE_SUCCESS;
}
打印表中所有数据时,先在表的开头创建一个光标,打印当前行后将光标移动到下一行,直到光标移动到表的末尾为止。不要忘了最后也要释放光标。
ExecuteResult execute_select(Statement* Statement, Table* table) {
Cursor* cursor = table_start(table);
Row row;
while (!(cursor->end_of_table)) {
deserialize_row(cursor_value(cursor), &row);
print(&row);
cursor_advance(cursor);
}
free(cursor);
return EXECUTE_SUCCESS;
}
本节内容比较简单,主要的工作都是为了后续将我们的表数据结构重构为B-tree而做的铺垫。比如,execute_select()和execute_insert()能够完全通过光标与表进行交互,而无需对表的存储方式做任何假设。
原文链接:Let’s Build a Simple Database: Part 6 - The Cursor Abstraction