52
课程设计报告 Hustbase 数据管理系统设计与实现 专业班级:计卓 1201 小组成员:杨腾达 U201215005 马俊伟 U201214874 U201215011 指导教师:谢美意 报告日期:2015.7.5 计算机科学与技术学院

课程设计报告 - Huazhong University of Science and ...cs.hust.edu.cn/__local/D/B2/1E/556FBEBBBA639329663... · 课程设计报告 Hustbase 数据管理系统设计与实现 专业班级:计卓1201

  • Upload
    others

  • View
    12

  • Download
    0

Embed Size (px)

Citation preview

Page 1: 课程设计报告 - Huazhong University of Science and ...cs.hust.edu.cn/__local/D/B2/1E/556FBEBBBA639329663... · 课程设计报告 Hustbase 数据管理系统设计与实现 专业班级:计卓1201

课程设计报告

Hustbase 数据管理系统设计与实现

专业班级:计卓 1201

小组成员:杨腾达 U201215005

马俊伟 U201214874

戚晏旻 U201215011

指导教师:谢美意

报告日期:2015.7.5

计算机科学与技术学院

Page 2: 课程设计报告 - Huazhong University of Science and ...cs.hust.edu.cn/__local/D/B2/1E/556FBEBBBA639329663... · 课程设计报告 Hustbase 数据管理系统设计与实现 专业班级:计卓1201

目录

1 课设概述 ............................................................................................................................... 1

1.1 课程设计目的 ............................................................................................................ 1

1.2 课程设计要求 ................................................................................................................ 1

1.3 HustBase 体系结构 .................................................................................................... 2

2 页面管理模块 ................................................................................................................... 4

2.1 页面管理模块说明 .................................................................................................... 4

2.1.1 分页文件 ................................................................................................................. 4

2.1.2 页面缓冲区 .............................................................................................................. 5

2.2 页面管理模块对外提供的函数调用接口 ................................................................ 6

2.2.1 文件操作函数 .................................................................................................... 6

2.2.2 页面操作函数 .................................................................................................... 6

3 记录管理模块 ................................................................................................................... 8

3.1 记录管理模块说明 ........................................................................................................ 8

3.1.1 控制页 ................................................................................................................ 8

3.1.2 数据页 ................................................................................................................ 8

3.1.3 文件操作 ............................................................................................................ 8

3.1.4 记录操作 ............................................................................................................ 9

3.2 记录管理模块对外提供的函数调用接口 .............................................................. 10

3.2.1 记录文件管理函数 .......................................................................................... 10

3.2.2 记录操作函数 .................................................................................................. 12

3.2.3 文件浏览函数 .................................................................................................. 16

4 索引管理模块 ................................................................................................................. 19

4.1 索引管理模块说明 ...................................................................................................... 19

4.1.1 索引文件 .......................................................................................................... 19

4.1.2 B+树 .................................................................................................................. 20

4.1.3 B+树索引的操作 .............................................................................................. 21

4.2 索引管理模块对外提供的函数调用接口 .............................................................. 23

4.2.1 索引文件管理函数 .......................................................................................... 23

4.2.2 索引操作函数 .................................................................................................. 24

Page 3: 课程设计报告 - Huazhong University of Science and ...cs.hust.edu.cn/__local/D/B2/1E/556FBEBBBA639329663... · 课程设计报告 Hustbase 数据管理系统设计与实现 专业班级:计卓1201

5 系统管理模块 ................................................................................................................. 29

5.1 系统管理模块说明 ...................................................................................................... 29

5.2 系统管理模块对外提供的函数调用接口 .............................................................. 30

6 查询处理模块 ................................................................................................................. 38

6.1 查询处理模块说明 ...................................................................................................... 38

6.2 查询处理模块对外提供的函数调用接口 .............................................................. 38

7 语法分析模块 ..................................................................................................................... 40

7.1 HustBase 支持的 SQL 语句 ..................................................................................... 40

7.2 语法分析流程 .......................................................................................................... 41

7.3 SQL 语句对应的数据结构 ....................................................................................... 41

7.4 语法分析模块对外提供的函数调用接口 .............................................................. 44

8 课设总结和建议 ............................................................................................................. 45

8.1 课设问题分析 .......................................................................................................... 45

8.2 课设感想和建议 ...................................................................................................... 47

参考文献 ................................................................................................................................. 49

Page 4: 课程设计报告 - Huazhong University of Science and ...cs.hust.edu.cn/__local/D/B2/1E/556FBEBBBA639329663... · 课程设计报告 Hustbase 数据管理系统设计与实现 专业班级:计卓1201

1

1 课设概述

1.1 课程设计目的

1) 了解关系数据库管理软件的体系结构和设计方法;

2) 了解关系数据库中数据和元数据的组织方法;

3) 掌握关系数据的底层存储实现技术;

4) 掌握基本关系操作在关系数据上的实现算法;

5) 掌握基本 SQL 语句的实现方法;

6) 掌握基本的查询优化技术。

1.2 课程设计要求

基于 HustBase 系统的总体设计架构,根据预先给定的系统框架、部分模块

和接口要求,设计并实现系统中的其它模块功能,完成一个具有基本数据定义、

数据操纵和数据查询功能的单用户关系数据库管理系统。

课程设计课时安排为四周,按周次划分为四个阶段。除去已预先提供的用户

界面、语法分析和页面管理模块,要求学生自己设计实现记录管理、索引管理、

系统管理和数据操纵模块以及所有模块的联调,完成整个HustBase系统的开发。

具体课程内容安排如表 2.1 所示。

表 2.1 数据库课程设计课程内容安排

周次 内容

第一周 学习 HustBase 的系统结构和工作原理,在已提供的页面管理模块的

基础上,完成记录管理模块的编码和调试。

第二周 在页面管理模块的基础上,完成索引管理模块的编码和调试。

第三周 在记录管理模块和索引管理模块的基础上,完成系统管理模块的编码

和调试。

第四周 在记录管理模块和索引管理模块的基础上,完成数据操纵模块的编码

和调试,完成系统整体联调。

HustBase 系统的开发环境为 Windows 操作系统及 Visual C++,开发语言为

C 语言。为保证良好的模块独立性,除用户界面模块外,其它模块均采用链接库

的形式实现。每个模块必须按规定的格式对外提供调用接口,但是模块内部的实

Page 5: 课程设计报告 - Huazhong University of Science and ...cs.hust.edu.cn/__local/D/B2/1E/556FBEBBBA639329663... · 课程设计报告 Hustbase 数据管理系统设计与实现 专业班级:计卓1201

2

现方案不做统一规定,由学生自行设计。

具体要求:

1) 课程设计内容采取 2-3 人小组的形式合作完成,每个学生在小组中均应

承担合理的工作量,基本原则是“共同设计,分工编码”。

2) 考核方式为公开演示及答辩,最终成绩根据系统完成情况、答辩情况和

课程设计报告质量综合评分,同组学生的成绩相同。

3) 课程结束后,以小组为单位提交系统的源码、编译后的程序及课程设计

报告。

1.3 HustBase 体系结构

HustBase 的体系架构如图 1 所示。系统由页面管理、记录管理、索引管理、

系统管理、查询处理和语法分析等主要模块构成。各模块功能如下:

(1) 页面管理:数据库中所有的数据、元数据和索引数据均以文件的形式存

储在磁盘上。本模块提供的功能包括:创建、销毁、打开和关闭分页文

件;扫描指定文件的所有页面;从指定文件中读取一个特定页面;在指

定文件中添加、删除及修改页面等。

(2) 记录管理:数据表中的数据以记录为单位存取。本模块包括一组函数,

用于管理存储在一组文件中的数据表记录。记录管理模块依赖于底层的

页面管理模块,页面管理模块以页面为单位实现文件级别的 I/O,而记录

管理模块则以记录为单位实现数据表级的读写。

(3) 索引管理:系统通过索引为查询提供快速访问路径。本模块用于管理存

储在一组文件中的索引记录。与数据表不同的是,索引采用 B+树结构来

组织索引记录。索引管理模块同样依赖于底层的页面管理模块。

(4) 系统管理:本模块用于数据定义(建立数据表和索引)和数据操纵(插

入、删除、修改记录)功能。系统管理模块的功能依赖于记录管理模块

和索引管理模块。

(5) 查询处理:HustBase 支持基本的 SQL 语句,包括简单的数据表定义

(CREATE TABLE)、数据操纵(INSERT、DELETE、UPDATE)和数

据查询(SELECT)语句。查询处理模块用于执行用户的 SELECT 命令,

并向客户端返回最终的查询结果集。本模块的功能依赖于记录管理模块

和索引管理模块。

Page 6: 课程设计报告 - Huazhong University of Science and ...cs.hust.edu.cn/__local/D/B2/1E/556FBEBBBA639329663... · 课程设计报告 Hustbase 数据管理系统设计与实现 专业班级:计卓1201

3

客户端(提供)

语法分析(提供)

查询处理

页面管理(提供)

索引管理 记录管理

系统管理

缓冲区

元数据

索 引数 据

SQL

结果集

索引扫描

读、写记录

创建文件,读写页面

索引

数据,元数据

创建、删除索引

获取元数据

DDL,DMLSelect

文件

图 1HustBase 系统体系架构图

(6) 语法分析:语法分析模块提供对 SQL 命令的语法分析,并将分析结果以

语法树的形式提供给系统管理模块和查询处理模块,以便其做进一步的

分析和处理。

(7) 客户端:为用户提供交互界面。显示当前数据库中的表模式信息,接收

用户输入的 SQL 命令,并显示命令的执行结果。

Page 7: 课程设计报告 - Huazhong University of Science and ...cs.hust.edu.cn/__local/D/B2/1E/556FBEBBBA639329663... · 课程设计报告 Hustbase 数据管理系统设计与实现 专业班级:计卓1201

4

2 页面管理模块

2.1 页面管理模块说明

2.1.1 分页文件

一个分页文件由若干个页(Page)构成,申请空间、读数据、写数据,均以

页为单位。每页大小为 4K 字节。其中,前 4 个字节存放页号,后 4092 字节存

放数据。对应数据结构为:

typedef struct {

PageNum pageNum; //页号(由 0 开始编号)

char pData[PF_PAGE_SIZE]; //数据

} Page;

每个文件的第 0 页为控制页,该页的数据区存放的信息为:

从 data[0]开始,存放一个数据结构:

typedef struct {

PageNum pageCount; //尾页的页号

int nAllocatedPages; //已分配页的数目(包括控制页)

} PF_FileSubHeader;

从 data[PF_FILESUBHDR_SIZE] ( PF_FILESUBHDR_SIZE =

sizeof(PF_FileSubHeader))开始,存放一个位图。位图中的每一位对应着一个页

面的分配情况:0 表示该页为空闲页,1 表示该页已分配。显然,每个分页文件

的最大长度受位图大小的限制。

控制页在创建文件(CreateFile)时生成并初始化,之后每次申请新页面、

释放页面、分配空闲页时,PF_FileSubHeader 结构和位图中的对应位都要做相应

的修改。

使用一个分页文件前,需要先打开文件(OpenFile),获取一个文件句柄,

后续的文件操作均以该句柄为标识,不再使用该文件时,要关闭文件(CloseFile)

释放资源。文件句柄的结构为:

typedef struct {

bool bOpen; //该文件句柄是否已经与一个文件关联

char *fileName; //与该句柄关联的文件名

int fileDesc; //与该句柄关联的文件描述符

Frame *pHdrFrame; //指向该文件头帧(控制页对应的帧)的指针

Page *pHdrPage; //指向该文件头页(控制页)的指针

Page 8: 课程设计报告 - Huazhong University of Science and ...cs.hust.edu.cn/__local/D/B2/1E/556FBEBBBA639329663... · 课程设计报告 Hustbase 数据管理系统设计与实现 专业班级:计卓1201

5

char *pBitmap; //指向控制页中位图的指针

PF_FileSubHeader fileSubHeader; //该文件的 PF_FileSubHeader 结构

} PF_FileHandle;

2.1.2 页面缓冲区

为提高页面访问效率,页面管理模块维护一个页面缓冲区。该缓冲区由以下

数据结构表示:

typedef struct {

int nReads; //I/O 统计已读

int nWrites; //I/O 统计已写

Frame frame[PF_BUFFER_SIZE]; //缓冲池是一组帧页

bool allocated[PF_BUFFER_SIZE]; //用来标明每一帧是否已被分配

} BF_Manager;

缓冲池大小为 PF_BUFFER_SIZE 个帧(Frame),对应数据结构为:

typedef struct {

bool bDirty; //True 表示页面内容已经被修改,False 表示没有被修改

unsigned int pinCount; //正在访问当前页面的线程数,当pinCount>0时,

//此页面必须驻留在缓冲区中,不允许被置换

time_t accTime; //页面最后访问时间

char *fileName; //访问页面所在的文件名

int fileDesc; //与该文件相关联的文件描述符

Page page; //从硬盘中获取的实际页面

} Frame;

每次从文件中读一个页面时,从页面缓存区为该页分配一个帧(采用 LRU

算法),将该页的内容读到帧中。每个页面对应一个页面句柄,作为该页面的标

识。页面句柄的数据结构为:

typedef struct {

bool bOpen; //True 表示页面句柄被打开

Frame *pFrame; //指向当前对象句柄的指针

}PF_PageHandle;

Page 9: 课程设计报告 - Huazhong University of Science and ...cs.hust.edu.cn/__local/D/B2/1E/556FBEBBBA639329663... · 课程设计报告 Hustbase 数据管理系统设计与实现 专业班级:计卓1201

6

2.2 页面管理模块对外提供的函数调用接口

2.2.1 文件操作函数

RC CreateFile (const char *fileName);

此函数创建一个名为 fileName 的分页文件。

RC OpenFile(char *fileName,PF_FileHandle *fileHandle);

此函数打开一个分页文件,返回它的句柄指针 fileHandle。

RC CloseFile (PF_FileHandle *fileHandle);

此函数关闭句柄 fileHandle 对应的分页文件。

2.2.2 页面操作函数

RC GetThisPage (PF_FileHandle *fileHandle, PageNum pageNum,

PF_PageHandle *pageHandle);

此函数从文件句柄 fileHandle 对应文件中,获取页面号为 pageNum 的页面,

返回其句柄指针 pageHandle。

RC AllocatePage (PF_FileHandle *fileHandle, PF_PageHandle

*pageHandle);

此函数在文件句柄 fileHandle 对应文件中分配一个新的页面,并返回它的页

面句柄 pageHandle(如果文件中有空闲页,就直接分配一个空闲页,否则扩展文

件规模)。

RC GetPageNum (PF_PageHandle *pageHandle,PageNum *pageNum);

此函数返回页面句柄 pageHandle 对应的页面号 pageNum。

RC GetData (PF_PageHandle *pageHandle,char **pData);

此函数返回页面句柄 pageHandle 对应的数据区指针 pData。

RC DisposePage (PF_FileHandle *fileHandle,PageNum pageNum);

此函数丢弃文件句柄 fileHandle 对应文件中编号为 pageNum 的页面,将其变

为空闲页面。

RC MarkDirty(PF_PageHandle *pageHandle);

此函数标记 pageHandle 对应页面为"脏"页。当页面内容被修改时需调用该函

数,以保证该页面被置换出缓冲区前能被写入磁盘

RC UnpinPage(PF_PageHandle *pageHandle);

此函数用于解除 pageHandle 对应页面的驻留缓冲区限制。在调用

GetThisPage 或 AllocatePage 函数将一个页面读入缓冲区后,该页面被设置为驻

Page 10: 课程设计报告 - Huazhong University of Science and ...cs.hust.edu.cn/__local/D/B2/1E/556FBEBBBA639329663... · 课程设计报告 Hustbase 数据管理系统设计与实现 专业班级:计卓1201

7

留缓冲区状态,以防止其在处理过程中被置换出去,因此在该页面使用完之后应

调用此函数解除该限制。

Page 11: 课程设计报告 - Huazhong University of Science and ...cs.hust.edu.cn/__local/D/B2/1E/556FBEBBBA639329663... · 课程设计报告 Hustbase 数据管理系统设计与实现 专业班级:计卓1201

8

3 记录管理模块

3.1 记录管理模块说明

数据表中的数据以记录为单位存取。本模块包括一组函数,用于管理存储在

一组文件中的数据表记录。记录管理模块依赖于底层的页面管理模块,页面管理

模块以页面为单位实现文件级别的 I/O,而记录管理模块则以记录为单位实现数

据表级的读写,记录根据页面号和插槽号以顺序方式存取。为简化实现,可规定

文件中存储的记录为定长,且记录格式相同。即:一个数据表对应一个文件。

3.1.1 控制页

每个文件包含若干页面,其中第 0 页为分页信息控制页,具体内容见 4.1 节。

文件的第 1 页为记录信息控制页,从 data[0]开始,记录当前数据文件所存储的记

录长度、记录数量、每个页面的状态(是否还有空槽可存放记录)等控制信息。

3.1.2 数据页

从分页文件的第 2 页开始为数据页面,每个数据页的存储空间被逻辑划分为

若干个插槽,每个插槽可存放一条记录,记录采用顺序存储方式。一个页面中插

槽的数量取决于记录的长度。在一个记录文件中,通过(页面号,插槽号)可以

唯一确定一个记录。

3.1.3 文件操作

使用一个记录文件前,需要先打开文件(RM_OpenFile),获取一个记录文

件句柄,后续的文件操作均以该句柄为标识,不再使用该文件时,要关闭文件

(RM_CloseFile)释放资源。文件句柄的参考结构为:

typedef struct{

bool bOpen;//句柄是否打开(是否正在被使用)

PF_FileHandle *pfFileHandle; //该记录文件对应的页面文件句柄

Frame *pHdrFrame; //指向该记录文件第一帧(控制页对应的帧)的指针

Page *pHdrPage; //指向该记录文件第一页(控制页)的指针

char *pBitmap;//指向记录文件第一页中位图的指针(标记每个页面是否有空

槽)

Page 12: 课程设计报告 - Huazhong University of Science and ...cs.hust.edu.cn/__local/D/B2/1E/556FBEBBBA639329663... · 课程设计报告 Hustbase 数据管理系统设计与实现 专业班级:计卓1201

9

RM_FileSubHeader *pFileSubHeader; //该文件的 RM_FileSubHeader 结构

}RM_FileHandle;

在记录文件数据页的开头,用位图的方法记录该页每个插槽的状态,所用的字节

数为( (pFileSubHeader->slotCount - 1) / 8 + 1),某位值为 1 代表该插槽为空。

记录文件控制信息结构如下:

typedef struct{

int recordSize;//记录长度

SlotNum slotCount;//每页插槽数

SlotNum recordCount;//记录数量

}RM_FileSubHeader;

3.1.4 记录操作

记录对应的数据结构为:

typedef struct{

bool bValid; //False 表示还未被读入记录

RID rid; //记录的标识符

char *pData; //记录所存储的数据

}RM_Record;

1) 插入记录

插入记录时,首先在文件中找到一个非满页(若没有则需要申请一个新的页

面),在该页中找到一个空插槽并插入记录。记录插入后,要更新控制页及插槽

状态的相关信息。

2) 删除记录

删除记录时,根据给定的记录 ID,通过页面号和插槽号找到相应的记录。

记录删除并不需要将页面中记录所占用的区域清零,只需要将插槽状态置空,同

时更新控制页的相关信息。

3) 记录扫描

记录扫描为用户提供基于特定条件的扫描记录文件中记录的功能,在扫描期

间,只有那些满足条件的记录将被获取。

记录扫描的数据结构:

typedef struct{

bool bOpen; //扫描是否打开

RM_FileHandle *pRMFileHandle; //扫描的记录文件句柄

int conNum; //扫描涉及的条件数量

Page 13: 课程设计报告 - Huazhong University of Science and ...cs.hust.edu.cn/__local/D/B2/1E/556FBEBBBA639329663... · 课程设计报告 Hustbase 数据管理系统设计与实现 专业班级:计卓1201

10

Con *conditions; //扫描涉及的条件数组指针

PF_PageHandle PageHandle; //处理中的页面句柄

PageNum pn; //扫描即将处理的页面号

SlotNum sn; //扫描即将处理的插槽号

}RM_FileScan;

涉及条件的数据结构:

typedef struct

{

int bLhsIsAttr,bRhsIsAttr;//标明条件的左、右分别是属性(1)还是值(0)

AttrType attrType; //该条件中数据的类型

int LattrLength,RattrLength;//若是属性的话,标明属性的长度

int LattrOffset,RattrOffset; //若是属性的话,标明属性的偏移量

CompOp compOp; //用于比较的操作符

void *Lvalue,*Rvalue; //若是值的话,保存该值

}Con;

其中 attrType 和 LattrLength/RattrLength 分别表示所要进行比较的属性的类

型和长度。当属性的类型为整型或浮点型时,LattrLength/RattrLength 固定为 4。

当属性的类型为字符串类型时,LattrLength/RattrLength 表示的是字符串的长度。

LattrOffset/RattrOffset 表示所指定的属性在记录中的偏移量,通过它就能够在记

录中找到所要比较的属性值。

比较操作符的数据结构为:

typedef enum {

EQual, LessT, GreatT, //等于,小于,大于

NEqual, LEqual, GEqual, //不等于,小于等于,大于等于

NO_OP //无比较,用于索引扫描,详见 6.1.3

} CompOp;

3.2 记录管理模块对外提供的函数调用接口

3.2.1 记录文件管理函数

RC RM_CreateFile (char *fileName, int recordSize);

此函数创建一个文件名为 fileName 的记录文件,该文件中每条记录的大小

为 recordSize。函数的流程图如图 2 所示,具体代码见提交源码。

Page 14: 课程设计报告 - Huazhong University of Science and ...cs.hust.edu.cn/__local/D/B2/1E/556FBEBBBA639329663... · 课程设计报告 Hustbase 数据管理系统设计与实现 专业班级:计卓1201

11

开始

结束

调用页面文件模块的CreateFile函

数创建新页面文件

调用页面文件模块的openFile

函数得到新创建文件的句柄

调用页面文件模块的AllocatePage

函数在文件中申请一个新页面

调用页面文件模块的MarkDirty函

数将申请的新页面标记为dirty

调用页面文件模块的

CloseFile函数关闭文件

将记录文件的控制信息结构RM_FileSubHeader

和插槽信息位图写入新页面

图 2RM_CreateFile 函数流程图

RC RM_OpenFile (char *fileName, RM_FileHandle *fileHandle);

此函数打开名为 fileName 的记录文件,返回它的句柄指针 fileHandle。函数

实现流程图如图 3 所示:

开始

结束

调用页面文件模块的

openFile函数得到页面文

件句柄

调用页面文件模块的

GetThisPage函数获得页面文件

第一页的页面句柄

根据页面文件第一页(记录文件控制页)的页面句柄填充记录文件句柄数据

结构

图 3RM_OpenFile 函数流程图

Page 15: 课程设计报告 - Huazhong University of Science and ...cs.hust.edu.cn/__local/D/B2/1E/556FBEBBBA639329663... · 课程设计报告 Hustbase 数据管理系统设计与实现 专业班级:计卓1201

12

RC RM_CloseFile (RM_FileHandle *fileHandle);

此函数关闭句柄 fileHandle 对应的记录文件。直接调用页面文件模块的

CloseFile 函数关闭页面文件,会将所做的修改写回磁盘。

3.2.2 记录操作函数

RC GetRec (RM_FileHandle *fileHandle, RID *rid, RM_Record *rec);

此函数检索 fileHandle 指向的文件,获取记录标识符为 rid 的记录,rec 为所

获取的记录结构指针。函数的流程图如图 4 所示:

开始

结束

调用页面文件模块的

GetThisPage函数获得rid指向记

录所在页面的页面句柄

文件是否打

开?

返回RM_FHCLOSED

N

rid是否有效?

返回RM_INVALIDRID

将页面中rid指向的记录读入

RM_Record数据结构的pData中

N

Y

Y

图 4GetRec 函数流程图

RC InsertRec (RM_FileHandle *fileHandle,char *pData, RID *rid);

此函数插入一个新的记录到 fileHandle 指向的数据文件中,pData 指向新纪

录的内容,并返回该记录的标识符 rid。函数的流程图图 5 如所示:

RC DeleteRec (RM_FileHandle *fileHandle,RID *rid);

此函数从文件中删除标识符为 rid 的记录。函数实现的流程图如图 6 所示:

Page 16: 课程设计报告 - Huazhong University of Science and ...cs.hust.edu.cn/__local/D/B2/1E/556FBEBBBA639329663... · 课程设计报告 Hustbase 数据管理系统设计与实现 专业班级:计卓1201

13

控制页记录数目加一

开始

调用记录管理模块的RM_CheckSlot函数

检查页面i是否还有空槽

文件是否打开?

返回RM_FHCLOSED

N

根据记录文件控制页位图

查找有空槽的页面

找到有空槽的页面i?

调用页面文件模块的GetThisPage

函数页面i的页面句柄

根据页面开头的插槽状态信息查找为

空的插槽j,将插槽j状态位置为0

结束

Y

Y

在页面i的插槽j对应

位置写入数据

调用页面文件模块的MarkDirty函

数将修改过的页面标记为dirty

调用页面文件模块的AllocatePage

函数申请一个新页面

在新页面的插槽0对

应位置写入数据

初始化该页面的插槽状态信息,每位

置为1,将插槽0状态位置为0

N

图 5InsertRec 函数流程图

RC UpdateRec (RM_FileHandle *fileHandle,RM_Record *rec);

此函数更新与 rec 有关联的文件中的记录内容,rec 的 pdata 字段存放了记录

的新值。函数实现的流程图如图 7 所示:

Page 17: 课程设计报告 - Huazhong University of Science and ...cs.hust.edu.cn/__local/D/B2/1E/556FBEBBBA639329663... · 课程设计报告 Hustbase 数据管理系统设计与实现 专业班级:计卓1201

14

开始

结束

调用页面文件模块的MarkDirty函

数将修改过的页面标记为dirty

调用页面文件模块的

GetThisPage函数获得rid指向记

录所在页面i的页面句柄

文件是否打

开?

返回RM_FHCLOSED

rid是否有效?

返回RM_INVALIDRID

Y

Y

N

N

调用记录管理模块的RM_CheckSlot函数

检查页面i是否还有空槽

在页面i的rid所指插槽对

应位置写入记录大小个0

控制页记录数目减一

图 6DeleteRec 函数流程图

开始

结束

调用页面文件模块的MarkDirty函

数将修改过的页面标记为dirty

调用页面文件模块的

GetThisPage函数获得rid指向记

录所在页面i的页面句柄

文件是否打

开?

返回RM_FHCLOSED

rid是否有效?

返回RM_INVALIDRID

Y

Y

N

N

在页面i的rid所指插槽对应

位置写入新纪录的数据

图 7UpdateRec 函数流程图

Page 18: 课程设计报告 - Huazhong University of Science and ...cs.hust.edu.cn/__local/D/B2/1E/556FBEBBBA639329663... · 课程设计报告 Hustbase 数据管理系统设计与实现 专业班级:计卓1201

15

void RM_CheckSlot(Page *pPage, RM_FileSubHeader *pFileSubHeader,

char * pBitmap);

检查 pPage 指向的页面中是否有空槽,并修改记录文件控制页(第一页)

位图。pFileSubHeader 存放 pPage 指向的页面所在文件的控制信息,pBitmap 指

向 pPage 指向的页面所在文件第一页中位图的指针,该页位图标记每个页面是否

有空槽。函数流程图如图 8 所示:

开始

结束

K=0;SLOT_CONTROL_SIZE=插

槽控制信息占用字节数

K<SLOT_CONTROL

_SIZE?

pPage->pData[k] != 0

将pBitmap相应

位置为1将pBitmap相应

位置为0

K=SLOT_CONTROL_SIZE

(页面中无空插槽)?

K++;

Y

Y

N

Y

N N

图 8RM_CheckSlot 函数流程图

bool GetComResult(AttrType attrType, void *Lvalue, void *Rvalue,

CompOp compOp);

计算条件的比较结果。attrType 是待比较的两个值的属性,compOp 是

两个值之间的比较运算符,Lvalue 和 Rvalue 是待比较的两个值。如果 Lvalue 和

Rvalue 满足条件返回 true,否则返回 false。

这个函数实现思路很简单,首先判断两个值的属性是 chars、floats 和 ints 中的哪

一个,然后根据 compOp 对 Lvalue 和 Rvalue 两个值进行比较,最后返回比较结

果。如果是 chars 类型就将 Lvalue 和 Rvalue 强制转换为(char *)类型调用 strcmp

函数比较;如果是 ints 类型就用*(int *)Lvalue 和*(int *)Rvalue 将值转换为整数进

行比较;如果是 floats 类型就用*(float *)Lvalue 和*(float *)Rvalue 将值转换为单

精度浮点数进行比较;

Page 19: 课程设计报告 - Huazhong University of Science and ...cs.hust.edu.cn/__local/D/B2/1E/556FBEBBBA639329663... · 课程设计报告 Hustbase 数据管理系统设计与实现 专业班级:计卓1201

16

RC CheckRID(RM_FileHandle *fileHandle, RID *rid);

检查在 fileHandle 所指向的文件中,rid 所标记的记录是否有效,并修改

rid->bValid 属性。函数流程图如图 9 所示:

调用GetThisPage函数获得rid页面号

对应的页面句柄

开始

结束

Rid页面号>记录文件

总页数?

Rid插槽号对应的位图

值为1?

rid->bValid = false;

返回PF_EOF

rid->bValid = true;

返回SUCCESS

rid->bValid = false;

返回RM_INVALIDRID

N

N

Y

Y

图 9CheckRID 函数流程图

3.2.3 文件浏览函数

RC OpenScan(RM_FileScan *rmFileScan,

RM_FileHandle *fileHandle,

int conNum,

Con *conditions);

初始化文件扫描。此函数初始化参数 rmFileScan 指向的一个文件扫描结构,

其它参数均为用于初始化该结构的输入参数。初始化成功后,记录获取操作

GetNextRec 通过该指针进行文件扫描并获取符合条件的记录。扫描中,如果条

件的数量为 0,条件是一个空指针,表示该扫描为全表扫描,即所有记录都被检

索;如果条件不为空,则只有满足所有条件的记录才被检索。函数实现的流程图

如图 10 所示:

Page 20: 课程设计报告 - Huazhong University of Science and ...cs.hust.edu.cn/__local/D/B2/1E/556FBEBBBA639329663... · 课程设计报告 Hustbase 数据管理系统设计与实现 专业班级:计卓1201

17

开始

从第2页的插槽0开始,循环调用记录管

理模块的CheckRID函数检查rid是否有

效,找到第一个有效的rid

文件是否打开?

返回RM_FHCLOSED

N

结束

Y

记录数目为0?

rmFileScan->bOpen = false;

调用页面文件模块的

GetThisPage函数获得rid指向记录所在页面的页面句柄,赋给

rmFileScan的页面句柄变量

给rmFileScan赋值:rmFileScan->bOpen = true;

rmFileScan页号、插槽号

置为rid的页号和插槽号

N

Y

图 10OpenScan 函数流程图

RC GetNextRec(RM_FileScan *rmFileScan,RM_Record *rec);

此函数用于获得下一个满足扫描条件的记录。如果该函数成功,返回值 rec

应包含记录数据及记录标识符。如果没有满足扫描条件的记录,返回 RM_EOF。

函数实现的流程图如图 11 所示:

Page 21: 课程设计报告 - Huazhong University of Science and ...cs.hust.edu.cn/__local/D/B2/1E/556FBEBBBA639329663... · 课程设计报告 Hustbase 数据管理系统设计与实现 专业班级:计卓1201

18

开始

循环调用记录管理模块的CheckRID函数

检查rid是否有效,找到下一个有效的rid

文件扫描已打

开?

返回RM_FSCLOSED

N

结束

Y

调用记录管理模块的

GetRec函数得到记录?

i<条件个数?

Int i=0;Find=false;

Y

i++;

调用记录管理模块的GetComResult

函数判断记录是否满足条件i

记录满足条件i?

关闭文件扫描

i=条件个数(记录满

足所有条件)?

Y

已扫描到文件末尾?

Find=true(查找到符合

条件的记录)?

Find=true;

Y

N

N

Y

Y

N

N

返回SUCCESS

Y

N

N

图 11GetNextRec 函数流程图

Page 22: 课程设计报告 - Huazhong University of Science and ...cs.hust.edu.cn/__local/D/B2/1E/556FBEBBBA639329663... · 课程设计报告 Hustbase 数据管理系统设计与实现 专业班级:计卓1201

19

4 索引管理模块

4.1 索引管理模块说明

本模块用于管理存储在一组文件中的索引记录。索引管理类似于记录管理,

但不同于记录管理中的顺序存储数据表记录,索引管理采用 B+树结构来组织索

引记录。索引管理模块同样依赖于底层的页面管理模块。

4.1.1 索引文件

一个 B+树索引是一个索引文件,由若干页面构成。B+树索引节点的大小固

定,为简化实现,可规定每个页面存储一个索引节点。类似于记录文件,索引文

件的第 1 页为控制页,用于存放控制信息,从第 2 页开始为数据页,用于存放索

引节点信息。

控制页中的控制信息包括索引值的长度及数据类型、根节点页号、树的阶数

等信息。参考数据结构如下:

typedef struct{

int attrLength; //建立索引的属性值的长度

int keyLength; //B+树中关键字的长度

AttrType attrType; //建立索引的属性值的类型

PageNum rootPage; //B+树根节点的页面号

int order; //B+树的阶数

}IX_FileHeader;

索引文件的文件头在索引建立的时候初始化,并且随着索引的变化,其根节

点会发生变化。关于 keyLength 的解释,具体见 4.1.3 节“非唯一索引的重复键

值处理”。B+树的阶数是指一个节点可拥有的最大分支数。IX_FileHeader 结构可

根据需要优化设计。

使用一个索引文件前,需要先打开索引(OpenIndex),获取一个索引句柄,

后续的索引操作均以该句柄为标识,不再使用该索引时,要关闭索引(CloseIndex)

释放资源。索引句柄的参考结构为:

typedef struct{

bool bOpen; //该索引句柄是否已经与一个文件关联

PF_FileHandle fileHandle; //该索引文件对应的页面文件句柄

IX_FileHeader fileHeader; //该索引文件的控制页句柄

}IX_IndexHandle;

Page 23: 课程设计报告 - Huazhong University of Science and ...cs.hust.edu.cn/__local/D/B2/1E/556FBEBBBA639329663... · 课程设计报告 Hustbase 数据管理系统设计与实现 专业班级:计卓1201

20

4.1.2 B+树

B+树是一种多路平衡树,在 B+树中的节点通常被表示为一组有序的元素和

子指针。如果 B+树的阶数(order)为 m,则 B+树要满足下列的性质:

1) 根结点只有 1 个,关键字个数的范围为[1,m-1],分支数量范围[2,m];

2) 除根以外的内部结点,每个结点包含分支数范围为[[m/2],m],即关键字

字数的范围是[[m/2]-1,m-1],其中[m/2]表示取大于等于 m/2 的最小整数;

3) 所有的叶子结点中包含了全部关键字的信息,及指向含这些关键字记录

的指针,且叶子结点本身依关键字的大小自小而大顺序链接。并且所有

的叶子节点都在同一层。

4) 叶子节点包含的关键字个数范围为[[m/2]-1,m-1]。

B+树中的每个节点都是存储在分页文件中的一页上,这样每个页面号就能

够唯一的标识一个 B+树节点。

B+树中每个节点数据的参考结构为:

typedef struct{

int is_leaf; //该节点是否为叶子节点

int keynum; //该节点实际包含的关键字个数

PageNum parent; //父节点的页面号

KEY *keys; //关键字数组

RID *rids; //记录标识符区域指针(叶子节点)或

//子节点页面号区域指针(非叶子节点)

}IX_Node;

typedef struct{

char *keyval; //关键字

}KEY;

查询节点的结果数据结构为:

typedef struct{

PageNum pageNum; //找到的节点

int num; //找到的值在节点中的关键字序号

bool tag; //是否找到

}IX_Result;

每个 B+树节点都占据一个页面,页面的大小固定为 4K,但是由于不同的索

引关键字的大小不同,因此不同索引的 B+树节点能够容纳的最大的关键字个数

不同,即不同索引的 B+树阶数不同。这是在索引创建时通过给定的属性值的大

Page 24: 课程设计报告 - Huazhong University of Science and ...cs.hust.edu.cn/__local/D/B2/1E/556FBEBBBA639329663... · 课程设计报告 Hustbase 数据管理系统设计与实现 专业班级:计卓1201

21

小来进行计算的。

计算公式为:

m=(PF_PAGE_SIZE-节点控制信息长度)/(atrrLength +2*sizeof(RID))

其中,sizeof(RID)乘以 2 的原因是通过在属性值中加上 RID 来使得每个关键

字都唯一,详见 4.1.3 节。

4.1.3 B+树索引的操作

1) B+树的查找

对 B+树可以进行两种查找运算:

(1) 从最小关键字起顺序查找;

(2) 从根节点开始,进行随机查找。

在查找时,如内部节点上的关键字等于给定值,查找并不终止,而是继续向

下直到叶子节点。这是因为内部节点并不包含指向记录的记录标识符。因此,在

B+树中,不管查找成功与否,每次查找都是走了一条从根到叶子节点的路径。

2) B+树的插入

首先,查找到要插入其中的叶子节点的位置,接着把值插入到这个节点当中。

如果节点处于合法状态,则处理结束。如果该节点有过多的关键字,即关键字的

个数达到了 m 个,则把它平均分裂为两个节点,每个结点都有至少为最小数目

个关键字。如果是叶子节点,则将分裂后的后面一颗树的第一个关键字插入到它

的父节点当中,否则,将中间关键字插入到它的父节点当中。再递归的处理父结

点的插入,直到到达根节点,如果根节点被分裂,则创建一个新的根节点。

3) B+树的删除

首先,查找到要删除的值,接着从包含它的节点中删除这个值。如果没有节

点处于非法状态则处理结束。如果节点处于非法状态,则有两种可能情况:

(1) 它的兄弟节点有多余子节点。此时可以把兄弟节点的一个或是多个子节

点转移到当前节点,从而把它变为合法状态。这种情况下,在更改父节

点和两个兄弟节点的分离值之后处理结束。

(2) 它的兄弟节点由于处在最低边界上而没有额外的子节点。在这种情况下

把两个兄弟节点合并到一个单一的节点中,并递归处理父节点(因为它

被删除了一个子节点),持续这个处理直到当前节点是合法状态或是到

达根节点,在其上根节点的子节点被合并而且合并后的节点成为新的根

节点。

(3) 非唯一索引的重复键值处理

由于建索引的列不一定是唯一列,因此可能出现多个元组的索引列值相同的

Page 25: 课程设计报告 - Huazhong University of Science and ...cs.hust.edu.cn/__local/D/B2/1E/556FBEBBBA639329663... · 课程设计报告 Hustbase 数据管理系统设计与实现 专业班级:计卓1201

22

问题。解决方法为:将索引列的值和记录的 ID 值组合成索引关键字,由于记录

ID 是唯一的,因此组合而成的关键字一定是唯一的。因为上述原因,索引关键

字的长度 keyLength 为索引列值的长度+记录 ID 的长度。关键字比较过程中,先

比较属性值,然后在属性值相同的情况下比较记录 ID。这样就使得索引在总体

上按关键字从小到大排列,在关键字相同的情况下,按记录 ID 的大小从左向右

排。

(4) 索引扫描

索引扫描是基于条件来扫描一个索引中的索引项,它是对 B+树查找功能的

进一步加强。

索引扫描的数据结构:

typedef struct{

bool bOpen; //扫描是否打开

IX_IndexHandle *pIXIndexHandle; //指向索引操作的指针

CompOp compOp; //用于比较的操作符

char *value; //与属性值进行比较的值

PF_PageHandle PageHandle; //处理中的页面句柄

PageNum pn; //扫描即将处理的页面号

intridIx; //扫描即将处理的索引项编号

}IX_IndexScan;

由于比较操作符只有六种:大于,大于等于,小于,小于等于,等于,以及

无比较操作。这里的无比较操作(NO_OP)具有其特殊的意义,用于将记录按

照某个属性值从小到大的顺序全部扫描。

通过分析所有叶子节点关键字的排列顺序特性可知,找到第一个符合条件的

索引项后,就可以顺序扫描,直到遇到第一个不符合条件的索引项就可以停止扫

描,比如小于比较符,如果扫描到一个索引项的属性值大于或等于所给定的属性

值,则这个索引项后面的索引项的属性值都大于等于这个给定的属性值。

因此对于索引扫描初始化时,最重要的工作就是初始化 ridIx 和 pn 的值,对

于小于和小于等于比较操作符,ridIx 为 0,pn 为第一个叶子节点所在的页面。

对于等于号,通过 B+树查找操作找到叶子节点,然后顺序比较属性值,如果找

到相等的就将 ridIx 以及 pn 置为相应的值,如果找到了一个大于的值,则返回错

误。对于大于和大于等于操作也是相同的原理。

(4)索引查找

对于索引的查找,自定义了一个数据结构,如下所示:

typedef struct{

PageNum pageNum; //找到的节点的页号

Page 26: 课程设计报告 - Huazhong University of Science and ...cs.hust.edu.cn/__local/D/B2/1E/556FBEBBBA639329663... · 课程设计报告 Hustbase 数据管理系统设计与实现 专业班级:计卓1201

23

int num; //找到的值在节点中的关键字序号

bool tag; //是否找到

}IX_Result;

主要用于记录是否找到,以及找到的索引项在哪里,如果没有找到,反映的

是应该插入的位置。

4.2 索引管理模块对外提供的函数调用接口

4.2.1 索引文件管理函数

RC CreateIndex (const char *fileName, AttrType attrType,int attrLength)

此函数创建一个名为 fileName 的索引。attrType 描述被索引属性的类型,

attrLength 描述被索引属性的长度。

首先调用页面管理的 CreateFile()函数创建一个名为 fileName 的索引文件。

此时该文件已有第一页即分页信息控制页,之后再调用 AllocatePage()函数分配

一个页面,作为索引信息控制页。然后初始化 IX_FileHeader 这个结构。将其写

入文件的第 1 页即索引信息控制页。其中,attrLengt 和 attrType,直接由函数参

数得到,keyLength 为索引列值的长度+记录 ID 的长度,索引的阶数由于要处理

唯一索引的重复键值,按照老师提供的公式计算。创建索引文件时,根节点页号

为 1,即指向索引信息控制页,后续可以用 rootPage<2 来判断索引是否为空。由

于改写了第 1 页的数据,需要调用 MarkDirty()函数将其标记为脏页,同时解除

这个页面的驻留缓冲区限制,最后关闭该索引文件句柄。

RC IX_GetIndexSearchResult(IX_IndexHandle *indexhandle, AttrType attrType,

char *value, RID *rid, IX_Result *pIXResult);

此函数在索引句柄 indexhandle 指向的索引文件里查找类型为 attrType,指向

属性值的指针为 value,对应的记录 ID 为 rid 的索引项在节点中的位置,结果通

过 pIXResult 返回。如果找到,IX_Result 结构的 tag 项为 true,置该索引项所在

的页号与属性值序号。如果没有找到,tag 置为 false,置该索引项该插入的位置

的叶子节点页号和序号。

实现思路:首先判断,索引句柄是否打开,若打开,就继续,否则就直接返

回 IX_IHCLOSED,再读取索引信息控制页的内容,判断索引是否为空,即

rootPage 是否小于等于 1,若是,返回索引空,否则继续。然后,读取根节点信

息,接着一层一层往下找,如果读到的节点不是叶子节点,就读下一层的节点,

一直循环。在循环中,调用上文提到的 IX_GetNodeSearchResult()函数,找到索

引项的位置或应该插入的位置,再通过该返回值判断是否找到,如果找到,说明

Page 27: 课程设计报告 - Huazhong University of Science and ...cs.hust.edu.cn/__local/D/B2/1E/556FBEBBBA639329663... · 课程设计报告 Hustbase 数据管理系统设计与实现 专业班级:计卓1201

24

叶子节点里也会有,进行置位。然后读取,rids 数组里对应序号的页面,继续循

环。跳出循环,是因为找到了叶子节点,接着,在叶子节点里进行查找,也是通

过 IX_GetNodeSearchResult()函数,找到索引项的位置或应该插入的位置,再通

过该返回值判断是否找到,如果找到,置 pIXResult 的 tag 为 true,否则置为 false,

置 pageNum 为当前页面号,num 为节点查找函数的返回值。

RC OpenIndex (const char *fileName, IX_IndexHandle *indexHandle)

此函数打开名为 fileName 的索引文件。如果方法调用成功,则 indexHandle

为指向被打开的索引句柄的指针。索引句柄用于在索引中插入或删除索引项,也

可用于索引的扫描。

该函数的主要操作就是初始化索引句柄的指针 indexHandle。为了便于操作,

对老师给的数据结构进行了优化。如下所示:

typedef struct{

bool bOpen; //该索引句柄是否已经与一个文件关联

PF_FileHandle *pFileHandle; //该索引文件对应的页面文件句柄

IX_FileHeader *pFileHeader; //该索引文件的控制页句柄

}IX_IndexHandle;

将老师给的 IX_IndexHandle 结构中的两个结构类型改为了结构指针类型。

首先调用页面管理的 openFile(char *fileName,PF_FileHandle *fileHandle)函

数打开名为 fileName 的索引文件,得到该文件的文件句柄,将其赋值给

indexHandle 中的对应指针。然后读取第一页的数据,即得到 IX_FileHeader 结构

体中的内容,将结构指针赋值给 indexHandle 中的对应指针,然后将 bOpen 置位

true,表示索引句柄已打开。

RC CloseIndex(IX_IndexHandle *indexHandle)

此函数关闭句柄 indexHandle 对应的索引文件。直接调用页面管理的

CloseFile()函数关闭 indexHandle 结构中的文件句柄。

4.2.2 索引操作函数

int IX_GetNodeSearchResult(IX_Node *pIXNode, AttrType attrType, char

*value, RID *rid);

此函数在 pIXNode 节点中查找类型为 attrType,指向属性值的指针为

value,对应的记录 ID 为 rid 的索引项在节点中的位置,并返回该值。注意:

如果找到该索引项,返回它在该节点中的序号,如果没有找到,则返回大于

等于前一个关键字,小于该关键字的所对应的的序号,即返回值 i 满足

key[i-1]<=value<key[i],当然,如果 i 是第一个或最后一个会有特殊处理:

Page 28: 课程设计报告 - Huazhong University of Science and ...cs.hust.edu.cn/__local/D/B2/1E/556FBEBBBA639329663... · 课程设计报告 Hustbase 数据管理系统设计与实现 专业班级:计卓1201

25

value<key[i]&&i==0 或 value>=key[i] && i == keynum – 1。

实现思 路: 使用 循 环, 逐 一 扫 描所 有 的关键 字, 如果 找 到

value<key[i]&&i==0,key[i-1]<=value<key[i],value>=key[i] && i == keynum

– 1。就返回对应的 i 值。对于叶子节点和非叶子节点,处理稍有不一样。如

果找到,就返回属性值对应的序号,如果没有找到,返回值是该属性值应该

插入的位置。对于找到时的情况,叶子节点和非叶子节点的返回值不同,返

回 i + 1 - pIXNode->is_leaf,主要是因为叶子节点找到了返回的是该索引项的

次序,而非叶子节点返回的是该索引项指向的下一个索引节点的在 rids 数组

中的序号,这是因为 B+树索引的查找是一定要找到叶子节点的。

RC InsertEntry (IX_IndexHandle *indexHandle,void *pData, const RID *rid)

此函数向 IX_IndexHandle 对应的索引中插入一个索引项。参数 pData 指向要

插入的属性值,参数 rid 标识该索引项对应的元组,即向索引中插入一个值为

(*pData,rid)的键值对。

实现思路:

首先分为几种情况:在叶子节点进行插入而不必分裂,在叶子节点进行

插入而需要分裂,还有根节点是否需要分裂。

(1)叶子节点进行插入而不分裂,直接由 IX_GetIndexSearchResult()函数

函数找到插入位置,如果属性值重复,也没有关系,因为有唯一索引的重复值处

理机制。插入完毕后返回 SUCCESS。

(2)叶子节点要分裂,如果没有分裂到根结点,对节点进行分裂,修改

新节点的父节点号,修改新节点下的子节点的父节点号。

(3) 叶子节点要分裂,如果分裂到根结点,对节点进行分裂,修改新节

点的父节点号,修改新节点下的子节点的父节点号。对根节点进行分裂,分配新

的页面,产生新的根节点,修改根节点号。

辅 助 函 数 : RC SplitNode(IX_IndexHandle *indexHandle, IX_Node

*pIXNode, IX_Node *newpIXNode, char *pData, RID *rid, int insertnumber);

函数功能:对节点 pIXNode 进行分裂,insertnumber 为新加节点要插入

的位置,由 IX_GetNodeSearchResult()函数获得。newpIXNode 为分裂后新产生的

节点。(*pData,rid)为待插入的键值对。

实现思路:首先由 insertnumber 判断新节点的 keynum,以保证原节点

和新节点二者 keynum 的均衡。如果插入的节点在原节点中,根据新节点的

keynum,将原节点中的后 keynum 个数据拷贝到新节点中,然后将(*pData,rid)

为待插入的键值对插入到原节点中,对于原节点是叶子节点,还是非叶子节点,

拷贝时的处理是不同的。因为叶子节点与非叶子节点虽然 keys 和 rids 的关系一

样,但是意义不一样,对于非叶子节点,大于等于 keys[i]的节点的页面由 rids[i+1]

Page 29: 课程设计报告 - Huazhong University of Science and ...cs.hust.edu.cn/__local/D/B2/1E/556FBEBBBA639329663... · 课程设计报告 Hustbase 数据管理系统设计与实现 专业班级:计卓1201

26

确定。而对于叶子节点,等于 key[i]的记录由 rids[i]确定。所以,拷贝的时候区

别对待。对于非叶子节点,分裂后的节点的 rids[0]指向非法页面。

图 12DeleteEntry 函数流程图

开始

是否为根节点

页面

查找 B+树,找到要删除索引

项所在的页面以及索引项号

删除索引项是

否下溢

向兄弟节点借一个索引

项兄弟节点是否下溢

兄弟节点是否

为非法页面

根节点是否为

叶子节点

利用Result->num删

除索引项,并进行数

据结构的更新

向兄弟节点借一个

索引项,更新父节点

索引项 合并兄弟节点,删除

父节点中指向多余

那个节点的索引项

向表兄弟节点借一个

索引项表兄弟是否下

向兄弟节点借一个

索引项,更新父节点

索引项

合并表兄弟节点,删

除父节点中指向多

余那个节点的索引

结束

根节点为叶子节点

时不存在下溢,直接

删除

删除相应索引项,如

果根节点删除完毕,

生成新的根节点

y

y y

y

y y

n

n

n

n n

向兄弟节点借一个索引

项兄弟节点是否下溢

Page 30: 课程设计报告 - Huazhong University of Science and ...cs.hust.edu.cn/__local/D/B2/1E/556FBEBBBA639329663... · 课程设计报告 Hustbase 数据管理系统设计与实现 专业班级:计卓1201

27

RC DeleteEntry (IX_IndexHandle *indexHandle,void *pData, const RID *rid)

此函数从 IX_IndexHandle 句柄对应的索引中删除一个值为(*pData,rid)

的索引项。函数实现的流程图如图 12 所示:

RC OpenIndexScan(IX_IndexScan *indexScan,IX_IndexHandle

*indexhandle,CompOp compOp,char *value);

此函数用于在 indexHandle 对应的索引上初始化一个基于条件的扫描。

compOp 和*value 指定比较符和比较值,indexScan 为初始化后的索引扫描结构指

针。

首先判断索引句柄是否打开,若打开,就继续,否则就直接返回

IX_IHCLOSED,再读取索引信息控制页的内容,判断索引是否为空,即 rootPage

是否小于等于 1,若是,返回索引空,否则继续。然后,对索引扫描结构体进行

赋值,compOp、value 直接使用函数参数赋值,然后根据比较操作符的不同,得

到不同的 pn 和 ridIx 的值。比较操作符只有六种:大于,大于等于,小于,小于

等于,等于,以及无比较操作。这无比较操作(NO_OP)具有其特殊的意义,

用于将记录按照某个属性值从小到大的顺序全部扫描。

通过分析所有叶子节点关键字的排列顺序特性可知,找到第一个符合条件的

索引项后,就可以顺序扫描,直到遇到第一个不符合条件的索引项就可以停止扫

描,比如小于比较符,如果扫描到一个索引项的属性值大于或等于所给定的属性

值,则这个索引项后面的索引项的属性值都大于等于这个给定的属性值。对于索

引扫描初始化时,最重要的工作就是初始化 ridIx 和 pn 的值。

对于空操作(NO_OP)、小于、小于等于、不等于这四个操作符,pn 为第一个

叶子节点所在的页面, ridIx 为 0,需要找到第一个叶子节点,调用 RC

IX_GetKeyLeastNode(IX_IndexHandle *indexhandle, PageNum *pPageNum)函数

得到第一个叶子节点即值最小的节点的页号。

对于等于号,调用 IX_GetIndexSearchResult()函数,找到该值对应的索引项,

如果没有找到,直接关闭索引扫描,返回失败。否则,置对应的页号和序号。

对于大于号,调用 IX_GetIndexSearchResult()函数,找到该值对应的索引项

的位置或应该插入的位置,如果找到,则跳到下一个索引项,如果不等于,则置

pn 为该索引项所在的页号和序号,否则继续下一个,直至结尾。如果到了最后

一个索引项还不符合条件,就关闭索引扫描,返回失败。判断索引完毕的条件是

rids 数组的最后一项是否指向非法页面,指定非法页面为 0,且 bValid = false。

对于大于等于号,调用 IX_GetIndexSearchResult()函数,找到该值对应的索

引项的位置或应该插入的位置,如果找到置 pn 为该索引项所在的页号和序号,

对应的分别为 IX_Result 结构的 pageNum 成员和 num 成员。否则关闭索引扫描,

Page 31: 课程设计报告 - Huazhong University of Science and ...cs.hust.edu.cn/__local/D/B2/1E/556FBEBBBA639329663... · 课程设计报告 Hustbase 数据管理系统设计与实现 专业班级:计卓1201

28

返回失败。

RC IX_GetNextEntry(IX_IndexScan *indexScan,RID *rid);

此函数用于继续 IX_IndexScan 句柄对应的索引扫描,获得下一个满足条件

的索引项,并返回该索引项对应的记录的 ID。

Page 32: 课程设计报告 - Huazhong University of Science and ...cs.hust.edu.cn/__local/D/B2/1E/556FBEBBBA639329663... · 课程设计报告 Hustbase 数据管理系统设计与实现 专业班级:计卓1201

29

5 系统管理模块

5.1 系统管理模块说明

系统管理模块负责数据定义(建立数据表和索引)和数据操纵(插入、删除、

修改记录),其功能依赖于记录管理模块和索引管理模块。

一个数据库由包含在指定目录中的若干数据库文件构成。这些数据库文件包

括一个系统表文件、一个系统列文件、若干记录文件和若干索引文件。模块的主

要数据结构如下:

typedef struct _ AttrInfo AttrInfo;

struct _AttrInfo {

char *attrName; // 属性的名字

AttrType attrType; // 属性的类型

int attrLength; // 属性的长度

};

typedef struct _Condition Condition;

struct _Condition{

int bLhsIsAttr; //操作符左边是属性为 1,是值时为 0

Value lhsValue; //当左边是值时,保存该值的信息

RelAttr lhsAttr; //当左边是属性时,保存属性信息

CompOp op; //比较运算符

int bRhsIsAttr; //操作符右边是属性为 1,是值时为 0

RelAttr rhsAttr; //当右边是属性时,保存属性信息

Value rhsValue; //当左边是值时,保存该值的信息

};

typedef struct {

char *relName; //表名,可能为 NULL

char *attrName; //属性名

}RelAttr;

typedef struct _Value Value;

struct _Value{

AttrType type; // 值的类型

Page 33: 课程设计报告 - Huazhong University of Science and ...cs.hust.edu.cn/__local/D/B2/1E/556FBEBBBA639329663... · 课程设计报告 Hustbase 数据管理系统设计与实现 专业班级:计卓1201

30

Void *data; // 值

};

系统表文件 SYSTABLES 中记录的结构如下:

表名 tablename 该表中属性的数量 attrcount

其中,表名占 21 个字节,即表名的最大长度为 20 个字节。表中属性的数量

占用 sizeof(int)共 4 个字节。

系统列文件 SYSCOLUMNS 中记录的结构如下:

tablename Attrname attrtype attrlength attroffset ix_flag indexname

表名(tablename)与属性名(attrname)各占 21 个字节,即表名与属性名的最大

长度均为 20 个字节。属性的类型(attrtype)占 sizeof(AttrType)共 4 个字节。属性

的长度(attrlength)与偏移量(attroffset)均占 sizeof(int)共 4 个字节。该属性列上是

否存在索引的标识(ix_flag)占 1 个字节,’1’表示存在索引,’0’表示不存在索引。

索引的名称(indexname)占 21 个字节,即索引名的最大长度为 20 个字节。

5.2 系统管理模块对外提供的函数调用接口

RC CreateDb (char *dbPath ,char *dbName)

该函数在 dbPath 的路径下创建一个名为 dbName 的空库,生成相应的系统表

文件。首先调用_mkdir 函数在 dbPath 的路径下创建一个名为 dbName 的文件夹,

然后调用记录管理模块的 RM_CreateFile 函数创建两个文件 SYSTABLES.xx 和

SYSCOLUMNS.xx,调用记录管理模块的 RM_OpenFile 函数分别打开这两个文

件,调用记录管理模块的 InsertRec 函数向这两张表中插入 SYSTABLES 表和

SYSCOLUMNS 表相关的记录。

RC DropDb (char *dbName)

该函数删除名为 dbName 的目录下的所有数据库文件。调用 CFileFind 类的

FindFile 函数遍历当前目录下所有文件,并删除文件,最后 dbName 文件夹。

RC OpenDb (char *dbName)

该函数改变当前目录名为 dbName。调用 SetCurrentDirectory 函数设置当前目录

为 dbName。

RC CloseDb ()

该函数关闭所有在当前数据库中打开的文件。关闭文件操作将自动使所有相关的

缓冲区更新到磁盘。调用 SetCurrentDirectory 函数设置目录为“..//”,即当前目

录的上级目录。

RC CreateTable (char *relName, int attrCount, AttrInfo *attributes)

此函数创建一个新的名为 relName 的表。参数 attrCount 表示关系中属性的

Page 34: 课程设计报告 - Huazhong University of Science and ...cs.hust.edu.cn/__local/D/B2/1E/556FBEBBBA639329663... · 课程设计报告 Hustbase 数据管理系统设计与实现 专业班级:计卓1201

31

数量(取值为 1 到 MAXATTRS 之间)。参数 attributes 是一个长度为 attrCount

的数组。对于新关系中第 i 个属性,attributes 数组中的第 i 个元素包含名称、类

型和属性的长度(见 AttrInfo 结构定义)。

首先调用记录管理模块的 RM_CreateFile 函数创建一个文件名为 relName.xx

的记录文件,然后调用记录管理模块的 RM_OpenFile 函数分别打开

SYSTABLES.xx 和 SYSCOLUMNS.xx 文件,调用记录管理模块的 InsertRec 函数

向这两张表中插入 SYSTABLES 表和 SYSCOLUMNS 表相关的记录。函数的流

程图如图 13 所示:

开始

结束

调用记录管理模块的RM_CreateFile函数

创建一个文件名为relName.xx的记录文件

调用记录管理模块的RM_OpenFile函数打

开SYSTABLES.xx文件

调用记录管理模块的InsertRec函数向

SYSTABLES.xx表中插入表信息

调用记录管理模块的RM_CloseFile函数关

闭SYSTABLES.xx文件

调用记录管理模块的RM_OpenFile函数打

开SYSCOLUMNS.xx文件

调用记录管理模块的RM_CloseFile函数关

闭SYSCOLUMNS.xx文件

int i = 0;

i < attrCount?

i++;

调用记录管理模块的InsertRec函数向

SYSCOLUMNS.xx表中插入列信息

Y

N

图 13CreateTable 函数流程图

RC DropTable (char *relName)

该函数用来销毁名为 relName 的表和所有在其上建立的索引。函数的流程图

如图 14 所示:

Page 35: 课程设计报告 - Huazhong University of Science and ...cs.hust.edu.cn/__local/D/B2/1E/556FBEBBBA639329663... · 课程设计报告 Hustbase 数据管理系统设计与实现 专业班级:计卓1201

32

开始

结束

调用记录管理模块的RM_OpenFile函数打

开SYSTABLES.xx文件

调用记录管理模块的OpenScan函数打开文件扫描

调用记录管理模块的RM_CloseFile函数关

闭SYSTABLES.xx文件

调用记录管理模块的RM_OpenFile函数打

开SYSCOLUMNS.xx文件

删除表文件

取记录中索

引信息

存在索引?

删除索引文件

Y

N

调用记录管理模块的GetNextRec函数在

SYSTABLES表中查找要删除表对应记录rid

调用记录管理模块的DeleteRec函数从

SYSTABLES表中删除rid所指的记录

调用记录管理模块的OpenScan函数打开文件扫描

GetNextRec返回SUCCESS?

调用记录管理模块的GetNextRec函数在

SYSCOLUMNS表中查找要删除表对应记录rid

Y

调用记录管理模块的DeleteRec函数从

SYSCOLUMNS表中删除rid所指的记录

N

图 14DropTable 函数流程图

RC CreateIndex (char *indexName,char *relName, char *attrName)

该函数在关系 relName 的属性 attrName 上创建索引,索引名为 indexName,

并建立关系当前内容的索引。函数首先检查在标记属性上是否已经存在一个索引,

如果存在,则返回一个非零的错误码。否则,创建该索引。创建索引的工作包括:

Page 36: 课程设计报告 - Huazhong University of Science and ...cs.hust.edu.cn/__local/D/B2/1E/556FBEBBBA639329663... · 课程设计报告 Hustbase 数据管理系统设计与实现 专业班级:计卓1201

33

①打开索引;②扫描被索引的记录,插入索引项;③关闭索引。函数的流程图如

图 15 所示:

开始

结束

调用记录管理模块的RM_OpenFile函数打

开SYSCOLUMNS.xx文件

取记录中索

引信息

存在索引?

返回INDEX_EXIST

Y

调用记录管理模块的OpenScan函数打开文件扫描

调用记录管理模块的GetNextRec函数在

SYSCOLUMNS表中查找要创建索引的列的记录

调用记录管理模块的UpdateRec函数更新

SYSCOLUMNS表中系统列信息索引部分

N

调用记录管理模块的RM_OpenFile函数打

开relName.xx文件

调用记录管理模块的OpenScan函数打开文件扫描

调用记录管理模块的GetNextRec函数在

relName表中查找所有记录

调用索引管理模块的IX_CreateIndex函数

创建索引文件

调用索引管理模块的OpenIndex函数打开

索引文件

GetNextRec返回SUCCESS?

调用索引管理模块的InsertEntry函数向索

引文件中插入索引项

调用索引管理模块的CloseIndex函数关闭

索引文件

调用记录管理模块的RM_CloseFile函数关

闭SYSCOLUMNS.xx文件

调用记录管理模块的RM_CloseFile函数关

闭relName.xx文件

N

Y

图 15CreateIndex 函数流程图

RC DropIndex (char *indexName)

该函数用来删除名为 indexName 的索引。函数首先检查索引是否存在,如

果不存在,则返回一个非零的错误码。否则,销毁该索引。函数的流程图如图 16

所示:

Page 37: 课程设计报告 - Huazhong University of Science and ...cs.hust.edu.cn/__local/D/B2/1E/556FBEBBBA639329663... · 课程设计报告 Hustbase 数据管理系统设计与实现 专业班级:计卓1201

34

开始

结束

调用记录管理模块的RM_OpenFile函数打

开SYSCOLUMNS.xx文件

取记录中索

引信息

存在索引?

返回INDEX_NOTEXIST

N

调用记录管理模块的OpenScan函数打开文件扫描

调用记录管理模块的GetNextRec函数在

SYSCOLUMNS表中查找要创建索引的列的记录

Y

删除索引文件

调用记录管理模块的UpdateRec函数更新

SYSCOLUMNS表中系统列信息索引部分

调用记录管理模块的RM_CloseFile函数关

闭SYSCOLUMNS.xx文件

调用索引管理模块的CloseIndex函数关闭

索引文件

图 16DropIndex 函数流程图

RC Insert (char *relName, int nValues, Value *values)

该函数用来在 relName 表中插入具有指定属性值的新元组。函数根据给定参

数构建一条元组,调用记录管理模块的函数插入该元组,然后在该表的每个索引

中为该元组创建合适的索引项。函数的流程图如图 17 所示:

Page 38: 课程设计报告 - Huazhong University of Science and ...cs.hust.edu.cn/__local/D/B2/1E/556FBEBBBA639329663... · 课程设计报告 Hustbase 数据管理系统设计与实现 专业班级:计卓1201

35

开始

结束

查询SYSTABLES表,检查属性个数是否合法

遍历索引名map,向存在索引的属性

列对应的索引文件中插入一条记录

查询SYSCOLUMNS表,进行属性类型检查,并用

nValues和values两个参数得到一条完整的记录数据

打开要插入记录的表,调用InsertRec函数向表

中插入记录数据

将存在索引的属性列的索引名保存在map中

图 17Insert 函数流程图

RC Delete (char *relName, int nConditions, Condition *conditions)

该函数用来删除 relName 表中所有满足指定条件的元组以及该元组对应的

索引项。如果没有指定条件,则此方法删除 relName 关系中所有元组。如果包含

多个条件,则这些条件之间为与关系。函数的流程图如图 18 所示:

Page 39: 课程设计报告 - Huazhong University of Science and ...cs.hust.edu.cn/__local/D/B2/1E/556FBEBBBA639329663... · 课程设计报告 Hustbase 数据管理系统设计与实现 专业班级:计卓1201

36

开始

结束

查询SYSTABLES表,得到属性个数

遍历IX_Array,调用DeleteEntry函数从存在索引的属性列对应的索引文件

中删除相应索引项

查询SYSCOLUMNS表,得到存在索引的列的信息并

保存在数组IX_Array中

调用GetNextRec函数得到满足删

除条件的记录的rid

打开所有建立在该表上的索引文件,

打开要删除记录的表

调用记录管理模块的Condition2Con函数

将所有条件都转换成Con类型

调用DeleteRec函数删除rid所指的记录

GetNextRec返回SUCCESS?

Y

N

调用RM_CloseFile函数

关闭表文件

调用CloseIndex函数关闭

所有打开的索引文件

图 18Delete 函数流程图

RC Update (char *relName, char *attrName,Value*Value, int nConditions,

Condition *conditions);

该函数用于更新 relName 表中所有满足指定条件的元组,在每一个更新的元

组中将属性 attrName 的值设置为一个新的值。如果没有指定条件,则此方法更

新 relName 中所有元组。如果要更新一个被索引的属性,应当先删除每个被更新

元组对应的索引条目,然后插入一个新的索引条目。函数的流程图如图 19 所示:

Page 40: 课程设计报告 - Huazhong University of Science and ...cs.hust.edu.cn/__local/D/B2/1E/556FBEBBBA639329663... · 课程设计报告 Hustbase 数据管理系统设计与实现 专业班级:计卓1201

37

开始

结束

查询SYSTABLES表,得到属性个数

调用DeleteEntry函数从索引文件中删

除相应索引项

查询SYSCOLUMNS表,待更新列的索引信息

调用GetNextRec函数得到满足更

新条件的记录的rid

打开要更新记录的表

调用记录管理模块的Condition2Con函数

将所有条件都转换成Con类型

GetNextRec返回SUCCESS?

Y

N

调用RM_CloseFile函数

关闭表文件

调用CloseIndex函数关闭

所有打开的索引文件

调用UpdateRec函数删除rid所指的记录

待更新列上存在

索引?

待更新列上存在

索引?

调用OpenIndex函数打开索引文件

调用InsertEntry函数向索引

文件中插入新的索引项

得到新的记录数据

Y

得到新的记录数据

Y

N

待更新列上存在

索引?

Y

N

N

图 19Update 函数流程图

RC Condition2Con(Con *con, Condition condition, char *relName);

将 Condition 类型的变量转换成 Con 类型的变量。查询 SYSCOLUMNS 表得到某

张表的某个属性列的偏移量、长度等信息,赋值给 con 的相应成员变量。

Page 41: 课程设计报告 - Huazhong University of Science and ...cs.hust.edu.cn/__local/D/B2/1E/556FBEBBBA639329663... · 课程设计报告 Hustbase 数据管理系统设计与实现 专业班级:计卓1201

38

6 查询处理模块

6.1 查询处理模块说明

查询处理模块负责 select 语句的处理。select 语句的语法与标准 SQL 相比简

化了很多,详见第 9.1 节。本模块涉及的主要数据结构包括:

struct RelAttr {

char *relName; // 表名,可以为 NULL

char *attrName; // 属性名

};

typedef struct _Condition Condition;

struct _Condition{

int bLhsIsAttr; //操作符左边是属性为 1,是值时为 0

Value lhsValue; //当左边是值时,保存该值的信息

RelAttr lhsAttr; //当左边是属性时,保存属性信息

CompOp op; //比较运算符

int bRhsIsAttr; //操作符右边是属性为 1,是值时为 0

RelAttr rhsAttr; //当右边是属性时,保存属性信息

Value rhsValue; //当左边是值时,保存该值的信息

};

6.2 查询处理模块对外提供的函数调用接口

RC Select (int nSelAttrs, RelAttr *selAttrs, int nRelations, char ** relations, int

nConditions, Condition *conditions, char *res)

该方法的前六个参数逻辑上可分为三组。每一组的第一个参数是一个整型 n,

表示本组中第二个参数的条目数量;第二个参数是一个包含实际条目数的为 n

的数组。例如,参数 nSelAttrs 包含所选属性的数量,而参数 selAttrs 是包含实际

属性的长度为 nSelAttrs 的数组,其余类似。

返回给用户的查询结果 res 表示一个结果关系,这个字符流应包含以下信息:

关系头、元组数、及一组元组值。

最基本的查询处理方法为先计算关系的笛卡尔集,然后选择并投影。但是应

Page 42: 课程设计报告 - Huazhong University of Science and ...cs.hust.edu.cn/__local/D/B2/1E/556FBEBBBA639329663... · 课程设计报告 Hustbase 数据管理系统设计与实现 专业班级:计卓1201

39

尽可能利用代数优化规则及索引对查询过程进行优化。

实现查询时,将情况分为单表无索引查询、单表有索引查询、多表查询。首

先对 nRelations 的值进行判断,大于 1 为多表查询,等于为单表查询,小于 1 非

法,再判断是否有索引,读取 conditions 中每个属性列的索引标记,如果有,使

用索引查询,如果有多个属性有索引,则用找到的第一个。

为了方便对结果进行记录,增加一个数据结构,如下所示:

typedef struct {

int rowcount;

int colcount;

char data[20][20][20];

}SELECT_Res;

data 字符数组里存放满足查询条件的结果元组,包括表头。

1. 单表无索引查询

首先是得到表头,计算投影属性的偏移量,然后将表头拷贝到结果字符数组

里。再对条件进行处理,将 Condition 结构转换为 Con 结构,即记录扫描中参数

所能接受的结构。最后,调用记录扫描里的 OpenScan()和 GetNextRec()两个函数,

得到满足全部 nConditions 个条件的元组,将其值在待选出的属性列上进行投影,

然后拷贝到

data 里。

2. 单表有索引查询

首先是得到表头,计算投影属性的偏移量,然后将表头拷贝到结果字符数组

里。再对条件进行处理,将 Condition 结构转换为 Con 结构,即记录扫描中参数

所能接受的结构。然后再来判断条件中涉及到的属性列是否有索引,如果有索引,

就利用 OpenIndexScan()函数以及 IX_GetNextEntry()函数来得到满足条件的记

录的 RID,然后根据这个 RID 调用 GetRec()函数来获取记录,之后通过判断其

他的条件来决定这条记录是否最终符合条件。然后将符合条件的记录显示出来。

3. 多表查询

为了方便对属性的偏移和长度,以及类型进行处理,自定义一个数据结构,如下

所示:

typedef struct {

AttrType attrType; // 属性类型

int attrLen; // 属性长度

int attrOffset; // 属性偏移量

}MyAttrInfo;

首先是对 nRelations 进行处理,得到做笛卡尔积后所有属性的偏移和长度,

Page 43: 课程设计报告 - Huazhong University of Science and ...cs.hust.edu.cn/__local/D/B2/1E/556FBEBBBA639329663... · 课程设计报告 Hustbase 数据管理系统设计与实现 专业班级:计卓1201

40

以及类型,然后再根据待投影的属性得到表头的偏移和长度以及类型,将表头拷

贝到结果字符数组里。然后再对条件进行处理,将 Condition 结构转换为 Con 结

构,即记录扫描中参数所能接受的结构。然后,对所有关系里的所有元组进行笛

卡尔积,每得到一个结果元组,就调用函数进行判断该元组是否满足全部条件,

如果是,则将其值在待选出的属性列上进行投影,然后拷贝到 data 里。否则,

继续扫描,直至结束。

7 语法分析模块

7.1 HustBase 支持的 SQL 语句

HustBase 目前暂支持下面 9 种 SQL 语句,不区分大小写。语法如下:

1) createtable relName(attrName1 Type1(len1),

attrName2 Type2(len2),

...,

attrNameN TypeN(lenN));

注:int 及 float 类型不需要注明长度

2) drop tablerelName;

3) create index indexName relName(attrName);

4) drop index indexName;

5) Select A1, A2, ..., Am

From R1, R2, ..., Rn

[Where A1' comp1 AV1 And A2' comp2 AV2 And ... And Ak' comp-k AVk];

comp 支持 6 种比较运算符:=,<,>,>=,<=,<>。

6) Select *

From R1, R2, ..., Rn

[Where A1' comp1 AV1 And A2' comp2 AV2 And ... And Ak' comp-k AVk];

7) Insert

Into relName

Values (V1, V2, ..., Vn);

8) Delete

From relName

[Where A1 comp1 AV1 And A2 comp2 AV2 And ... And Ak comp-k AVk];

9) Update relName

Page 44: 课程设计报告 - Huazhong University of Science and ...cs.hust.edu.cn/__local/D/B2/1E/556FBEBBBA639329663... · 课程设计报告 Hustbase 数据管理系统设计与实现 专业班级:计卓1201

41

Set attrName = AV

[Where A1 comp1 AV1 And A2 comp2 AV2 And ... And Ak comp-k AVk];

7.2 语法分析流程

SQL 语法分析的大致流程如图 20 所示。一条 SQL 语句通过词法器的解析,

返回对应的单词句柄,然后语法分析器根据单词句柄和定义的语法规则进行语法

分析,最后将分析的结果存入 sql_str 结构中。

图 20SQL 语法分析流程图

lex_sql.l 文件定义词法规则,使得词法分析程序能识别出输入字符串中的各

个单词。yacc_sql.y 文件定义语法规则,根据该程序中定义的语法规则,语法分

析程序能识别出合法的 SQL 语句,并根据语句类别将其语句信息存入相应的数

据结构中。数据结构的具体定义见 9.3 节。

7.3 SQL 语句对应的数据结构

对应上述 SQL 语句的数据结构定义如下:

typedef struct _Condition Condition;

struct _Condition{

int bLhsIsAttr; //操作符左边是属性为 1,是值时为 0

Value lhsValue; //当左边是值时,保存该值的信息

RelAttr lhsAttr; //当左边是属性时,保存属性信息

CompOp op; //比较运算符

int bRhsIsAttr; //操作符右边是属性为 1,是值时为 0

RelAttr rhsAttr; //当右边是属性时,保存属性信息

Value rhsValue; //当左边是值时,保存该值的信息

};

//struct of select

yacc_sql.y

语法规则

SQL 语句 yylex token(s)

yyparse 存入

sql_str 结构

Page 45: 课程设计报告 - Huazhong University of Science and ...cs.hust.edu.cn/__local/D/B2/1E/556FBEBBBA639329663... · 课程设计报告 Hustbase 数据管理系统设计与实现 专业班级:计卓1201

42

typedef struct {

int nSelAttrs; //select 子句中属性的数量

RelAttr *selAttrs[MAX_NUM]; //select 子句中的属性

int nRelations; //from 子句中表的数量

char *relations[MAX_NUM]; //from 子句中的表

int nConditions; //where 子句中条件的数量

Condition conditions[MAX_NUM];//where 子句中的条件

}selects;

//struct of insert

typedef struct {

char *relName; //要插入记录的表名

int nValues; //插入的值的数量

Value values[MAX_NUM]; //要插入的值

}inserts;

//struct of delete

typedef struct {

char *relName; //要删除记录的表名

int nConditions; //where 子句中条件的数量

Condition conditions[MAX_NUM]; //where 子句中的条件

}deletes;

//struct of update

typedef struct {

char *relName; //要修改记录的表名

char *attrName; //要修改的属性名

Value value; //修改成的值

int nConditions; //where 子句中条件的数量

Condition conditions[MAX_NUM]; // where 子句中的条件

}updates;

//struct of craete_table

typedef struct {

char *relName; //要创建的表的表名

Page 46: 课程设计报告 - Huazhong University of Science and ...cs.hust.edu.cn/__local/D/B2/1E/556FBEBBBA639329663... · 课程设计报告 Hustbase 数据管理系统设计与实现 专业班级:计卓1201

43

int attrCount; //表中属性的数量

AttrInfo attributes[MAX_NUM]; //属性的信息

}createTable;

//struct of drop_table

typedef struct {

char *relName; //要删除的表的表名

}dropTable;

//struct of create_index

typedef struct {

char *indexName; // 要创建的索引的名称

char *relName; // 该索引涉及的表名

char *attrName; // 该索引涉及的属性名

}createIndex;

//struct of drop_index

typedef struct {

char *indexName; // 要删除的索引的名称

}dropIndex;

注:上述结构中凡涉及到多个条件、表名、属性等内容,均与输入的语句中

的顺序相反。如 select * from table where con1 and con2 and con3;,对应的 selects

结构体中 conditions[0]对应 con3,conditions[1]对应 con2,conditions[2]对应 con1。

union sqls{

selects sel;

inserts ins;

deletes del;

updates upd;

createTable cret;

dropTable drt;

createIndex crei;

dropIndex dri;

char *errors;

};

Page 47: 课程设计报告 - Huazhong University of Science and ...cs.hust.edu.cn/__local/D/B2/1E/556FBEBBBA639329663... · 课程设计报告 Hustbase 数据管理系统设计与实现 专业班级:计卓1201

44

typedef struct {

int flag; //0--error;1--select;2--insert; 3--update;

// 4--delete;5--create table;6--drop table;

// 7--create index;8--drop index;

union sqls sstr;

}sqlstr;

7.4 语法分析模块对外提供的函数调用接口

语法分析模块对外只提供一个语法分析函数:

RC parse(char* st,sqlstr* sqln);

该函数用于解析参数 st 传入的 SQL 语句,将解析的结果存入参数 sqln 中。

若出错,返回错误类型 RC。

Page 48: 课程设计报告 - Huazhong University of Science and ...cs.hust.edu.cn/__local/D/B2/1E/556FBEBBBA639329663... · 课程设计报告 Hustbase 数据管理系统设计与实现 专业班级:计卓1201

45

8 课设总结和建议

8.1 课设问题分析

问题一:调用 Insert 插入记录后,打开文件发现文件中的数据与插入记录不

符。

分析:由于向记录文件数据页中写数据时,是通过调用 strcpy 函数来实现两

个地址间数据的传递的,而 strcpy 函数只会把首地址到‘\0’字符之间的数

据拷贝到数据页,因此文件中的记录只有一部分。

解决方案:把记录管理模块调用 strcpy 函数的地方换成 memcpy 函数,直接

拷贝记录大小个字节到指定位置。

问题二:调用 DropTable 函数删除表时,表文件没有被删除。

分析:跟踪查看删除文件的函数的返回值,发现是提示文件被占用,说明文

件被打开后忘记关闭了。

解决方案:在调用删除文件的函数前,先调用关闭记录文件的函数。

问题三:CreateDb 函数创建数据库后,读取系统表数据出错。

分析:最开始我用创建了两个数据结构 SYSTABLES 和 SYSCOLUMNS 分

别存放系统表记录和系统列记录,由于自动对齐的效果,记录大小和各属性

实际的偏移量与预期不符,比如 SYSTABLES 中记录大小应该是 25,而 sizeof

(SYSTABLES)的值是 28,所以 PopulateTree()函数在读取记录时读到了错

误的数据。

解决方案:不用数据结构 SYSTABLES 和 SYSCOLUMNS,直接按照这两个

表记录的格式向(char *)类型的变量中存数据。

问题四:在 while 循环中调用 GetNextRec 扫描文件时进入死循环。

分析:在扫描到记录文件末尾时,将文件扫描结构的 pn 和 sn 修改为第一个

记录的页号和插槽号,因此在下次调用 GetNextRec 函数时,又会从第一个

有效的记录开始扫描。

解决方案:在扫描到记录文件末尾时,将文件扫描关闭。

问题五:索引的插入中,当节点分裂后,会有一部分节点查询不到了

分析:按原来的逻辑,在索引插入中,节点分裂成两个节点后,前一个节点

的最后一个索引项的 RID 与后一个节点的第一个 RID 指向同一个子节点,

但是因为分裂后又要更新子节点的父节点页面,所以就会把那个子节点的父

节点更新为后一个分裂节点。所以导致破坏了 B+数分支之间的大小关系,

所以会导致那个子节点中的索引项在之后的查找中找不到。

解决方案:节点分裂后,将后面的分裂节点的第一个 RID 指向一个非法页面,

Page 49: 课程设计报告 - Huazhong University of Science and ...cs.hust.edu.cn/__local/D/B2/1E/556FBEBBBA639329663... · 课程设计报告 Hustbase 数据管理系统设计与实现 专业班级:计卓1201

46

无意义的页面。这样在更新子节点的父节点页面时就不会破坏 B+树的分支

大小关系。但是这样的话导致的结果是我们的插入函数和删除函数几乎重新

写了一遍,浪费了很多时间

问题六:在 select 查询时,写到查询语句 select * from dazai where sname=dddd;

这里明明有语法错误,但是却将表中所有的数据输了出来。

分析:单步执行的时候发现,在判断 where 条件属于那种类型的时候我们只

处理了它是左右都是值,左右都是属性,左属性右值或者右属性左值的情况,

但是没有处理它每种情况都不符合的时候,很明显 dddd 没有加单引号,这

种 where 条件哪种情况都不属于,属于错误情况。

解决方案:在处理 where 条件时加入一个错误条件的处理,返回错误码。

问题七:在进行索引项插入后,下一次插入时在新分配的页面进行插入,且

只有一个索引项。

分析:在发现问题后,进行单步调试,发现在第一次插入成功后,再次插入

另一值时,读出的 rootPage 数值不对,仍为 1,然后打开索引文件,通过十

六进制进行查看,页号对应位置的数值为 1,说明没有写入文件。由于在更

改页号时,仅在内存里进行了更改,即仅仅修改了 IX_FileHeader 结构中

rootPage 成员的值。

解决方案:先写回内存,再写回缓冲区,最后写回文件。所以,自定义了一

个函数来修改根节点的页号。写回缓冲区的时候,要读出索引信息控制页,

即第一页的内容,将对应的 rootPage 字段更改为新分配页面的页号,然后将

该页标记为脏页,解除驻留缓冲区限制,在关闭文件的时候,会把该页数据

写到文件。

问题八:在写多表查询时,需要全部关系的文件,每次打开一个关系对应的

的文件,使用 NO_OP 比较符,即空条件进行比较时,进行记录扫描,不同

关系得到的记录是一样的

分析:首先进行单步调试,发现在打开一个文件后,再打开另一个文件,之

前一个文件的文件句柄的文件名字段与第二次打开的文件相同。通过进入每

一个函数,发现这是由于页面管理的 openFile()函数里有一个 bug,

pfilehandle->pHdrFrame->fileName=fileName;这里是指针赋值,即浅拷贝,在

fileName 指针区域的值修改时,pfilehandle->pHdrFrame->fileName 的值也会

跟着修改。

解决方案:对不同的文件名申请不同的内存空间,更改 openFile()的实现,

将浅拷贝改为 strcpy()。这样就解决了问题。

Page 50: 课程设计报告 - Huazhong University of Science and ...cs.hust.edu.cn/__local/D/B2/1E/556FBEBBBA639329663... · 课程设计报告 Hustbase 数据管理系统设计与实现 专业班级:计卓1201

47

8.2 课设感想和建议

这次课设我们在 hustBase 数据库管理系统的框架下完成了一个支持基本

SQL 语句的 DBMS,可以支持数据库的创建,关系的创建以及基本的 SQL 语句

的实现,最后还完成了索引模块。

这次课设刚开始觉得任务很重,所以开始做得比较快,但是在中期由于索引

模块比较复杂,然后我们又比较懒散,所以索引模块的时间用得比较长。这次的

课设虽然说是数据库课设但感觉很多地方其实是一些底层的实现,和课堂上讲的

那些理论知识并没有什么太大关系。更有点文件系统的意思,然后这次比较新的

内容就是用 B+树实现索引,B+树在数据结构中有一点点了解,但是从来没有实

现过,所以这次在这上面下了很多功夫,查了很多资料,也很开心能够完成最后

的索引模块。

这次课设我们组有三个人,所以是一次小组合作完成的项目,我觉得抛开知

识点什么不说,首先最大的感想就是团队讨论和团队协作的重要性,我觉得这也

是我们这次做得比较好的地方,我们在写索引的时候,查了很多资料,但是很郁

闷的是几乎没有一个标准的说法,每份资料都有一些不一样,所以关于数据结构

讨论了很多次,最终才定下现在的数据结构,确定好数据结构以及总体的思路之

后会使得接下来的编码工作轻松很多。而且我们很多时候都是一起在图书馆编码,

有问题可以及时讨论。然后我们这次做的比较不好的一点是进度管理方面,我们

这次课设是属于前期紧凑,中期松散,后期努力赶的状态。原因就是因为我们没

有一个比较明确的进度管理,比如什么时候要完成什么,多久大家要一起来汇报

自己的进度等等,导致最后赶得很辛苦。所以这是在以后团队合作中我们可以加

强的地方。

通过这次课设我觉得首先是强化了我们的编程和调试能力,然后通过实践对

一些理论知识也有了更深的理解,最后也让我们收获了一些团队合作的经验。感

觉这次课设的收获还是很大的。

关于这次课设我们这组有两个小小的建议:(1)由于这次的课设我们这组在进

度管理方面有一些问题导致最后完成得比较辛苦。所以我们建议老师可以在进度

管理方面想一些办法,我们觉得之前组成原理的课设老师所采用的进度管理方法

很好,就是采用一张在线可多人编辑的表格,然后我们每个人每天都在上面填写

自己的进度,当时我们用的是百汇文件。我们觉得这种方法不仅可以让老师了解

到每个组目前的进度,也是对大家有一个激励作用。(2)检查课设的时候老师说

可能之后将编译和数据库的课设合在一起,我们觉得这样挺好的,但是我们觉得

每个组还是不要太多人的好,这样合作起来也不方面,然后效率也不是很高。还

有如果合并的话是不是就可以把我们数据库第一个课设给取消掉了,因为如果要

Page 51: 课程设计报告 - Huazhong University of Science and ...cs.hust.edu.cn/__local/D/B2/1E/556FBEBBBA639329663... · 课程设计报告 Hustbase 数据管理系统设计与实现 专业班级:计卓1201

48

自己写语法分析的话,还要做第一个课设时间可能有点紧张额。

Page 52: 课程设计报告 - Huazhong University of Science and ...cs.hust.edu.cn/__local/D/B2/1E/556FBEBBBA639329663... · 课程设计报告 Hustbase 数据管理系统设计与实现 专业班级:计卓1201

49

参考文献

[1]王珊,萨师瑄.数据库系统概论(第四版).北京:高等教育出版社.