插入

大多数应用程序属于称为“CRUD”应用程序的类别。 CRUD代表“创建,读取,更新,删除”。 Diesel为所有四个部分提供支持,但在本指南中,我们将介绍创建INSERT语句的不同方法。

本指南的示例将针对PostgreSQL显示,但您可以跟随任何后端。 所有后端的完整代码示例链接在本指南的底部。

insert语句始终以insert_into开头。 此函数的第一个参数是您要插入的表。

对于本指南,我们的架构将如下所示:

src/lib.rs

  1. table! {
  2. users {
  3. id -> Integer,
  4. name -> Text,
  5. hair_color -> Nullable<Text>,
  6. created_at -> Timestamp,
  7. updated_at -> Timestamp,
  8. }
  9. }

由于我们的函数只能在users表上运行,我们可以使用schema :: users :: dsl :: *;在我们函数的顶部,这将让我们写insert_into(users)而不是insert_into(users :: table)。 如果要导入table :: dsl :: *,请确保它始终位于函数内部,而不是模块的顶部。

如果表上的所有列都有默认值,我们可以做的最简单的事情就是调用.default_values。 我们可以编写一个运行该查询的函数,如下所示:

src/lib.rs

  1. use schema::users::dsl::*;
  2. insert_into(users).default_values().execute(conn)

值得注意的是,即使您的所有列上都没有默认值,此代码仍会编译。 Diesel将确保您分配的值具有正确的类型,但它无法验证列是否具有默认值,任何可能失败的约束或任何可能触发的触发器。

我们可以使用debug_query来检查生成的SQL。 生成的确切SQL可能会有所不同,具体取决于您使用的后端。 如果我们运行println!(“{}”,debug_query :: <Pg,_>(&our_query));,我们将看到以下内容:

src/lib.rs

  1. INSERT INTO "users" DEFAULT VALUES -- binds: []

如果我们想要实际提供值,我们可以调用.values。 我们可以在这里提供很多不同的论点。 最简单的是使用.eq的单列/值对。

src/lib.rs

  1. use schema::users::dsl::*;
  2. insert_into(users).values(name.eq("Sean")).execute(conn)

这将生成以下SQL:

src/lib.rs

  1. INSERT INTO "users" ("name") VALUES ($1)
  2. -- binds ["Sean"]

如果我们想为多个列提供值,我们可以传递一个元组。

src/lib.rs

  1. insert_into(users)
  2. .values((name.eq("Tess"), hair_color.eq("Brown")))
  3. .execute(conn)

这将生成以下SQL:

  1. INSERT INTO "users" ("name", "hair_color")
  2. VALUES ($1, $2) -- binds: ["Tess", "Brown"]

插入

如果您只想在数据库中添加一些值,则使用元组是执行插入的典型方法。 但是,如果您的数据来自其他来源,例如Serde反序列化的网络表单,该怎么办? 必须写(name.eq(user.name),hair_color.eq(user.hair_color))会很烦人。

Diesel为此案例提供了可Insertable trait。 Insertable将结构映射到数据库中的列。 我们可以通过在我们的类型中添加#[derive(Insertable)]来自动推导出这个。

src/lib.rs

  1. use schema::users;
  2. #[derive(Deserialize, Insertable)]
  3. #[table_name = "users"]
  4. pub struct UserForm<'a> {
  5. name: &'a str,
  6. hair_color: Option<&'a str>,
  7. }
  1. use schema::users::dsl::*;
  2. let json = r#"{ "name": "Sean", "hair_color": "Black" }"#;
  3. let user_form = serde_json::from_str::<UserForm>(json)?;
  4. insert_into(users).values(&user_form).execute(conn)?;
  5. Ok(())

这将生成与我们使用元组相同的SQL。

src/lib.rs

  1. INSERT INTO "users" ("name", "hair_color")
  2. VALUES ($1, $2) -- binds: ["Sean", "Black"]

如果其中一个字段为None,则将为该字段插入默认值。

src/lib.rs

  1. use schema::users::dsl::*;
  2. let json = r#"{ "name": "Ruby", "hair_color": null }"#;
  3. let user_form = serde_json::from_str::<UserForm>(json)?;
  4. insert_into(users).values(&user_form).execute(conn)?;
  5. Ok(())

这将生成以下SQL:

src/lib.rs

  1. INSERT INTO "users" ("name", "hair_color")
  2. VALUES ($1, DEFAULT) -- binds: ["Ruby"]

批量插入

如果我们想一次插入多行,我们可以通过传递上面使用的任何表单的&Vec或切片来实现。 请记住,您总是在这里传递引用。 从Diesel 1.0开始,如果您尝试传递Vec而不是&Vec,Rust将生成一个关于溢出的非常不透明的错误消息。

在支持DEFAULT关键字的后端(除SQLite之外的所有后端)上,数据将插入到单个查询中。 在SQLite上,每行将执行一个查询。

例如,如果我们想要插入具有单个值的两行,我们可以使用Vec

src/lib.rs

  1. use schema::users::dsl::*;
  2. insert_into(users)
  3. .values(&vec![name.eq("Sean"), name.eq("Tess")])
  4. .execute(conn)

生成以下SQL:

  1. INSERT INTO "users" ("name") VALUES ($1), ($2)
  2. -- binds ["Sean", "Tess"]

请注意,在SQLite上,您将无法使用debug_query,因为它不会映射到单个查询。 您可以像这样检查每一行:

src/lib.rs

  1. for row in &values {
  2. let query = insert_into(users).values(row);
  3. println!("{}", debug_query::<Sqlite, _>(&query));
  4. }

如果我们想对某些行使用DEFAULT,我们可以在这里使用一个选项。

src/lib.rs

  1. use schema::users::dsl::*;
  2. insert_into(users)
  3. .values(&vec![Some(name.eq("Sean")), None])
  4. .execute(conn)

请注意,此处的类型是Option <Eq <Column,Value >>not Eq <Column,Option <Value >>。 执行column.eq(None)会插入NULL而不是DEFAULT。 这会生成以下SQL:

src/lib.rs

  1. INSERT INTO "users" ("name") VALUES ($1), (DEFAULT)
  2. -- binds ["Sean"]

我们可以用元组做同样的事情。

src/lib.rs

  1. use schema::users::dsl::*;
  2. insert_into(users)
  3. .values(&vec![
  4. (name.eq("Sean"), hair_color.eq("Black")),
  5. (name.eq("Tess"), hair_color.eq("Brown")),
  6. ])
  7. .execute(conn)

生成以下SQL:

src/lib.rs

  1. INSERT INTO "users" ("name", "hair_color")
  2. VALUES ($1, $2), ($3, $4)
  3. -- binds: ["Sean", "Black", "Tess", "Brown"]

再次,我们可以使用Option为任何字段插入DEFAULT

src/lib.rs

  1. use schema::users::dsl::*;
  2. insert_into(users)
  3. .values(&vec![
  4. (name.eq("Sean"), Some(hair_color.eq("Black"))),
  5. (name.eq("Ruby"), None),
  6. ])
  7. .execute(conn)

生成以下SQL:

  1. INSERT INTO "users" ("name", "hair_color")
  2. VALUES ($1, $2), ($3, DEFAULT)
  3. -- binds: ["Sean", "Black", "Ruby"]

最后,Insertable结构也可用于批量插入。

src/lib.rs

  1. use schema::users::dsl::*;
  2. let json = r#"[
  3. { "name": "Sean", "hair_color": "Black" },
  4. { "name": "Tess", "hair_color": "Brown" }
  5. ]"#;
  6. let user_form = serde_json::from_str::<Vec<UserForm>>(json)?;
  7. insert_into(users).values(&user_form).execute(conn)?;
  8. Ok(())

这会生成与我们使用元组相同的SQL:

src/lib.rs

  1. INSERT INTO "users" ("name", "hair_color")
  2. VALUES ($1, $2), ($3, $4)
  3. -- binds: ["Sean", "Black", "Tess", "Brown"]

The RETURNINGClause

在支持RETURNING子句的后端(例如PostgreSQL)上,我们也可以从插入中获取数据。 MySQL和SQLite不支持RETURNING子句。 要获取所有插入的行,我们可以调用.get_results而不是.execute

src/lib.rs

  1. #[derive(Queryable, PartialEq, Debug)]
  2. struct User {
  3. id: i32,
  4. name: String,
  5. hair_color: Option<String>,
  6. created_at: SystemTime,
  7. updated_at: SystemTime,
  8. }

我们可以在这个测试中使用get_results

src/lib.rs

  1. use diesel::select;
  2. use schema::users::dsl::*;
  3. let now = select(diesel::dsl::now).get_result::<SystemTime>(&conn)?;
  4. let inserted_users = insert_into(users)
  5. .values(&vec![
  6. (id.eq(1), name.eq("Sean")),
  7. (id.eq(2), name.eq("Tess")),
  8. ])
  9. .get_results(&conn)?;
  10. let expected_users = vec![
  11. User {
  12. id: 1,
  13. name: "Sean".into(),
  14. hair_color: None,
  15. created_at: now,
  16. updated_at: now,
  17. },
  18. User {
  19. id: 2,
  20. name: "Tess".into(),
  21. hair_color: None,
  22. created_at: now,
  23. updated_at: now,
  24. },
  25. ];
  26. assert_eq!(expected_users, inserted_users);

要检查.get_results.get_result生成的SQL,我们需要在将其传递给debug_query之前调用.as_query。 上次测试中的查询生成以下SQL:

src/lib.rs

  1. INSERT INTO "users" ("id", "name")
  2. VALUES ($1, $2), ($3, $4)
  3. RETURNING "users"."id", "users"."name", "users"."hair_color",
  4. "users"."created_at", "users"."updated_at"
  5. -- binds: [1, "Sean", 2, "Tess"]

您会注意到我们在任何示例中都没有为created_atupdated_at提供明确的值。 使用Diesel时,通常不会在Rust中设置这些值。 通常,这些列使用DEFAULT CURRENT_TIMESTAMP进行设置,并使用触发器更新更新时的updated_at。 如果您正在使用PostgreSQL,则可以通过运行SELECT diesel_manage_updated_at('users')来使用内置触发器; 在迁移中。

如果我们期望一行而不是多行,我们可以调用.get_result而不是.get_results

src/lib.rs

  1. use diesel::select;
  2. use schema::users::dsl::*;
  3. let now = select(diesel::dsl::now).get_result::<SystemTime>(&conn)?;
  4. let inserted_user = insert_into(users)
  5. .values((id.eq(3), name.eq("Ruby")))
  6. .get_result(&conn)?;
  7. let expected_user = User {
  8. id: 3,
  9. name: "Ruby".into(),
  10. hair_color: None,
  11. created_at: now,
  12. updated_at: now,
  13. };
  14. assert_eq!(expected_user, inserted_user);

这会生成与get_results相同的SQL:

src/lib.rs

  1. INSERT INTO "users" ("id", "name") VALUES ($1, $2)
  2. RETURNING "users"."id", "users"."name", "users"."hair_color",
  3. "users"."created_at", "users"."updated_at"
  4. -- binds: [3, "Ruby"]

最后,如果我们只想要一个列,我们可以明确地调用.returning。 此代码将返回插入的ID:

src/lib.rs

  1. use schema::users::dsl::*;
  2. insert_into(users)
  3. .values(name.eq("Ruby"))
  4. .returning(id)
  5. .get_result(conn)

生成以下SQL:

src/lib.rs

  1. INSERT INTO "users" ("name") VALUES ($1)
  2. RETURNING "users"."id"
  3. -- binds: ["Ruby"]

Upsert

本指南中介绍的每种插入语句也可用于“插入或更新”查询,也称为“upsert”。 API文档中详细介绍了upsert的细节。

对于PostgreSQL,请参阅pg :: upsert模块。 对于MySQL和SQLite,upsert通过REPLACE完成。 有关详细信息,请参阅replace_into

Diesel不支持MySQL的ON DUPLICATE KEY冲突,因为它的结果是非确定性的,并且复制时不安全。

结论

虽然本指南中有很多示例,但最终各种insert语句之间的唯一区别是传递给.values的参数。

本指南中的所有示例均作为Diesel测试套件的一部分运行。 您可以在以下链接中找到每个后端的完整代码示例: