实现一个简单Database6

  • GreatSQL社区原创内容未经授权不得随意使用,转载请联系小编并注明来源。
  • GreatSQL是MySQL的国产分支版本,使用上与MySQL一致。

前文回顾

实现一个简单的Database1(译文)

实现一个简单的Database2(译文)

实现一个简单的Database3(译文)

实现一个简单的Database4(译文)

实现一个简单的Database5(译文)


译注:cstsck在github维护了一个简单的、类似sqlite的数据库实现,通过这个简单的项目,可以很好的理解数据库是如何运行的。本文是第六篇,主要是实现数据持久化游标

Part 6 游标抽象

跟上一节相比,这一节篇幅相对要简短的多。我们只是稍微重构一下,这样就可以让开始实现B-tree更简单一些。

在代码中新添加一个Cursor对象,它用来代表表中的一个位置(location)。

你可能希望对游标执行的操作:

  • 在表开头时创建一个游标
  • 在表结尾时创建一个游标
  • 访问游标指向的行
  • 将游标移动到下一行

这就是我们将要实现的游标的一些行为。然后,我们还想做到:

  • 删除游标指向的一行数据
  • 修改游标指向的一行数据
  • 使用给定的ID搜索一张表,并创建一个游标指向这个ID所在的行

_译注:这里简单介绍一下游标,Cursor原本就有箭头、光标的意思,用来指示事物以示关注。游标是数据的一种访问机制,一种处理数据的方法,例如查询返回的结果是一行或者多行的结果集(这已经是SQL被处理后的结果集),我们需要对结果集进行查询,sql语句就不管用了,因为这已经是返回的结果集了,这个时候就需要用游标来遍历这个返回的结果集了。可以理解游标是一个指向Row的指针,访问一行后,游标就会指向下一行。例如 fetchone()、fetchall() 等函数就是通过游标来访问结果集的,返回具体一行或者多行的数据。下面是游标图示:

image-20221030153346199

不磨叽了,下面就是Cursor类型结构:

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

根据现在我们的表数据结构,只需要行号即可识别表中的位置。

游标对它所属的表还有一个引用(所以我们的游标函数还可以仅仅把游标作为参数)。

最后,它还有一个boolean类型的属性叫做 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* 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-tree书中会有一点小复杂。

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

最后我们就能改变我们的“虚拟机”方法来使用游标抽象了。当插入一行数据时,我们在表结尾打开一个游标,写入这个游标的位置,然后关闭游标。

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

好了,就是它!就像我说的,这是短小的重构,应该能够有助于我们把表的数据结构重写为B-tree。execute_select() 和 execute_insert() 函数完全可以通过游标来与表交互,而对于表的存储方式不需要假设任何事情。

译注:在之前实现数据库的page组织方式的代码中,作者使用数组(array)来组织数据页page,主要是考虑快速实现存放数据,没有考虑性能优化。后面会使用B-tree来进行重构,而在此之前,先实现了游标。_

这是于上一部分对比,代码的不同:

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

Enjoy GreatSQL :)

关于 GreatSQL

GreatSQL是由万里数据库维护的MySQL分支,专注于提升MGR可靠性及性能,支持InnoDB并行查询特性,是适用于金融级应用的MySQL分支版本。

相关链接: GreatSQL社区 Gitee GitHub Bilibili

GreatSQL社区:

欢迎来GreatSQL社区发帖提问 https://greatsql.cn/

20221008151816

技术交流群:

微信:扫码添加GreatSQL社区助手微信好友,发送验证信息加群

打开链接下载源码: https://pan.quark.cn/s/a4b39357ea24 在Qt框架中,QSerialPort类被视为一个关键组件,用于执行与串行端口之间的通信任务,它具备多样化的功能,涵盖了串口的开启与关闭操作,以及波特率、数据位、停止位和奇偶校验等参数的设定,同时还包括数据的发送和接收功能。在标题和描述中提及的“Qt5的QSerialPort类通过信号槽实现串口读写”,这代表了一种在Qt编程中普遍采用的事件驱动策略,借助信号槽机制,能够便捷地管理串口数据的传输与接收。 1. **QSerialPort类的基础操作**: - 初始化阶段:必须构建一个QSerialPort实例,并为其指定串口名称,例如"/dev/ttyUSB0"。 - 参数配置:利用`setPortName()`、`setBaudRate()`、`setDataBits()`、`setParity()`、`setStopBits()`、`setFlowControl()`等方法,依据具体需求对串口参数进行配置。 - 串口开启/终止:借助`open()`方法启动串口,通过`close()`方法终止串口。务必验证`isOpen()`的返回状态,以确保操作的有效性。 2. **信号槽机制的应用**: - 信号的生成:QSerialPort类中定义了若干信号,诸如`readyRead()`表明有数据可读,`error()`指示出现错误,`bytesWritten()`显示数据已传输等。当这些事件发生时,将触发相应的信号。 - 槽函数的关联:相应地,可以将这些信号与自定义的槽函数相连接,比如,当`readyRead()`信号被激活时,可以调用一个用于处理读取数据的函数。 3. **串口数据...
内容概要:本文档聚焦于超宽带(UWB)技术的核心研究,系统探讨了干扰对齐与抵消机制、UWB单天线与多天线系统的建模与仿真,并提供了完整的Matlab代码实现方案。文档强调科研工作不仅需要严谨的逻辑与扎实的努力,更应注重“借力”思维与创新突破,建议读者按照知识体系循序渐进地学习,避免陷入碎片化理解的困境。除UWB专题外,文档还全面展示了基于Matlab/Simulink的多领域科研支持能力,涵盖智能优化算法、机器学习、电力系统、路径规划、通信与信号处理、图像融合、雷达追踪、车间调度等多个前沿方向,形成了一套完整的科研方法论与技术生态体系。所有相关资源可通过指定公众号或百度网盘获取,便于快速复现与二次开发。; 适合人群:具备一定Matlab编程基础和通信系统理论知识,从事电子信息、通信工程、自动化、电力系统及相关交叉学科的研究生、科研人员及工程技术人员。; 使用场景及目标:①掌握UWB系统中干扰抑制与天线设计的关键技术原理;②利用配套Matlab代码完成算法仿真、性能验证与参数优化;③借鉴成熟的优化模型与仿真框架,拓展至自身研究课题如路径规划、微电网调度、信号处理等;④通过复现高水平论文模型,提升科研实践能力与学术竞争力。; 阅读建议:建议严格按照文档的知识结构顺序阅读,优先聚焦与自身研究方向契合的内容模块,结合提供的Matlab代码动手实践,积极利用公众号“荔枝科研社”及百度网盘中的完整资源包,实现从理论理解到项目落地的高效转化。
已经博主授权,源码转载自 https://pan.quark.cn/s/a4b39357ea24 ### 批处理脚本实现指定文件夹内所有文件与子目录的移除 #### 简介 在Windows系统环境下,批处理脚本是一种极具价值的应用工具,它能够协助用户执行一系列预先设定好的指令,达成自动化处理的目的。本说明着重阐述如何借助批处理脚本移除特定文件夹内的全部文件及子文件夹,并对几种常用技巧的效果进行剖析。 #### 批处理脚本的基础知识 批处理脚本是一种基于DOS命令行环境构建的文本性文档,其文件后缀为`.bat`。借助编写批处理脚本,使用者可以完成复杂任务流程的自动化,例如文件复制、移动、清除等动作。 #### 第一种方法:运用`RD`指令 `RD`指令专用于移除目录(即文件夹)。该指令的标准格式如下所示: ```batch RD [drive:]path [parameters] ``` 其中,`[drive:]path`代表待清除的目录路径,`[parameters]`为若干可选参数,常用的包括: - `/S`:递归式地移除目录及其所有嵌套子目录。 - `/Q`:执行静默模式,不进行确认提示。 ##### 示例1:直接运用`RD`指令 若采用`RD /S /Q c:\temp`指令来移除`C:\temp`目录中的所有文件及子文件夹,将连同`temp`目录本体一同被清除。 ```batch rd /s /q c:\temp ``` #### 第二种方法:灵活运用`RD`指令 为防止误删`temp`目录本身,可以通过先利用`RD`指令清空`temp`目录内的所有内容,随后重新构建`temp`目录的技巧来实现。 ##### 示例2:灵活运用`RD`指令 ```batch rd ...
已经博主授权,源码转载自 https://pan.quark.cn/s/a4b39357ea24 在“WEB前端-案例汇总”这一资源集合中,收录了大量的前端开发实践范例,其核心目的在于引导初学者逐步提升,并系统性地掌握前端开发所需的关键技能。这个广泛的案例合集几乎包罗了前端开发的所有重要范畴,对于渴望深入研究和理解Web前端技术的人来说,无疑是一份极具价值的参考资料。 1. HTML基础:HTML(超文本标记语言)是网页构建的根基,其涉及的基本构成要素包括标记、属性以及结构等。相关的实例可能涵盖基础的静态页面构建,例如个人履历、产品介绍页面等,通过这些范例,学习者可以领会到如何合理地安排网页的内容与结构。 2. CSS样式设计:CSS(层叠样式表)主要用于调控网页的布局与视觉呈现。相关的案例或许会涉及盒模型、选择器、浮动、定位以及响应式设计等,使学习者能够设计出既美观又能适应不同设备的页面。 3. JavaScript交互:JavaScript作为前端开发的核心,负责实现动态效果与用户交互功能。相关的实例可能包含事件管理、文档对象模型操作、异步JavaScript与XML请求、函数及对象的应用等,通过这些实例,学习者能够学会如何增强网页的互动性。 4. jQuery库的应用:jQuery简化了JavaScript的操作,提供了功能丰富的接口和插件。相关的案例或许会涉及动画效果、文档对象模型操作、事件管理等方面,使初学者能够迅速掌握并提高开发效率。 5. 响应式设计:随着移动设备的广泛使用,响应式设计已成为一项必备技能。相关的案例可能包括运用媒体查询、弹性盒模型或网格布局来达成不同屏幕尺寸下的适配效果。 6. 模块化与框架:在现代前端开发实践中,Vu...
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值