# 第 6 部分 - 游标抽象

> 原文： [https://cstack.github.io/db_tutorial/parts/part6.html](https://cstack.github.io/db_tutorial/parts/part6.html)

这应该比最后一篇更短。我们只是重构一下，以便更容易启用 B-Tree 实现。

我们将添加一个表示表中位置的`Cursor`对象。你可能想用游标做的事情：

*   在表的开头创建一个游标
*   在表的末尾创建一个游标
*   访问游标指向的行
*   将游标推进到下一行

这些是我们现在要实现的行为。之后，我们还想：

*   删除游标指向的行
*   修改游标指向的行
*   在表中搜索给定的 ID，并创建一个指向具有该 ID 的行的游标

不用多说了，这是`Cursor`类型：

```diff
+struct Cursor_t {
+  Table* table;
+  uint32_t row_num;
+  bool end_of_table;  // Indicates a position one past the last element
+};
+typedef struct Cursor_t Cursor; 
```

根据我们当前的表数据结构，你需要用于在表中标识位置的所有东西都是行号。

游标还有一个对它所属表的引用（因此我们的游标函数只能将游标作为参数）。

最后，它有一个名为`end_of_table`的布尔值。这样我们就可以表示超出表末尾的位置（我们可能想要插入一行）。

`table_start()`和`table_end()`创建新游标：

```diff
+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()`，它返回指向游标所描述位置的指针：

```diff
-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 树中会更复杂一些。

```diff
+void cursor_advance(Cursor* cursor) {
+  cursor->row_num += 1;
+  if (cursor->row_num >= cursor->table->num_rows) {
+    cursor->end_of_table = true;
+  }
+} 
```

最后我们可以改变我们的“虚拟机”方法来使用游标抽象。插入行时，我们在表的末尾打开一个游标，写入该游标位置，然后关闭游标。

```diff
 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;
 } 
```

当选择表中的所有行时，我们在表的开头打开一个游标，打印该行，然后将游标前进到下一行。重复，直到我们到达表的末尾。

```diff
 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 树。`execute_select()`和`execute_insert()`可以完全通过游标与表进行交互，而无需假设表的存储方式。

这是这部分的完整差异：

```diff
 };
 typedef struct Table_t Table;

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

-void* row_slot(Table* table, uint32_t row_num) { +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(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;
 }

+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) {
   int fd = open(filename,
                 O_RDWR |      // Read/Write mode
@@ -315,19 +348,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;
 } 
```