如何用c语言自己开发一个数据库?
网友回复
我们模仿以下sqlite数据库,因为他很小,只有一个c文件,功能少模仿简单。
说明:整体来说sqlite将sql命令通过分词、解析和代码生成器最后生成字节码,在虚拟机上运行,直到它完成或返回结果、或遇到致命错误、或中断。
开始
主要开发工具vscode,采用xmake做c的构建工具,主要是比较简单和方便,可以跨平台使用,不了解的可以参考我的xmake构建工具的文章。 新建个项目,最终展示如下:
先来仿照sqlite的命令行实现个简单的解释器,它会循环执行:读取用户输入-->执行命令-->输出结果-->继续等待用户输出。 关键代码如下:
int main(int argc, char **argv) { InputBuffer *input_buffer = new_input_buffer(); while (true) { print_prompt(); read_input(input_buffer); if (strcmp(input_buffer->buffer, ".exit") == 0) { close_input_buffer(input_buffer); exit(EXIT_SUCCESS); } else { printf("unkown command: '%s'.\n", input_buffer->buffer); } } return 0; }其中InputBuffer为自定义的保存用户输入的缓冲区,读取一行数据后判断命令是否为".exit"命令,不是退出命令则打印不识别命令,是退出命令则退出,非常简单:
typedef struct { char* buffer; size_t buffer_length; ssize_t input_length; } InputBuffer; int getline_my(char **buffer, int *length, FILE *fd) { int i = 0; char ch; char buf[MAX_LEN] = {0}; while ((ch = fgetc(fd)) != EOF && ch != '\n') { if (MAX_LEN - 1 == i) { break; } buf[i++] = ch; } *length = i; buf[i] = '\0'; *buffer = (char *)malloc(sizeof(char) * (i + 1)); assert(buffer != NULL); strncpy(*buffer, buf, i + 1); return i; } void read_input(InputBuffer *input_buffer) { ssize_t bytes_read = getline_my(&(input_buffer->buffer), &(input_buffer->buffer_length), stdin); if (bytes_read <= 0) { printf("Error reading input\n"); exit(EXIT_FAILURE); } input_buffer->input_length = bytes_read ; input_buffer->buffer[bytes_read] = 0; }运行提示行: F:\ccode\microdb-my\build\windows\x86\debug>microdb-my.exe
microdb > .test unkown command: '.test'. microdb > .help unkown command: '.help'. microdb > .exit
小小的改进版本
1 支持语句识别
上个版本的功能除了识别.exit退出外,没啥其他有用的功能,这个版本先添加对语句的支持,另外我们区分下点号开头的表示是元命令,否则就是正常的sql语句,需要我们解析执行的先不管怎么解析和执行。 第2版的main函数,内容如下:
int main(int argc, char **argv) { InputBuffer *input_buffer = new_input_buffer(); while (true) { print_prompt(); read_input(input_buffer); if (input_buffer->buffer[0] == '.') { switch (do_meta_command(input_buffer)) { case (META_COMMAND_SUCCESS): continue; case (META_COMMAND_UNRECOGNIZED_COMMAND): printf("Unrecognized command '%s'\n", input_buffer->buffer); continue; } } else { Statement statement; switch (prepare_statement(input_buffer, &statement)) { case (PREPARE_SUCCESS): break; case (PREPARE_UNRECOGNIZED_STATEMENT): printf("Unrecognized keyword at start of '%s'.\n", input_buffer->buffer); continue; } execute_statement(&statement); printf("Executed.\n"); } } return 0; }将整个输入分成两个部分,以点号开头的表示元命令,不然就当成sql语句进行处理, 解析好语句后,就执行,执行只是打印个输出内容: F:\ccode\microdb-my\build\windows\x86\debug>microdb-my.exe microdb > .test Unrecognized command '.test' microdb > .help Unrecognized command '.help' microdb > insert a This is where we would do an insert. Executed. microdb > select a This is where we would do a select. Executed. microdb > .exit 关于语句和解析语句的定义如下:
typedef enum { META_COMMAND_SUCCESS, META_COMMAND_UNRECOGNIZED_COMMAND } MetaCommandResult; typedef enum { STATEMENT_INSERT, STATEMENT_SELECT } StatementType; typedef enum { PREPARE_SUCCESS, PREPARE_UNRECOGNIZED_STATEMENT } PrepareResult; typedef struct { StatementType type; } Statement; MetaCommandResult do_meta_command(InputBuffer* input_buffer) { if (strcmp(input_buffer->buffer, ".exit") == 0) { close_input_buffer(input_buffer); exit(EXIT_SUCCESS); } else { return META_COMMAND_UNRECOGNIZED_COMMAND; } } PrepareResult prepare_statement(InputBuffer *input_buffer, Statement *statement) { if (strncmp(input_buffer->buffer, "insert", 6) == 0) { statement->type = STATEMENT_INSERT; return PREPARE_SUCCESS; } else if (strncmp(input_buffer->buffer, "select",6) == 0) { statement->type = STATEMENT_SELECT; return PREPARE_SUCCESS; } return PREPARE_UNRECOGNIZED_STATEMENT; } void execute_statement(Statement *statement) { switch (statement->type) { case (STATEMENT_INSERT): printf("This is where we would do an insert.\n"); break; case (STATEMENT_SELECT): printf("This is where we would do a select.\n"); break; } }2 支持在内存中插入和查询语句
首先数据保存在内存中,采用硬编码的方式来保存用户信息,用户信息如下表:
插入语句和查询语句的语法也进行了简化,插入语句如下:
insert 1 cstack foo@bar.com
select
整体实现思路如下: 扩展解析功能,将插入的数据以行为基本单元保存在称为页的一块内存中。 每个页面有很多行组成,行紧凑排列。 整个系统会根据需要申请多个页面,所有的页面都保存在一个大数组里面。 对row的定义如下:
typedef struct { uint32_t id; char username[COLUMN_USERNAME_SIZE]; char email[COLUMN_EMAIL_SIZE]; } Row; // 判断数据类型的长度,(Struct*)0是把null强转为Struct*指针,后获取属性,这里面只获取类型 所以不会报错 #define size_of_attribute(Struct, Attribute) sizeof(((Struct*)0)->Attribute) // 获取ID的属性的长度 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); // ID偏移量 const uint32_t ID_OFFSET = 0; // 用户名偏移量 const uint32_t USERNAME_OFFSET = ID_OFFSET + ID_SIZE; // 邮箱的偏移量 const uint32_t EMAIL_OFFSET = USERNAME_OFFSET + USERNAME_SIZE; // 整行长度 const uint32_t ROW_SIZE = ID_SIZE + USERNAME_SIZE + EMAIL_SIZE;

点击查看剩余70%