diesel 中直接拼接 sql 无法通过编译,因类型安全机制严格依赖 schema.rs、dsl 和 queryablebyname 衍生三者协同;sql_query 绕过校验,仅适用于 ddl 等特殊场景,日常 crud 应优先使用 filter/eq 等 dsl。

直接用字符串拼接 SQL 在 Rust 里不是“不推荐”,而是根本过不了编译——Diesel 的类型安全机制会拦在第一步。
为什么 diesel::sql_query 不该是你的默认选择
很多人看到“要写 SQL”就直奔 diesel::sql_query,结果发现返回值是 SqlQuery,没法直接 load<user>()</user>。这不是 bug,是设计:它绕过了 Diesel 的类型校验层,只做参数绑定,不校验列名、类型或数量。
- 你得手动实现
QueryableByName<pg></pg>才能解包结果,稍有不匹配就编译失败 -
sql_query("SELECT id, name FROM users")中若表结构已变(比如name改成full_name),编译器不会报错,运行时才 panic - 它适合执行 DDL、存储过程或极复杂 JOIN,但日常 CRUD 应优先走 DSL
filter 和 eq 组合为何比 sql_query 更安全
DSL 查询不是“语法糖”,而是编译器可推导的类型流。比如 users.filter(users::name.eq("Alice")):
-
users::name是从schema.rs生成的字段常量,类型为Text;"Alice"是&str,eq方法只接受兼容类型,传i32直接编译不过 - 如果数据库里
users表根本没有name字段,schema.rs就不会生成users::name,代码根本无法 import -
filter返回的是users::BoxedSelectStatement,最终load<user>()</user>时,编译器会比对 SELECT 列与User结构体字段——少一个、多一个、类型错一个,全报错
如何让自定义 SQL 也获得类型安全
真需要手写 SQL?用 sql_query + as_expressions + 自定义 QueryableByName 是唯一正路:
- 先定义结果结构体并 derive
QueryableByName:#[derive(QueryableByName)] #[diesel(field_names = "id, full_name")] struct UserSummary { #[diesel(sql_type = diesel::sql_types::Integer)] id: i32, #[diesel(sql_type = diesel::sql_types::Text)] full_name: String, } - 然后调用:
sql_query("SELECT id, name AS full_name FROM users") .get_results::<usersummary>(&mut conn)?</usersummary> - 注意:别漏掉
AS full_name—— 字段别名必须和field_names完全一致,否则编译失败 -
sql_type注解不能省,否则编译器无法推导底层类型,报the trait bound ... is not satisfied
容易被忽略的陷阱:迁移没跑,schema.rs 就失效
Diesel 的所有类型安全都依赖 schema.rs 与真实数据库 schema 严格一致。但很多人改了表结构后只改代码,忘了跑迁移:
-
diesel migration generate add_full_name新增迁移文件 -
diesel migration run执行变更 -
diesel print-schema > src/schema.rs重新生成 —— 这步漏了,users::full_name就不存在,所有引用立即编译失败 - CI 中建议把
print-schema加进cargo test前置检查,避免本地环境和 CI schema 不一致
类型安全不是自动生效的魔法,它靠的是 schema.rs、DSL 调用链、结构体 derive 三者咬合。任何一环脱节,编译器就会停机——这恰恰是它最可靠的地方。










