8个版本
0.3.0 | 2024年1月10日 |
---|---|
0.2.1 | 2024年1月1日 |
0.2.0 | 2023年12月9日 |
0.1.5 | 2023年10月28日 |
0.1.2 | 2022年10月30日 |
#450 在 数据库接口
每月下载量56
用于 2 个Crates(通过 flagrant)
34KB
657 行
Hug SQLx - 拥抱SQL
HugSQLx是一个derive宏,将SQL查询转换为纯Rust函数。这是将查询与源代码解耦、拥抱IDE对SQL的正确格式化和语法高亮的能力,并基于底层LSP - 免费获得自动完成和文档字符串的尝试。
安装
HugSQLx建立在SQLx之上
[dependencies]
sqlx = { version = "0.6.2", features = ["sqlite"] }
hugsqlx = { version = "0.3.0", features = ["sqlite"] }
HugSQLx和SQLx本身应具有与features中提到的相同的数据库名称(sqlite、postgres或mysql)。
深入探讨命名查询
这里的想法是区分三种类型的查询
- 类型化的 - 返回具体类型结果的查询,如
<User>
。这是SQLx通过query_as!
返回的内容。 - 无类型的 - 返回“原始”数据库结果,该结果被包装在特定数据库的类型中(
PgRow
、SqliteRow
或MysqlRow
) - 映射的 - 结果通过映射函数转换,而不是使用提前提供的类型强制转换。这是SQLx通过调用
query(..).map(|f| ...)
所执行的。
这些查询(以下将提到一些例外)可能返回不同类型和数量的结果:一个结果、多个结果、可选结果或结果流。在所有情况下,结果可能是类型化的,也可能是只是一个数据库行。一个例外是低级“执行”查询,它始终是无类型的,并返回低级数据库特定结果(PgQueryResult
、SqliteQueryResult
或MySqlQueryResult
)。
查询定义
查询通过简单的结构来描述
-- :name fetch_users
-- :doc Returns all the users from DB
SELECT user_id, email, name, picture FROM users
这里的关键部分是两行注释:一行包含 :name
标识符,另一行包含 :doc
文档字符串。注意,名称必须是有效的标识符 - 它最终用于生成函数名。请明智地使用它,如果您不希望被panic吓到,则不要使用空白字符、连字符或任何其他奇怪字符:
:doc
另一方面提供了更多的自由度。在这里放置您通常添加为函数文档字符串的任何内容。如果您需要多行文档字符串,请按照以下方式操作:
-- :name set_picture
-- :doc Sets user's picture.
-- Picture is expected to be a valid URL.
UPDATE users
-- expected URL to the picture
SET picture = ?
WHERE user_id = ?
此示例还显示,在查询内部使用SQL注释是完全有效的,只要注释行不以 -- :name
或 -- :doc
开始,显然。
查询类型定义
按照类型/无类型/映射分类,以下是向查询定义添加类型提示的方法
-- :name untyped_query
-- :name untyped_query :untyped
-- :name typed_query :typed
-- :name mapped_query :mapped
查询默认是 无类型的。不需要告诉HugSqlx生成它们(尽管您仍然可以使用 :untyped
提示)。然而,其他类型需要一个明确的提示 - 要为有类型的查询使用 :typed
提示(别名为 :<>
)或有映射查询的 :mapped
提示(别名为 :||
)。
查询结果
同样需要提示,以便代码生成器知道我们期望的结果类型
-- :name execute
-- :name one_result :1
-- :name optional_result :?
-- :name many_results :*
-- :name stream_of_results :^
与查询类型类似,执行查询是默认类型。这里不需要提示。其他类型的结果则需要提示 - 当查询预期返回精确的一个结果时使用 :1
,当预期结果是可选的时使用 :?
,当预期结果为多个时使用 :*
(向量),当预期结果为流时使用 :^
。
查询和结果类型可以混合使用
-- :name delete_user
-- :name fetch_user :<> :?
-- :name fetch_users :<> :*
-- :name fetch_profile :mapped :1
回到代码
当使用Hugsqlx时,您首先需要决定代码应该为哪种数据库(postgres、sqlite或mysql)生成
hugsqlx = {version = "0.3.0", features = ["sqlite"]}
添加依赖项后,是时候添加一个结构体了
use hugsqlx::{params, HugSqlx};
#[derive(HugSqlx)]
#[queries = "resources/db/queries/users.sql"]
struct Users {}
queries
属性必须是 CARGO_MANIFEST_DIR
(crate的Cargo.toml目录)或工作区相对路径,可以指向单个文件(仅从此文件中提取查询定义)或目录。后者强制宏遍历路径,并在找到的文件上生成相应的函数。
示例
假设以下查询位于 "resources/db/queries/users.sql" 中
-- :name fetch_users :mapped :*
-- :doc Returns all the users from DB
SELECT user_id, email, name, picture FROM users WHERE role=?
HugSqlx生成一个名为 fetch_users
的特例函数,其形状可能因提供的查询提示而异。然而,不管提示如何,所有生成的查询至少需要两个参数 - 一个 Executor
(Pool、PoolConnection或Connection)和查询参数。如预期的那样,映射查询还需要一个额外的参数 - 一个将数据库行转换为具体类型数据的映射函数。让我们调用上面查询生成的函数:
let users = Users::fetch_users(&pool, params!("guest"), |row| { ... }).await?;
由于Rust机制禁止创建不同类型元素的向量,因此需要使用params!
宏传递参数。
技巧与窍门(使用Emacs)
如何通过:name
和:doc
来获得更好的注释语法高亮?
(font-lock-add-keywords
'sql-mode `(("\".+?\"" 0 'font-lock-string-face t)
(":[a-zA-Z0-9+-><?!\\*\\|]?+" 0 'font-lock-constant-face t)
(":name \\([[:graph:]]+\\)" 1 'font-lock-function-name-face t)))
如何让ctags与命名查询一起工作?
--kinddef-sql=q,query,Queries
--regex-sql=/\-\-[ \t]+(:name[\ \t]+)([[:alnum:]_-]+)/\2/q/
限制
查询定义同时使用:name
和:doc
时,期望:name
注释首先出现。HugSqlx不会抱怨,但结果可能令人惊讶。
不会递归遍历子文件夹以读取查询定义。
此外,由于SQLx的限制,尚未实现命名参数。
依赖项
~2–9MB
~86K SLoC