8个版本

0.3.3 2020年1月12日
0.3.2 2020年1月12日
0.2.1 2020年1月2日
0.1.1 2019年9月10日

#2494 in 数据库接口

MIT/Apache

70KB
1K SLoC

postgres-query

Crates.io Build Status License Minimum Rust Version Documentation

此crate提供方便的宏和特性,帮助编写SQL查询并将结果收集到静态类型结构中。

文档

示例

// Connect to the database
let client: Client = connect(/* ... */);

// Construct the query
let query = query!(
    "SELECT name, age FROM people WHERE age >= $min_age",
    min_age = 18
);

// Define the structure of the data returned from the query
#[derive(FromSqlRow)]
struct Person {
    age: i32,
    name: String,
}

// Execute the query
let people: Vec<Person> = query.fetch(&client).await?;

// Use the results
for person in people {
    println!("{} is {} years young", person.name, person.age);
}

特性

提取器

此crate允许您通过给结构体标记 #[derive(FromSqlRow)] 属性来简单地提取查询的结果

#[derive(FromSqlRow)]
struct Book {
    id: i32,
    title: String,
    genre: String,
}

let books: Vec<Book> = query!("SELECT * FROM books")
    .fetch(&client)
    .await?;

多映射

您还可以从单行中提取多个结构。当您正在连接两个表时,这可能很有用。作为一个激励的例子,我们可以在一个 Book 实例中存储一个 Author 实例,这可以更容易地处理

#[derive(FromSqlRow)]
#[row(split)]
struct Book {
    #[row(split = "id")]
    id: i32,
    title: String,
    genre: String,

    #[row(flatten, split = "id")]
    author: Author,
}

#[derive(FromSqlRow)]
struct Author {
    id: i32,
    name: String,
    birthyear: i32,
}

let books: Vec<Book> = query!(
        "SELECT books.*, authors.* 
        FROM books
        INNER JOIN authors ON authors.id = books.id"
    )
    .fetch(&client)
    .await?;

注意 #[row(split = "id")] 属性在 Book 的字段上。为了正确提取值,我们首先将行拆分成更小的段。我们通过指定第一个 id 出现是书籍的一部分,第二个 id 是作者的一部分来实现。其余的由您来完成。

拆分/段将看起来像这样

Splits:   id                id
Columns:  id, title, genre, id, name, birthyear
Segments: +-----Book-----+  +-----Author------+

如果我们想重用已经存在的 Book,我们可以轻松地这样做

#[derive(FromSqlRow)]
#[row(split)]
struct Listings {
    #[row(flatten, split = "id")]
    book: Book
    #[row(flatten, split = "id")]
    author: Author,
}

一对一关系

在前面的示例中,我们有一个包含 AuthorBook。这被称为多对一关系,因为一本书只有一个作者,但许多书可能共享同一个作者(或者至少我们这样假设)。如果相反,你有一个包含许多 BookAuthor 呢?我们知道一个作者可能写许多书,所以这是一个一对多关系。我们也可以为这种情况编写一个提取器。

#[derive(FromSqlRow)]
#[row(split, group)]
struct Author {
    #[row(split = "id", key)]
    id: i32,
    name: String,
    birthyear: i32,

    #[row(split = "id", merge)]
    books: Vec<Book>,
}

#[derive(FromSqlRow)]
struct Book {
    id: i32,
    title: String,
    genre: String,
}

let authors: Vec<Author> = query!(
        "SELECT authors.*, books.*
         INNER JOIN books ON books.author = authors.id
         GROUP BY authors.id"
    )
    .fetch(&client)
    .await?;

动态查询

查询可以由字符串字面量或任何其他 String 动态构建,这在运行时进行。参数绑定也是如此,在后一种情况下可以动态添加。

假设我们想动态地向我们的查询添加过滤器

// We have the query we want to execute
let mut sql = "SELECT * FROM people".to_string();

// and some filters we got from the user.
let age_filter: Option<i32> = Some(32);
let name_filter: Option<&str> = None;

// Then we dynamically build a list of filters and bindings to use:
let mut filters = Vec::new();
let mut bindings = Vec::new();

// We add the filters as needed.
if let Some(age) = age_filter.as_ref() {
    filters.push("age > $min_age");
    bindings.push(("min_age", age as Parameter));
}

if let Some(name) = name_filter.as_ref() {
    filters.push("name LIKE $name");
    bindings.push(("name", name as Parameter));
}

// And append them to the query.
if filters.len() > 0 {
    sql += &format!(" WHERE {}", filters.join(" AND "));
}

// Then we can use it as normal.
let query: Query = query_dyn!(&sql, ..bindings)?;

许可证

根据您的选择,许可协议为 Apache License, Version 2.0MIT license

除非您明确声明,否则您有意提交给 rust-postgres-query 的任何贡献,根据 Apache-2.0 许可证定义,将作为上述双重许可,没有任何附加条款或条件。

依赖项

~11MB
~220K SLoC