kurtbuilds / ormlite

An ORM in Rust for developers that love SQL.
https://crates.io/crates/ormlite
MIT License
216 stars 11 forks source link

Unable to insert when passing value by reference to another function #7

Closed heroin-moose closed 2 years ago

heroin-moose commented 2 years ago

Code:

use ormlite::Model;
use ormlite::model::*;
use sqlx::FromRow;
use sqlx::SqlitePool;

#[derive(Model, FromRow)]
struct Example {
    #[ormlite(primary_key)]
    id: i64
}

async fn insert(e: &Example, pool: &SqlitePool) -> ormlite::Result<Example> {
    e.insert(pool).await
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let pool = SqlitePool::connect("/tmp/database").await?;
    let e1 = Example { id: 20 };

    insert(&e1, &pool).await?;

    Ok(())
}

Error:

   Compiling ormlite-issue v0.1.0 (/tmp/ormlite-issue)
error[E0507]: cannot move out of `*e` which is behind a shared reference
  --> src/main.rs:13:5
   |
13 |     e.insert(pool).await
   |     ^^^^^^^^^^^^^^ move occurs because `*e` has type `Example`, which does not implement the `Copy` trait

For more information about this error, try `rustc --explain E0507`.
error: could not compile `ormlite-issue` due to previous error
heroin-moose commented 2 years ago

However, this works (given e implements Clone):

e.clone().insert(pool).await
kurtbuilds commented 2 years ago

I believe this is working as designed.

The query depends on the Example because it needs to populate the query parameters. Since you're only borrowing it, the compiler can't guarantee that the &Example lives as long as the query.

You can solve this by moving example into your insert, or cloneing e, as you did.

heroin-moose commented 2 years ago

You mean the query has the lifetime of pool?

heroin-moose commented 2 years ago

Because raw query works, e.g. this compiles ok:

async fn explicit_insert(e: &Example, pool: &SqlitePool) -> sqlx::Result<()> {
    sqlx::query("INSERT INTO example (id) VALUES ($1)")
        .bind(&e.id)
        .execute(pool)
        .await?;
    Ok(())
}

I still pass the id by reference but compiler is happy. Am I missing something obvious?

kurtbuilds commented 2 years ago

You mean the query has the lifetime of pool?

Not exactly. In your function, e and pool have different (unspecified) lifetimes.

ormlite::Model.insert creates a ::sqlx::QueryAs which takes the model's fields as borrowed arguments. Importantly, the binding happens asynchronously, because it happens inside the Model.insert function. Which is why the compiler complains about &Example. If you move it fn insert(e: Example, ...), or clone it, this issue should go away.

In your second post, you manually do the binding, thus the binding happens synchronously, and the problem goes away.

heroin-moose commented 2 years ago

Thanks for the details!