CDB自制数据库引擎

CDB

我想学习数据结构,最好的方式就是使用数据结构的知识做出一个系统,而我认为对于目前的我来讲,最好的选择就是做一个小型的数据库引擎。取名为CDB

学习GitHub上的这个项目。

第一步 数据库的交互框架

数据库工作的流程为:
1. 打印提示符接受输入
2. 解析输入,得到真实的命令
3. 将解析得到的语义进行数据库底层操作
4. 数据库底层操作很复杂,现在还不懂,懂了再加上

所以首先我们需要解析每一次的输入,并解析为程序可以读懂的信息,根据解析结果进行进一步操作。

所以我们需要一个缓冲区来存储输入。

typedef struct
{
    char* buffer;
    size_t buffer_length;
    size_t input_length;
}InputBuffer;

在得到每一次的输入之后还首先需要进行元命令判断其结果使用enum进行选择。

typedef enum
{
    META_COMMAND_EXIT,
    META_COMMAND_SUCCESS,
    META_COMMAND_UNRECOGNIZED_COMMAND
}MetaCommandResult;

元命令目前分为两类,以类是以 . 开头的非SQL命令,另一类位SQL命令。

进行元命令判断之后进行处理,目前只有退出一项。

与元命令并行的是SQL命令的处理,其不以 . 开头。同理,这种命令的处理也存在几种状态。

typedef enum
{
	PREPARE_SUCCESS,
	PREPARE_SYNTAX_ERROR,
	PREPARE_UNRECOGNIZED_STATEMENT
} PrepareResult;

typedef enum {
	STATEMENT_INSERT,
	STATEMENT_SELECT
} StatementType;

typedef struct 
{
	StatementType type;
	Row row_to_insert;
} Statement;

程序预处理SQL命令得到这样的状态,根据状态来执行真正的SQL命令。

以上就是数据库的交互框架,就是一个巨大的状态机,虽然我不是很喜欢状态机,可是目前能力不足,只能使用状态机来实现了。希望以后做一个不使用状态机的大项目。

这一步的程序被覆盖掉了。

第二步 数据库的简单插入操作

这一步要实现数据库与底层的互连,打通和底层内存间的交互。实现简单的insert和使用select打印出数据表。这里全部使用硬编码。

下面讲一下数据库的底层安排。

table使用一个简单的示例:

//ROW
typedef struct 
{
	uint32_t id;
	char username[COLUMN_USERNAME_SIZE];
	char email[COLUMN_EMAIL_SIZE];
} Row;
//Table
typedef struct 
{
	uint32_t num_rows;
	void* pages[TABLE_MAX_PAGES];
} Table;

将一张表分为数个page,这里的page和操作系统提供的分页机制相吻合,都是4KB大小,这样可以从操作系统层面上提高数据库的效率。在预处理中已经将命令的参数存入一行中了。(statement->row),现在需要获得数据要存入的内存地址和存入该内存地址的方法。

分配内存:

Row* row_slot(Table* table, uint32_t row_num) 
{
	uint32_t page_num = row_num / ROWS_PER_PAGE;
	void* page = table->pages[page_num];
	if (page == NULL) 
	{
		// Allocate memory only when we try to access page
		page = table->pages[page_num] = malloc(PAGE_SIZE);
	}
	uint32_t row_offset = row_num % ROWS_PER_PAGE;
	uint32_t byte_offset = row_offset * ROW_SIZE;
	//之所以报 “必须是指向完整类型”的错,是因为指针指向类型不确定,则其单位不确定,加上byte_offset后也不知道到底指向哪一块内存。
	//由于 byte_offset 是字节单位,所以page页要变为指向8位内存的指针。所以转化位 uint8_t
	return (Row*)((uint8_t*)page + byte_offset);
}

这个函数返回一个指向要插入的内存地址的指针。

插入内存:

//将行的信息存储到内存中
//A void pointer can hold address of any type and can be typcasted to any type
void serialize_row(Row* source, Row* destination) 
{	
	// ID_SIZE 指的是字节数
	memcpy((uint8_t*)destination + ID_OFFSET, &(source->id), ID_SIZE);
	memcpy((uint8_t*)destination + USERNAME_OFFSET, &(source->username), USERNAME_SIZE);
	memcpy((uint8_t*)destination + EMAIL_OFFSET, &(source->email), EMAIL_SIZE);
}

这里将destination从row* 转换为 **(uint8_t*)**来将其单位转化为字节,这样下面加上各个属性的offset之后就正确地指向对应地内存位置了。第三个参数代表赋值地字节数。这里终于意识到c语言指针地强大之处了。

同理该有一个相反地函数用于输出数据:

void deserialize_row(Row* source, Row* destination) 
{
	memcpy(&(destination->id), (uint8_t*)source + ID_OFFSET, ID_SIZE);
	memcpy(&(destination->username), (uint8_t*)source + USERNAME_OFFSET, USERNAME_SIZE);
	memcpy(&(destination->email), (uint8_t*)source + EMAIL_OFFSET, EMAIL_SIZE);
}

这样与内存的底层交互就完成了,现在只需要得到offset即可。

#define COLUMN_USERNAME_SIZE 32
#define COLUMN_EMAIL_SIZE 255
//表中最大页数
#define TABLE_MAX_PAGES 100
//页的大小
#define PAGE_SIZE 4096
//定义宏获取size
#define size_of_attribute(Struct, Attribute) sizeof(((Struct*)0)->Attribute)

//获取行中每一个元素的size和offset
const uint32_t ID_SIZE = size_of_attribute(Row, id);
const uint32_t USERNAME_SIZE = size_of_attribute(Row, username);
const uint32_t EMAIL_SIZE = size_of_attribute(Row, email);
const uint32_t ID_OFFSET = 0;
const uint32_t USERNAME_OFFSET = 0 + size_of_attribute(Row, id);
const uint32_t EMAIL_OFFSET = 0 + size_of_attribute(Row, id) + size_of_attribute(Row, username);
const uint32_t ROW_SIZE = size_of_attribute(Row, id) + size_of_attribute(Row, username) + size_of_attribute(Row, email);
//每一页有多少行
const uint32_t ROWS_PER_PAGE = PAGE_SIZE /(size_of_attribute(Row, id) + size_of_attribute(Row, username) + size_of_attribute(Row, email));
//一张表里面最大行数
const uint32_t TABLE_MAX_ROWS = PAGE_SIZE / (size_of_attribute(Row, id) + size_of_attribute(Row, username) + size_of_attribute(Row, email)) * TABLE_MAX_PAGES;

代码

CDB.h
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <stdint.h>


#define COLUMN_USERNAME_SIZE 32
#define COLUMN_EMAIL_SIZE 255
//表中最大页数
#define TABLE_MAX_PAGES 100
//页的大小
#define PAGE_SIZE 4096
//定义宏获取size
#define size_of_attribute(Struct, Attribute) sizeof(((Struct*)0)->Attribute)


//输入缓冲
typedef struct
{
	char* buffer;
	// _int64
	size_t buffer_length;
	size_t input_length;
} InputBuffer;

//判断输入得到的命令状态
typedef enum
{
	META_COMMAND_EXIT,
	META_COMMAND_SUCCESS,
	META_COMMAND_UNRECOGNIZED_COMMAND
} MetaCommandResult;

//预处理得到的状态
typedef enum
{
	PREPARE_SUCCESS,
	PREPARE_NEGATIVE_ID,
	PREPARE_SYNTAX_ERROR,
	PREPARE_STRING_TOO_LONG,
	PREPARE_TOO_MANY_PARAMETER,
	PREPARE_UNRECOGNIZED_STATEMENT
} PrepareResult;

//处理后的状态
typedef enum
{
	EXECUTE_SUCCESS,
	EXECUTE_TABLE_FULL
}ExecuteResult;

//得到的输入的表达式的状态
typedef enum
{
	STATEMENT_INSERT,
	STATEMENT_SELECT
} StatementType;

//定义 一行 
typedef struct
{
	uint32_t id;
	//加一是因为c语言需要最后一个字符为0,所以可用字符就减一了。
	//不过即使这样也还需要在输入的时候判断是否大小超界
	char username[COLUMN_USERNAME_SIZE + 1];
	char email[COLUMN_EMAIL_SIZE + 1];
} Row;

//statement
typedef struct
{
	StatementType type;
	Row row_to_insert;
} Statement;


//获取行中每一个元素的size和offset
const uint32_t ID_SIZE = size_of_attribute(Row, id);
const uint32_t USERNAME_SIZE = size_of_attribute(Row, username);
const uint32_t EMAIL_SIZE = size_of_attribute(Row, email);
const uint32_t ID_OFFSET = 0;
const uint32_t USERNAME_OFFSET = 0 + size_of_attribute(Row, id);
const uint32_t EMAIL_OFFSET = 0 + size_of_attribute(Row, id) + size_of_attribute(Row, username);
const uint32_t ROW_SIZE = size_of_attribute(Row, id) + size_of_attribute(Row, username) + size_of_attribute(Row, email);
//每一页有多少行
const uint32_t ROWS_PER_PAGE = PAGE_SIZE / (size_of_attribute(Row, id) + size_of_attribute(Row, username) + size_of_attribute(Row, email));
//一张表里面最大行数
const uint32_t TABLE_MAX_ROWS = PAGE_SIZE / (size_of_attribute(Row, id) + size_of_attribute(Row, username) + size_of_attribute(Row, email)) * TABLE_MAX_PAGES;

//定义一张表
typedef struct
{
	uint32_t num_rows;
	void* pages[TABLE_MAX_PAGES];
} Table;


//初始化一个input_buffer
InputBuffer* new_input_buffer();
//打印命令提示符
void print_prompt();
// 获取一行的输入,以换行符为终结
size_t getline(char** buffer, size_t* n);
//读取输入
void read_input(InputBuffer* input_buffer);
//关闭输入
void close_input_buffer(InputBuffer* input_buffer);
// 判断是何种命令
MetaCommandResult do_meta_command(InputBuffer* input_buffer);
//预处理结果
PrepareResult prepare_statement(InputBuffer* input_buffer, Statement* statement);
void prepare_trim(InputBuffer* input_buffer);
PrepareResult prepare_insert(InputBuffer* input_buffer, Statement* statement);
//执行命令 根据 statement 来执行相应的命令
ExecuteResult execute_statement(Statement* statement, Table* table);
ExecuteResult execute_insert(Statement* statement, Table* table);
ExecuteResult execute_select(Statement* statement, Table* table);
//将行的信息存储到内存中
//A void pointer can hold address of any type and can be typcasted to any type
void serialize_row(Row* source, Row* destination);
//
void deserialize_row(Row* source, Row* destination);
//插入前判断插入的内存位置 以及适时分配内存 
Row* row_slot(Table* table, uint32_t row_num);
Table* new_table();
void free_table(Table* table);
void print_row(Row* row);
CDB.c
#define _CRT_SECURE_NO_WARNINGS
//在最前面加上这个宏
#include "CDB.h"

InputBuffer* new_input_buffer()
{
	InputBuffer* input_buffer = (InputBuffer*)malloc(sizeof(InputBuffer));
	if (input_buffer)
	{
		input_buffer->buffer = NULL;
		input_buffer->buffer_length = 0;
		input_buffer->input_length = 0;
		return input_buffer;
	}
	exit(EXIT_FAILURE);
}

void print_prompt()
{
	printf("CDB >> ");
}

// 获取一行的输入,以换行符为终结
size_t getline(char** buffer, size_t* n)
{
	char c = 0;
	size_t bytes_read = 0;
	//一行最多不超过80个字符
	uint32_t COLUMN_MAX_SIZE = COLUMN_USERNAME_SIZE + COLUMN_EMAIL_SIZE + 100;
	char* buffer_temp = (char*)malloc(COLUMN_MAX_SIZE * sizeof(char));
	if (buffer_temp == NULL)
	{
		exit(EXIT_FAILURE);
	}
	while ((c = getchar()) && c != '\n')
	{

		*(buffer_temp + bytes_read++) = c;
		if (bytes_read == COLUMN_MAX_SIZE)
		{
			printf("too long\n");
			exit(EXIT_FAILURE);
		}
	}
	*buffer = buffer_temp;
	return bytes_read;
}
void read_input(InputBuffer* input_buffer)
{
	// void 对于所有类型都非法 
	// 返回读取得到的字节数量
	size_t bytes_read = getline(&(input_buffer->buffer), &(input_buffer->buffer_length));

	//printf("这次读取到的字节数量为 %llu\n", bytes_read);
	if (bytes_read < 0) {
		printf("Error reading input\n");
		exit(EXIT_FAILURE);
	}

	// Ignore trailing newline    
	input_buffer->input_length = bytes_read;
	input_buffer->buffer[bytes_read] = 0;
}
//关闭输入
void close_input_buffer(InputBuffer* input_buffer)
{
	free(input_buffer->buffer);
	free(input_buffer);
	printf("关闭输入\n");
}

// 判断是何种命令
MetaCommandResult do_meta_command(InputBuffer* input_buffer)
{
	if (strcmp(input_buffer->buffer, ".exit") == 0)
	{
		//退出
		return META_COMMAND_EXIT;
	}
	else if (strcmp(input_buffer->buffer, ".q") == 0)
	{
		//退出
		return META_COMMAND_EXIT;
	}
	else if (strcmp(input_buffer->buffer, ".v") == 0)
	{
		//显示当前版本
		printf("CDB version 0.1\n");
		return META_COMMAND_SUCCESS;
	}
	else
	{
		//不可解析
		return META_COMMAND_UNRECOGNIZED_COMMAND;
	}

}

//预处理,就是前缀命令识别
PrepareResult prepare_statement(InputBuffer* input_buffer, Statement* statement)
{
	prepare_trim(input_buffer);
	if (strncmp(input_buffer->buffer, "insert", 6) == 0)
	{
		return prepare_insert(input_buffer, statement);
	}
	if (strncmp(input_buffer->buffer, "select", 6) == 0)
	{
		statement->type = STATEMENT_SELECT;
		return PREPARE_SUCCESS;
	}
	return PREPARE_UNRECOGNIZED_STATEMENT;
}
//预处理的预处理 去掉多余的空格
void prepare_trim(InputBuffer* input_buffer)
{
	char* temp = (char*)malloc(strlen(input_buffer->buffer) * sizeof(char));
	char* p1 = temp;
	char* p2 = input_buffer->buffer;
	//首先去掉前面的空格
	while ((*p2) == ' ')
	{
		p2++;
	}
	//现在p2指向真正的命令起始
	//去掉文本中间多余的空格
	bool flag = true;
	while ((*p2) != 0)
	{
		if (((*p2) == ' ') && (flag == true))
		{
			*p1 = ' ';
			p1++;
			p2++;
			flag = false;
		}
		else if ((*p2) != ' ')
		{
			*p1 = *p2;
			p1++;
			p2++;
			flag = true;
		}
		else
		{
			p2++;
		}
	}
	//此时 p1 指向最后,需要变为0
	*p1 = 0;
	free(input_buffer->buffer);
	input_buffer->buffer = temp;
}

PrepareResult prepare_insert(InputBuffer* input_buffer, Statement* statement)
{
	statement->type = STATEMENT_INSERT;
	//这里应该可以去掉开头的空格,目前还不可以
	char* keyword = strtok(input_buffer->buffer, " ");
	char* id_string = strtok(NULL, " ");
	char* username = strtok(NULL, " ");
	char* email = strtok(NULL, " ");
	char* test_parameter = strtok(NULL, "");
	if (id_string == NULL || username == NULL || email == NULL)
	{
		return PREPARE_SYNTAX_ERROR;
	}
	if (test_parameter != NULL)
	{
		return PREPARE_TOO_MANY_PARAMETER;
	}
	int id = atoi(id_string);
	if (strlen(username) > COLUMN_USERNAME_SIZE)
	{
		return PREPARE_STRING_TOO_LONG;
	}
	if (strlen(email) > COLUMN_EMAIL_SIZE)
	{
		return PREPARE_STRING_TOO_LONG;
	}
	statement->row_to_insert.id = id;
	strcpy(statement->row_to_insert.username, username);
	strcpy(statement->row_to_insert.email, email);
	return PREPARE_SUCCESS;
}

//执行命令
ExecuteResult execute_statement(Statement* statement, Table* table)
{
	switch (statement->type)
	{
	case (STATEMENT_INSERT):
		return execute_insert(statement, table);
	case (STATEMENT_SELECT):
		return execute_select(statement, table);
	}
}
ExecuteResult execute_insert(Statement* statement, Table* table)
{
	if (table->num_rows >= TABLE_MAX_ROWS)
	{
		return EXECUTE_TABLE_FULL;
	}
	Row* row_to_insert = &(statement->row_to_insert);
	//将一行存入内存
	serialize_row(row_to_insert, row_slot(table, table->num_rows));
	table->num_rows += 1;
	return EXECUTE_SUCCESS;
}
ExecuteResult execute_select(Statement* statement, Table* table)
{
	Row row;
	for (uint32_t i = 0; i < table->num_rows; i++)
	{
		//解析出一行
		deserialize_row(row_slot(table, i), &row);
		print_row(&row);
	}
	return EXECUTE_SUCCESS;
}
//将行的信息存储到内存中
//A void pointer can hold address of any type and can be typcasted to any type
void serialize_row(Row* source, Row* destination)
{
	// ID_SIZE 指的是字节数
	memcpy((uint8_t*)destination + ID_OFFSET, &(source->id), ID_SIZE);
	memcpy((uint8_t*)destination + USERNAME_OFFSET, &(source->username), USERNAME_SIZE);
	memcpy((uint8_t*)destination + EMAIL_OFFSET, &(source->email), EMAIL_SIZE);
}
//
void deserialize_row(Row* source, Row* destination)
{
	memcpy(&(destination->id), (uint8_t*)source + ID_OFFSET, ID_SIZE);
	memcpy(&(destination->username), (uint8_t*)source + USERNAME_OFFSET, USERNAME_SIZE);
	memcpy(&(destination->email), (uint8_t*)source + EMAIL_OFFSET, EMAIL_SIZE);
}
//插入前判断插入的内存位置 以及适时分配内存 
Row* row_slot(Table* table, uint32_t row_num)
{
	uint32_t page_num = row_num / ROWS_PER_PAGE;
	void* page = table->pages[page_num];
	if (page == NULL)
	{
		// Allocate memory only when we try to access page
		page = table->pages[page_num] = malloc(PAGE_SIZE);
	}
	uint32_t row_offset = row_num % ROWS_PER_PAGE;
	uint32_t byte_offset = row_offset * ROW_SIZE;
	//之所以报 “必须是指向完整类型”的错,是因为指针指向类型不确定,则其单位不确定,加上byte_offset后也不知道到底指向哪一块内存。
	//由于 byte_offset 是字节单位,所以page页要变为指向8位内存的指针。所以转化位 uint8_t
	return (Row*)((uint8_t*)page + byte_offset);
}
Table* new_table()
{
	Table* table = (Table*)malloc(sizeof(Table));
	table->num_rows = 0;
	for (uint32_t i = 0; i < TABLE_MAX_PAGES; i++)
	{
		table->pages[i] = NULL;
	}
	return table;
}

void free_table(Table* table)
{
	for (int i = 0; table->pages[i]; i++)
	{
		free(table->pages[i]);
	}
	free(table);
}
void print_row(Row* row)
{
	printf("| id: %u username: %s email: %s |\n", row->id, row->username, row->email);
}
main.c
#include "CDB.h"

int main()
{
	Table* table = new_table();
	InputBuffer* input_buffer = new_input_buffer();
	while (true)
	{
		print_prompt();
		read_input(input_buffer);
		//判断是否有输入,若是回车直接 continue
		if (input_buffer->input_length == 0)
		{
			continue;
		}
		//以 . 开头则为非 sql 命令
		else if (input_buffer->buffer[0] == '.')
		{
			switch (do_meta_command(input_buffer))
			{
				//退出
			case (META_COMMAND_EXIT):
				//printf("META_COMMAND_EXIT");
				close_input_buffer(input_buffer);
				exit(EXIT_SUCCESS);
				break;
				//此命令可以解析
			case (META_COMMAND_SUCCESS):
				//printf("META_COMMAND_SUCCESS");
				break;
				//命令不可解析
			case (META_COMMAND_UNRECOGNIZED_COMMAND):
				printf("Unrecognized command '%s'\n", input_buffer->buffer);
				break;
			}
		}
		//sql命令
		else if (input_buffer->buffer[0] != '.')
		{
			//进行预处理  即判断 insert create 等命令
			Statement statement;
			switch (prepare_statement(input_buffer, &statement))
			{
			case (PREPARE_SUCCESS):
				break;
			case (PREPARE_STRING_TOO_LONG):
				printf("String is too long.\n");
				continue;
			case(PREPARE_NEGATIVE_ID):
				printf("your id id negative.\n");
				continue;
			case(PREPARE_TOO_MANY_PARAMETER):
				printf("too many parameter.\n");
				continue;
			case (PREPARE_UNRECOGNIZED_STATEMENT):
				printf("Unrecognized keyword at start of '%s'.\n", input_buffer->buffer);
				//break 会break switch本身, 而continue 会continue 上一层,而忽略掉 switch
				//这里直接continue 到while(true)
				continue;
			case(PREPARE_SYNTAX_ERROR):
				printf("Syntax error. Could not parse statement.\n");
				continue;
			}
			switch (execute_statement(&statement, table))
			{
			case (EXECUTE_SUCCESS):
				printf("Executed.\n");
				break;
			case (EXECUTE_TABLE_FULL):
				printf("Error: Table full.\n");
				break;
			}
		}
		else
		{
			printf("error\n");
			exit(EXIT_SUCCESS);
		}
	}
}

第三步 保存到硬盘

这一步,我们要将内存中的数据库存入磁盘中。

数据定义

首先从程序入口处得知数据库的文件名,当前一个数据库只有一张表,一个文件。即这样:

int main(int argc,char* argv[])
{
	if (argc <= 1) 
	{
		printf("Must supply a database filename.\n");
		exit(EXIT_FAILURE);
	}
	char* filename = argv[1];
}

进行如下定义:

//存储page   这里page和table的关系也说明了一张表可以使用很多文件来存储
typedef struct 
{
	int file_descriptor;
	uint32_t file_length;
	void* pages[TABLE_MAX_PAGES];
} Pager;

//定义一张表
typedef struct
{
	uint32_t num_rows;
	Pager* pager;
} Table;

一个表内包含一个pager用来管理数据库中的page,另外还需要存储数据库中的条目数量。一个pager就是一个文件的索引,负责存储对应的文件句柄,并且存储pages,一张表可以用有很多个pager,一个pager存储一个文件,这样就可以让一张表使用多个文件来存储了。另外,这里pager用到了操作系统中缓存的概念,其中每一个page都指向内存中的一块区域,但是如果对应的page没有在内存中(NULL)就需要进行内存和和硬盘的交换。

初始化table && pager

程序要连接数据库,因为目前数据库中只有一张表故连接这一张表就好。所以 db_open 自然就是 table_open.

table中的pager需要先进行初始化,然后利用pager中的file_length就可以得到table中的 num_rows.

对于pager,首先需要根据上层传来的filename打开文件,然后根据文件信息算出来 file_length, 由此得出 page的数量,根据数量将 void* pages[TABLE_MAX_PAGES]; 全部指向null,因为当前内存中含没有任何数据库数据。

这杨初始化的工作就完成了。

内存替换

这里实现了一个简单的抽象内存管理模块,入口:pager page_num 出口:在内存中找到或者开辟一块page区域,并以指针的形式返回。

加入要使用的页(根据页号来判断)不在内存中则发生缺页中断,这时在内存中开辟一个页空间,并且判断磁盘中是否有该页(根据该页号和磁盘中的页数量作比较),若有的话则读取磁盘内容填充该页的内容,若没有的话则什么都不做直接饭返回这个空页即可。

这样就在内存中得到了想要的页。

同理,反过来在结束程序的时候还需要将内存中更新的页和磁盘中的相同步。

程序抽象

现在使用光标进行程序的抽象,即使用光标标识某一个行,使用一个函数获取该光标真实表示的行(在内存中),当然,若内存中本就不存在的话则使用上面提到的内存替换策略。

//定义一张表
typedef struct
{
	uint32_t num_rows;
	Pager* pager;
} Table;

//光标指针
typedef struct {
	Table* table;
	uint32_t row_num;
	bool end_of_table;  // Indicates a position one past the last element
} Cursor;
//插入前判断插入的内存位置 以及适时分配内存 光标指针的作用就是指示某个table中的某一行,并表明该行是否为最后一行。
//入口:一个光标指针   出口:指向内存中光标指针对应行的指针
Row* cursor_value(Cursor* cursor)
{
	uint32_t row_num = cursor->row_num;
	uint32_t page_num = row_num / ROWS_PER_PAGE;
	//算出来吧光标指针指向哪一个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;
	//之所以报 “必须是指向完整类型”的错,是因为指针指向类型不确定,则其单位不确定,加上byte_offset后也不知道到底指向哪一块内存。
	//由于 byte_offset 是字节单位,所以page页要变为指向8位内存的指针。所以转化位 uint8_t
	return (Row*)((uint8_t*)page + byte_offset);
}

代码

CDB.h
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <stdint.h>
#include <io.h>
#include <sys/types.h>    
#include <sys/stat.h>    
#include <fcntl.h>

#define COLUMN_USERNAME_SIZE 32
#define COLUMN_EMAIL_SIZE 255
//表中最大页数
#define TABLE_MAX_PAGES 100
//页的大小
#define PAGE_SIZE 4096
//定义宏获取size
#define size_of_attribute(Struct, Attribute) sizeof(((Struct*)0)->Attribute)


//输入缓冲
typedef struct
{
	char* buffer;
	// _int64
	size_t buffer_length;
	size_t input_length;
} InputBuffer;

//判断输入得到的命令状态
typedef enum
{
	META_COMMAND_EXIT,
	META_COMMAND_SUCCESS,
	META_COMMAND_UNRECOGNIZED_COMMAND
} MetaCommandResult;

//预处理得到的状态
typedef enum
{
	PREPARE_SUCCESS,
	PREPARE_NEGATIVE_ID,
	PREPARE_SYNTAX_ERROR,
	PREPARE_STRING_TOO_LONG,
	PREPARE_TOO_MANY_PARAMETER,
	PREPARE_UNRECOGNIZED_STATEMENT
} PrepareResult;

//处理后的状态
typedef enum
{
	EXECUTE_SUCCESS,
	EXECUTE_TABLE_FULL
}ExecuteResult;

//得到的输入的表达式的状态
typedef enum
{
	STATEMENT_INSERT,
	STATEMENT_SELECT
} StatementType;

//定义 一行 
typedef struct
{
	uint32_t id;
	//加一是因为c语言需要最后一个字符为0,所以可用字符就减一了。
	//不过即使这样也还需要在输入的时候判断是否大小超界
	char username[COLUMN_USERNAME_SIZE + 1];
	char email[COLUMN_EMAIL_SIZE + 1];
} Row;

//statement
typedef struct
{
	StatementType type;
	Row row_to_insert;
} Statement;


//获取行中每一个元素的size和offset
const uint32_t ID_SIZE = size_of_attribute(Row, id);
const uint32_t USERNAME_SIZE = size_of_attribute(Row, username);
const uint32_t EMAIL_SIZE = size_of_attribute(Row, email);
const uint32_t ID_OFFSET = 0;
const uint32_t USERNAME_OFFSET = 0 + size_of_attribute(Row, id);
const uint32_t EMAIL_OFFSET = 0 + size_of_attribute(Row, id) + size_of_attribute(Row, username);
const uint32_t ROW_SIZE = size_of_attribute(Row, id) + size_of_attribute(Row, username) + size_of_attribute(Row, email);
//每一页有多少行
const uint32_t ROWS_PER_PAGE = PAGE_SIZE / (size_of_attribute(Row, id) + size_of_attribute(Row, username) + size_of_attribute(Row, email));
//一张表里面最大行数
const uint32_t TABLE_MAX_ROWS = PAGE_SIZE / (size_of_attribute(Row, id) + size_of_attribute(Row, username) + size_of_attribute(Row, email)) * TABLE_MAX_PAGES;

//存储page   这里page和table的关系也说明了一张表可以使用很多文件来存储
typedef struct
{
	int file_descriptor;
	uint32_t file_length;
	void* pages[TABLE_MAX_PAGES];
} Pager;

//定义一张表
typedef struct
{
	uint32_t num_rows;
	Pager* pager;
} Table;

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



//初始化一个input_buffer
InputBuffer* new_input_buffer();
//打印命令提示符
void print_prompt();
// 获取一行的输入,以换行符为终结
size_t getline(char** buffer, size_t* n);
//读取输入
void read_input(InputBuffer* input_buffer);
//关闭输入
void close_input_buffer(InputBuffer* input_buffer);
// 判断是何种命令
MetaCommandResult do_meta_command(InputBuffer* input_buffer, Table* table);
//预处理结果
PrepareResult prepare_statement(InputBuffer* input_buffer, Statement* statement);
void prepare_trim(InputBuffer* input_buffer);
PrepareResult prepare_insert(InputBuffer* input_buffer, Statement* statement);
//执行命令 根据 statement 来执行相应的命令
ExecuteResult execute_statement(Statement* statement, Table* table);
ExecuteResult execute_insert(Statement* statement, Table* table);
ExecuteResult execute_select(Statement* statement, Table* table);
//将行的信息存储到内存中
//A void pointer can hold address of any type and can be typcasted to any type
void serialize_row(Row* source, Row* destination);
//
void deserialize_row(Row* source, Row* destination);
//插入前判断插入的内存位置 以及适时分配内存 
Row* cursor_value(Cursor* cursor);
//从磁盘中获取page
void* get_page(Pager* pager, uint32_t page_num);
//同步内存中的额数据和磁盘中的数据
void pager_flush(Pager* pager, uint32_t page_num, uint32_t size);
//连接数据库  使用文件名
Table* db_open(const char* filename);
Pager* pager_open(const char* filename);
void db_close(Table* table);
void free_table(Table* table);
void print_row(Row* row);
//指向表的开头 第一行
Cursor* table_start(Table* table);
//指向当前要插入的一行
Cursor* table_end(Table* table);
//更新 cursor
void cursor_advance(Cursor* cursor);


CDB.cpp
#define _CRT_SECURE_NO_WARNINGS
//在最前面加上这个宏
#include "CDB.h"

InputBuffer* new_input_buffer()
{
	InputBuffer* input_buffer = (InputBuffer*)malloc(sizeof(InputBuffer));
	if (input_buffer)
	{
		input_buffer->buffer = NULL;
		input_buffer->buffer_length = 0;
		input_buffer->input_length = 0;
		return input_buffer;
	}
	exit(EXIT_FAILURE);
}

void print_prompt()
{
	printf("CDB >> ");
}

// 获取一行的输入,以换行符为终结
size_t getline(char** buffer, size_t* n)
{
	char c = 0;
	size_t bytes_read = 0;
	//一行最多不超过80个字符
	uint32_t COLUMN_MAX_SIZE = COLUMN_USERNAME_SIZE + COLUMN_EMAIL_SIZE + 100;
	char* buffer_temp = (char*)malloc(COLUMN_MAX_SIZE * sizeof(char));
	if (buffer_temp == NULL)
	{
		exit(EXIT_FAILURE);
	}
	while ((c = getchar()) && c != '\n')
	{

		*(buffer_temp + bytes_read++) = c;
		if (bytes_read == COLUMN_MAX_SIZE)
		{
			printf("too long\n");
			exit(EXIT_FAILURE);
		}
	}
	*buffer = buffer_temp;
	return bytes_read;
}
void read_input(InputBuffer* input_buffer)
{
	// void 对于所有类型都非法 
	// 返回读取得到的字节数量
	size_t bytes_read = getline(&(input_buffer->buffer), &(input_buffer->buffer_length));

	//printf("这次读取到的字节数量为 %llu\n", bytes_read);
	if (bytes_read < 0) {
		printf("Error reading input\n");
		exit(EXIT_FAILURE);
	}

	// Ignore trailing newline    
	input_buffer->input_length = bytes_read;
	input_buffer->buffer[bytes_read] = 0;
}
//关闭输入
void close_input_buffer(InputBuffer* input_buffer)
{
	free(input_buffer->buffer);
	free(input_buffer);
	printf("关闭输入\n");
}

// 判断是何种命令
MetaCommandResult do_meta_command(InputBuffer* input_buffer, Table* table)
{
	if (strcmp(input_buffer->buffer, ".exit") == 0)
	{
		//退出
		db_close(table);
		return META_COMMAND_EXIT;
	}
	else if (strcmp(input_buffer->buffer, ".q") == 0)
	{
		//退出
		db_close(table);
		return META_COMMAND_EXIT;
	}
	else if (strcmp(input_buffer->buffer, ".v") == 0)
	{
		//显示当前版本
		printf("CDB version 0.1\n");
		return META_COMMAND_SUCCESS;
	}
	else
	{
		//不可解析
		return META_COMMAND_UNRECOGNIZED_COMMAND;
	}

}

//预处理,就是前缀命令识别
PrepareResult prepare_statement(InputBuffer* input_buffer, Statement* statement)
{
	prepare_trim(input_buffer);
	if (strncmp(input_buffer->buffer, "insert", 6) == 0)
	{
		return prepare_insert(input_buffer, statement);
	}
	if (strncmp(input_buffer->buffer, "select", 6) == 0)
	{
		statement->type = STATEMENT_SELECT;
		return PREPARE_SUCCESS;
	}
	return PREPARE_UNRECOGNIZED_STATEMENT;
}
//预处理的预处理 去掉多余的空格
void prepare_trim(InputBuffer* input_buffer)
{
	char* temp = (char*)malloc(strlen(input_buffer->buffer) * sizeof(char));
	char* p1 = temp;
	char* p2 = input_buffer->buffer;
	//首先去掉前面的空格
	while ((*p2) == ' ')
	{
		p2++;
	}
	//现在p2指向真正的命令起始
	//去掉文本中间多余的空格
	bool flag = true;
	while ((*p2) != 0)
	{
		if (((*p2) == ' ') && (flag == true))
		{
			*p1 = ' ';
			p1++;
			p2++;
			flag = false;
		}
		else if ((*p2) != ' ')
		{
			*p1 = *p2;
			p1++;
			p2++;
			flag = true;
		}
		else
		{
			p2++;
		}
	}
	//此时 p1 指向最后,需要变为0
	*p1 = 0;
	free(input_buffer->buffer);
	input_buffer->buffer = temp;
}

PrepareResult prepare_insert(InputBuffer* input_buffer, Statement* statement)
{
	statement->type = STATEMENT_INSERT;
	//这里应该可以去掉开头的空格,目前还不可以
	char* keyword = strtok(input_buffer->buffer, " ");
	char* id_string = strtok(NULL, " ");
	char* username = strtok(NULL, " ");
	char* email = strtok(NULL, " ");
	char* test_parameter = strtok(NULL, "");
	if (id_string == NULL || username == NULL || email == NULL)
	{
		return PREPARE_SYNTAX_ERROR;
	}
	if (test_parameter != NULL)
	{
		return PREPARE_TOO_MANY_PARAMETER;
	}
	int id = atoi(id_string);
	if (strlen(username) > COLUMN_USERNAME_SIZE)
	{
		return PREPARE_STRING_TOO_LONG;
	}
	if (strlen(email) > COLUMN_EMAIL_SIZE)
	{
		return PREPARE_STRING_TOO_LONG;
	}
	statement->row_to_insert.id = id;
	strcpy(statement->row_to_insert.username, username);
	strcpy(statement->row_to_insert.email, email);
	return PREPARE_SUCCESS;
}

//执行命令
ExecuteResult execute_statement(Statement* statement, Table* table)
{
	switch (statement->type)
	{
	case (STATEMENT_INSERT):
		return execute_insert(statement, table);
	case (STATEMENT_SELECT):
		return execute_select(statement, table);
	}
}
ExecuteResult execute_insert(Statement* statement, Table* table)
{
	if (table->num_rows >= TABLE_MAX_ROWS)
	{
		return EXECUTE_TABLE_FULL;
	}
	Row* row_to_insert = &(statement->row_to_insert);
	Cursor* cursor = table_end(table);
	//将一行存入内存
	serialize_row(row_to_insert, cursor_value(cursor));
	table->num_rows += 1;
	return EXECUTE_SUCCESS;
}
ExecuteResult execute_select(Statement* statement, Table* table)
{
	Cursor* cursor = table_start(table);
	Row row;
	while (!(cursor->end_of_table)) {
		deserialize_row(cursor_value(cursor), &row);
		print_row(&row);
		cursor_advance(cursor);
	}
	free(cursor);
	return EXECUTE_SUCCESS;
}
//将行的信息存储到内存中
//A void pointer can hold address of any type and can be typcasted to any type
void serialize_row(Row* source, Row* destination)
{
	// ID_SIZE 指的是字节数
	memcpy((uint8_t*)destination + ID_OFFSET, &(source->id), ID_SIZE);
	memcpy((uint8_t*)destination + USERNAME_OFFSET, &(source->username), USERNAME_SIZE);
	memcpy((uint8_t*)destination + EMAIL_OFFSET, &(source->email), EMAIL_SIZE);
}
//
void deserialize_row(Row* source, Row* destination)
{
	memcpy(&(destination->id), (uint8_t*)source + ID_OFFSET, ID_SIZE);
	memcpy(&(destination->username), (uint8_t*)source + USERNAME_OFFSET, USERNAME_SIZE);
	memcpy(&(destination->email), (uint8_t*)source + EMAIL_OFFSET, EMAIL_SIZE);
}
//插入前判断插入的内存位置 以及适时分配内存 光标指针的作用就是指示某个table中的某一行,并表明该行是否为最后一行。
//入口:一个光标指针   出口:指向内存中光标指针对应行的指针
Row* cursor_value(Cursor* cursor)
{
	uint32_t row_num = cursor->row_num;
	uint32_t page_num = row_num / ROWS_PER_PAGE;
	//算出来吧光标指针指向哪一个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;
	//之所以报 “必须是指向完整类型”的错,是因为指针指向类型不确定,则其单位不确定,加上byte_offset后也不知道到底指向哪一块内存。
	//由于 byte_offset 是字节单位,所以page页要变为指向8位内存的指针。所以转化位 uint8_t
	return (Row*)((uint8_t*)page + byte_offset);
}

//从磁盘中获取page  
//入口:pager page_num  出口:在内存中找到或者开辟一块page区域,并以指针的形式返回。
void* get_page(Pager* pager, uint32_t page_num)
{
	if (page_num > TABLE_MAX_PAGES)
	{
		printf("Tried to fetch page number out of bounds. %d > %d\n", page_num, TABLE_MAX_PAGES);
		exit(EXIT_FAILURE);
	}
	else if (pager->pages[page_num] == NULL)
	{
		// Cache miss. Allocate memory and load from file.
		void* page = malloc(PAGE_SIZE);
		if (!page)
		{
			printf("load page error.\n");
			exit(EXIT_FAILURE);
		}
		//pager中没有存储page的数量,所以才需要在这里算出来
		uint32_t num_pages = pager->file_length / PAGE_SIZE;

		// We might save a partial page at the end of the file
		if (pager->file_length % PAGE_SIZE)
		{
			num_pages += 1;
		}
		//这里应为 num_pages-1 因为都是从零开始,num_pages算出来的是真实的page数量,比下标大一
		if (page_num <= num_pages - 1)
		{
			//设定文件指针位置
			_lseek(pager->file_descriptor, page_num * PAGE_SIZE, SEEK_SET);
			//获取一个page到内存中。并将读取到的字节数存储到bytes_read中
			size_t bytes_read = _read(pager->file_descriptor, page, PAGE_SIZE);
			if (bytes_read == -1)
			{
				//打印出程序错误代码,目前我还不会用。
				printf("Error reading file: %d\n", errno);
				exit(EXIT_FAILURE);
			}
		}
		//如果大于当前文件中的页数,则说明文件中没有这个页,这是一个新页,直接使用即可。
		pager->pages[page_num] = page;
	}
	return pager->pages[page_num];
}
//目前数据库中只有一张表
Table* db_open(const char* filename)
{
	Pager* pager = pager_open(filename);
	//当前存储的行数,因为现在一张表只用一个文件来存储故这样是也可以的。
	uint32_t num_rows = pager->file_length / ROW_SIZE;
	Table* table = (Table*)malloc(sizeof(Table));
	table->pager = pager;
	table->num_rows = num_rows;
	return table;
}

Pager* pager_open(const char* filename)
{
	/*
		int fd = open(filename,
		O_RDWR |      // Read/Write mode
		O_CREAT,  // Create file if it does not exist
		S_IWUSR |     // User write permission
		S_IRUSR   // User read permission
		);
	*/
	//Read/Write mode Create file if it does not exist
	int fd = _open(filename, O_RDWR | O_CREAT, _S_IREAD | _S_IWRITE);

	if (fd == -1)
	{
		printf("Unable to open file\n");
		exit(EXIT_FAILURE);
	}
	//long  文件中的偏移地址从0到seek_end的长度即为文件的长度
	//returns the offset, in bytes, of the new position from the beginning of the file
	//将文件指针从 SEEK_END向后移动0为,并返回新的文件指针相对于文件起始 SEEK_SET 的偏移量,即文件大小
	off_t file_length = _lseek(fd, 0, SEEK_END);
	//初始化pager
	Pager* pager = (Pager*)malloc(sizeof(Pager));
	pager->file_descriptor = fd;
	pager->file_length = file_length;
	for (uint32_t i = 0; i < TABLE_MAX_PAGES; i++)
	{
		pager->pages[i] = NULL;
	}
	return pager;
}

//将某一页存储到硬盘中
void pager_flush(Pager* pager, uint32_t page_num, uint32_t size)
{
	if (pager->pages[page_num] == NULL)
	{
		printf("Tried to flush null page\n");
		exit(EXIT_FAILURE);
	}
	//SEEK_SET: Beginning of the file.  将文件指针指向对应位置,下一次操控文件就从这里开始了
	off_t offset = _lseek(pager->file_descriptor, page_num * PAGE_SIZE, SEEK_SET);
	if (offset == -1)
	{
		printf("Error seeking: %d\n", errno);
		exit(EXIT_FAILURE);
	}
	//从当前文件指针位置写入 大小为size的内存 内存起始为pager->pages[page_num]
	size_t bytes_written = _write(pager->file_descriptor, pager->pages[page_num], size);
	if (bytes_written == -1)
	{
		printf("Error writing: %d\n", errno);
		exit(EXIT_FAILURE);
	}
}
//关闭数据库连接
void db_close(Table* table)
{
	Pager* pager = table->pager;
	//满的 page
	uint32_t num_full_pages = table->num_rows / ROWS_PER_PAGE;
	for (uint32_t i = 0; i < num_full_pages; i++)
	{
		//判断page是否在内存中,不在的话就从内存中更新到硬盘上。并释放内存
		if (pager->pages[i] == NULL)
		{
			continue;
		}
		pager_flush(pager, i, PAGE_SIZE);
		free(pager->pages[i]);
		pager->pages[i] = NULL;
	}
	// There may be a partial page to write to the end of the file
	// This should not be needed after we switch to a B-tree
	uint32_t num_additional_rows = table->num_rows % ROWS_PER_PAGE;
	if (num_additional_rows > 0)
	{
		uint32_t page_num = num_full_pages;
		//剩下不足一个page的行所在的page_num
		if (pager->pages[page_num] != NULL)
		{
			pager_flush(pager, page_num, num_additional_rows * ROW_SIZE);
			free(pager->pages[page_num]);
			pager->pages[page_num] = NULL;
		}
	}
	int result = _close(pager->file_descriptor);
	if (result == -1)
	{
		printf("Error closing db file.\n");
		exit(EXIT_FAILURE);
	}
	for (uint32_t i = 0; i < TABLE_MAX_PAGES; i++)
	{
		void* page = pager->pages[i];
		if (page)
		{
			free(page);
			pager->pages[i] = NULL;
		}
	}
	free(pager);
	free(table);
}
void free_table(Table* table)
{
	for (int i = 0; table->pager->pages[i]; i++)
	{
		free(table->pager->pages[i]);
	}
	free(table->pager);
	free(table);
}
void print_row(Row* row)
{
	printf("| id: %u username: %s email: %s |\n", row->id, row->username, row->email);
}
//指向表的开头 第一行
Cursor* table_start(Table* table)
{
	Cursor* 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 = (Cursor*)malloc(sizeof(Cursor));
	cursor->table = table;
	cursor->row_num = table->num_rows;
	cursor->end_of_table = true;
	return cursor;
}
//更新 cursor
void cursor_advance(Cursor* cursor)
{
	cursor->row_num += 1;
	if (cursor->row_num >= cursor->table->num_rows)
	{
		cursor->end_of_table = true;
	}
}
main.c
#include "CDB.h"

int main(int argc, char* argv[])
{
	if (argc <= 1)
	{
		printf("Must supply a database filename.\n");
		exit(EXIT_FAILURE);
	}
	char* filename = argv[1];
	Table* table = db_open(filename);
	InputBuffer* input_buffer = new_input_buffer();
	while (true)
	{
		print_prompt();
		read_input(input_buffer);
		//判断是否有输入,若是回车直接 continue
		if (input_buffer->input_length == 0)
		{
			continue;
		}
		//以 . 开头则为非 sql 命令
		else if (input_buffer->buffer[0] == '.')
		{
			switch (do_meta_command(input_buffer, table))
			{
				//退出
			case (META_COMMAND_EXIT):
				//printf("META_COMMAND_EXIT");
				close_input_buffer(input_buffer);
				exit(EXIT_SUCCESS);
				break;
				//此命令可以解析
			case (META_COMMAND_SUCCESS):
				//printf("META_COMMAND_SUCCESS");
				break;
				//命令不可解析
			case (META_COMMAND_UNRECOGNIZED_COMMAND):
				printf("Unrecognized command '%s'\n", input_buffer->buffer);
				break;
			}
		}
		//sql命令
		else if (input_buffer->buffer[0] != '.')
		{
			//进行预处理  即判断 insert create 等命令
			Statement statement;
			switch (prepare_statement(input_buffer, &statement))
			{
			case (PREPARE_SUCCESS):
				break;
			case (PREPARE_STRING_TOO_LONG):
				printf("String is too long.\n");
				continue;
			case(PREPARE_NEGATIVE_ID):
				printf("your id id negative.\n");
				continue;
			case(PREPARE_TOO_MANY_PARAMETER):
				printf("too many parameter.\n");
				continue;
			case (PREPARE_UNRECOGNIZED_STATEMENT):
				printf("Unrecognized keyword at start of '%s'.\n", input_buffer->buffer);
				//break 会break switch本身, 而continue 会continue 上一层,而忽略掉 switch
				//这里直接continue 到while(true)
				continue;
			case(PREPARE_SYNTAX_ERROR):
				printf("Syntax error. Could not parse statement.\n");
				continue;
			}
			switch (execute_statement(&statement, table))
			{
			case (EXECUTE_SUCCESS):
				printf("Executed.\n");
				break;
			case (EXECUTE_TABLE_FULL):
				printf("Error: Table full.\n");
				break;
			}
		}
		else
		{
			printf("error\n");
			exit(EXIT_SUCCESS);
		}
	}
}

第三步 使用 B TREE

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值