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

本系列文章一共13篇,本文为第6篇,请关注公众号,后续文章会陆续发布。

系列文章列表:

  1. 《手把手教你从零开始实现一个数据库系统》

  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只是用做参数)。

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

tablestart()和tableend()创建新的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;
+}

我们的rowslot()函数将变为cursorvalue(),该函数将返回一个指向游标描述的位置的指针:

-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时提供帮助。executeselect()和executeinsert()可以完全通过游标与表进行交互,而无需考虑如何存储表。

本篇代码如下:

@@ -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;
 }

原文链接:https://cstack.github.io/db_tutorial/parts/part6.html

基于Kubernetes的DevOps实战培训

基于Kubernetes的DevOps实战培训将于2020年6月19日在上海开课,3天时间带你系统掌握Kubernetes,学习效果不好可以继续学习。本次培训包括:容器特性、镜像、网络;Kubernetes架构、核心组件、基本功能;Kubernetes设计理念、架构设计、基本功能、常用对象、设计原则;Kubernetes的数据库、运行时、网络、插件已经落地经验;微服务架构、组件、监控方案等,点击下方图片或者阅读原文链接查看详情。

你可能感兴趣的:(一起做个简单的数据库(六):The Cursor Abstraction)