35 个版本
新增 0.18.1 | 2024年8月4日 |
---|---|
0.18.0 | 2024年1月1日 |
0.17.5 | 2023年11月19日 |
0.17.1 | 2023年7月30日 |
0.7.0 | 2022年12月29日 |
#1780 in 数据库接口
114 每月下载量
在 4 个包中使用 (3 直接使用)
41KB
1K SLoC
ormlite
ormlite
是一个专为喜爱 SQL 的开发者设计的 Rust ORM。 让我们看看它是如何工作的。
use ormlite::model::*;
use ormlite::sqlite::SqliteConnection;
#[derive(Model, Debug)]
pub struct Person {
pub id: i32,
pub name: String,
pub age: i32,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
/// Start by making a database connection.
let mut conn = SqliteConnection::connect(":memory:").await.unwrap();
/// You can insert the model directly.
let mut john = Person {
id: 1,
name: "John".to_string(),
age: 99,
}.insert(&mut conn).await?;
println!("{:?}", john);
/// After modifying the object, you can update all its fields.
john.age += 1;
john.update_all_fields(&mut conn).await?;
/// Query builder syntax closely follows SQL syntax, translated into chained function calls.
let people = Person::select()
.where_("age > ?").bind(50)
.fetch_all(&mut conn).await?;
println!("{:?}", people);
}
你可能喜欢 ormlite
因为
- 它可以从 Rust 结构体自动生成迁移。据我所知,这是唯一一个具有这种功能的 Rust ORM。
- 联合 API(处于 alpha 版本)的组件比其他任何 Rust ORM 都少。它仅依赖于表
struct
本身,而不依赖于关系特性(SeaORM)或模块(Diesel)。 - 几乎没有查询构建器语法需要学习。查询构建器基本上是将原始 SQL 的 &str 片段连接起来。它找到了组合性和接近零学习曲线之间的平衡,对于那些已经熟悉 SQL 的人来说。
快速入门
安装
使用 cargo
安装
# For postgres
cargo add ormlite --features postgres
# For sqlite
cargo add ormlite --features sqlite
或更新你的 Cargo.toml
[dependencies]
# For postgres
ormlite = { version = "..", features = ["postgres"] }
# For sqlite
ormlite = { version = "..", features = ["sqlite"] }
支持其他数据库和运行时,但测试较少。如果您遇到任何问题,请提交问题。
环境设置
您需要在环境中设置 DATABASE_URL
。我们推荐使用类似 just
的工具,它可以拉入 .env
文件,但为了简单起见,这里我们将直接使用 shell。
export DATABASE_URL=postgres://postgres:postgres@localhost:5432/postgres
迁移
如果您正在查询静态数据库且不需要迁移,请跳过本节。如果您需要迁移,请继续阅读。
首先,安装 ormlite-cli
。目前,CLI 仅支持 Postgres。虽然 ormlite-cli
与 sqlx-cli
是分开的,但它们 100% 兼容。 sqlx-cli
不支持自动生成迁移或快照(在开发中回滚而不编写迁移),但它不如最前沿,支持更多数据库类型。
cargo install ormlite-cli
接下来,创建数据库和迁移表。 init
创建一个 _sqlx_migrations
表,用于跟踪您的迁移。
# Create the database if it doesn't exist. For postgres, that's:
# createdb <dbname>
ormlite init
让我们看看迁移是如何工作的。创建一个带有 #[derive(Model)]
的 Rust 结构体,CLI 工具可以检测到它来自动生成迁移
# src/models.rs
use ormlite::model::*;
#[derive(Model, Debug)]
pub struct Person {
pub id: i32,
pub name: String,
pub age: i32,
}
接下来,自动生成迁移。
ormlite migrate initial
这将在 migrations/
中创建一个普通的 SQL 文件。在我们执行它之前先来看看它
cat migrations/*.sql
当你对它满意后,就可以执行它了
ormlite up
默认情况下,up
还会创建一个快照,所以如果需要的话,你可以使用 ormlite down
进行回滚。你也可以选择生成成对的 up/down 迁移而不是只生成 up 迁移。
设置到此结束。现在让我们看看如何运行查询。
插入 & 更新
README 顶部的插入和更新语法对具有 UUID 主键的表最有效。
use ormlite::model::*;
use uuid::Uuid;
#[derive(Model, Debug)]
pub struct Event {
pub id: Uuid,
pub name: String,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut conn = ormlite::sqlite::SqliteConnection::connect(":memory:").await.unwrap();
let mut event = Event {
id: Uuid::new_v4(),
name: "user_clicked".to_string(),
}.insert(&mut conn).await?;
println!("{:?}", event);
}
这个语法有两个可能的问题。首先,id
不是 Option
,所以它必须被设置,这会导致自增 id 字段出现问题。其次,结构体无法跟踪哪些字段被修改了,所以更新方法必须更新所有列。
插入结构体
为了解决自增问题,你可以使用这里显示的插入结构体或下面的构建器。
use ormlite::types::Json;
use serde_json::Value;
#[derive(Model, Debug)]
#[ormlite(insertable = InsertPerson)]
pub struct Person {
pub id: i32,
// Because the other fields are the primary key, and marked as default and default_value respectively,
// `name` is the only field in the InsertPerson struct.
pub name: String,
// This field will not be part of the InsertPerson struct,
// and rows will take the database-level default upon insertion.
#[ormlite(default)]
pub archived_at: Option<DateTime<Utc>>,
// This field will not be part of the InsertPerson struct,
// which will always pass the provided value when inserting.
#[ormlite(default_value = "serde_json::json!({})")]
pub metadata: Json<Value>,
}
async fn insertion_struct_example(conn: &mut SqliteConnection) {
let john: Person = InsertPerson {
name: "John".to_string(),
}.insert(&mut conn).await?;
println!("{:?}", john);
}
如果你需要的派生结构体不满足你的需求,你可以手动定义一个只包含你想要字段的结构体,指定 table = "<table>"
以将结构体路由到同一个数据库表。
#[derive(Model, Debug)]
#[ormlite(table = "person")]
pub struct InsertPerson {
pub name: String,
pub age: i32,
}
插入构建器 & 更新构建器
你还可以使用构建器语法进行插入或仅更新某些字段。
#[derive(Model, Debug)]
pub struct Person {
pub id: i32,
pub name: String,
pub age: i32,
}
async fn builder_syntax_example() {
// builder syntax for insert
let john = Person::builder()
.name("John".to_string())
.age(99)
.insert(&mut conn).await?;
println!("{:?}", john);
// builder syntax for update
let john = john.update_partial()
.age(100)
.update(&mut conn).await?;
println!("{:?}", john);
}
Upsert
你可以使用 OnConflict
(文档)来处理冲突时的插入。
use ormlite::{
model::*,
query_builder::OnConflict,
};
#[derive(Debug, Model)]
pub struct Users {
#[ormlite(primary_key)]
pub id: i32,
pub name: String,
pub email: String,
}
async fn upsert_example(conn: &mut PgConnection) {
Users {
id: 1,
name: String::from("New name"),
email: String::from("New email"),
}
.insert(&mut conn)
// update values of all columns on primary key conflict
.on_conflict(OnConflict::do_update_on_pkey("id"))
.await
.unwrap();
}
选择查询
你可以使用 Model::select
来使用 Rust 逻辑构建 SQL 查询。
注意:Postgres 使用带编号的美元符号占位符构建查询的方法很快就会崩溃。相反,即使在使用 Postgres 时,也请使用
?
作为参数,ormlite 将在构建最终查询时将?
占位符替换为$
占位符。
#[derive(Model, Debug)]
pub struct Person {
pub id: i32,
pub name: String,
pub age: i32,
}
async fn query_builder_example() {
let people = Person::select()
.where_("age > ?")
.bind(50i32)
.fetch_all(&mut conn)
.await?;
println!("All people over 50: {:?}", people);
}
原始查询
如果你不希望 ORM 方法为你工作,你可以回退到原始查询。你可以包含手写的字符串,或者如果你需要一个更底层的查询构建器,你可以使用 sqlmo
,它是 ormlite
查询构建器和迁移自动生成器背后的底层引擎。
async fn model_query_example() {
// Query using the Model to still deserialize results into the struct
let _person = Person::query("SELECT * FROM person WHERE id = ?")
.bind(1)
.fetch_one(&mut conn)
.await?;
}
async fn raw_query_example() {
// You can also use the raw query API, which will return tuples to decode as you like
let _used_ids: Vec<i32> = ormlite::query_as("SELECT id FROM person")
.fetch_all(pool)
.await
.unwrap()
.into_iter()
.map(|row: (i32, )| row.0)
.collect();
}
表定制
属性定义在这些结构体中。
以下示例展示了它们的使用情况
#[derive(Model, Debug)]
#[ormlite(table = "people", insertable = InsertPerson)]
pub struct Person {
#[ormlite(primary_key)]
pub id: i32,
pub name: String,
#[ormlite(column = "name_of_column_in_db")]
pub age: i32,
}
连接
连接支持目前处于 alpha 阶段。目前,ormlite 仅支持多对一关系(例如,Person 属于 Organization)。计划支持多对多和一对多。如果你使用此功能,请报告你遇到的任何错误。
#[derive(Model, Debug)]
pub struct Person {
pub id: Uuid,
pub name: String,
pub age: i32,
// Note that we don't declare a separate field `pub organization_id: Uuid`.
// It is implicitly defined by the Join and the join_column attribute.
#[ormlite(join_column = "organization_id")]
pub organization: Join<Organization>,
}
#[derive(Model, Debug)]
pub struct Organization {
pub id: Uuid,
pub name: String,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Note we don't need to insert it.
let org = Organization {
id: Uuid::new_v4(),
name: "Acme".to_string(),
};
let user = Person {
id: Uuid::new_v4(),
name: "John".to_string(),
age: 99,
organization: Join::new(org),
};
let mut conn = ormlite::sqlite::SqliteConnection::connect(":memory:").await.unwrap();
let user = user.insert(&mut conn).await?;
assert_eq!(user.organization.loaded(), true);
println!("{:?}", user);
// You can choose whether you want to load the relation or not. The value will be Join::NotQueried if you don't
// opt-in to loading it.
let users = Person::select()
.join(Person::organization())
.fetch_all(&mut conn)
.await?;
for user in users {
assert!(user.organization.loaded());
println!("{:?}", user);
}
}
功能 & 数据类型
Uuid、Chrono & 时间
如果你需要 Uuid 或 DateTime,结合 serde,你需要直接依赖于 uuid
、time
或 chrono
,并为每个它们添加 serde
功能。
# Cargo.toml
[dependencies]
uuid = { version = "...", features = ["serde"] }
chrono = { version = "...", features = ["serde"] }
time = { version = "...", features = ["serde"] }
use ormlite::model::*;
use serde::{Serialize, Deserialize};
use ormlite::types::Uuid;
use ormlite::types::chrono::{DateTime, Utc};
#[derive(Model, Debug, Serialize, Deserialize)]
pub struct Person {
pub uuid: Uuid,
pub created_at: DateTime<Utc>,
pub name: String,
}
Json/Jsonb 列
您可以使用 ormlite::types::Json
来处理 JSON 或 JSONB 字段。对于非结构化数据,使用 serde_json::Value
作为内部类型。使用具有 Deserialize + Serialize
的结构体作为结构化数据的泛型。
use ormlite::model::*;
use ormlite::types::Json;
use serde_json::Value;
#[derive(Debug, Serialize, Deserialize)]
pub struct JobData {
pub name: String,
}
#[derive(Model, Serialize, Deserialize)]
pub struct Job {
pub id: i32,
pub structured_data: Json<JobData>,
pub unstructured_data: Json<Value>,
}
日志记录
您可以使用 sqlx 的日志记录器来记录查询:RUST_LOG=sqlx=info
路线图
- 在模型实例上直接插入、更新、删除
- 用于部分更新和插入的构建器
- 用户可以创建忽略默认值的插入模型
- 选择查询构建器
- 构建 derive 宏
- Get() 函数用于获取单个实体。
- 可以指定表名和主键列名
- 自动生成插入模型
- 自动生成迁移
- 消除 FromRow 宏的需求
- 多对一连接
- 自动为迁移生成索引
- 多对多连接
- 一对多连接
- 确保功能正确连接以支持 mysql 和不同的运行时以及 SSL 库。
- 宏选项自动调整列,例如 updated_at
- Upsert 功能
- 批量插入
- 用于批量更新的查询构建器
- 处理批量更新中的冲突子句
- 与原始 SQL、sqlx、ormx、seaorm、sqlite3-sys、pg、diesel 进行基准测试
- 宏选项使用 deleted_at 而不是
DELETE
删除 - 支持补丁记录,即使用静态字段进行更新。
- 考虑一个阻塞接口,可能仅适用于 sqlite/Rusqlite。
贡献
开源依赖于贡献,并且 ormlite
是一个社区项目。我们欢迎您提交错误报告、功能请求、更好的文档请求、拉取请求等!
依赖项
~5–14MB
~163K SLoC