一个新的方式使用Rust操作数据库

举报
码乐 发表于 2024/12/01 21:55:14 2024/12/01
【摘要】 1 简介Rust 和数据库交互的一个新的操作库,现有的库没有提供足够的编译时保证,并且像 SQL 一样冗长或笨拙。我如此关心的原因是数据库真的很酷。它们解决了制作支持原子事务的抗崩溃软件的巨大问题。结构化查询语言 (SQL) 是一种协议对于那些不知道的人来说,SQL 是与数据库交互的标准。以至于几乎所有数据库都只接受某种 SQL 方言的查询。我的观点是 SQL 应该供计算机编写。这将使它与...

1 简介

Rust 和数据库交互的一个新的操作库,现有的库没有提供足够的编译时保证,并且像 SQL 一样冗长或笨拙。

image.png

我如此关心的原因是数据库真的很酷。它们解决了制作支持原子事务的抗崩溃软件的巨大问题。

结构化查询语言 (SQL) 是一种协议对于那些不知道的人来说,SQL 是与数据库交互的标准。以至于几乎所有数据库都只接受某种 SQL 方言的查询。

我的观点是 SQL 应该供计算机编写。这将使它与 LLVM IR 牢牢地归为同一类别。它是人类可读的,这一事实对于调试和测试很有用,但我不认为这是你想要的查询编写方式。

  • rust-query

rust-query 是对 Rust 中关系数据库查询的回答。它是一个固执己见的库,与 Rust 的类型系统深度集成,使数据库操作感觉像 Rust 原生。

主要特点和设计决策

  显式表别名:联接表会返回一个表示该表的虚拟对象。let user = User::join(rows);
  Null 安全性:查询中的可选值具有 type,需要特别小心处理。Option
  直观的聚合:我们的聚合保证为它们联接的每一行提供单个结果。尝试后,您会发现这比传统操作直观得多。GROUP BY
  类型安全的外键导航:数据库约束类似于类型签名,因此您可以通过外键(例如)易于使用的隐式连接来依赖它们进行查询。track.album().artist().name()
  类型安全的唯一查找:例如可以使用 .Option<Rating>Rating::unique(my_user, my_story)
  多版本架构:它是声明性的,您可以立即看到架构的所有过去版本之间的差异!
  类型安全迁移:迁移具有查询的所有功能,并且可以使用任意 Rust 代码来处理行。是否曾经不得不查阅数据库之外的东西才能在迁移中使用?现在你可以了!
  类型安全的唯一冲突:在具有唯一约束的表中插入和更新行会导致专门的错误类型。

与事务生命周期相关的行引用:只有在保证行存在时,才能使用行引用。

封装的类型化行 ID:实际的行号永远不会从库 API 中公开。应用程序 logic 不需要了解它们。

2 执行和操作

您始终从定义架构开始。这样,以后就可以轻松迁移到不同的架构。

  #[schema]
  enum Schema {
      User {
          name: String,
      },
      Story {
          author: User,
          title: String,
          content: String
      },
      #[unique(user, story)]
      Rating {
          user: User,
          story: Story,
          stars: i64
      },
  }
  use v0::*;

中的架构定义使用 enum 语法,但此处未定义实际的 enum。 此架构定义了三个具有指定列和关系的表:rust-query

使用另一个表名作为列类型会创建外键约束。

该属性创建命名的唯一约束。
该宏解析枚举语法并生成包含数据库 API 的模块。

3 编写查询

首先,让我们看看如何将一些数据插入到我们的 schema 中:

  fn insert_data(txn: &mut TransactionMut<Schema>) {
      // Insert users
      let alice = txn.insert(User {
          name: "alice",
      });
      let bob = txn.insert(User {
          name: "bob",
      });

      // Insert a story
      let dream = txn.insert(Story {
          author: alice,
          title: "My crazy dream",
          content: "A dinosaur and a bird...",
      });

      // Insert a rating - note the try_insert due to the unique constraint
      let rating = txn.try_insert(Rating {
          user: bob,
          story: dream,
          stars: 5,
      }).expect("no rating for this user and story exists yet");
  }

关于插入的几个要点:

我们需要一个可变的事务 (TransactionMut) 来修改数据库。
Insert 操作返回对新插入的行的引用。

当插入到具有唯一约束的表中时,try_insert用于处理潜在的冲突。

try_insert的错误类型取决于表具有的唯一约束数。

现在让我们查询此数据:

  fn query_data(txn: &Transaction<Schema>) {
      let results = txn.query(|rows| {
          let story = Story::join(rows);
          let avg_rating = aggregate(|rows| {
              let rating = Rating::join(rows);
              rows.filter_on(rating.story(), &story);
              rows.avg(rating.stars().as_float())
          });
          rows.into_vec((story.title(), avg_rating))
      });

      for (title, avg_rating) in results {
          println!("story '{title}' has avg rating {avg_rating:?}");
      }
  }

有关查询的要点:

rows表示查询中的当前行集。
联接可以添加行,筛选器可以删除行。

  • 详细信息

aggregate用于计算聚合不会更改查询中的行数。

rows.filter_on可用于筛选聚合中的行以匹配外部范围中的值。

rows.avgNone该方法返回聚合中行的平均值,如果没有行,则平均值的计算结果为。

结果可以收集到元组或结构的向量中。
架构演变和迁移。

假设您要为每个用户添加一个电子邮件地址。以下是创建新架构版本的方法:

  #[schema]
  #[version(0..=1)]
  enum Schema {
      User {
          name: String,
          #[version(1..)]
          email: String,
      },
      // ... rest of schema ...
  }
  use v1::*;

以下是迁移数据的方法:

let m = m.migrate(v1::update::Schema {
    user: Box::new(|old_user| {
        Alter::new(v1::update::UserMigration {
            email: old_user
                .name()
                .map_dummy(|name| format!("{name}@example.com")),
        })
    }),
});

该模块包含定义v1 schema 和 v0 schema 之间差异的结构.

我们使用这些结构体来实现迁移。这样,将针对旧架构和新架构对迁移进行类型检查。

请注意,在迁移中,我们可以执行我们想要的所有单行查询:聚合、唯一约束查找等!

我们还可以与任意 Rust 一起使用来进一步处理map_dummy行。

4 结论

rust-query代表了 Rust 中数据库交互的新方法,优先考虑:

  在编译时检查所有可能的内容。
  使彼此和任意 Rust 组合查询成为可能。
  通过类型检查迁移启用架构演变。
【版权声明】本文为华为云社区用户翻译文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容, 举报邮箱:cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。