Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

an incomplete attempt at sqlite support #18

Closed
wants to merge 7 commits into from

Conversation

rich-murphey
Copy link

This is an incomplete attempt to add sqlite support.

I hope some part of it is useful.

I got stuck trying to decide how to resolve an issue reported by the ormx::Table macro: "temporary value dropped while borrowed".

It does not report which value is dropped, so I'm not sure why this is occruing. One wild guess is that ormx::Table impl returns a future referencing fields in self. But that doesn't make sense because in the caller, self outlives the return from await on that future.

   Compiling example-sqlite v0.1.0 (/u20c/b/rich/w/ormx/example-sqlite)
error[E0716]: temporary value dropped while borrowed
  --> example-sqlite/src/main.rs:64:17
   |
64 | #[derive(Debug, ormx::Table)]
   |                 ^^^^^^^^^^^
   |                 |
   |                 creates a temporary which is freed while still in use
   |                 temporary value is freed at the end of this statement
   |                 borrow later used by call
   |
   = note: consider using a `let` binding to create a longer lived value
   = note: this error originates in the derive macro `ormx::Table` (in Nightly builds, run with -Z macro-backtrace for more info)

where the expanded macro contains this:

impl ormx::Table for User {
    type Id = i64;
    fn id(&self) -> Self::Id {
        self.user_id
    }
    fn get<'a, 'c: 'a>(
        db: impl sqlx::Executor<'c, Database = ormx::Db> + 'a,
        id: Self::Id,
    ) -> ormx::exports::futures::future::BoxFuture<'a, sqlx::Result<Self>> {
        Box::pin(async move {
            { { # [allow (clippy :: all)]
                   { use :: sqlx :: Arguments as _ ;
                     let arg0 = & (id) ;
                     ...
                     . fetch_one (db) . await
        })
    }                                                                                                                                                                                                                                                                                                                                        Syntax Error: expected SEMICOLON [syntax-error]
....

I'm not sure how to get better error messages to narrow down the issue.

@NyxCode
Copy link
Owner

NyxCode commented Nov 8, 2021

first of all, thanks for your work! really appreciate it.

I remember running into this - this is caused by the streams returned by query(_as)! on sqlite. They seem to borrow a local variable and cannot be returned from methods.
I briefly talked with mehcode on the sqlx discord about this - here are some links to the conversation:

I think we should just put the functions which return streams behind a #[cfg(not(feature = "sqlite"))] feature gate until the API of sqlx is consistent across backends in this regard

@rich-murphey
Copy link
Author

Thanks so much! I really appreciate the pointers to the discussion, and the encouragement.

I'm interested in trying an idea about the lifetime issue, potentially just for the sqlite case, and temporarily till sqlx 0.6 may make it unnecessary.

I've seen the same kind of issue occur between the interfaces of actix-web an sqlx. An acix-web streaming response needs a BoxStream that owns it's data, whereas a sqlx stream references it's data. One possible solution is to have an outer wrapper stream that owns the query parameter values, and owns the inner sqlx stream of rows.

Here's an example. Note the SelfRefStream is an outer stream that owns the query parameters referenced by an inner sqlx stream.

#[post("/widgets")]
pub async fn widgets(
    web::Json(params): web::Json<WidgetParams>,
    pool: web::Data<PgPool>,
) -> HttpResponse {
    HttpResponse::Ok()
        .content_type("application/json")
        .streaming(
            // a stream of text (Bytes) containing a JSON array of sqlx records
            ByteStream::new(
                // a stream of WidgetRecords that owns pool and params


                SelfRefStream::build(
                    (pool.as_ref().clone(), params),
                    // a stream of WidgetRecords that borrows pool and params
                    move |(pool, params)| {
                        sqlx::query_as!(
                            WidgetRecord,
                            "SELECT * FROM widgets LIMIT $1 OFFSET $2 ",
                            params.limit,
                            params.offset
                        )
                            .fetch(pool)
                    }),


                |buf: &mut BytesWriter, record: &WidgetRecord| {
                    // this writes a WidgetRecords as JSON text to the output buffer
                    serde_json::to_writer(buf, record).map_err(ErrorInternalServerError)
                },
            ),
        )
}

and here's the implementation of SelfRefStream and more discussion about this example.

Maybe this has some potential for a temporary solution. It has some overhead for the additional allocations when the SelfRefStream takes ownership of the query parameters, so perhaps it should be limited to only where it's rquired -- for sqlite only.

@rich-murphey
Copy link
Author

The above commit is a proof of concept.

One crucial issue is shown above in the actix-web widgets() method. Note that widgets() creates a stream, and returns the stream to actix-web. The lifetime of the stream is longer than the lifetime of the widgets() method. For that reason, the stream cannot reference values that live only within the widgets() method. Rather, the stream must own all of the parameters used to create it.

Similarly, ormx methods such as stream_all and stream_all_paginated return a stream that in the general case, will outlive the caller of these methods. To achieve that, all of the ormx methods that return a stream need to take ownership of the parameters, including the executor, and any offset or limit on paged stream. In order to do that, these methods take a &Pool, and clone the pool in order for the stream to own the pool object. This is cheap since Pool is an Arc.

I should probably discuss much more, but gad, debugging proc macros has been a little overwhelming, and I need a break. I'm sure there are issues, but this compiles and runs example-sqlite, so I thought I'd share this first draft rather than delay further.

I have not tested how these changes affect postgres and mysql. In that respect, these changes are incomplete, and this only works for sqlite. More work is needed to ensure the changes affect only the sqlite feature.

@rich-murphey
Copy link
Author

This coming week I plan to take another pass at this. I'll try to reduce this down to the minimum changes necessary to make the SQLite support equivalent to postgres support.

@rich-murphey
Copy link
Author

I've made some progress. The SelfRefStream is not needed whenever the stream is consumed within the scope it is constructed. This in turn means, all of the methods can take an Executor, so that they accept either a pool or connection.

I'll clean up the diffs and submit probably by early next week.

@rich-murphey
Copy link
Author

I minimized the changes, and tested both example-postgres and example-sqlite.

For sqlite, this is still incomplete in that I was not able to implement the conditional_query_as!() macro. I don't yet understand enough of the __build_query macro to identify where changes are needed to support sqlite.

If you find any of this useful, just let me know if changes or further work is preferred!

@rich-murphey
Copy link
Author

abonander opened a sqlx PR 1551 that may make it unnecessary to use a wrapper to take ownership of limit and offset (see SqliteArgumentValue::into_static). I haven't actually tried it, but it looks like converting the arguments to a 'static lifetime would satisfy the borrow checker.

That may also make the conditional_query_as!() macro work. It should eliminate many of these changes. I'll plan to further reduce this PR when sqlx PR 1551 is merged.

@rich-murphey
Copy link
Author

On Discord, abonander said that sqlx PR 1551 changes are internal and do not change the sqlx API, so it won't solve the ownership issues.

@Quiark
Copy link

Quiark commented Nov 7, 2022

Hi @rich-murphey isn't the ormx-macros/src/backend/sqlite file missing?

@NyxCode
Copy link
Owner

NyxCode commented Sep 8, 2024

I'm closing this for now.
I haven't touched ormx for quite a while, so I have to assume most of this is sadly outdated.

@NyxCode NyxCode closed this Sep 8, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants