Now I want to use this module and return the resulting stream, so it cam be consumed somewhere else:
pub async fn query_database() -> Result<Pin<Box<dyn Stream<Item = Result<Result<Value>>>>>> {
let connection = SqlxDatabaseConnection::establish_connection(&db_settings).await?;
let stream = connection.query(statement);
Ok(stream)
]
But this is what the compiler says:
error[E0515]: cannot return value referencing local variable `connection`
--> engine/src/persistance/db.rs:30:2
|
28 | let stream = connection.query(statement);
| ---------- `connection` is borrowed here
29 |
30 | Ok(stream)
| ^^^^^^^^^^ returns a value referencing data owned by the current function
I'd like to keep the method syntax. But how can I resolve the borrowing issue?
B is not an option, since I want to consume the stream in the frontend.
I tried option A. But when I get rid of the struct and the method syntax and try to query the database like this:
let pool = establish_connection(&db_settings).await?;
let stream = query(&pool, statement);
Ok(stream)
The compile gives me the same error basically:
error[E0515]: cannot return value referencing local variable `pool`
--> engine/src/persistance/db.rs:30:2
|
28 | let stream = query(&pool, statement);
| ----- `pool` is borrowed here
29 |
30 | Ok(stream)
| ^^^^^^^^^^ returns a value referencing data owned by the current function
The issue is that you're trying to return a borrow to a value you create inside a function. You can't do that. The code that wants the result from query has to be the code to call establish_connection. You cannot abstract this without getting rid of the borrow, and you've said that isn't an option.
... okay, you can get around this by using one of the "self-referential borrowing" crates, but every time I've ever used or recommended one of those, it turned out to be unsafe and dangerous to use, so I make a point to not use or recommend them any more.
The way you solve these problems in Rust is to promote the establish_connection up the call stack until it's at the same level as (or higher than) where you need the borrowed data.
Actually, there is another way, but it's even weirder and only very occasionally better: change query to take conn: &mut Option<SqlxDatabaseConnection> and, if it's empty, store the result of establish_connection in there, and then return a borrow derived from that. This works because it lets you store the connection "up the stack" from inside the function.
This is less than ideal for a few reasons, though. First of all, it still breaks the abstraction of "users don't need to know about the connection" by forcing them to define and then pass an argument either way. Secondly, because it's a mutable borrow, it forces the connection to be unusable for anything else so long as the query result exists.
Just thought I should mention it so that you're aware this option exists, even if it's probably not what you want to use here.
Don't have the function own the data if you want to return a reference. So have the caller of query_database() make the call to SqlxDatabaseConnection::establish_connection() and then pass the connection down by reference:
However, you will still have to keep the connection around until after you've used the stream. Think of the current error this way: it's saying the current function owns data. Ie. the function owns the connection and will drop it at the end of the function. Dropping it means closing the connection. Thus, how will the returned stream be able to fetch the data at a later point if the connection is no longer open.
Thanks for your answers!! I think all of the answers suggest to "promote" the establish_connection function and pass the connection as argument to the query method.
I doubt that this is a good idea in my case. Since there will be more data sources where some of them don't support pooling, I'm actually leaning in an opposite direction, in which I only make the query function public within the SqlxDatabaseConnection implementation, hide the establish_connection from the user and handle the connection internally. not sure if that's possible but I'm currently trying it out.
with this change I now have different questions to answer, like how abstract the connection, how to automatically create a connection when there is no one open, etc. If I'll need help with that, I'll create a new topic.