#sqlite #postgresql #mysql #async #async-await

nightly qjack

nightly Rust 的 ergonomics sqlx 包装器

4 个版本

0.1.3 2023年5月22日
0.1.2 2023年5月21日
0.1.1 2023年5月20日
0.1.0 2023年5月20日

#946数据库接口

MIT 许可证

40KB
739 代码行

qjack

nightly Rust 的 ergonomics sqlx 包装器

  • 仅限于 nightly
  • 无宏
  • 支持的数据库:PostgreSQL, MySQL, SQLite
  • 支持的运行时:tokio, async-std
qjack build check status

示例;如何使用

[dependencies]
qjack = { version = "0.1", features = ["rt_tokio", "db_postgres"] }
tokio = { version = "1", features = ["macros"] }

部分:sample/src/bin/friends.rs

#[derive(Model, Debug)]
struct Friend {
    id:       i32,
    name:     String,
    password: String,
}

impl Friend {
    async fn create_table_if_not_exists() -> Result<()> {
        q("CREATE TABLE IF NOT EXISTS friends (
            id SERIAL PRIMARY KEY,
            name VARCHAR(32) NOT NULL,
            password VARCHAR(64) NOT NULL
        )").await?; Ok(())
    }

    async fn find_by_id(id: i32) -> Result<Self> {
        q(Self::one("
            SELECT id, name, password FROM friends
            WHERE id = $1
        "), id).await
    }

    async fn search_by_password(password: &str) -> Result<Option<Self>> {
        q(Self::optional("
            SELECT id, name, password FROM friends
            WHERE password = $1
        "), password).await
    }

    async fn find_all_with_limit_by_name_like(like: &str, limit: i32) -> Result<Vec<Friend>> {
        q(Self::all("
            SELECT id, name, password FROM friends
            WHERE name LIKE $1
            LIMIT $2
        "), like, limit).await
    }

    async fn create_many(name_passwords: impl IntoIterator<Item = (String, String)>) -> Result<()> {
        let mut name_passwords = name_passwords.into_iter();

        let mut insert = String::from("INSERT INTO friends (name, password) VALUES");
        if let Some((first_name, first_password)) = name_passwords.next() {
            insert.push_str(&format!(" ('{first_name}', '{first_password}')"))
        } else {return Ok(())}
        for (name, password) in name_passwords {
            insert.push_str(&format!(", ('{name}', '{password}')"))
        }

        q(&*insert).await?; Ok(())
    }
}

#[tokio::main]
async fn main() -> Result<()> {
    q.jack("postgres://qjack:password@postgres:5432/db")
        .max_connections(42)
        .await?;
    println!("Hi, jacked!");

    Friend::create_table_if_not_exists().await?;

    // ...

部分:sample/src/bin/transfer.rs

#[derive(Model, Debug)]
struct Account {
    id:      i64,
    name:    String,
    balance: i64,
}

impl Account {
    async fn create_table_if_not_exists() -> Result<()> {
        q("CREATE TABLE IF NOT EXISTS accounts (
            id BIGSERIAL PRIMARY KEY,
            name VARCHAR(32) NOT NULL,
            balance INT8 DEFAULT 0
        )").await?; Ok(())
    }

    async fn create_new(name: &str) -> Result<Self> {
        let created = q(Self::one("
            INSERT INTO accounts
            (name, balance) VALUES ($1, $2)
            RETURNING id, name, balance
        "), name, 0).await?;

        Ok(created)
    }

    async fn gets_income(&mut self, income: i64) -> Result<()> {
        q("UPDATE accounts
            SET balance = balance + $1
            WHERE id = $2
        ", income, &self.id).await?;

        self.balance += income;

        Ok(())
    }

    // transaction is unsafe in current version
    async unsafe fn transfer_to(
        &mut self,
        payee: &mut Account,
        ammount: i64
    ) -> Result<()> {
        q.transaction(|mut x| async {
            if let Err(e) = x("
                UPDATE accounts
                SET balance = balance - $1
                WHERE id = $2
            ", &ammount, &self.id).await {
                eprintln!("Failed to subtract balance: {e}");
                return x.rollback().await
            }

            if let Err(e) = x("
                UPDATE accounts
                SET balance = balance + $1
                WHERE id = $2
            ", &ammount, &payee.id).await {
                eprintln!("Failed to add balance: {e}");
                return x.rollback().await
            }

            self.balance  -= ammount;
            payee.balance += ammount;

            x.commit().await
        }).await
    }
}

#[tokio::main]
async fn main() -> Result<()> {
    q.jack("postgres://qjack:password@postgres:5432/db").await?;

    // ...

q 魔法

q.jack("DB_URL") /* config */ .await?

在后台创建连接池。所有查询必须在之后执行。


q.transaction(|mut x| async {
    /*
        returns
            x.rollback().await
        or
            x.commit().await
    */
}).await?

执行一个事务。当前版本中这是 unsafe


q("query string" /* , params, ... */ ).await?

执行一个非检索查询。这返回 QueryResult


q( M::one("query string") /* , params, ... */ ).await?
q( M::all("query string") /* , params, ... */ ).await?
q( M::optional("query string") /* , params, ... */ ).await?

执行一个检索查询。返回类型:

  • oneM
  • allVec<M>
  • optionalOption<M>

( 其中 M 代表一个实现了 Model 的 struct )


许可证

qjack 根据 MIT 许可证授权 (LICENSEhttps://opensource.org/licenses/MIT)

依赖项

~8–23MB
~357K SLoC