The query macro docs state that:
The QueryAs instance will be bound to the same database type as query!() was compiled against (e.g. you cannot build against a Postgres database and then run the query against a MySQL database).
However, the function does not have this limitation. Knowing this we can build some functions that are generic over any executor type of any database type.
When you're trying to be generic over any backend in sqlx you need to be generic over the database (sqlite, mysql, postgresql, etc), the executor (PgPool
, SqlitePool
, a raw Connection
, a Transaction
, etc), any encoded (parameters/bind variables), and decoded (query_as return types) values.
For example, creating a table:
pub async fn create_products_table<'a, DB, E>(e: E) -> Result<(), Error>
where
DB: Database,
<DB as HasArguments<'a>>::Arguments: IntoArguments<'a, DB>,
E: Executor<'a, Database = DB>,
{
sqlx::query(include_str!(
"../sql/create_products_table.sql"
))
.execute(e)
.await?;
Ok(())
}
Selecting items from a table:
pub struct Product {
name: String,
other: String,
}
impl<'r, R> FromRow<'r, R> for Product
where
R: Row,
//Here we have bounds for index by str
for<'c> &'c str: ColumnIndex<R>,
//We need this bound because `Product` contains `String`
for<'c> String: Decode<'c, R::Database> + Type<R::Database>,
{
fn from_row(row: &'r R) -> Result<Self, Error> {
//Here we index by str
let name = row.try_get("name")?;
//Here we index by str
let other = row.try_get("other")?;
Ok(Self { name, other })
}
}
pub async fn select_products<'a, 'b, DB, E>(
e: E,
name: &'a str,
) -> impl Stream<Item = Result<Product, Error>> + 'b
where
'a: 'b,
DB: Database,
<DB as HasArguments<'a>>::Arguments: IntoArguments<'a, DB>,
for<'c> E: 'a + Executor<'c, Database = DB>,
//We need this bound because `Product` contains `String`
for<'c> String: Decode<'c, DB> + Type<DB>,
//We need this bound because 'name' function argument is of type `&str`
for<'c> &'c str: Encode<'c, DB> + Type<DB>,
//We need this bound for `FromRow` implementation or if we intend on indexing rows by str.
//You will probably need to write your own `FromRow` implementation.
for<'c> &'c str: ColumnIndex<<DB as Database>::Row>,
{
query_as(include_str!("../sql/select_products.sql"))
.bind(name)
.fetch(e)
}
I would love for this to be more streamlined or documented as it seems ripe for macros. Perhaps I will draft some docs for a PR to sqlx. Let me know if you need more examples.
I am also aware of the Any database driver, however it has the distinct disadvantage of requiring an opaque type with no trivial way of converting from concrete to opaque types. This means you have to use AnyPool
everywhere you'd be generic over a database and can never use a concrete PgPool
or SqlitePool
. You also must solely rely on connection strings to differentiate database implementations (yuck)