query refactors

This commit is contained in:
Austin Bonander 2019-12-04 01:22:17 -08:00
parent acca40c88e
commit 193e79569a
16 changed files with 172 additions and 162 deletions

View file

@ -41,36 +41,33 @@ where
{
type Backend = DB;
fn execute<'e, 'q: 'e, I: 'e>(
fn execute<'e, 'q: 'e>(
&'e mut self,
query: &'q str,
params: I,
) -> BoxFuture<'e, crate::Result<u64>>
where
I: IntoQueryParameters<Self::Backend> + Send,
{
params: <DB as Backend>::QueryParameters,
) -> BoxFuture<'e, crate::Result<u64>> {
self.live.execute(query, params)
}
fn fetch<'e, 'q: 'e, I: 'e, T: 'e>(
fn fetch<'e, 'q: 'e, T: 'e>(
&'e mut self,
query: &'q str,
params: I,
params: <DB as Backend>::QueryParameters,
) -> BoxStream<'e, crate::Result<T>>
where
I: IntoQueryParameters<Self::Backend> + Send,
T: FromRow<Self::Backend> + Send + Unpin,
{
self.live.fetch(query, params)
}
fn fetch_optional<'e, 'q: 'e, I: 'e, T: 'e>(
fn fetch_optional<'e, 'q: 'e, T: 'e>(
&'e mut self,
query: &'q str,
params: I,
params: <DB as Backend>::QueryParameters,
) -> BoxFuture<'e, crate::Result<Option<T>>>
where
I: IntoQueryParameters<Self::Backend> + Send,
T: FromRow<Self::Backend> + Send,
{
self.live.fetch_optional(query, params)

View file

@ -1,6 +1,8 @@
//! Types and traits related to serializing values for the database.
use crate::{backend::Backend, types::HasSqlType};
use std::mem;
/// Annotates the result of [Encode] to differentiate between an empty value and a null value.
pub enum IsNull {
/// The value was null (and no data was written to the buffer).
@ -26,6 +28,11 @@ pub trait Encode<DB: Backend> {
/// The return value indicates if this value should be represented as `NULL`.
/// If this is the case, implementations **must not** write anything to `out`.
fn encode(&self, buf: &mut Vec<u8>) -> IsNull;
/// Calculate the number of bytes this type will use when encoded.
fn size_hint(&self) -> usize {
mem::size_of_val(self)
}
}
/// [Encode] is implemented for `Option<T>` where `T` implements `Encode`. An `Option<T>`
@ -43,6 +50,10 @@ where
IsNull::Yes
}
}
fn size_hint(&self) -> usize {
if self.is_some() { mem::size_of::<T>() } else { 0 }
}
}
impl<T: ?Sized, DB> Encode<DB> for &'_ T
@ -54,4 +65,8 @@ where
fn encode(&self, buf: &mut Vec<u8>) -> IsNull {
(*self).encode(buf)
}
fn size_hint(&self) -> usize {
(*self).size_hint()
}
}

View file

@ -16,57 +16,51 @@ pub trait Executor: Send {
Box::pin(
self.execute(
"SELECT 1",
<Self::Backend as Backend>::QueryParameters::new(),
Default::default(),
)
.map_ok(|_| ()),
)
}
fn execute<'e, 'q: 'e, I: 'e>(
fn execute<'e, 'q: 'e>(
&'e mut self,
query: &'q str,
params: I,
) -> BoxFuture<'e, crate::Result<u64>>
where
I: IntoQueryParameters<Self::Backend> + Send;
params: <Self::Backend as Backend>::QueryParameters,
) -> BoxFuture<'e, crate::Result<u64>>;
fn fetch<'e, 'q: 'e, I: 'e, T: 'e>(
fn fetch<'e, 'q: 'e, T: 'e>(
&'e mut self,
query: &'q str,
params: I,
params: <Self::Backend as Backend>::QueryParameters,
) -> BoxStream<'e, crate::Result<T>>
where
I: IntoQueryParameters<Self::Backend> + Send,
T: FromRow<Self::Backend> + Send + Unpin;
fn fetch_all<'e, 'q: 'e, I: 'e, T: 'e>(
fn fetch_all<'e, 'q: 'e, T: 'e>(
&'e mut self,
query: &'q str,
params: I,
params: <Self::Backend as Backend>::QueryParameters,
) -> BoxFuture<'e, crate::Result<Vec<T>>>
where
I: IntoQueryParameters<Self::Backend> + Send,
T: FromRow<Self::Backend> + Send + Unpin,
{
Box::pin(self.fetch(query, params).try_collect())
}
fn fetch_optional<'e, 'q: 'e, I: 'e, T: 'e>(
fn fetch_optional<'e, 'q: 'e, T: 'e>(
&'e mut self,
query: &'q str,
params: I,
params: <Self::Backend as Backend>::QueryParameters,
) -> BoxFuture<'e, crate::Result<Option<T>>>
where
I: IntoQueryParameters<Self::Backend> + Send,
T: FromRow<Self::Backend> + Send;
fn fetch_one<'e, 'q: 'e, I: 'e, T: 'e>(
fn fetch_one<'e, 'q: 'e, T: 'e>(
&'e mut self,
query: &'q str,
params: I,
params: <Self::Backend as Backend>::QueryParameters,
) -> BoxFuture<'e, crate::Result<T>>
where
I: IntoQueryParameters<Self::Backend> + Send,
T: FromRow<Self::Backend> + Send,
{
let fut = self.fetch_optional(query, params);

View file

@ -248,7 +248,7 @@ impl MariaDb {
self.write(ComStmtExecute {
statement_id,
params: &params.params,
null: &params.null,
null: &params.null_bitmap,
flags: StmtExecFlag::NO_CURSOR,
param_types: &params.param_types,
});

View file

@ -19,14 +19,11 @@ use futures_core::{future::BoxFuture, stream::BoxStream};
impl Executor for MariaDb {
type Backend = Self;
fn execute<'e, 'q: 'e, I: 'e>(
fn execute<'e, 'q: 'e>(
&'e mut self,
query: &'q str,
params: I,
) -> BoxFuture<'e, crate::Result<u64>>
where
I: IntoQueryParameters<Self::Backend> + Send,
{
params: MariaDbQueryParameters,
) -> BoxFuture<'e, crate::Result<u64>> {
let params = params.into_params();
Box::pin(async move {
@ -67,13 +64,12 @@ impl Executor for MariaDb {
})
}
fn fetch<'e, 'q: 'e, I: 'e, O: 'e, T: 'e>(
fn fetch<'e, 'q: 'e, O: 'e, T: 'e>(
&'e mut self,
query: &'q str,
params: I,
params: MariaDbQueryParameters,
) -> BoxStream<'e, crate::Result<T>>
where
I: IntoQueryParameters<Self::Backend> + Send,
T: FromRow<Self::Backend, O> + Send + Unpin,
{
let params = params.into_params();
@ -108,13 +104,12 @@ impl Executor for MariaDb {
})
}
fn fetch_optional<'e, 'q: 'e, I: 'e, O: 'e, T: 'e>(
fn fetch_optional<'e, 'q: 'e, O: 'e, T: 'e>(
&'e mut self,
query: &'q str,
params: I,
params: MariaDbQueryParameters,
) -> BoxFuture<'e, crate::Result<Option<T>>>
where
I: IntoQueryParameters<Self::Backend> + Send,
T: FromRow<Self::Backend, O> + Send,
{
let params = params.into_params();

View file

@ -6,21 +6,25 @@ use crate::{
types::HasSqlType,
};
#[derive(Default)]
pub struct MariaDbQueryParameters {
pub(crate) param_types: Vec<MariaDbTypeMetadata>,
pub(crate) params: Vec<u8>,
pub(crate) null: Vec<u8>,
pub(crate) null_bitmap: Vec<u8>,
}
impl QueryParameters for MariaDbQueryParameters {
type Backend = MariaDb;
fn new() -> Self {
Self {
param_types: Vec::with_capacity(4),
params: Vec::with_capacity(32),
null: vec![0],
}
fn reserve(&mut self, binds: usize, bytes: usize) {
self.param_types.reserve(binds);
self.params.reserve(bytes);
// ensure we have enough bytes in the bitmap to hold at least `binds` extra bits
// the second `& 7` gives us 0 spare bits when param_types.len() is a multiple of 8
let spare_bits = (8 - (self.param_types.len()) & 7) & 7;
// ensure that if there are no spare bits left, `binds = 1` reserves another byte
self.null_bitmap.reserve( (binds + 7 - spare_bits) / 8);
}
fn bind<T>(&mut self, value: T)
@ -33,9 +37,10 @@ impl QueryParameters for MariaDbQueryParameters {
let index = self.param_types.len();
self.param_types.push(metadata);
self.null_bitmap.resize(index / 8, 0);
if let IsNull::Yes = value.encode(&mut self.params) {
self.null[index / 8] = self.null[index / 8] & (1 << index % 8);
self.null_bitmap[index / 8] &= (1 << index % 8);
}
}
}

View file

@ -1,11 +1,9 @@
use crate::{backend::Backend, encode::Encode, types::HasSqlType};
pub trait QueryParameters: Send {
pub trait QueryParameters: Default + Send {
type Backend: Backend;
fn new() -> Self
where
Self: Sized;
fn reserve(&mut self, binds: usize, bytes: usize);
fn bind<T>(&mut self, value: T)
where
@ -29,8 +27,12 @@ macro_rules! impl_into_query_parameters {
$($T: crate::encode::Encode<$B>,)+
{
fn into_params(self) -> <$B as crate::backend::Backend>::QueryParameters {
let mut params = <<$B as crate::backend::Backend>::QueryParameters
as crate::params::QueryParameters>::new();
let mut params = <$B as crate::backend::Backend>::QueryParameters::default();
let binds = 0 $(+ { $idx; 1 } )+;
let bytes = 0 $(+ crate::encode::Encode::size_hint(&self.$idx))+;
params.reserve(binds, bytes);
$(crate::params::QueryParameters::bind(&mut params, self.$idx);)+
@ -57,8 +59,7 @@ macro_rules! impl_into_query_parameters_for_backend {
{
#[inline]
fn into_params(self) -> <$B as crate::backend::Backend>::QueryParameters {
<<$B as crate::backend::Backend>::QueryParameters
as crate::params::QueryParameters>::new()
Default::default()
}
}

View file

@ -11,24 +11,20 @@ where
{
type Backend = DB;
fn execute<'e, 'q: 'e, I: 'e>(
fn execute<'e, 'q: 'e>(
&'e mut self,
query: &'q str,
params: I,
) -> BoxFuture<'e, crate::Result<u64>>
where
I: IntoQueryParameters<Self::Backend> + Send,
{
params: DB::QueryParameters,
) -> BoxFuture<'e, crate::Result<u64>> {
Box::pin(async move { <&Pool<DB> as Executor>::execute(&mut &*self, query, params).await })
}
fn fetch<'e, 'q: 'e, I: 'e, T: 'e>(
fn fetch<'e, 'q: 'e, T: 'e>(
&'e mut self,
query: &'q str,
params: I,
params: DB::QueryParameters,
) -> BoxStream<'e, crate::Result<T>>
where
I: IntoQueryParameters<Self::Backend> + Send,
T: FromRow<Self::Backend> + Send + Unpin,
{
Box::pin(async_stream::try_stream! {
@ -41,13 +37,12 @@ where
})
}
fn fetch_optional<'e, 'q: 'e, I: 'e, T: 'e>(
fn fetch_optional<'e, 'q: 'e, T: 'e>(
&'e mut self,
query: &'q str,
params: I,
params: DB::QueryParameters,
) -> BoxFuture<'e, crate::Result<Option<T>>>
where
I: IntoQueryParameters<Self::Backend> + Send,
T: FromRow<Self::Backend> + Send,
{
Box::pin(async move {
@ -69,24 +64,20 @@ where
{
type Backend = DB;
fn execute<'e, 'q: 'e, I: 'e>(
fn execute<'e, 'q: 'e>(
&'e mut self,
query: &'q str,
params: I,
) -> BoxFuture<'e, crate::Result<u64>>
where
I: IntoQueryParameters<Self::Backend> + Send,
{
params: DB::QueryParameters,
) -> BoxFuture<'e, crate::Result<u64>> {
Box::pin(async move { self.0.acquire().await?.execute(query, params).await })
}
fn fetch<'e, 'q: 'e, I: 'e, T: 'e>(
fn fetch<'e, 'q: 'e, T: 'e>(
&'e mut self,
query: &'q str,
params: I,
params: DB::QueryParameters,
) -> BoxStream<'e, crate::Result<T>>
where
I: IntoQueryParameters<Self::Backend> + Send,
T: FromRow<Self::Backend> + Send + Unpin,
{
Box::pin(async_stream::try_stream! {
@ -99,13 +90,12 @@ where
})
}
fn fetch_optional<'e, 'q: 'e, I: 'e, T: 'e>(
fn fetch_optional<'e, 'q: 'e, T: 'e>(
&'e mut self,
query: &'q str,
params: I,
params: DB::QueryParameters,
) -> BoxFuture<'e, crate::Result<Option<T>>>
where
I: IntoQueryParameters<Self::Backend> + Send,
T: FromRow<Self::Backend> + Send,
{
Box::pin(async move { self.0.acquire().await?.fetch_optional(query, params).await })

View file

@ -8,21 +8,17 @@ use crate::{
url::Url,
};
use futures_core::{future::BoxFuture, stream::BoxStream};
use crate::postgres::query::PostgresQueryParameters;
impl Executor for Postgres {
type Backend = Self;
fn execute<'e, 'q: 'e, I: 'e>(
fn execute<'e, 'q: 'e>(
&'e mut self,
query: &'q str,
params: I,
) -> BoxFuture<'e, crate::Result<u64>>
where
I: IntoQueryParameters<Self::Backend> + Send,
{
params: PostgresQueryParameters,
) -> BoxFuture<'e, crate::Result<u64>> {
Box::pin(async move {
let params = params.into_params();
self.parse("", query, &params);
self.bind("", "", &params);
self.execute("", 1);
@ -40,17 +36,14 @@ impl Executor for Postgres {
})
}
fn fetch<'e, 'q: 'e, I: 'e, T: 'e>(
fn fetch<'e, 'q: 'e, T: 'e>(
&'e mut self,
query: &'q str,
params: I,
params: PostgresQueryParameters,
) -> BoxStream<'e, crate::Result<T>>
where
I: IntoQueryParameters<Self::Backend> + Send,
T: FromRow<Self::Backend> + Send + Unpin,
{
let params = params.into_params();
self.parse("", query, &params);
self.bind("", "", &params);
self.execute("", 0);
@ -66,18 +59,15 @@ impl Executor for Postgres {
})
}
fn fetch_optional<'e, 'q: 'e, I: 'e, T: 'e>(
fn fetch_optional<'e, 'q: 'e, T: 'e>(
&'e mut self,
query: &'q str,
params: I,
params: PostgresQueryParameters,
) -> BoxFuture<'e, crate::Result<Option<T>>>
where
I: IntoQueryParameters<Self::Backend> + Send,
T: FromRow<Self::Backend> + Send,
{
Box::pin(async move {
let params = params.into_params();
self.parse("", query, &params);
self.bind("", "", &params);
self.execute("", 2);
@ -104,7 +94,7 @@ impl Executor for Postgres {
query: &'q str,
) -> BoxFuture<'e, crate::Result<Describe<Self::Backend>>> {
Box::pin(async move {
self.parse("", query, &QueryParameters::new());
self.parse("", query, &Default::default());
self.describe("");
self.sync().await?;

View file

@ -7,6 +7,7 @@ use crate::{
};
use byteorder::{BigEndian, ByteOrder, NetworkEndian};
#[derive(Default)]
pub struct PostgresQueryParameters {
// OIDs of the bind parameters
pub(super) types: Vec<u32>,
@ -17,13 +18,9 @@ pub struct PostgresQueryParameters {
impl QueryParameters for PostgresQueryParameters {
type Backend = Postgres;
fn new() -> Self {
Self {
// Estimates for average number of bind parameters were
// chosen from sampling from internal projects
types: Vec::with_capacity(4),
buf: Vec::with_capacity(32),
}
fn reserve(&mut self, binds: usize, bytes: usize) {
self.types.reserve(binds);
self.buf.reserve(bytes);
}
fn bind<T>(&mut self, value: T)

View file

@ -31,6 +31,10 @@ impl Encode<Postgres> for Vec<u8> {
fn encode(&self, buf: &mut Vec<u8>) -> IsNull {
<[u8] as Encode<Postgres>>::encode(self, buf)
}
fn size_hint(&self) -> usize {
self.len()
}
}
impl Decode<Postgres> for Vec<u8> {

View file

@ -38,6 +38,10 @@ impl Encode<Postgres> for String {
fn encode(&self, buf: &mut Vec<u8>) -> IsNull {
<str as Encode<Postgres>>::encode(self.as_str(), buf)
}
fn size_hint(&self) -> usize {
self.len()
}
}
impl Decode<Postgres> for String {

View file

@ -1,84 +1,68 @@
use crate::{
backend::Backend,
encode::Encode,
error::Error,
executor::Executor,
params::{IntoQueryParameters, QueryParameters},
row::FromRow,
types::HasSqlType,
Row,
};
use crate::{backend::Backend, encode::Encode, error::Error, executor::Executor, params::{IntoQueryParameters, QueryParameters}, row::FromRow, types::HasSqlType, Row, Decode};
use bitflags::_core::marker::PhantomData;
use futures_core::{future::BoxFuture, stream::BoxStream};
pub struct Query<'q, DB, I = <DB as Backend>::QueryParameters, O = <DB as Backend>::Row>
pub struct Query<'q, DB, P = <DB as Backend>::QueryParameters, R = <DB as Backend>::Row>
where
DB: Backend,
{
#[doc(hidden)]
pub query: &'q str,
#[doc(hidden)]
pub input: I,
#[doc(hidden)]
pub output: PhantomData<O>,
#[doc(hidden)]
pub backend: PhantomData<DB>,
query: &'q str,
params: P,
record: PhantomData<R>,
backend: PhantomData<DB>,
}
impl<'q, DB, I: 'q, O: 'q> Query<'q, DB, I, O>
impl<'q, DB, P: 'q, R: 'q> Query<'q, DB, P, R>
where
DB: Backend,
DB::QueryParameters: 'q,
I: IntoQueryParameters<DB> + Send,
O: FromRow<DB> + Send + Unpin,
P: IntoQueryParameters<DB> + Send,
R: FromRow<DB> + Send + Unpin,
{
#[inline]
pub fn execute<E>(self, executor: &'q mut E) -> BoxFuture<'q, crate::Result<u64>>
where
E: Executor<Backend = DB>,
{
executor.execute(self.query, self.input)
executor.execute(self.query, self.params.into_params())
}
pub fn fetch<E>(self, executor: &'q mut E) -> BoxStream<'q, crate::Result<O>>
pub fn fetch<E>(self, executor: &'q mut E) -> BoxStream<'q, crate::Result<R>>
where
E: Executor<Backend = DB>,
{
executor.fetch(self.query, self.input)
executor.fetch(self.query, self.params.into_params())
}
pub fn fetch_all<E>(self, executor: &'q mut E) -> BoxFuture<'q, crate::Result<Vec<O>>>
pub fn fetch_all<E>(self, executor: &'q mut E) -> BoxFuture<'q, crate::Result<Vec<R>>>
where
E: Executor<Backend = DB>,
{
executor.fetch_all(self.query, self.input)
executor.fetch_all(self.query, self.params.into_params())
}
pub fn fetch_optional<E>(self, executor: &'q mut E) -> BoxFuture<'q, crate::Result<Option<O>>>
pub fn fetch_optional<E>(self, executor: &'q mut E) -> BoxFuture<'q, crate::Result<Option<R>>>
where
E: Executor<Backend = DB>,
{
executor.fetch_optional(self.query, self.input)
executor.fetch_optional(self.query, self.params.into_params())
}
pub fn fetch_one<E>(self, executor: &'q mut E) -> BoxFuture<'q, crate::Result<O>>
pub fn fetch_one<E>(self, executor: &'q mut E) -> BoxFuture<'q, crate::Result<R>>
where
E: Executor<Backend = DB>,
{
executor.fetch_one(self.query, self.input)
executor.fetch_one(self.query, self.params.into_params())
}
}
impl<DB> Query<'_, DB>
impl<'q, DB> Query<'q, DB>
where
DB: Backend,
{
/// Bind a value for use with this SQL query.
///
/// # Safety
/// # Logic Safety
///
/// This function should be used with care, as SQLx cannot validate
/// that the value is of the right type nor can it validate that you have
@ -88,17 +72,47 @@ where
DB: HasSqlType<T>,
T: Encode<DB>,
{
self.input.bind(value);
self.params.bind(value);
self
}
}
impl<'q, DB, I, O> Query<'q, DB, I, O> where DB: Backend {
pub fn with_output_type<O_>(self) -> Query<'q, DB, I, O_> {
/// Bind all query parameters at once.
///
/// If any parameters were previously bound with `.bind()` they are discarded.
///
/// # Logic Safety
///
/// This function should be used with care, as SQLx cannot validate
/// that the value is of the right type nor can it validate that you have
/// passed the correct number of parameters.
pub fn bind_all<I>(self, values: I) -> Query<'q, DB, I> where I: IntoQueryParameters<DB> {
Query {
query: self.query,
input: self.input,
output: PhantomData,
params: values,
record: PhantomData,
backend: PhantomData
}
}
}
//noinspection RsSelfConvention
impl<'q, DB, I, R> Query<'q, DB, I, R> where DB: Backend {
/// Change the expected output type of the query to a single scalar value.
pub fn as_scalar<R_>(self) -> Query<'q, DB, I, R_> where R_: Decode<DB> {
Query {
query: self.query,
params: self.params,
record: PhantomData,
backend: PhantomData,
}
}
/// Change the expected output of the query to a new type implementing `FromRow`.
pub fn as_record<R_>(self) -> Query<'q, DB, I, R_> where R_: FromRow<DB> {
Query {
query: self.query,
params: self.params,
record: PhantomData,
backend: PhantomData,
}
}
@ -112,8 +126,8 @@ where
{
Query {
query,
input: DB::QueryParameters::new(),
output: PhantomData,
params: Default::default(),
record: PhantomData,
backend: PhantomData,
}
}

View file

@ -39,6 +39,13 @@ macro_rules! impl_from_row {
};
}
/// Scalar conversions for rows
impl<T, DB> FromRow<DB> for T where DB: Backend + HasSqlType<T>, T: Decode<DB> {
fn from_row(row: <DB as Backend>::Row) -> Self {
row.get(0)
}
}
#[allow(unused)]
macro_rules! impl_from_row_for_backend {
($B:ident, $row:ident) => {

View file

@ -132,12 +132,9 @@ pub async fn process_sql<DB: BackendExt>(
#params
sqlx::Query::<#backend_path, _, (#record_type)> {
query: #query,
input: params,
output: ::core::marker::PhantomData,
backend: ::core::marker::PhantomData,
}
sqlx::query::<#backend_path>(#query)
.bind_all(params)
.as_record::<#record_type>()
}})
}

View file

@ -6,12 +6,12 @@ async fn postgres_query() -> sqlx::Result<()> {
sqlx::Connection::<sqlx::Postgres>::open(&dotenv::var("DATABASE_URL").unwrap()).await?;
let uuid: Uuid = "256ba9c8-0048-11ea-b0f0-8f04859d047e".parse().unwrap();
let account = sqlx::query!("SELECT * from accounts where id != $1", uuid)
let account_id = sqlx::query!("SELECT id from accounts where id != $1", uuid)
.as_scalar::<Uuid>()
.fetch_one(&mut conn)
.await?;
println!("account ID: {:?}", account.id);
println!("account name: {}", account.name);
println!("account ID: {:?}", account_id);
Ok(())
}