實現一個簡單的Database2(譯文)

来源:https://www.cnblogs.com/greatsql/archive/2022/09/24/16725217.html
-Advertisement-
Play Games

前文回顧:實現一個簡單的Database1(譯文) 譯註:cstsck在github維護了一個簡單的、類似sqlite的資料庫實現,通過這個簡單的項目,可以很好的理解資料庫是如何運行的。本文是第二篇,主要是實現資料庫的前端組件,編譯器與虛擬機部分功能 Part 2 世界上最簡單的SQL編譯器與虛擬機 ...


前文回顧:實現一個簡單的Database1(譯文)

譯註:cstsck在github維護了一個簡單的、類似sqlite的資料庫實現,通過這個簡單的項目,可以很好的理解資料庫是如何運行的。本文是第二篇,主要是實現資料庫的前端組件,編譯器與虛擬機部分功能

Part 2 世界上最簡單的SQL編譯器與虛擬機

我們正在實現一個sqlite的克隆版本。sqlite的前端是SQL編譯器,編譯器用來解析字元串並輸出一個內部的表示,叫做位元組碼。

這些位元組碼被傳到虛擬機(virtual machine),在虛擬機中,位元組碼將被執行。

1654132

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

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • .NET運行時之書(Book of the Runtime,簡稱BotR)是一系列描述.NET運行時的文檔,2007年左右在微軟內部創建,最初目的是為了幫助其新員工快速上手.NET運行時;隨著.NET開源,BotR也被公開了出來,如果想深入理解CLR,這系列文章不可錯過。 BotR系列目錄: [1] ...
  • 在上一篇文章UWP/WinUI3 Win2d PixelShaderEffect 實現ColorPlacementEffect (顏色替換) 濾鏡。 - 吃飯/睡覺 - 博客園 (cnblogs.com)中實現了 ”顏色替換濾鏡“,那麼本文將製作一個“漸變映射濾鏡”。 效果圖: 一.漸變映射流程 1 ...
  • 有時我們需要在圖像上添加水印。例如,在圖像上添加版權或名稱。我們可能還需要在文檔中創建水印。 在這篇博客和代碼示例中,我解釋瞭如何使用 C# 在圖像上編寫文本。此代碼可用於 Windows 或 Web 應用程式。 首先,將需要添加水印的圖片放在程式運行目錄,水印示例圖片具體如下 其次,在項目中添加“ ...
  • String在C#中其實是不可變的,每次操作字元串變數增加或減少時,都會重新分配記憶體。試想一下,如果創建一個迴圈10000次的字元串加減操作,每次迴圈都將一個字元連接到字元串,這樣記憶體中就會有10000個字元串,每個字元串僅僅與前一個字元串相伴只是有一個字元不同,性能影響是很大的。這個時候我們可以使 ...
  • 在Winform開發中中,我們為了方便客戶選擇,往往使用系統的字典數據選擇,畢竟選擇總比輸入來的快捷、統一,一般我們都會簡單封裝一下,以便方便對控制項的字典值進行展示處理,本篇隨筆介紹DevExpress控制項的幾種常見的字典綁定展示方式,希望我們在實際WInform項目中使用到。 ...
  • 在上一個文章中,傳送門,給大家介紹了怎麼在配置文件中使用 Kestrel 部署 Https,正好今天有小伙伴穩問到:可以通過代碼的方式實現 Kestrel 的 Https 的部署嗎?答案是肯定的,我們這次一樣去不是多個功能變數名稱。 在使用代碼實現中,我是主要使用到 ListenOptions.UseHtt ...
  • 出現的原因一般是伺服器的root用戶沒有開啟訪問許可權,一般來說值允許本地的訪問。 解決方法: 一:第一種方法 1、首先打開xshell連接伺服器的終端 2、以root許可權登錄 mysql -u root -p 如果不知道伺服器的root密碼的話就去寶塔面板那裡修改 3、選擇mysql mysql> ...
  • 1.1 資料庫系統概述: 1.1.1資料庫的4個基本概念 資料庫的四個基本概念 - 數據 - 資料庫 - 資料庫管理系統 - 資料庫系統 數據:數據是資料庫中存儲的基本對象 數據是描述事物的一個符號,可以描述數字、圖形、聲音、語言等待,但都要經過數字化後存入計算器 資料庫(簡稱DB):資料庫是長期存 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...