使用 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 环境。让运算符 ( let* )
和 ( let*? )
像往常一样为 Lwt 定义,以获得简洁的链式 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
从提供的记录值中读取 name
、firstname
和 age
值,而 %[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
)也有输出值(name
、firstname
、age
)。
这里缺少 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
参数。函数 insert_employee
必须使用类型为 employee
和 connection
的值调用。要从列表中插入多个记录,我们使用 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
参考页面。