+
95
-

如何用c语言自己开发一个数据库?

如何用c语言自己开发一个数据库?


网友回复

+
15
-

我们模仿以下sqlite数据库,因为他很小,只有一个c文件,功能少模仿简单。

800_auto 说明:整体来说sqlite将sql命令通过分词、解析和代码生成器最后生成字节码,在虚拟机上运行,直到它完成或返回结果、或遇到致命错误、或中断。 开始

主要开发工具vscode,采用xmake做c的构建工具,主要是比较简单和方便,可以跨平台使用,不了解的可以参考我的xmake构建工具的文章。 新建个项目,最终展示如下:

800_auto 先来仿照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 支持在内存中插入和查询语句

首先数据保存在内存中,采用硬编码的方式来保存用户信息,用户信息如下表: 800_auto 插入语句和查询语句的语法也进行了简化,插入语句如下: 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;
800_auto 列的长度和偏移量 下面两个函数比较关键,即序列化列和反序列化列,这里面序列化并没有把数据写...

点击查看剩余70%

我知道答案,我要回答