01. 简介和设置REPL
简介和设置REPL
作为一个
- 数据保存的格式是什么
? (在内存和硬盘中) - 什么是候将它从内存移动到硬盘中?
- 为什么一个数据表只能有一个主键?
- 回滚操作是怎么完成的?
- 索引是如何格式化的?
- 何时及如何进行全表扫描?
- 准备好的语句以什么格式保存?
换句话说,一个数据库是如何工作的?
为了搞清楚这些事,我正在从头编写数据库。它是基于
Sqlite
他们的网站上有许多 内部
查询通过一系列组件来检索或是修改数据。前端包括:
- Tokenizer
- 解析器
- 代码生成器
输入到前端的是一个
后端包括:
- 虚拟机
B 树- Pager
- 系统接口
虚拟机将前端生成的字节码当成指令。然后,他可以对一个或多个表进行索引操作,每个表都存储在一种叫做
每个
系统接口是不同的层,具体取决于为哪个操作系统的
千里之行始于足下,让我们从一个简单的东西开始:REPL。
制作一个简单的REPL
当你在命令行启动
~ sqlite3
SQLite version 3.16.0 2016-11-04 19:09:39
Enter ".help" for usage hints.
Connected to a transient in-memory database.
Use ".open FILENAME" to reopen on a persistent database.
sqlite> create table users (id int, username varchar(255), email varchar(255));
sqlite> .tables
users
sqlite> .exit
~
为此,我们的 main
函数应有一个无限循环,这个循环打印提示,获取输入,然后处理输入:
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("Unrecognized command '%s'.\n", input_buffer->buffer);
}
}
}
我们将 InputBuffer
定义为一个围绕状态存储的小包装,以便和 getline() 函数进行交互(稍后详细介绍)
typedef struct {
char* buffer;
size_t buffer_length;
ssize_t input_length;
} InputBuffer;
InputBuffer* new_input_buffer() {
InputBuffer* input_buffer = (InputBuffer*)malloc(sizeof(InputBuffer));
input_buffer->buffer = NULL;
input_buffer->buffer_length = 0;
input_buffer->input_length = 0;
return input_buffer;
}
接着,print_prompt()
打印一个提示给用户。我们在读每一行输入之前都要进行这个操作。
void print_prompt() { printf("db > "); }
如果要读取一行输入,那就使用 getline():
ssize_t getline(char **lineptr, size_t *n, FILE *stream);
lineptr
:一个指向字符串的指针,我们用来指向包含读取行的缓冲区。如果将它设置为
n
:一个指向用于保存为缓冲区分配的大小的变量的指针。
stream
:输入来源。我们使用标准输入。
返回值
:读取的字节数,可能小于缓冲区的大小。
我们让 getline
将读取的行存储在 input_buffer->buffer
中,将分配的缓冲区大小存储在 input_buffer->buffer_length
中。我们将返回值存储在 input_buffer->input_length
中。
buffer
开始为空,所以 getline
分配足够的内存来容纳输入行,并让缓冲区指向输入行。
void read_input(InputBuffer* input_buffer) {
ssize_t bytes_read =
getline(&(input_buffer->buffer), &(input_buffer->buffer_length), stdin);
if (bytes_read <= 0) {
printf("Error reading input\n");
exit(EXIT_FAILURE);
}
// Ignore trailing newline
input_buffer->input_length = bytes_read - 1;
input_buffer->buffer[bytes_read - 1] = 0;
}
现在定义一个释放为 InputBuffer *
实例分配的内存和响应结构的 buffer
元素(getline
在 read_input
中为 input_buffer->buffer
分配的内存)
void close_input_buffer(InputBuffer* input_buffer) {
free(input_buffer->buffer);
free(input_buffer);
}
最后,我们解析并执行命令。 现在只有一个可识别的命令:.exit,它将终止程序。 否则,我们将显示错误消息并继续循环。
if (strcmp(input_buffer->buffer, ".exit") == 0) {
close_input_buffer(input_buffer);
exit(EXIT_SUCCESS);
} else {
printf("Unrecognized command '%s'.\n", input_buffer->buffer);
}
来试试!
~ ./db
db > .tables
Unrecognized command '.tables'.
db > .exit
~
好了,我们有一个有效的
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
char* buffer;
size_t buffer_length;
ssize_t input_length;
} InputBuffer;
InputBuffer* new_input_buffer() {
InputBuffer* input_buffer = malloc(sizeof(InputBuffer));
input_buffer->buffer = NULL;
input_buffer->buffer_length = 0;
input_buffer->input_length = 0;
return input_buffer;
}
void print_prompt() { printf("db > "); }
void read_input(InputBuffer* input_buffer) {
ssize_t bytes_read =
getline(&(input_buffer->buffer), &(input_buffer->buffer_length), stdin);
if (bytes_read <= 0) {
printf("Error reading input\n");
exit(EXIT_FAILURE);
}
// Ignore trailing newline
input_buffer->input_length = bytes_read - 1;
input_buffer->buffer[bytes_read - 1] = 0;
}
void close_input_buffer(InputBuffer* input_buffer) {
free(input_buffer->buffer);
free(input_buffer);
}
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("Unrecognized command '%s'.\n", input_buffer->buffer);
}
}
}