使用 ppx_rapper_lwt 的 SQLite CREATE、INSERT、SELECT

任務

資料庫 / SQLite / SQLite CREATE、INSERT、SELECT

使用的 Opam 套件

  • ppx_rapper_lwt 測試版本:3.1.0 — 使用的函式庫:ppx_rapper_lwt
  • ppx_rapper 測試版本:3.1.0 — 使用的函式庫:ppx_rapper
  • caqti-driver-sqlite3 測試版本:1.9.0 — 使用的函式庫:caqti-driver-sqlite3
  • caqti-lwt 測試版本:1.9.0 — 使用的函式庫:caqti-lwt
  • lwt 測試版本:5.7.0 — 使用的函式庫:lwt, lwt.unix

程式碼

Caqti/ppx_rapper 組合使用 Lwt 環境。 Lwt 的 let 運算子 ( let* )( let*? ) 的定義與平常一樣,以便簡潔地表示鏈式 promise。 ( let*? ) 從回傳的 Ok result 中提取結果,如果值為 Error err,則停止執行。

let ( let* ) = Lwt.bind
let ( let*? ) = Lwt_result.bind

輔助函式 iter_queries 循序排程查詢清單。每個查詢都是一個函式,它將資料庫的連線控制代碼作為引數。

let iter_queries queries connection =
  List.fold_left
    (fun a f ->
      Lwt_result.bind a (fun () -> f connection))
    (Lwt.return (Ok ()))
    queries

此處的 %rapper 節點使 ppx_rapper 產生程式碼,以便在套用 create_employees_table () connection 函式時,提供的 SQL CREATE 查詢將在沒有任何參數且沒有從資料庫接收任何資料的情況下執行。

如果查詢成功執行,我們會取回 Ok () 值,否則我們會取得 Error 值。

let create_employees_table =
  [%rapper
    execute {sql| CREATE TABLE employees
              (name VARCHAR,
              firstname VARCHAR,
              age INTEGER)
            |sql}
  ]

type employee =
  { name:string; firstname:string; age:int }
let employees = [
  {name = "Dupont"; firstname = "Jacques"; age = 36};
  {name = "Legendre"; firstname = "Patrick"; age = 42}
  ]

對於 SQL INSERT 查詢,ppx_rapper 會產生函式 insert_employee (p: employee) connection。標籤 record_in 會告訴 ppx_rapper 從提供的記錄值讀取 namefirstnameage 的值,而 %[TYPE_NAME]{[INPUT_FIELD_NAME]} 標記法指定對輸入值執行哪些轉換。

let insert_employee =
  [%rapper
    execute
    {sql| INSERT INTO employees VALUES
        (%string{name},
        %string{firstname},
        %int{age})
    |sql}
    record_in
  ]

get_many 標籤使 ppx_rapper 產生程式碼,該程式碼查詢資料庫並接收值清單。 record_out 標籤指定每個清單項目都將是一個記錄。

@[TYPE_NAME]{[COLUMN_NAME]} 標記法指定對輸出值執行哪些轉換。

let get_all_employees =
  [%rapper
    get_many
    {sql|SELECT
        @string{name},
        @string{firstname},
        @int{age}
      FROM employees
    |sql}
    record_out
  ]

這是另一個範例查詢,使用 get_opt 標籤透過 SQL WHERE 子句選取單列。此查詢同時具有輸入 (name) 和輸出值 (namefirstnameage)。

這裡缺少 record_in 標籤使 ppx_rapper 產生程式碼,其中輸入值作為具名引數傳遞。 get_opt 標籤表示結果將是一個選項:如果找不到符合條件的列,則為 None;如果符合條件的列,則為 Some r

let get_employee_by_name =
  [%rapper
    get_opt
    {sql|SELECT
        @string{name},
        @string{firstname},
        @int{age}
      FROM employees
      WHERE name=%string{name}
    |sql}
    record_out
  ]


ppx_rapper 產生的所有查詢函式都採用一個引數和一個 connection 參數。必須使用 employee 類型的值和 connection 呼叫函式 insert_employee。若要從清單插入多個記錄,我們使用 List.map 來建立函式清單。當呼叫時,這些函式中的每一個都會執行其相關聯的查詢。函式 iter_queries 會依序執行查詢。

請注意,如果您必須插入多個記錄,則執行批次插入查詢是合理的。

let execute_queries connection =
  let*? () = create_employees_table () connection in
  let*? () =
    iter_queries
      (List.map insert_employee employees)
      connection
  in
  let*? employees = get_all_employees () connection in
  employees |> List.iter (fun employees ->
    Printf.printf
      "name=%s, firstname=%s, age=%d\n"
      employees.name
      employees.firstname
      employees.age);
  let*? employees =
    get_employee_by_name ~name:"Dupont" connection
  in
  match employees with
  | Some employees' ->
    Printf.printf
      "found:name=%s, firstname=%s, age=%d\n"
      employees'.name
      employees'.firstname
      employees'.age;
    Lwt_result.return ()
  | None ->
    print_string "Not found";
    Lwt_result.return ()

主程式首先建立一個 Lwt 環境。函式 with_connection 會開啟資料庫,使用 connection 資料庫控制代碼執行函式,並再次關閉資料庫連線,即使發生例外狀況也是如此。

let () =
  match Lwt_main.run @@
  Caqti_lwt.with_connection
    (Uri.of_string "sqlite3:essai.sqlite")
    execute_queries
  with
  | Result.Ok () ->
    print_string "OK\n"
  | Result.Error err ->
    print_string (Caqti_error.show err)

討論

Caqti 函式庫允許使用 SQLite、MariaDB 和 PostgreSQL 進行可攜式程式設計。 ppx_rapper 將註解的 SQL 字串轉換為 Caqti 查詢。此預處理器使所有類型轉換透明化,並利用 OCaml 的強類型。它也會檢查給定查詢的 SQL 語法。請參閱 Caqti 參考頁面ppx_rapper 參考頁面

食譜無法運作?註解不夠清楚或已過時?

開啟一個 issue貢獻此食譜

此任務的其他食譜