use qbrs_core::delete::Delete;
use qbrs_core::dialect::Postgres;
use qbrs_core::expr::Value;
use qbrs_core::insert::Insert;
use qbrs_core::row::{Row, RowCons, RowNil};
use qbrs_core::select::{DynSelect, Prepared, PreparedParams, Select, Selection, SetOp, Total};
use qbrs_core::statement::{Returning, Statement, WrittenTable};
use qbrs_core::update::Update;
use sqlx::Row as _;
use sqlx::postgres::PgRow;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error(transparent)]
Sqlx(#[from] sqlx::Error),
#[error(transparent)]
UnresolvedPlaceholder(#[from] qbrs_core::select::UnresolvedPlaceholder),
#[error(transparent)]
NothingToSet(#[from] qbrs_core::update::NothingToSet),
#[error(transparent)]
NothingToInsert(#[from] qbrs_core::insert::NothingToInsert),
#[error("`{0}` values need the matching feature on `qbrs-sqlx` too")]
FeatureNotEnabled(&'static str),
}
pub type Result<T> = std::result::Result<T, Error>;
pub mod prelude {
pub use crate::Error;
pub use crate::{
CountExt, CountQuery, DecodeRow, ExecuteExt, LoadExt, PreparedCountExt, PreparedExt,
PreparedQuery, PreparedTotal, RowQuery, WriteStatement,
};
}
fn bind_value<'q>(
query: sqlx::query::Query<'q, sqlx::Postgres, sqlx::postgres::PgArguments>,
v: Value,
) -> Result<sqlx::query::Query<'q, sqlx::Postgres, sqlx::postgres::PgArguments>> {
Ok(match v {
Value::I32(x) => query.bind(x),
Value::I64(x) => query.bind(x),
Value::F64(x) => query.bind(x),
Value::Text(x) => query.bind(x),
Value::Bool(x) => query.bind(x),
Value::Bytes(x) => query.bind(x),
Value::NullI32 => query.bind(None::<i32>),
Value::NullI64 => query.bind(None::<i64>),
Value::NullF64 => query.bind(None::<f64>),
Value::NullText => query.bind(None::<String>),
Value::NullBool => query.bind(None::<bool>),
Value::NullBytes => query.bind(None::<Vec<u8>>),
#[cfg(feature = "chrono")]
Value::Timestamptz(x) => query.bind(x),
#[cfg(feature = "chrono")]
Value::NullTimestamptz => query.bind(None::<chrono::DateTime<chrono::Utc>>),
#[cfg(feature = "chrono")]
Value::Date(x) => query.bind(x),
#[cfg(feature = "chrono")]
Value::NullDate => query.bind(None::<chrono::NaiveDate>),
#[cfg(feature = "uuid")]
Value::Uuid(x) => query.bind(x),
#[cfg(feature = "uuid")]
Value::NullUuid => query.bind(None::<uuid::Uuid>),
#[cfg(feature = "decimal")]
Value::Numeric(x) => query.bind(x),
#[cfg(feature = "decimal")]
Value::NullNumeric => query.bind(None::<rust_decimal::Decimal>),
Value::Placeholder(name) => {
return Err(qbrs_core::select::UnresolvedPlaceholder(name).into());
}
#[allow(unreachable_patterns)]
other => return Err(Error::FeatureNotEnabled(other.type_name())),
})
}
fn bind_all<'q>(
mut query: sqlx::query::Query<'q, sqlx::Postgres, sqlx::postgres::PgArguments>,
params: Vec<Value>,
) -> Result<sqlx::query::Query<'q, sqlx::Postgres, sqlx::postgres::PgArguments>> {
for p in params {
query = bind_value(query, p)?;
}
Ok(query)
}
async fn fetch_all<'e, T: DecodeRow, E: sqlx::PgExecutor<'e>>(
executor: E,
sql: &str,
params: Vec<Value>,
) -> Result<Vec<T>> {
let rows = bind_all(sqlx::query(sqlx::AssertSqlSafe(sql)), params)?
.fetch_all(executor)
.await?;
rows.iter()
.map(|row| T::decode_at(row, &mut 0).map_err(Error::from))
.collect()
}
async fn fetch_optional<'e, T: DecodeRow, E: sqlx::PgExecutor<'e>>(
executor: E,
sql: &str,
params: Vec<Value>,
) -> Result<Option<T>> {
let row = bind_all(sqlx::query(sqlx::AssertSqlSafe(sql)), params)?
.fetch_optional(executor)
.await?;
row.as_ref()
.map(|r| T::decode_at(r, &mut 0).map_err(Error::from))
.transpose()
}
async fn execute_only<'e, E: sqlx::PgExecutor<'e>>(
executor: E,
sql: &str,
params: Vec<Value>,
) -> Result<u64> {
let result = bind_all(sqlx::query(sqlx::AssertSqlSafe(sql)), params)?
.execute(executor)
.await?;
Ok(result.rows_affected())
}
#[diagnostic::on_unimplemented(
message = "`{Self}` isn't a query this crate can run",
label = "a `Select`, a `RETURNING`, a `DynSelect` or a set operation, in the `Postgres` dialect, whose values are all types `DecodeRow` covers"
)]
pub trait RowQuery<Idx> {
type Output: DecodeRow;
#[doc(hidden)]
fn rendered(&self) -> (String, Vec<Value>);
}
pub trait LoadExt {
fn load<'e, Idx, E: sqlx::PgExecutor<'e>>(
&self,
executor: E,
) -> impl std::future::Future<Output = Result<Vec<<Self as RowQuery<Idx>>::Output>>>
where
Self: RowQuery<Idx>,
{
let (sql, params) = self.rendered();
async move { fetch_all::<<Self as RowQuery<Idx>>::Output, E>(executor, &sql, params).await }
}
fn load_one<'e, Idx, E: sqlx::PgExecutor<'e>>(
&self,
executor: E,
) -> impl std::future::Future<Output = Result<Option<<Self as RowQuery<Idx>>::Output>>>
where
Self: RowQuery<Idx>,
{
let (sql, params) = self.rendered();
async move { fetch_optional::<<Self as RowQuery<Idx>>::Output, E>(executor, &sql, params).await }
}
}
impl<D, Scope, Sel, Outer> LoadExt for Select<D, Scope, Sel, Outer> {}
impl<S, Sel> LoadExt for Returning<S, Sel> {}
impl<D, Output> LoadExt for DynSelect<D, Output> {}
impl<D, Output> LoadExt for SetOp<D, Output> {}
impl<D, R: qbrs_core::insert::InsertRow> LoadExt for Insert<D, R> {}
impl<D, T: qbrs_core::scope::Table> LoadExt for Update<D, T> {}
impl<D, T: qbrs_core::scope::Table> LoadExt for Delete<D, T> {}
impl<Scope, Sel, Idx> RowQuery<Idx> for Select<Postgres, Scope, Sel>
where
Sel: Selection<Scope, Idx>,
Sel::Output: DecodeRow,
{
type Output = Sel::Output;
fn rendered(&self) -> (String, Vec<Value>) {
self.to_sql::<Idx>(Postgres)
}
}
#[diagnostic::on_unimplemented(
message = "`{Self}` isn't a query this crate can count",
label = "a `Select`, a `DynSelect` or a set operation in the `Postgres` dialect is; a writing statement reports rows affected through `.execute(..)` instead"
)]
pub trait CountQuery<Idx> {
#[doc(hidden)]
fn count_rendered(&self) -> (String, Vec<Value>);
}
pub trait CountExt {
fn count<'e, Idx, E: sqlx::PgExecutor<'e>>(
&self,
executor: E,
) -> impl std::future::Future<Output = Result<i64>>
where
Self: CountQuery<Idx>,
{
count_rows(executor, self.count_rendered())
}
}
impl<D, Scope, Sel, Outer> CountExt for Select<D, Scope, Sel, Outer> {}
impl<S, Sel> CountExt for Returning<S, Sel> {}
impl<D, Output> CountExt for DynSelect<D, Output> {}
impl<D, Output> CountExt for SetOp<D, Output> {}
impl<D, R: qbrs_core::insert::InsertRow> CountExt for Insert<D, R> {}
impl<D, T: qbrs_core::scope::Table> CountExt for Update<D, T> {}
impl<D, T: qbrs_core::scope::Table> CountExt for Delete<D, T> {}
impl<Scope, Sel: Selection<Scope, Idx>, Idx> CountQuery<Idx> for Select<Postgres, Scope, Sel> {
fn count_rendered(&self) -> (String, Vec<Value>) {
self.count_sql::<Idx>(Postgres)
}
}
impl<Output> CountQuery<()> for DynSelect<Postgres, Output> {
fn count_rendered(&self) -> (String, Vec<Value>) {
self.count_sql(Postgres)
}
}
impl<Output> CountQuery<()> for SetOp<Postgres, Output> {
fn count_rendered(&self) -> (String, Vec<Value>) {
self.count_sql(Postgres)
}
}
async fn count_rows<'e, E: sqlx::PgExecutor<'e>>(
executor: E,
(sql, params): (String, Vec<Value>),
) -> Result<i64> {
let row = bind_all(sqlx::query(sqlx::AssertSqlSafe(sql.as_str())), params)?
.fetch_one(executor)
.await?;
Ok(row.try_get::<i64, _>(0)?)
}
#[diagnostic::on_unimplemented(
message = "`{Self}` isn't a statement this crate can execute",
label = "an `INSERT`, `UPDATE` or `DELETE` in the `Postgres` dialect is; a `SELECT` or a `RETURNING` yields rows, so it goes through `.load(..)` — and a prepared query through `.load(.., params)`"
)]
pub trait WriteStatement {
#[doc(hidden)]
fn write_rendered(&self) -> (String, Vec<Value>);
}
#[diagnostic::do_not_recommend]
impl<S: Statement<Dialect = Postgres>> WriteStatement for S {
fn write_rendered(&self) -> (String, Vec<Value>) {
self.to_sql(Postgres)
}
}
pub trait ExecuteExt {
fn execute<'e, E: sqlx::PgExecutor<'e>>(
&self,
executor: E,
) -> impl std::future::Future<Output = Result<u64>>
where
Self: WriteStatement,
{
let (sql, params) = self.write_rendered();
async move { execute_only(executor, &sql, params).await }
}
}
impl<D, Scope, Sel, Outer> ExecuteExt for Select<D, Scope, Sel, Outer> {}
impl<S, Sel> ExecuteExt for Returning<S, Sel> {}
impl<D, Output> ExecuteExt for DynSelect<D, Output> {}
impl<D, Output> ExecuteExt for SetOp<D, Output> {}
impl<D, R: qbrs_core::insert::InsertRow> ExecuteExt for Insert<D, R> {}
impl<D, T: qbrs_core::scope::Table> ExecuteExt for Update<D, T> {}
impl<D, T: qbrs_core::scope::Table> ExecuteExt for Delete<D, T> {}
impl<S: Statement<Dialect = Postgres>, Sel, Idx> RowQuery<Idx> for Returning<S, Sel>
where
Sel: Selection<WrittenTable<S::Table>, Idx>,
Sel::Output: DecodeRow,
{
type Output = Sel::Output;
fn rendered(&self) -> (String, Vec<Value>) {
self.to_sql(Postgres)
}
}
#[diagnostic::on_unimplemented(
message = "`{Self}` isn't a value this crate can decode",
label = "every selected column has to decode to one of the six built-in natives, or to a type whose feature is on here as well as on `qbrs`",
note = "`chrono`/`uuid`/`decimal` have to be enabled on `qbrs-sqlx` too — they are separate `cfg`s over one `Value`"
)]
pub trait DecodeRow: Sized {
#[doc(hidden)]
fn decode_at(row: &PgRow, idx: &mut usize) -> sqlx::Result<Self>;
}
macro_rules! decode_row_leaf {
($ty:ty) => {
impl DecodeRow for $ty {
fn decode_at(row: &PgRow, idx: &mut usize) -> sqlx::Result<Self> {
let v = row.try_get::<$ty, _>(*idx)?;
*idx += 1;
Ok(v)
}
}
impl DecodeRow for Option<$ty> {
fn decode_at(row: &PgRow, idx: &mut usize) -> sqlx::Result<Self> {
let v = row.try_get::<Option<$ty>, _>(*idx)?;
*idx += 1;
Ok(v)
}
}
};
}
decode_row_leaf!(i32);
decode_row_leaf!(i64);
decode_row_leaf!(f64);
decode_row_leaf!(String);
decode_row_leaf!(bool);
decode_row_leaf!(Vec<u8>);
#[cfg(feature = "chrono")]
decode_row_leaf!(chrono::DateTime<chrono::Utc>);
#[cfg(feature = "chrono")]
decode_row_leaf!(chrono::NaiveDate);
#[cfg(feature = "uuid")]
decode_row_leaf!(uuid::Uuid);
#[cfg(feature = "decimal")]
decode_row_leaf!(rust_decimal::Decimal);
impl DecodeRow for RowNil {
fn decode_at(_row: &PgRow, _idx: &mut usize) -> sqlx::Result<Self> {
Ok(RowNil)
}
}
impl<K, V: DecodeRow, Tail: DecodeRow> DecodeRow for RowCons<K, V, Tail> {
fn decode_at(row: &PgRow, idx: &mut usize) -> sqlx::Result<Self> {
let value = V::decode_at(row, idx)?;
Ok(RowCons::new(value, Tail::decode_at(row, idx)?))
}
}
impl<L: DecodeRow> DecodeRow for Row<L> {
fn decode_at(row: &PgRow, idx: &mut usize) -> sqlx::Result<Self> {
Ok(Row::new(L::decode_at(row, idx)?))
}
}
impl<Output: DecodeRow> RowQuery<()> for DynSelect<Postgres, Output> {
type Output = Output;
fn rendered(&self) -> (String, Vec<Value>) {
self.to_sql(Postgres)
}
}
impl<Output: DecodeRow> RowQuery<()> for SetOp<Postgres, Output> {
type Output = Output;
fn rendered(&self) -> (String, Vec<Value>) {
self.to_sql(Postgres)
}
}
#[diagnostic::on_unimplemented(
message = "`{Self}` isn't a prepared query this crate can run",
label = "a `.prepare()`-built query is — `Prepared<D, Params, Output>`, params before output — and its `Params` have to be the ones it declared"
)]
pub trait PreparedQuery<Params> {
type Output: DecodeRow;
#[doc(hidden)]
fn resolved(&self, params: Params) -> Result<(String, Vec<Value>)>;
}
pub trait PreparedExt {
fn load<'e, Params, E: sqlx::PgExecutor<'e>>(
&self,
executor: E,
params: Params,
) -> impl std::future::Future<Output = Result<Vec<<Self as PreparedQuery<Params>>::Output>>>
where
Self: PreparedQuery<Params>,
{
let resolved = self.resolved(params);
async move {
let (sql, values) = resolved?;
fetch_all::<<Self as PreparedQuery<Params>>::Output, E>(executor, &sql, values).await
}
}
fn load_one<'e, Params, E: sqlx::PgExecutor<'e>>(
&self,
executor: E,
params: Params,
) -> impl std::future::Future<Output = Result<Option<<Self as PreparedQuery<Params>>::Output>>>
where
Self: PreparedQuery<Params>,
{
let resolved = self.resolved(params);
async move {
let (sql, values) = resolved?;
fetch_optional::<<Self as PreparedQuery<Params>>::Output, E>(executor, &sql, values)
.await
}
}
}
impl<D, Params, Output> PreparedExt for Prepared<D, Params, Output> {}
impl<D, Params, Output> ExecuteExt for Prepared<D, Params, Output> {}
#[diagnostic::do_not_recommend]
impl<Params: PreparedParams, Output: DecodeRow> PreparedQuery<Params>
for Prepared<Postgres, Params, Output>
{
type Output = Output;
fn resolved(&self, params: Params) -> Result<(String, Vec<Value>)> {
Ok(self.resolve(params)?)
}
}
#[diagnostic::on_unimplemented(
message = "`{Self}` isn't a prepared total this crate can run",
label = "`.prepare_count()` builds one; `.prepare()` builds a query whose rows go through `.load(..)`"
)]
pub trait PreparedTotal<Params> {
#[doc(hidden)]
fn resolved_count(&self, params: Params) -> Result<(String, Vec<Value>)>;
}
impl<Params: PreparedParams> PreparedTotal<Params> for Prepared<Postgres, Params, Total> {
fn resolved_count(&self, params: Params) -> Result<(String, Vec<Value>)> {
Ok(self.resolve(params)?)
}
}
pub trait PreparedCountExt {
fn count<'e, Params, E: sqlx::PgExecutor<'e>>(
&self,
executor: E,
params: Params,
) -> impl std::future::Future<Output = Result<i64>>
where
Self: PreparedTotal<Params>,
{
let resolved = self.resolved_count(params);
async move { count_rows(executor, resolved?).await }
}
}
impl<D, Params, Output> PreparedCountExt for Prepared<D, Params, Output> {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn unresolved_placeholder_is_a_typed_error_not_a_sqlx_configuration_string() {
let query = sqlx::query(sqlx::AssertSqlSafe("SELECT $1"));
let err = match bind_all(query, vec![Value::Placeholder("email")]) {
Err(e) => e,
Ok(_) => panic!("unresolved placeholder must fail to bind"),
};
assert!(matches!(
err,
Error::UnresolvedPlaceholder(qbrs_core::select::UnresolvedPlaceholder("email"))
));
let _: &dyn std::error::Error = &err;
assert_eq!(err.to_string(), "no value provided for placeholder `email`");
}
#[test]
fn sqlx_errors_convert_via_from() {
let err: Error = sqlx::Error::RowNotFound.into();
assert!(matches!(err, Error::Sqlx(sqlx::Error::RowNotFound)));
}
}