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数据库接口

Download history 8/week @ 2024-05-20 7/week @ 2024-05-27 3/week @ 2024-06-03 56/week @ 2024-07-29

每月下载量56
用于 2 个Crates(通过 flagrant

MIT 许可证

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!返回的内容。
  • 无类型的 - 返回“原始”数据库结果,该结果被包装在特定数据库的类型中(PgRowSqliteRowMysqlRow
  • 映射的 - 结果通过映射函数转换,而不是使用提前提供的类型强制转换。这是SQLx通过调用query(..).map(|f| ...)所执行的。

这些查询(以下将提到一些例外)可能返回不同类型和数量的结果:一个结果、多个结果、可选结果或结果流。在所有情况下,结果可能是类型化的,也可能是只是一个数据库行。一个例外是低级“执行”查询,它始终是无类型的,并返回低级数据库特定结果(PgQueryResultSqliteQueryResultMySqlQueryResult)。

查询定义

查询通过简单的结构来描述

-- :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