一起做个简单的数据库(六):The Cursor Abstraction


用C语言从零开始实现SQLite clone系列:
  1. REPL的介绍和设置
  2. 世上最简单的SQL编译器和虚拟机
  3. 一个在内存中仅能做追加操作的单表数据库
  4. 第一次测试 (含bug处理)
  5. 持久化存储


相较上篇,本片篇幅较短。我们将进行部分重构,以使B-Tree更加容易实施。

我们将添加一个Cursor对象,该对象代表表中的位置。你可能要对游标执行的操作:
  • 在表头创建cursor
  • 在表末尾创建cursor
  • 访问cursor指向的行
  • 将cursor移到下一行


我们将要实现这些功能,之后,我们还希望能实现:
  • 删除cursor指向的行
  • 修改cursor指向的行
  • 通过ID搜索表,并生成指向该ID所在行的cursor


话不多说,下面是Cursor类型:
+typedef struct {
+  Table* table;
+  uint32_t row_num;
+  bool end_of_table;  // Indicates a position one past the last element
+} Cursor;

在已知数据结构的表中,只需要行号就可以定位在表中的位置。

Cursor也具有相同的功能(所以我的cursor只是用做参数)。

最后,设定一个名为end_of_table的布尔值。这样一来,我们可以表示表格末尾之后的位置(这是我们可能要插入行的位置)。

table_start()和table_end()创建新的cursor:
+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* row_slot(Table* table, uint32_t row_num) {
+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(table->pager, page_num);
+  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树中会比较复杂。
+void cursor_advance(Cursor* cursor) {
+  cursor->row_num += 1;
+  if (cursor->row_num >= cursor->table->num_rows) {
+    cursor->end_of_table = true;
+  }
+} 

最后,我们可以更改“虚拟机”的方法以使用cursor abstraction。 当插入行,我们就在表格末尾创建一个cursor,写入该游标的位置,然后关闭该cursor。
Row* row_to_insert = &(statement->row_to_insert);
+  Cursor* cursor = table_end(table);
-  serialize_row(row_to_insert, row_slot(table, table->num_rows));
+  serialize_row(row_to_insert, cursor_value(cursor));
table->num_rows += 1;
+  free(cursor);
+
return EXECUTE_SUCCESS;


当选中表中所有行时,我们在表头打开一个cursor,当cursor移动到下一行时,打印当前行。重复这个动作直到cursor到达表末尾。
ExecuteResult execute_select(Statement* statement, Table* table) {
+  Cursor* cursor = table_start(table);
+
Row row;
-  for (uint32_t i = 0; i < table->num_rows; i++) {
-    deserialize_row(row_slot(table, i), &row);
+  while (!(cursor->end_of_table)) {
+    deserialize_row(cursor_value(cursor), &row);
 print_row(&row);
+    cursor_advance(cursor);
}
+
+  free(cursor);
+
return EXECUTE_SUCCESS;


好吧,就是这样!就像之前说的,这是一个部分重构,它为我们将表数据结构重写为B-Tree时提供帮助。 execute_select()和execute_insert()可以完全通过游标与表进行交互,而无需考虑如何存储表。

本篇代码如下:
@@ -78,6 +78,13 @@ struct {
} Table;

+typedef struct {
+  Table* table;
+  uint32_t row_num;
+  bool end_of_table; // Indicates a position one past the last element
+} Cursor;
+
void print_row(Row* row) {
 printf("(%d, %s, %s)\n", row->id, row->username, row->email);
}
@@ -126,12 +133,38 @@ void* get_page(Pager* pager, uint32_t page_num) {
 return pager->pages[page_num];
}

-void* row_slot(Table* table, uint32_t row_num) {
-  uint32_t page_num = row_num / ROWS_PER_PAGE;
-  void *page = get_page(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;
+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;
+}
+
+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;
+}
+
+void cursor_advance(Cursor* cursor) {
+  cursor->row_num += 1;
+  if (cursor->row_num >= cursor->table->num_rows) {
+    cursor->end_of_table = true;
+  }
}

Pager* pager_open(const char* filename) {
@@ -327,19 +360,28 @@ ExecuteResult execute_insert(Statement* statement, Table* table) {
 }

Row* row_to_insert = &(statement->row_to_insert);
+  Cursor* cursor = table_end(table);
-  serialize_row(row_to_insert, row_slot(table, table->num_rows));
+  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;
-  for (uint32_t i = 0; i < table->num_rows; i++) {
-     deserialize_row(row_slot(table, i), &row);
+  while (!(cursor->end_of_table)) {
+     deserialize_row(cursor_value(cursor), &row);
  print_row(&row);
+     cursor_advance(cursor);
}
+
+  free(cursor);
+
return EXECUTE_SUCCESS;



原文链接:Part 6 - The Cursor Abstraction(翻译:吴世曦)

0 个评论

要回复文章请先登录注册