前文回顧:實作一個簡單的Database1(譯文)
譯注:cstsck在github維護了一個簡單的、類似sqlite的資料庫實作,通過這個簡單的專案,可以很好的理解資料庫是如何運行的,本文是第二篇,主要是實作資料庫的前端組件,編譯器與虛擬機部分功能
Part 2 世界上最簡單的SQL編譯器與虛擬機
我們正在實作一個sqlite的克隆版本,sqlite的前端是SQL編譯器,編譯器用來決議字串并輸出一個內部的表示,叫做位元組碼,
這些位元組碼被傳到虛擬機(virtual machine),在虛擬機中,位元組碼將被執行,

SQLite Architecture (https://www.sqlite.org/arch.html)
像這樣把事情分成兩個步驟(SQL編譯和虛擬機)有以下兩個優點:
- 減少各個部分的復雜性(例如:虛擬機不用關心輸入陳述句語法錯誤)
- 允許只編譯通用查詢一次,然后對生成的位元組碼進行快取,以此來提升性能
有了這些想法,讓我們來重構主函式,在程式中支持了兩個新的關鍵字:
譯注:下面代碼中行開頭加減號是相對與第一部分(part 1)的實作,增加或者洗掉的代碼,代碼對main()重構以適合識別新關鍵字,在第一部分中,main()函式只能識別“.exit”關鍵字,也就是程式退出命令,
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) {
- exit(EXIT_SUCCESS);
- } else {
- printf("Unrecognized command '%s'.\n", input_buffer->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;
+ }
}
+
+ 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");
}
}
非SQL陳述句,像“.exit”這樣的命令被稱為“meta-commands”,它們都是以“.”開頭,所以我們在一個獨立的函式中檢查并且處理它們,
譯注:在上邊代碼中使用了單獨的if+switch來處理了以“.”開頭的“meta-commands”,
接下來,增加一個步驟,將輸入行命令轉換成內部表示的陳述句,這是sqlite前端的一個破解版本,
最后,我門將預編譯陳述句傳遞到execute_statement()函式,這個函式將最終變成我們的虛擬機,
注意我們的兩個新函式回傳enum(列舉)型別的來表示成功或者失敗:
typedef enum {
META_COMMAND_SUCCESS,
META_COMMAND_UNRECOGNIZED_COMMAND
} MetaCommandResult;
typedef enum { PREPARE_SUCCESS, PREPARE_UNRECOGNIZED_STATEMENT } PrepareResult;
在輸入命令列陳述句無法識別時,列印“Unrecognized statement”輸出?這個看起來像是例外(exception),我不喜歡使用exception(并且C語言甚至不支持exception),所以我在任何可行的地方都是用enum結果碼做回傳,如果我的switch陳述句沒有處理enum成員,C編譯器會報錯,所以我們能感到小有信心,我們能處理所有函式結果,預計將來會有更多的結果代碼被加入,
do_meta_command()函式只是對已有的功能的一個封裝,為更多的命令留出空間:
MetaCommandResult do_meta_command(InputBuffer* input_buffer) {
if (strcmp(input_buffer->buffer, ".exit") == 0) {
exit(EXIT_SUCCESS);
} else {
return META_COMMAND_UNRECOGNIZED_COMMAND;
}
}
我們的“prepared statement”現在只包含一個enum(有兩個可能值),在陳述句中將會包含更多的我們允許的引數資料:
typedef enum { STATEMENT_INSERT, STATEMENT_SELECT } StatementType;
typedef struct {
StatementType type;
} Statement;
prepare_statement()函式(我們的SQL編譯器)現在還不能理解SQL,事實上,它現在只能理解兩個單詞:
譯注:下面的代碼實作了對insert和select關鍵的決議,
PrepareResult prepare_statement(InputBuffer* input_buffer,
Statement* statement) {
if (strncmp(input_buffer->buffer, "insert", 6) == 0) {
statement->type = STATEMENT_INSERT;
return PREPARE_SUCCESS;
}
if (strcmp(input_buffer->buffer, "select") == 0) {
statement->type = STATEMENT_SELECT;
return PREPARE_SUCCESS;
}
return PREPARE_UNRECOGNIZED_STATEMENT;
}
注意,因為“insert”關鍵字后面有跟隨資料,所以為“insert”使用了strncmp()庫函式來比對輸入值,(例如輸入陳述句為:insert 1 cstack [email protected])
譯注:C 庫函式 int strncmp(const char *str1, const char *str2, size_t n) 是把輸入引數 str1 和 str2 進行比較,最多比較入參的前 n 個位元組,
最后,execute_statement()函式中包含了一些樁(stubs):
譯注:stubs(一小塊代碼),是為了實作測驗代碼進行,會硬編碼一些輸入和輸出,即在execute_statement()函式中對prepare_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;
}
}
注意這里沒有回傳任何錯誤碼,這是因為在這里還不會有任何報錯發生,
譯注:目前為止,程式可決議“.exit”、“insert xxx”、"select xxx"命令,其余不會識別,只輸出“Unrecognized command 'xxx'”,所以不會有什么報錯輸出,參考下面的演示,
做了這些重構后,我們的程式就能識別兩個新的關鍵字了,
~ ./db
db > insert foo bar
This is where we would do an insert.
Executed.
db > delete foo
Unrecognized keyword at start of 'delete foo'.
db > select
This is where we would do a select.
Executed.
db > .tables
Unrecognized command '.tables'
db > .exit
~
我們的資料庫骨架正在形成...如果它能存盤資料不是很好嗎?在下一部分,我們會實作insert和select,創建世界上最差勁的資料存盤,
同時,下面是這部分重構的整個代碼不同之處:
@@ -10,6 +10,23 @@ struct InputBuffer_t {
} InputBuffer;
+typedef enum {
+ META_COMMAND_SUCCESS,
+ META_COMMAND_UNRECOGNIZED_COMMAND
+} MetaCommandResult;
+
+typedef enum { PREPARE_SUCCESS, PREPARE_UNRECOGNIZED_STATEMENT } PrepareResult;
+
+typedef enum { STATEMENT_INSERT, STATEMENT_SELECT } StatementType;
+
+typedef struct {
+ StatementType type;
+} Statement;
+
InputBuffer* new_input_buffer() {
InputBuffer* input_buffer = malloc(sizeof(InputBuffer));
input_buffer->buffer = NULL;
@@ -40,17 +57,67 @@ void close_input_buffer(InputBuffer* input_buffer) {
free(input_buffer);
}
+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;
+ }
+ if (strcmp(input_buffer->buffer, "select") == 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;
+ }
+}
+
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);
+ 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;
+ }
}
+
+ 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");
}
}
Enjoy GreatSQL ??
關于 GreatSQL
GreatSQL是由萬里資料庫維護的MySQL分支,專注于提升MGR可靠性及性能,支持InnoDB并行查詢特性,是適用于金融級應用的MySQL分支版本,
相關鏈接: GreatSQL社區 Gitee GitHub Bilibili
GreatSQL社區:
歡迎來GreatSQL社區發帖提問
https://greatsql.cn/

技術交流群:
微信:掃碼添加
GreatSQL社區助手微信好友,發送驗證資訊加群,

轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/509443.html
標籤:其他
上一篇:第一章-緒論
下一篇:第一章-緒論
