Make Encode return a result (#3126)

* Make encode and encode_by_ref fallible

This only changes the trait for now and makes it compile, calling .expect() on all users. Those will be removed in a later commit.

* PgNumeric: Turn TryFrom Decimal to an infallible From

* Turn panics in Encode implementations into errors

* Add Encode error analogous to the Decode error

* Propagate decode errors through Arguments::add

This pushes the panics one level further to mostly bind calls. Those will also be removed later.

* Only check argument encoding at the end

* Use Result in Query internally

* Implement query_with functions in terms of _with_result

* Surface encode errors when executing a query.

* Remove remaining panics in AnyConnectionBackend implementations

* PostgreSQL BigDecimal: Return encode error immediately

* Arguments: Add len method to report how many arguments were added

* Query::bind: Report which argument failed to encode

* IsNull: Add is_null method

* MySqlArguments: Replace manual bitmap code with NullBitMap helper type

* Roll back buffer in MySqlArguments if encoding fails

* Roll back buffer in SqliteArguments if encoding fails

* Roll back PgArgumentBuffer if encoding fails
This commit is contained in:
Max Bruckner 2024-05-31 21:42:36 +02:00 committed by GitHub
parent 6c1e3a4e61
commit c57b46ceb6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
92 changed files with 791 additions and 455 deletions

View file

@ -1,7 +1,8 @@
use crate::any::value::AnyValueKind;
use crate::any::Any;
use crate::arguments::Arguments;
use crate::encode::Encode;
use crate::encode::{Encode, IsNull};
use crate::error::BoxDynError;
use crate::types::Type;
pub struct AnyArguments<'q> {
@ -16,11 +17,16 @@ impl<'q> Arguments<'q> for AnyArguments<'q> {
self.values.0.reserve(additional);
}
fn add<T>(&mut self, value: T)
fn add<T>(&mut self, value: T) -> Result<(), BoxDynError>
where
T: 'q + Encode<'q, Self::Database> + Type<Self::Database>,
{
let _ = value.encode(&mut self.values);
let _: IsNull = value.encode(&mut self.values)?;
Ok(())
}
fn len(&self) -> usize {
self.values.0.len()
}
}
@ -36,7 +42,7 @@ impl<'q> Default for AnyArguments<'q> {
impl<'q> AnyArguments<'q> {
#[doc(hidden)]
pub fn convert_to<'a, A: Arguments<'a>>(&'a self) -> A
pub fn convert_to<'a, A: Arguments<'a>>(&'a self) -> Result<A, BoxDynError>
where
'q: 'a,
Option<i32>: Type<A::Database> + Encode<'a, A::Database>,
@ -62,9 +68,9 @@ impl<'q> AnyArguments<'q> {
AnyValueKind::Double(d) => out.add(d),
AnyValueKind::Text(t) => out.add(&**t),
AnyValueKind::Blob(b) => out.add(&**b),
}
}?
}
out
Ok(out)
}
}

View file

@ -5,6 +5,8 @@ use crate::executor::{Execute, Executor};
use either::Either;
use futures_core::future::BoxFuture;
use futures_core::stream::BoxStream;
use futures_util::{stream, FutureExt, StreamExt};
use std::future;
impl<'c> Executor<'c> for &'c mut AnyConnection {
type Database = Any;
@ -17,7 +19,10 @@ impl<'c> Executor<'c> for &'c mut AnyConnection {
'c: 'e,
E: Execute<'q, Any>,
{
let arguments = query.take_arguments();
let arguments = match query.take_arguments().map_err(Error::Encode) {
Ok(arguments) => arguments,
Err(error) => return stream::once(future::ready(Err(error))).boxed(),
};
self.backend.fetch_many(query.sql(), arguments)
}
@ -29,7 +34,10 @@ impl<'c> Executor<'c> for &'c mut AnyConnection {
'c: 'e,
E: Execute<'q, Self::Database>,
{
let arguments = query.take_arguments();
let arguments = match query.take_arguments().map_err(Error::Encode) {
Ok(arguments) => arguments,
Err(error) => return future::ready(Err(error)).boxed(),
};
self.backend.fetch_optional(query.sql(), arguments)
}

View file

@ -67,12 +67,15 @@ impl<'q, T> Encode<'q, Any> for Option<T>
where
T: Encode<'q, Any> + 'q,
{
fn encode_by_ref(&self, buf: &mut AnyArgumentBuffer<'q>) -> crate::encode::IsNull {
fn encode_by_ref(
&self,
buf: &mut AnyArgumentBuffer<'q>,
) -> Result<crate::encode::IsNull, crate::error::BoxDynError> {
if let Some(value) = self {
value.encode_by_ref(buf)
} else {
buf.0.push(AnyValueKind::Null);
crate::encode::IsNull::Yes
Ok(crate::encode::IsNull::Yes)
}
}
}

View file

@ -15,9 +15,12 @@ impl Type<Any> for [u8] {
}
impl<'q> Encode<'q, Any> for &'q [u8] {
fn encode_by_ref(&self, buf: &mut <Any as Database>::ArgumentBuffer<'q>) -> IsNull {
fn encode_by_ref(
&self,
buf: &mut <Any as Database>::ArgumentBuffer<'q>,
) -> Result<IsNull, BoxDynError> {
buf.0.push(AnyValueKind::Blob((*self).into()));
IsNull::No
Ok(IsNull::No)
}
}
@ -42,9 +45,12 @@ impl Type<Any> for Vec<u8> {
}
impl<'q> Encode<'q, Any> for Vec<u8> {
fn encode_by_ref(&self, buf: &mut <Any as Database>::ArgumentBuffer<'q>) -> IsNull {
fn encode_by_ref(
&self,
buf: &mut <Any as Database>::ArgumentBuffer<'q>,
) -> Result<IsNull, BoxDynError> {
buf.0.push(AnyValueKind::Blob(Cow::Owned(self.clone())));
IsNull::No
Ok(IsNull::No)
}
}

View file

@ -14,9 +14,12 @@ impl Type<Any> for bool {
}
impl<'q> Encode<'q, Any> for bool {
fn encode_by_ref(&self, buf: &mut <Any as Database>::ArgumentBuffer<'q>) -> IsNull {
fn encode_by_ref(
&self,
buf: &mut <Any as Database>::ArgumentBuffer<'q>,
) -> Result<IsNull, BoxDynError> {
buf.0.push(AnyValueKind::Bool(*self));
IsNull::No
Ok(IsNull::No)
}
}

View file

@ -14,9 +14,9 @@ impl Type<Any> for f32 {
}
impl<'q> Encode<'q, Any> for f32 {
fn encode_by_ref(&self, buf: &mut AnyArgumentBuffer<'q>) -> IsNull {
fn encode_by_ref(&self, buf: &mut AnyArgumentBuffer<'q>) -> Result<IsNull, BoxDynError> {
buf.0.push(AnyValueKind::Real(*self));
IsNull::No
Ok(IsNull::No)
}
}
@ -38,9 +38,12 @@ impl Type<Any> for f64 {
}
impl<'q> Encode<'q, Any> for f64 {
fn encode_by_ref(&self, buf: &mut <Any as Database>::ArgumentBuffer<'q>) -> IsNull {
fn encode_by_ref(
&self,
buf: &mut <Any as Database>::ArgumentBuffer<'q>,
) -> Result<IsNull, BoxDynError> {
buf.0.push(AnyValueKind::Double(*self));
IsNull::No
Ok(IsNull::No)
}
}

View file

@ -18,9 +18,12 @@ impl Type<Any> for i16 {
}
impl<'q> Encode<'q, Any> for i16 {
fn encode_by_ref(&self, buf: &mut <Any as Database>::ArgumentBuffer<'q>) -> IsNull {
fn encode_by_ref(
&self,
buf: &mut <Any as Database>::ArgumentBuffer<'q>,
) -> Result<IsNull, BoxDynError> {
buf.0.push(AnyValueKind::SmallInt(*self));
IsNull::No
Ok(IsNull::No)
}
}
@ -43,9 +46,12 @@ impl Type<Any> for i32 {
}
impl<'q> Encode<'q, Any> for i32 {
fn encode_by_ref(&self, buf: &mut <Any as Database>::ArgumentBuffer<'q>) -> IsNull {
fn encode_by_ref(
&self,
buf: &mut <Any as Database>::ArgumentBuffer<'q>,
) -> Result<IsNull, BoxDynError> {
buf.0.push(AnyValueKind::Integer(*self));
IsNull::No
Ok(IsNull::No)
}
}
@ -68,9 +74,12 @@ impl Type<Any> for i64 {
}
impl<'q> Encode<'q, Any> for i64 {
fn encode_by_ref(&self, buf: &mut <Any as Database>::ArgumentBuffer<'q>) -> IsNull {
fn encode_by_ref(
&self,
buf: &mut <Any as Database>::ArgumentBuffer<'q>,
) -> Result<IsNull, BoxDynError> {
buf.0.push(AnyValueKind::BigInt(*self));
IsNull::No
Ok(IsNull::No)
}
}

View file

@ -16,15 +16,18 @@ impl Type<Any> for str {
}
impl<'a> Encode<'a, Any> for &'a str {
fn encode(self, buf: &mut <Any as Database>::ArgumentBuffer<'a>) -> IsNull
fn encode(self, buf: &mut <Any as Database>::ArgumentBuffer<'a>) -> Result<IsNull, BoxDynError>
where
Self: Sized,
{
buf.0.push(AnyValueKind::Text(self.into()));
IsNull::No
Ok(IsNull::No)
}
fn encode_by_ref(&self, buf: &mut <Any as Database>::ArgumentBuffer<'a>) -> IsNull {
fn encode_by_ref(
&self,
buf: &mut <Any as Database>::ArgumentBuffer<'a>,
) -> Result<IsNull, BoxDynError> {
(*self).encode(buf)
}
}
@ -50,9 +53,12 @@ impl Type<Any> for String {
}
impl<'q> Encode<'q, Any> for String {
fn encode_by_ref(&self, buf: &mut <Any as Database>::ArgumentBuffer<'q>) -> IsNull {
fn encode_by_ref(
&self,
buf: &mut <Any as Database>::ArgumentBuffer<'q>,
) -> Result<IsNull, BoxDynError> {
buf.0.push(AnyValueKind::Text(Cow::Owned(self.clone())));
IsNull::No
Ok(IsNull::No)
}
}

View file

@ -2,6 +2,7 @@
use crate::database::Database;
use crate::encode::Encode;
use crate::error::BoxDynError;
use crate::types::Type;
use std::fmt::{self, Write};
@ -14,10 +15,13 @@ pub trait Arguments<'q>: Send + Sized + Default {
fn reserve(&mut self, additional: usize, size: usize);
/// Add the value to the end of the arguments.
fn add<T>(&mut self, value: T)
fn add<T>(&mut self, value: T) -> Result<(), BoxDynError>
where
T: 'q + Encode<'q, Self::Database> + Type<Self::Database>;
/// The number of arguments that were already added.
fn len(&self) -> usize;
fn format_placeholder<W: Write>(&self, writer: &mut W) -> fmt::Result {
writer.write_str("?")
}

View file

@ -3,8 +3,10 @@
use std::mem;
use crate::database::Database;
use crate::error::BoxDynError;
/// The return type of [Encode::encode].
#[must_use]
pub enum IsNull {
/// The value is null; no data was written.
Yes,
@ -15,11 +17,16 @@ pub enum IsNull {
No,
}
impl IsNull {
pub fn is_null(&self) -> bool {
matches!(self, IsNull::Yes)
}
}
/// Encode a single value to be sent to the database.
pub trait Encode<'q, DB: Database> {
/// Writes the value of `self` into `buf` in the expected format for the database.
#[must_use]
fn encode(self, buf: &mut <DB as Database>::ArgumentBuffer<'q>) -> IsNull
fn encode(self, buf: &mut <DB as Database>::ArgumentBuffer<'q>) -> Result<IsNull, BoxDynError>
where
Self: Sized,
{
@ -30,8 +37,10 @@ pub trait Encode<'q, DB: Database> {
///
/// Where possible, make use of `encode` instead as it can take advantage of re-using
/// memory.
#[must_use]
fn encode_by_ref(&self, buf: &mut <DB as Database>::ArgumentBuffer<'q>) -> IsNull;
fn encode_by_ref(
&self,
buf: &mut <DB as Database>::ArgumentBuffer<'q>,
) -> Result<IsNull, BoxDynError>;
fn produces(&self) -> Option<DB::TypeInfo> {
// `produces` is inherently a hook to allow database drivers to produce value-dependent
@ -50,12 +59,15 @@ where
T: Encode<'q, DB>,
{
#[inline]
fn encode(self, buf: &mut <DB as Database>::ArgumentBuffer<'q>) -> IsNull {
fn encode(self, buf: &mut <DB as Database>::ArgumentBuffer<'q>) -> Result<IsNull, BoxDynError> {
<T as Encode<DB>>::encode_by_ref(self, buf)
}
#[inline]
fn encode_by_ref(&self, buf: &mut <DB as Database>::ArgumentBuffer<'q>) -> IsNull {
fn encode_by_ref(
&self,
buf: &mut <DB as Database>::ArgumentBuffer<'q>,
) -> Result<IsNull, BoxDynError> {
<&T as Encode<DB>>::encode(self, buf)
}
@ -90,11 +102,11 @@ macro_rules! impl_encode_for_option {
fn encode(
self,
buf: &mut <$DB as $crate::database::Database>::ArgumentBuffer<'q>,
) -> $crate::encode::IsNull {
) -> Result<$crate::encode::IsNull, $crate::error::BoxDynError> {
if let Some(v) = self {
v.encode(buf)
} else {
$crate::encode::IsNull::Yes
Ok($crate::encode::IsNull::Yes)
}
}
@ -102,11 +114,11 @@ macro_rules! impl_encode_for_option {
fn encode_by_ref(
&self,
buf: &mut <$DB as $crate::database::Database>::ArgumentBuffer<'q>,
) -> $crate::encode::IsNull {
) -> Result<$crate::encode::IsNull, $crate::error::BoxDynError> {
if let Some(v) = self {
v.encode_by_ref(buf)
} else {
$crate::encode::IsNull::Yes
Ok($crate::encode::IsNull::Yes)
}
}

View file

@ -78,6 +78,10 @@ pub enum Error {
source: BoxDynError,
},
/// Error occured while encoding a value.
#[error("error occured while encoding a value: {0}")]
Encode(#[source] BoxDynError),
/// Error occurred while decoding a value.
#[error("error occurred while decoding: {0}")]
Decode(#[source] BoxDynError),

View file

@ -1,6 +1,6 @@
use crate::database::Database;
use crate::describe::Describe;
use crate::error::Error;
use crate::error::{BoxDynError, Error};
use either::Either;
use futures_core::future::BoxFuture;
@ -199,10 +199,12 @@ pub trait Execute<'q, DB: Database>: Send + Sized {
/// Returns the arguments to be bound against the query string.
///
/// Returning `None` for `Arguments` indicates to use a "simple" query protocol and to not
/// prepare the query. Returning `Some(Default::default())` is an empty arguments object that
/// Returning `Ok(None)` for `Arguments` indicates to use a "simple" query protocol and to not
/// prepare the query. Returning `Ok(Some(Default::default()))` is an empty arguments object that
/// will be prepared (and cached) before execution.
fn take_arguments(&mut self) -> Option<<DB as Database>::Arguments<'q>>;
///
/// Returns `Err` if encoding any of the arguments failed.
fn take_arguments(&mut self) -> Result<Option<<DB as Database>::Arguments<'q>>, BoxDynError>;
/// Returns `true` if the statement should be cached.
fn persistent(&self) -> bool;
@ -222,8 +224,8 @@ impl<'q, DB: Database> Execute<'q, DB> for &'q str {
}
#[inline]
fn take_arguments(&mut self) -> Option<<DB as Database>::Arguments<'q>> {
None
fn take_arguments(&mut self) -> Result<Option<<DB as Database>::Arguments<'q>>, BoxDynError> {
Ok(None)
}
#[inline]
@ -244,8 +246,8 @@ impl<'q, DB: Database> Execute<'q, DB> for (&'q str, Option<<DB as Database>::Ar
}
#[inline]
fn take_arguments(&mut self) -> Option<<DB as Database>::Arguments<'q>> {
self.1.take()
fn take_arguments(&mut self) -> Result<Option<<DB as Database>::Arguments<'q>>, BoxDynError> {
Ok(self.1.take())
}
#[inline]

View file

@ -7,7 +7,7 @@ use futures_util::{future, StreamExt, TryFutureExt, TryStreamExt};
use crate::arguments::{Arguments, IntoArguments};
use crate::database::{Database, HasStatementCache};
use crate::encode::Encode;
use crate::error::Error;
use crate::error::{BoxDynError, Error};
use crate::executor::{Execute, Executor};
use crate::statement::Statement;
use crate::types::Type;
@ -16,7 +16,7 @@ use crate::types::Type;
#[must_use = "query must be executed to affect database"]
pub struct Query<'q, DB: Database, A> {
pub(crate) statement: Either<&'q str, &'q DB::Statement<'q>>,
pub(crate) arguments: Option<A>,
pub(crate) arguments: Option<Result<A, BoxDynError>>,
pub(crate) database: PhantomData<DB>,
pub(crate) persistent: bool,
}
@ -59,8 +59,11 @@ where
}
#[inline]
fn take_arguments(&mut self) -> Option<<DB as Database>::Arguments<'q>> {
self.arguments.take().map(IntoArguments::into_arguments)
fn take_arguments(&mut self) -> Result<Option<<DB as Database>::Arguments<'q>>, BoxDynError> {
self.arguments
.take()
.transpose()
.map(|option| option.map(IntoArguments::into_arguments))
}
#[inline]
@ -78,13 +81,43 @@ impl<'q, DB: Database> Query<'q, DB, <DB as Database>::Arguments<'q>> {
///
/// There is no validation that the value is of the type expected by the query. Most SQL
/// flavors will perform type coercion (Postgres will return a database error).
///
/// If encoding the value fails, the error is stored and later surfaced when executing the query.
pub fn bind<T: 'q + Encode<'q, DB> + Type<DB>>(mut self, value: T) -> Self {
if let Some(arguments) = &mut self.arguments {
arguments.add(value);
let Ok(arguments) = self.get_arguments() else {
return self;
};
let argument_number = arguments.len() + 1;
if let Err(error) = arguments.add(value) {
self.arguments = Some(Err(format!(
"Encoding argument ${argument_number} failed: {error}"
)
.into()));
}
self
}
/// Like [`Query::try_bind`] but immediately returns an error if encoding the value failed.
pub fn try_bind<T: 'q + Encode<'q, DB> + Type<DB>>(
&mut self,
value: T,
) -> Result<(), BoxDynError> {
let arguments = self.get_arguments()?;
arguments.add(value)
}
fn get_arguments(&mut self) -> Result<&mut DB::Arguments<'q>, BoxDynError> {
let Some(Ok(arguments)) = self.arguments.as_mut().map(Result::as_mut) else {
return Err("A previous call to Query::bind produced an error"
.to_owned()
.into());
};
Ok(arguments)
}
}
impl<'q, DB, A> Query<'q, DB, A>
@ -280,7 +313,7 @@ where
}
#[inline]
fn take_arguments(&mut self) -> Option<<DB as Database>::Arguments<'q>> {
fn take_arguments(&mut self) -> Result<Option<<DB as Database>::Arguments<'q>>, BoxDynError> {
self.inner.take_arguments()
}
@ -472,7 +505,7 @@ where
{
Query {
database: PhantomData,
arguments: Some(Default::default()),
arguments: Some(Ok(Default::default())),
statement: Either::Right(statement),
persistent: true,
}
@ -489,7 +522,7 @@ where
{
Query {
database: PhantomData,
arguments: Some(arguments),
arguments: Some(Ok(arguments)),
statement: Either::Right(statement),
persistent: true,
}
@ -625,7 +658,7 @@ where
{
Query {
database: PhantomData,
arguments: Some(Default::default()),
arguments: Some(Ok(Default::default())),
statement: Either::Left(sql),
persistent: true,
}
@ -635,6 +668,18 @@ where
///
/// See [`query()`][query] for details, such as supported syntax.
pub fn query_with<'q, DB, A>(sql: &'q str, arguments: A) -> Query<'q, DB, A>
where
DB: Database,
A: IntoArguments<'q, DB>,
{
query_with_result(sql, Ok(arguments))
}
/// Same as [`query_with`] but is initialized with a Result of arguments instead
pub fn query_with_result<'q, DB, A>(
sql: &'q str,
arguments: Result<A, BoxDynError>,
) -> Query<'q, DB, A>
where
DB: Database,
A: IntoArguments<'q, DB>,

View file

@ -7,10 +7,10 @@ use futures_util::{StreamExt, TryStreamExt};
use crate::arguments::IntoArguments;
use crate::database::{Database, HasStatementCache};
use crate::encode::Encode;
use crate::error::Error;
use crate::error::{BoxDynError, Error};
use crate::executor::{Execute, Executor};
use crate::from_row::FromRow;
use crate::query::{query, query_statement, query_statement_with, query_with, Query};
use crate::query::{query, query_statement, query_statement_with, query_with_result, Query};
use crate::types::Type;
/// A single SQL query as a prepared statement, mapping results using [`FromRow`].
@ -37,7 +37,7 @@ where
}
#[inline]
fn take_arguments(&mut self) -> Option<<DB as Database>::Arguments<'q>> {
fn take_arguments(&mut self) -> Result<Option<<DB as Database>::Arguments<'q>>, BoxDynError> {
self.inner.take_arguments()
}
@ -358,13 +358,27 @@ where
/// For details about type mapping from [`FromRow`], see [`query_as()`].
#[inline]
pub fn query_as_with<'q, DB, O, A>(sql: &'q str, arguments: A) -> QueryAs<'q, DB, O, A>
where
DB: Database,
A: IntoArguments<'q, DB>,
O: for<'r> FromRow<'r, DB::Row>,
{
query_as_with_result(sql, Ok(arguments))
}
/// Same as [`query_as_with`] but takes arguments as a Result
#[inline]
pub fn query_as_with_result<'q, DB, O, A>(
sql: &'q str,
arguments: Result<A, BoxDynError>,
) -> QueryAs<'q, DB, O, A>
where
DB: Database,
A: IntoArguments<'q, DB>,
O: for<'r> FromRow<'r, DB::Row>,
{
QueryAs {
inner: query_with(sql, arguments),
inner: query_with_result(sql, arguments),
output: PhantomData,
}
}

View file

@ -155,7 +155,7 @@ where
.arguments
.as_mut()
.expect("BUG: Arguments taken already");
arguments.add(value);
arguments.add(value).expect("Failed to add argument");
arguments
.format_placeholder(&mut self.query)
@ -450,7 +450,7 @@ where
Query {
statement: Either::Left(&self.query),
arguments: self.arguments.take(),
arguments: self.arguments.take().map(Ok),
database: PhantomData,
persistent: true,
}

View file

@ -5,11 +5,11 @@ use futures_util::{StreamExt, TryFutureExt, TryStreamExt};
use crate::arguments::IntoArguments;
use crate::database::{Database, HasStatementCache};
use crate::encode::Encode;
use crate::error::Error;
use crate::error::{BoxDynError, Error};
use crate::executor::{Execute, Executor};
use crate::from_row::FromRow;
use crate::query_as::{
query_as, query_as_with, query_statement_as, query_statement_as_with, QueryAs,
query_as, query_as_with_result, query_statement_as, query_statement_as_with, QueryAs,
};
use crate::types::Type;
@ -34,7 +34,7 @@ where
}
#[inline]
fn take_arguments(&mut self) -> Option<<DB as Database>::Arguments<'q>> {
fn take_arguments(&mut self) -> Result<Option<<DB as Database>::Arguments<'q>>, BoxDynError> {
self.inner.take_arguments()
}
@ -338,13 +338,27 @@ where
/// For details about prepared statements and allowed SQL syntax, see [`query()`][crate::query::query].
#[inline]
pub fn query_scalar_with<'q, DB, O, A>(sql: &'q str, arguments: A) -> QueryScalar<'q, DB, O, A>
where
DB: Database,
A: IntoArguments<'q, DB>,
(O,): for<'r> FromRow<'r, DB::Row>,
{
query_scalar_with_result(sql, Ok(arguments))
}
/// Same as [`query_scalar_with`] but takes arguments as Result
#[inline]
pub fn query_scalar_with_result<'q, DB, O, A>(
sql: &'q str,
arguments: Result<A, BoxDynError>,
) -> QueryScalar<'q, DB, O, A>
where
DB: Database,
A: IntoArguments<'q, DB>,
(O,): for<'r> FromRow<'r, DB::Row>,
{
QueryScalar {
inner: query_as_with(sql, arguments),
inner: query_as_with_result(sql, arguments),
}
}

View file

@ -2,6 +2,7 @@ use either::Either;
use futures_core::stream::BoxStream;
use crate::database::Database;
use crate::error::BoxDynError;
use crate::executor::{Execute, Executor};
use crate::Error;
@ -126,8 +127,8 @@ impl<'q, DB: Database> Execute<'q, DB> for RawSql<'q> {
None
}
fn take_arguments(&mut self) -> Option<<DB as Database>::Arguments<'q>> {
None
fn take_arguments(&mut self) -> Result<Option<<DB as Database>::Arguments<'q>>, BoxDynError> {
Ok(None)
}
fn persistent(&self) -> bool {

View file

@ -37,7 +37,10 @@ where
DB: Database,
&'q [u8]: Encode<'q, DB>,
{
fn encode_by_ref(&self, buf: &mut <DB as Database>::ArgumentBuffer<'q>) -> IsNull {
fn encode_by_ref(
&self,
buf: &mut <DB as Database>::ArgumentBuffer<'q>,
) -> Result<IsNull, BoxDynError> {
<&[u8] as Encode<DB>>::encode(self.as_bytes(), buf)
}
}
@ -47,7 +50,10 @@ where
DB: Database,
Vec<u8>: Encode<'q, DB>,
{
fn encode_by_ref(&self, buf: &mut <DB as Database>::ArgumentBuffer<'q>) -> IsNull {
fn encode_by_ref(
&self,
buf: &mut <DB as Database>::ArgumentBuffer<'q>,
) -> Result<IsNull, BoxDynError> {
<Vec<u8> as Encode<DB>>::encode(self.as_bytes().to_vec(), buf)
}
}

View file

@ -89,21 +89,15 @@ impl<T> AsMut<T> for Json<T> {
}
}
const JSON_SERIALIZE_ERR: &str = "failed to encode value as JSON; the most likely cause is \
attempting to serialize a map with a non-string key type";
// UNSTABLE: for driver use only!
#[doc(hidden)]
impl<T: Serialize> Json<T> {
pub fn encode_to_string(&self) -> String {
// Encoding is supposed to be infallible so we don't have much choice but to panic here.
// However, I believe that's the right thing to do anyway as an object being unable
// to serialize to JSON is likely due to a bug or a malformed datastructure.
serde_json::to_string(self).expect(JSON_SERIALIZE_ERR)
pub fn encode_to_string(&self) -> Result<String, serde_json::Error> {
serde_json::to_string(self)
}
pub fn encode_to(&self, buf: &mut Vec<u8>) {
serde_json::to_writer(buf, self).expect(JSON_SERIALIZE_ERR)
pub fn encode_to(&self, buf: &mut Vec<u8>) -> Result<(), serde_json::Error> {
serde_json::to_writer(buf, self)
}
}
@ -141,7 +135,10 @@ where
for<'a> Json<&'a Self>: Encode<'q, DB>,
DB: Database,
{
fn encode_by_ref(&self, buf: &mut <DB as Database>::ArgumentBuffer<'q>) -> IsNull {
fn encode_by_ref(
&self,
buf: &mut <DB as Database>::ArgumentBuffer<'q>,
) -> Result<IsNull, BoxDynError> {
<Json<&Self> as Encode<'q, DB>>::encode(Json(self), buf)
}
}

View file

@ -115,7 +115,7 @@ where
String: Encode<'q, DB>,
DB: Database,
{
fn encode_by_ref(&self, buf: &mut <DB as Database>::ArgumentBuffer<'q>) -> IsNull {
fn encode_by_ref(&self, buf: &mut <DB as Database>::ArgumentBuffer<'q>) -> Result<IsNull, BoxDynError> {
self.0.to_string().encode(buf)
}
}

View file

@ -85,7 +85,7 @@ fn expand_derive_encode_transparent(
fn encode_by_ref(
&self,
buf: &mut <DB as ::sqlx::database::Database>::ArgumentBuffer<#lifetime>,
) -> ::sqlx::encode::IsNull {
) -> ::std::result::Result<::sqlx::encode::IsNull, ::sqlx::error::BoxDynError> {
<#ty as ::sqlx::encode::Encode<#lifetime, DB>>::encode_by_ref(&self.0, buf)
}
@ -124,7 +124,7 @@ fn expand_derive_encode_weak_enum(
fn encode_by_ref(
&self,
buf: &mut <DB as ::sqlx::database::Database>::ArgumentBuffer<'q>,
) -> ::sqlx::encode::IsNull {
) -> ::std::result::Result<::sqlx::encode::IsNull, ::sqlx::error::BoxDynError> {
let value = match self {
#(#values)*
};
@ -174,7 +174,7 @@ fn expand_derive_encode_strong_enum(
fn encode_by_ref(
&self,
buf: &mut <DB as ::sqlx::database::Database>::ArgumentBuffer<'q>,
) -> ::sqlx::encode::IsNull {
) -> ::std::result::Result<::sqlx::encode::IsNull, ::sqlx::error::BoxDynError> {
let val = match self {
#(#value_arms)*
};
@ -228,7 +228,7 @@ fn expand_derive_encode_struct(
let id = &field.ident;
parse_quote!(
encoder.encode(&self. #id);
encoder.encode(&self. #id)?;
)
});
@ -249,14 +249,14 @@ fn expand_derive_encode_struct(
fn encode_by_ref(
&self,
buf: &mut ::sqlx::postgres::PgArgumentBuffer,
) -> ::sqlx::encode::IsNull {
) -> ::std::result::Result<::sqlx::encode::IsNull, ::sqlx::error::BoxDynError> {
let mut encoder = ::sqlx::postgres::types::PgRecordEncoder::new(buf);
#(#writes)*
encoder.finish();
::sqlx::encode::IsNull::No
::std::result::Result::Ok(::sqlx::encode::IsNull::No)
}
fn size_hint(&self) -> ::std::primitive::usize {

View file

@ -17,7 +17,7 @@ pub fn quote_args<DB: DatabaseExt>(
if input.arg_exprs.is_empty() {
return Ok(quote! {
let query_args = <#db_path as ::sqlx::database::Database>::Arguments::<'_>::default();
let query_args = ::core::result::Result::<_, ::sqlx::error::BoxDynError>::Ok(<#db_path as ::sqlx::database::Database>::Arguments::<'_>::default());
});
}
@ -109,7 +109,8 @@ pub fn quote_args<DB: DatabaseExt>(
#args_count,
0 #(+ ::sqlx::encode::Encode::<#db_path>::size_hint(#arg_name))*
);
#(query_args.add(#arg_name);)*
let query_args = ::core::result::Result::<_, ::sqlx::error::BoxDynError>::Ok(query_args)
#(.and_then(move |mut query_args| query_args.add(#arg_name).map(move |()| query_args) ))*;
})
}

View file

@ -276,7 +276,7 @@ where
let sql = &input.sql;
quote! {
::sqlx::query_with::<#db_path, _>(#sql, #query_args)
::sqlx::__query_with_result::<#db_path, _>(#sql, #query_args)
}
} else {
match input.record_type {

View file

@ -173,7 +173,7 @@ pub fn quote_query_as<DB: DatabaseExt>(
};
quote! {
::sqlx::query_with::<#db_path, _>(#sql, #bind_args).try_map(|row: #row_path| {
::sqlx::__query_with_result::<#db_path, _>(#sql, #bind_args).try_map(|row: #row_path| {
use ::sqlx::Row as _;
#(#instantiations)*
@ -216,7 +216,7 @@ pub fn quote_query_scalar<DB: DatabaseExt>(
let query = &input.sql;
Ok(quote! {
::sqlx::query_scalar_with::<#db, #ty, _>(#query, #bind_args)
::sqlx::__query_scalar_with_result::<#db, #ty, _>(#query, #bind_args)
})
}

View file

@ -6,7 +6,7 @@ use crate::{
use either::Either;
use futures_core::future::BoxFuture;
use futures_core::stream::BoxStream;
use futures_util::{StreamExt, TryFutureExt, TryStreamExt};
use futures_util::{stream, StreamExt, TryFutureExt, TryStreamExt};
use sqlx_core::any::{
Any, AnyArguments, AnyColumn, AnyConnectOptions, AnyConnectionBackend, AnyQueryResult, AnyRow,
AnyStatement, AnyTypeInfo, AnyTypeInfoKind,
@ -16,6 +16,7 @@ use sqlx_core::database::Database;
use sqlx_core::describe::Describe;
use sqlx_core::executor::Executor;
use sqlx_core::transaction::TransactionManager;
use std::future;
sqlx_core::declare_driver_with_optional_migrate!(DRIVER = MySql);
@ -77,10 +78,15 @@ impl AnyConnectionBackend for MySqlConnection {
arguments: Option<AnyArguments<'q>>,
) -> BoxStream<'q, sqlx_core::Result<Either<AnyQueryResult, AnyRow>>> {
let persistent = arguments.is_some();
let args = arguments.as_ref().map(AnyArguments::convert_to);
let arguments = match arguments.as_ref().map(AnyArguments::convert_to).transpose() {
Ok(arguments) => arguments,
Err(error) => {
return stream::once(future::ready(Err(sqlx_core::Error::Encode(error)))).boxed()
}
};
Box::pin(
self.run(query, args, persistent)
self.run(query, arguments, persistent)
.try_flatten_stream()
.map(|res| {
Ok(match res? {
@ -97,10 +103,15 @@ impl AnyConnectionBackend for MySqlConnection {
arguments: Option<AnyArguments<'q>>,
) -> BoxFuture<'q, sqlx_core::Result<Option<AnyRow>>> {
let persistent = arguments.is_some();
let args = arguments.as_ref().map(AnyArguments::convert_to);
let arguments = arguments
.as_ref()
.map(AnyArguments::convert_to)
.transpose()
.map_err(sqlx_core::Error::Encode);
Box::pin(async move {
let stream = self.run(query, args, persistent).await?;
let arguments = arguments?;
let stream = self.run(query, arguments, persistent).await?;
futures_util::pin_mut!(stream);
while let Some(result) = stream.try_next().await? {

View file

@ -2,34 +2,38 @@ use crate::encode::{Encode, IsNull};
use crate::types::Type;
use crate::{MySql, MySqlTypeInfo};
pub(crate) use sqlx_core::arguments::*;
use sqlx_core::error::BoxDynError;
use std::ops::Deref;
/// Implementation of [`Arguments`] for MySQL.
#[derive(Debug, Default, Clone)]
pub struct MySqlArguments {
pub(crate) values: Vec<u8>,
pub(crate) types: Vec<MySqlTypeInfo>,
pub(crate) null_bitmap: Vec<u8>,
pub(crate) null_bitmap: NullBitMap,
}
impl MySqlArguments {
pub(crate) fn add<'q, T>(&mut self, value: T)
pub(crate) fn add<'q, T>(&mut self, value: T) -> Result<(), BoxDynError>
where
T: Encode<'q, MySql> + Type<MySql>,
{
let ty = value.produces().unwrap_or_else(T::type_info);
let index = self.types.len();
let value_length_before_encoding = self.values.len();
let is_null = match value.encode(&mut self.values) {
Ok(is_null) => is_null,
Err(error) => {
// reset the value buffer to its previous value if encoding failed so we don't leave a half-encoded value behind
self.values.truncate(value_length_before_encoding);
return Err(error);
}
};
self.types.push(ty);
self.null_bitmap.resize((index / 8) + 1, 0);
self.null_bitmap.push(is_null);
if let IsNull::Yes = value.encode(&mut self.values) {
self.null_bitmap[index / 8] |= (1 << (index % 8)) as u8;
}
}
#[doc(hidden)]
pub fn len(&self) -> usize {
self.types.len()
Ok(())
}
}
@ -41,10 +45,64 @@ impl<'q> Arguments<'q> for MySqlArguments {
self.values.reserve(size);
}
fn add<T>(&mut self, value: T)
fn add<T>(&mut self, value: T) -> Result<(), BoxDynError>
where
T: Encode<'q, Self::Database> + Type<Self::Database>,
{
self.add(value)
}
fn len(&self) -> usize {
self.types.len()
}
}
#[derive(Debug, Default, Clone)]
pub(crate) struct NullBitMap {
bytes: Vec<u8>,
length: usize,
}
impl NullBitMap {
fn push(&mut self, is_null: IsNull) {
let byte_index = self.length / (u8::BITS as usize);
let bit_offset = self.length % (u8::BITS as usize);
if bit_offset == 0 {
self.bytes.push(0);
}
self.bytes[byte_index] |= u8::from(is_null.is_null()) << bit_offset;
self.length += 1;
}
}
impl Deref for NullBitMap {
type Target = [u8];
fn deref(&self) -> &Self::Target {
&self.bytes
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn null_bit_map_should_push_is_null() {
let mut bit_map = NullBitMap::default();
bit_map.push(IsNull::Yes);
bit_map.push(IsNull::No);
bit_map.push(IsNull::Yes);
bit_map.push(IsNull::No);
bit_map.push(IsNull::Yes);
bit_map.push(IsNull::No);
bit_map.push(IsNull::Yes);
bit_map.push(IsNull::No);
bit_map.push(IsNull::Yes);
assert_eq!([0b01010101, 0b1].as_slice(), bit_map.deref());
}
}

View file

@ -244,10 +244,11 @@ impl<'c> Executor<'c> for &'c mut MySqlConnection {
E: Execute<'q, Self::Database>,
{
let sql = query.sql();
let arguments = query.take_arguments();
let arguments = query.take_arguments().map_err(Error::Encode);
let persistent = query.persistent();
Box::pin(try_stream! {
let arguments = arguments?;
let s = self.run(sql, arguments, persistent).await?;
pin_mut!(s);

View file

@ -19,7 +19,7 @@ impl<'q> Encode<'_, Capabilities> for Execute<'q> {
buf.extend(&1_u32.to_le_bytes()); // iterations (always 1): int<4>
if !self.arguments.types.is_empty() {
buf.extend(&*self.arguments.null_bitmap);
buf.extend_from_slice(&self.arguments.null_bitmap);
buf.push(1); // send type to server
for ty in &self.arguments.types {

View file

@ -19,10 +19,10 @@ impl Type<MySql> for BigDecimal {
}
impl Encode<'_, MySql> for BigDecimal {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> IsNull {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> Result<IsNull, BoxDynError> {
buf.put_str_lenenc(&self.to_string());
IsNull::No
Ok(IsNull::No)
}
}

View file

@ -32,7 +32,7 @@ impl Type<MySql> for bool {
}
impl Encode<'_, MySql> for bool {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> IsNull {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> Result<IsNull, BoxDynError> {
<i8 as Encode<MySql>>::encode(*self as i8, buf)
}
}

View file

@ -27,10 +27,10 @@ impl Type<MySql> for [u8] {
}
impl Encode<'_, MySql> for &'_ [u8] {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> IsNull {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> Result<IsNull, BoxDynError> {
buf.put_bytes_lenenc(self);
IsNull::No
Ok(IsNull::No)
}
}
@ -51,7 +51,7 @@ impl Type<MySql> for Box<[u8]> {
}
impl Encode<'_, MySql> for Box<[u8]> {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> IsNull {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> Result<IsNull, BoxDynError> {
<&[u8] as Encode<MySql>>::encode(self.as_ref(), buf)
}
}
@ -73,7 +73,7 @@ impl Type<MySql> for Vec<u8> {
}
impl Encode<'_, MySql> for Vec<u8> {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> IsNull {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> Result<IsNull, BoxDynError> {
<&[u8] as Encode<MySql>>::encode(&**self, buf)
}
}

View file

@ -24,7 +24,7 @@ impl Type<MySql> for DateTime<Utc> {
/// Note: assumes the connection's `time_zone` is set to `+00:00` (UTC).
impl Encode<'_, MySql> for DateTime<Utc> {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> IsNull {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> Result<IsNull, BoxDynError> {
Encode::<MySql>::encode(&self.naive_utc(), buf)
}
}
@ -50,7 +50,7 @@ impl Type<MySql> for DateTime<Local> {
/// Note: assumes the connection's `time_zone` is set to `+00:00` (UTC).
impl Encode<'_, MySql> for DateTime<Local> {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> IsNull {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> Result<IsNull, BoxDynError> {
Encode::<MySql>::encode(&self.naive_utc(), buf)
}
}
@ -69,7 +69,7 @@ impl Type<MySql> for NaiveTime {
}
impl Encode<'_, MySql> for NaiveTime {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> IsNull {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> Result<IsNull, BoxDynError> {
let len = Encode::<MySql>::size_hint(self) - 1;
buf.push(len as u8);
@ -82,7 +82,7 @@ impl Encode<'_, MySql> for NaiveTime {
encode_time(self, len > 9, buf);
IsNull::No
Ok(IsNull::No)
}
fn size_hint(&self) -> usize {
@ -174,12 +174,12 @@ impl Type<MySql> for NaiveDate {
}
impl Encode<'_, MySql> for NaiveDate {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> IsNull {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> Result<IsNull, BoxDynError> {
buf.push(4);
encode_date(self, buf);
encode_date(self, buf)?;
IsNull::No
Ok(IsNull::No)
}
fn size_hint(&self) -> usize {
@ -216,17 +216,17 @@ impl Type<MySql> for NaiveDateTime {
}
impl Encode<'_, MySql> for NaiveDateTime {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> IsNull {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> Result<IsNull, BoxDynError> {
let len = Encode::<MySql>::size_hint(self) - 1;
buf.push(len as u8);
encode_date(&self.date(), buf);
encode_date(&self.date(), buf)?;
if len > 4 {
encode_time(&self.time(), len > 8, buf);
}
IsNull::No
Ok(IsNull::No)
}
fn size_hint(&self) -> usize {
@ -282,14 +282,16 @@ impl<'r> Decode<'r, MySql> for NaiveDateTime {
}
}
fn encode_date(date: &NaiveDate, buf: &mut Vec<u8>) {
fn encode_date(date: &NaiveDate, buf: &mut Vec<u8>) -> Result<(), BoxDynError> {
// MySQL supports years from 1000 - 9999
let year = u16::try_from(date.year())
.unwrap_or_else(|_| panic!("NaiveDateTime out of range for Mysql: {date}"));
.map_err(|_| format!("NaiveDateTime out of range for Mysql: {date}"))?;
buf.extend_from_slice(&year.to_le_bytes());
buf.push(date.month() as u8);
buf.push(date.day() as u8);
Ok(())
}
fn decode_date(mut buf: &[u8]) -> Result<Option<NaiveDate>, BoxDynError> {

View file

@ -33,18 +33,18 @@ impl Type<MySql> for f64 {
}
impl Encode<'_, MySql> for f32 {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> IsNull {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> Result<IsNull, BoxDynError> {
buf.extend(&self.to_le_bytes());
IsNull::No
Ok(IsNull::No)
}
}
impl Encode<'_, MySql> for f64 {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> IsNull {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> Result<IsNull, BoxDynError> {
buf.extend(&self.to_le_bytes());
IsNull::No
Ok(IsNull::No)
}
}

View file

@ -18,10 +18,10 @@ impl Type<MySql> for Ipv4Addr {
}
impl Encode<'_, MySql> for Ipv4Addr {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> IsNull {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> Result<IsNull, BoxDynError> {
buf.put_str_lenenc(&self.to_string());
IsNull::No
Ok(IsNull::No)
}
}
@ -46,10 +46,10 @@ impl Type<MySql> for Ipv6Addr {
}
impl Encode<'_, MySql> for Ipv6Addr {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> IsNull {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> Result<IsNull, BoxDynError> {
buf.put_str_lenenc(&self.to_string());
IsNull::No
Ok(IsNull::No)
}
}
@ -74,10 +74,10 @@ impl Type<MySql> for IpAddr {
}
impl Encode<'_, MySql> for IpAddr {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> IsNull {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> Result<IsNull, BoxDynError> {
buf.put_str_lenenc(&self.to_string());
IsNull::No
Ok(IsNull::No)
}
}

View file

@ -59,34 +59,34 @@ impl Type<MySql> for i64 {
}
impl Encode<'_, MySql> for i8 {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> IsNull {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> Result<IsNull, BoxDynError> {
buf.extend(&self.to_le_bytes());
IsNull::No
Ok(IsNull::No)
}
}
impl Encode<'_, MySql> for i16 {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> IsNull {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> Result<IsNull, BoxDynError> {
buf.extend(&self.to_le_bytes());
IsNull::No
Ok(IsNull::No)
}
}
impl Encode<'_, MySql> for i32 {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> IsNull {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> Result<IsNull, BoxDynError> {
buf.extend(&self.to_le_bytes());
IsNull::No
Ok(IsNull::No)
}
}
impl Encode<'_, MySql> for i64 {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> IsNull {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> Result<IsNull, BoxDynError> {
buf.extend(&self.to_le_bytes());
IsNull::No
Ok(IsNull::No)
}
}

View file

@ -26,7 +26,7 @@ impl<T> Encode<'_, MySql> for Json<T>
where
T: Serialize,
{
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> IsNull {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> Result<IsNull, BoxDynError> {
// Encode JSON as a length-prefixed string.
//
// The previous implementation encoded into an intermediate buffer to get the final length.
@ -47,14 +47,14 @@ where
buf.extend_from_slice(&[0u8; 9]);
let encode_start = buf.len();
self.encode_to(buf);
self.encode_to(buf)?;
let encoded_len = (buf.len() - encode_start) as u64;
// This prefix indicates that the following 8 bytes are a little-endian integer.
buf[lenenc_start] = 0xFE;
buf[lenenc_start + 1..][..8].copy_from_slice(&encoded_len.to_le_bytes());
IsNull::No
Ok(IsNull::No)
}
}

View file

@ -410,10 +410,13 @@ impl<'r> Decode<'r, MySql> for MySqlTime {
}
impl<'q> Encode<'q, MySql> for MySqlTime {
fn encode_by_ref(&self, buf: &mut <MySql as Database>::ArgumentBuffer<'q>) -> IsNull {
fn encode_by_ref(
&self,
buf: &mut <MySql as Database>::ArgumentBuffer<'q>,
) -> Result<IsNull, BoxDynError> {
if self.is_zero() {
buf.put_u8(0);
return IsNull::No;
return Ok(IsNull::No);
}
buf.put_u8(self.encoded_len());
@ -438,7 +441,7 @@ impl<'q> Encode<'q, MySql> for MySqlTime {
buf.put_u32_le(microseconds);
}
IsNull::No
Ok(IsNull::No)
}
fn size_hint(&self) -> usize {

View file

@ -19,10 +19,10 @@ impl Type<MySql> for Decimal {
}
impl Encode<'_, MySql> for Decimal {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> IsNull {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> Result<IsNull, BoxDynError> {
buf.put_str_lenenc(&self.to_string());
IsNull::No
Ok(IsNull::No)
}
}

View file

@ -49,10 +49,10 @@ impl Type<MySql> for str {
}
impl Encode<'_, MySql> for &'_ str {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> IsNull {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> Result<IsNull, BoxDynError> {
buf.put_str_lenenc(self);
IsNull::No
Ok(IsNull::No)
}
}
@ -73,7 +73,7 @@ impl Type<MySql> for Box<str> {
}
impl Encode<'_, MySql> for Box<str> {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> IsNull {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> Result<IsNull, BoxDynError> {
<&str as Encode<MySql>>::encode(&**self, buf)
}
}
@ -95,7 +95,7 @@ impl Type<MySql> for String {
}
impl Encode<'_, MySql> for String {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> IsNull {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> Result<IsNull, BoxDynError> {
<&str as Encode<MySql>>::encode(&**self, buf)
}
}
@ -117,7 +117,7 @@ impl Type<MySql> for Cow<'_, str> {
}
impl Encode<'_, MySql> for Cow<'_, str> {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> IsNull {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> Result<IsNull, BoxDynError> {
match self {
Cow::Borrowed(str) => <&str as Encode<MySql>>::encode(*str, buf),
Cow::Owned(str) => <&str as Encode<MySql>>::encode(&**str, buf),

View file

@ -20,7 +20,7 @@ impl<'q, T> Encode<'q, MySql> for Text<T>
where
T: Display,
{
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> IsNull {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> Result<IsNull, BoxDynError> {
// We can't really do the trick like with Postgres where we reserve the space for the
// length up-front and then overwrite it later, because MySQL appears to enforce that
// length-encoded integers use the smallest encoding for the value:

View file

@ -23,7 +23,7 @@ impl Type<MySql> for OffsetDateTime {
}
impl Encode<'_, MySql> for OffsetDateTime {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> IsNull {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> Result<IsNull, BoxDynError> {
let utc_dt = self.to_offset(UtcOffset::UTC);
let primitive_dt = PrimitiveDateTime::new(utc_dt.date(), utc_dt.time());
@ -46,7 +46,7 @@ impl Type<MySql> for Time {
}
impl Encode<'_, MySql> for Time {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> IsNull {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> Result<IsNull, BoxDynError> {
let len = Encode::<MySql>::size_hint(self) - 1;
buf.push(len as u8);
@ -59,7 +59,7 @@ impl Encode<'_, MySql> for Time {
encode_time(self, len > 9, buf);
IsNull::No
Ok(IsNull::No)
}
fn size_hint(&self) -> usize {
@ -149,12 +149,12 @@ impl Type<MySql> for Date {
}
impl Encode<'_, MySql> for Date {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> IsNull {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> Result<IsNull, BoxDynError> {
buf.push(4);
encode_date(self, buf);
encode_date(self, buf)?;
IsNull::No
Ok(IsNull::No)
}
fn size_hint(&self) -> usize {
@ -190,17 +190,17 @@ impl Type<MySql> for PrimitiveDateTime {
}
impl Encode<'_, MySql> for PrimitiveDateTime {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> IsNull {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> Result<IsNull, BoxDynError> {
let len = Encode::<MySql>::size_hint(self) - 1;
buf.push(len as u8);
encode_date(&self.date(), buf);
encode_date(&self.date(), buf)?;
if len > 4 {
encode_time(&self.time(), len > 8, buf);
}
IsNull::No
Ok(IsNull::No)
}
fn size_hint(&self) -> usize {
@ -267,14 +267,16 @@ impl<'r> Decode<'r, MySql> for PrimitiveDateTime {
}
}
fn encode_date(date: &Date, buf: &mut Vec<u8>) {
fn encode_date(date: &Date, buf: &mut Vec<u8>) -> Result<(), BoxDynError> {
// MySQL supports years from 1000 - 9999
let year = u16::try_from(date.year())
.unwrap_or_else(|_| panic!("Date out of range for Mysql: {date}"));
let year =
u16::try_from(date.year()).map_err(|_| format!("Date out of range for Mysql: {date}"))?;
buf.extend_from_slice(&year.to_le_bytes());
buf.push(date.month().into());
buf.push(date.day());
Ok(())
}
fn decode_date(buf: &[u8]) -> Result<Option<Date>, BoxDynError> {

View file

@ -69,34 +69,34 @@ impl Type<MySql> for u64 {
}
impl Encode<'_, MySql> for u8 {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> IsNull {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> Result<IsNull, BoxDynError> {
buf.extend(&self.to_le_bytes());
IsNull::No
Ok(IsNull::No)
}
}
impl Encode<'_, MySql> for u16 {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> IsNull {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> Result<IsNull, BoxDynError> {
buf.extend(&self.to_le_bytes());
IsNull::No
Ok(IsNull::No)
}
}
impl Encode<'_, MySql> for u32 {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> IsNull {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> Result<IsNull, BoxDynError> {
buf.extend(&self.to_le_bytes());
IsNull::No
Ok(IsNull::No)
}
}
impl Encode<'_, MySql> for u64 {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> IsNull {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> Result<IsNull, BoxDynError> {
buf.extend(&self.to_le_bytes());
IsNull::No
Ok(IsNull::No)
}
}

View file

@ -21,10 +21,10 @@ impl Type<MySql> for Uuid {
}
impl Encode<'_, MySql> for Uuid {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> IsNull {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> Result<IsNull, BoxDynError> {
buf.put_bytes_lenenc(self.as_bytes());
IsNull::No
Ok(IsNull::No)
}
}
@ -49,10 +49,10 @@ impl Type<MySql> for Hyphenated {
}
impl Encode<'_, MySql> for Hyphenated {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> IsNull {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> Result<IsNull, BoxDynError> {
buf.put_str_lenenc(&self.to_string());
IsNull::No
Ok(IsNull::No)
}
}
@ -79,10 +79,10 @@ impl Type<MySql> for Simple {
}
impl Encode<'_, MySql> for Simple {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> IsNull {
fn encode_by_ref(&self, buf: &mut Vec<u8>) -> Result<IsNull, BoxDynError> {
buf.put_str_lenenc(&self.to_string());
IsNull::No
Ok(IsNull::No)
}
}

View file

@ -4,7 +4,8 @@ use crate::{
};
use futures_core::future::BoxFuture;
use futures_core::stream::BoxStream;
use futures_util::{StreamExt, TryFutureExt, TryStreamExt};
use futures_util::{stream, StreamExt, TryFutureExt, TryStreamExt};
use std::future;
pub use sqlx_core::any::*;
@ -76,10 +77,15 @@ impl AnyConnectionBackend for PgConnection {
arguments: Option<AnyArguments<'q>>,
) -> BoxStream<'q, sqlx_core::Result<Either<AnyQueryResult, AnyRow>>> {
let persistent = arguments.is_some();
let args = arguments.as_ref().map(AnyArguments::convert_to);
let arguments = match arguments.as_ref().map(AnyArguments::convert_to).transpose() {
Ok(arguments) => arguments,
Err(error) => {
return stream::once(future::ready(Err(sqlx_core::Error::Encode(error)))).boxed()
}
};
Box::pin(
self.run(query, args, 0, persistent, None)
self.run(query, arguments, 0, persistent, None)
.try_flatten_stream()
.map(
move |res: sqlx_core::Result<Either<PgQueryResult, PgRow>>| match res? {
@ -96,10 +102,15 @@ impl AnyConnectionBackend for PgConnection {
arguments: Option<AnyArguments<'q>>,
) -> BoxFuture<'q, sqlx_core::Result<Option<AnyRow>>> {
let persistent = arguments.is_some();
let args = arguments.as_ref().map(AnyArguments::convert_to);
let arguments = arguments
.as_ref()
.map(AnyArguments::convert_to)
.transpose()
.map_err(sqlx_core::Error::Encode);
Box::pin(async move {
let stream = self.run(query, args, 1, persistent, None).await?;
let arguments = arguments?;
let stream = self.run(query, arguments, 1, persistent, None).await?;
futures_util::pin_mut!(stream);
if let Some(Either::Right(row)) = stream.try_next().await? {

View file

@ -8,6 +8,7 @@ use crate::types::Type;
use crate::{PgConnection, PgTypeInfo, Postgres};
pub(crate) use sqlx_core::arguments::Arguments;
use sqlx_core::error::BoxDynError;
// TODO: buf.patch(|| ...) is a poor name, can we think of a better name? Maybe `buf.lazy(||)` ?
// TODO: Extend the patch system to support dynamic lengths
@ -59,19 +60,27 @@ pub struct PgArguments {
}
impl PgArguments {
pub(crate) fn add<'q, T>(&mut self, value: T)
pub(crate) fn add<'q, T>(&mut self, value: T) -> Result<(), BoxDynError>
where
T: Encode<'q, Postgres> + Type<Postgres>,
{
// remember the type information for this value
self.types
.push(value.produces().unwrap_or_else(T::type_info));
let type_info = value.produces().unwrap_or_else(T::type_info);
let buffer_snapshot = self.buffer.snapshot();
// encode the value into our buffer
self.buffer.encode(value);
if let Err(error) = self.buffer.encode(value) {
// reset the value buffer to its previous value if encoding failed so we don't leave a half-encoded value behind
self.buffer.reset_to_snapshot(buffer_snapshot);
return Err(error);
};
// remember the type information for this value
self.types.push(type_info);
// increment the number of arguments we are tracking
self.buffer.count += 1;
Ok(())
}
// Apply patches
@ -112,7 +121,7 @@ impl<'q> Arguments<'q> for PgArguments {
self.buffer.reserve(size);
}
fn add<T>(&mut self, value: T)
fn add<T>(&mut self, value: T) -> Result<(), BoxDynError>
where
T: Encode<'q, Self::Database> + Type<Self::Database>,
{
@ -122,10 +131,14 @@ impl<'q> Arguments<'q> for PgArguments {
fn format_placeholder<W: Write>(&self, writer: &mut W) -> fmt::Result {
write!(writer, "${}", self.buffer.count)
}
fn len(&self) -> usize {
self.buffer.count
}
}
impl PgArgumentBuffer {
pub(crate) fn encode<'q, T>(&mut self, value: T)
pub(crate) fn encode<'q, T>(&mut self, value: T) -> Result<(), BoxDynError>
where
T: Encode<'q, Postgres>,
{
@ -134,7 +147,7 @@ impl PgArgumentBuffer {
self.extend(&[0; 4]);
// encode the value into our buffer
let len = if let IsNull::No = value.encode(self) {
let len = if let IsNull::No = value.encode(self)? {
(self.len() - offset - 4) as i32
} else {
// Write a -1 to indicate NULL
@ -145,6 +158,8 @@ impl PgArgumentBuffer {
// write the len to the beginning of the value
self[offset..(offset + 4)].copy_from_slice(&len.to_be_bytes());
Ok(())
}
// Adds a callback to be invoked later when we know the parameter type
@ -167,6 +182,44 @@ impl PgArgumentBuffer {
self.extend_from_slice(&0_u32.to_be_bytes());
self.type_holes.push((offset, type_name.clone()));
}
fn snapshot(&self) -> PgArgumentBufferSnapshot {
let Self {
buffer,
count,
patches,
type_holes,
} = self;
PgArgumentBufferSnapshot {
buffer_length: buffer.len(),
count: *count,
patches_length: patches.len(),
type_holes_length: type_holes.len(),
}
}
fn reset_to_snapshot(
&mut self,
PgArgumentBufferSnapshot {
buffer_length,
count,
patches_length,
type_holes_length,
}: PgArgumentBufferSnapshot,
) {
self.buffer.truncate(buffer_length);
self.count = count;
self.patches.truncate(patches_length);
self.type_holes.truncate(type_holes_length);
}
}
struct PgArgumentBufferSnapshot {
buffer_length: usize,
count: usize,
patches_length: usize,
type_holes_length: usize,
}
impl Deref for PgArgumentBuffer {

View file

@ -382,9 +382,10 @@ WHERE rngtypid = $1
bind + 2
);
args.add(i as i32);
args.add(column.relation_id);
args.add(column.relation_attribute_no);
args.add(i as i32).map_err(Error::Encode)?;
args.add(column.relation_id).map_err(Error::Encode)?;
args.add(column.relation_attribute_no)
.map_err(Error::Encode)?;
}
nullable_query.push_str(

View file

@ -370,10 +370,11 @@ impl<'c> Executor<'c> for &'c mut PgConnection {
{
let sql = query.sql();
let metadata = query.statement().map(|s| Arc::clone(&s.metadata));
let arguments = query.take_arguments();
let arguments = query.take_arguments().map_err(Error::Encode);
let persistent = query.persistent();
Box::pin(try_stream! {
let arguments = arguments?;
let s = self.run(sql, arguments, 0, persistent, metadata).await?;
pin_mut!(s);
@ -395,10 +396,11 @@ impl<'c> Executor<'c> for &'c mut PgConnection {
{
let sql = query.sql();
let metadata = query.statement().map(|s| Arc::clone(&s.metadata));
let arguments = query.take_arguments();
let arguments = query.take_arguments().map_err(Error::Encode);
let persistent = query.persistent();
Box::pin(async move {
let arguments = arguments?;
let s = self.run(sql, arguments, 1, persistent, metadata).await?;
pin_mut!(s);

View file

@ -136,7 +136,7 @@ where
T: Encode<'q, Postgres>,
{
#[inline]
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
self.as_slice().encode_by_ref(buf)
}
}
@ -146,7 +146,7 @@ where
for<'a> &'a [T]: Encode<'q, Postgres>,
T: Encode<'q, Postgres>,
{
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
self.as_slice().encode_by_ref(buf)
}
}
@ -155,7 +155,7 @@ impl<'q, T> Encode<'q, Postgres> for &'_ [T]
where
T: Encode<'q, Postgres> + Type<Postgres>,
{
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
let type_info = if self.len() < 1 {
T::type_info()
} else {
@ -178,10 +178,10 @@ where
buf.extend(&1_i32.to_be_bytes()); // lower bound
for element in self.iter() {
buf.encode(element);
buf.encode(element)?;
}
IsNull::No
Ok(IsNull::No)
}
}

View file

@ -137,24 +137,10 @@ impl TryFrom<&'_ BigDecimal> for PgNumeric {
#[doc=include_str!("bigdecimal-range.md")]
impl Encode<'_, Postgres> for BigDecimal {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull {
// If the argument is too big, then we replace it with a less big argument.
// This less big argument is already outside the range of allowed PostgreSQL DECIMAL, which
// means that PostgreSQL will return the 22P03 error kind upon receiving it. This is the
// expected error, and the user should be ready to handle it anyway.
PgNumeric::try_from(self)
.unwrap_or_else(|_| {
PgNumeric::Number {
digits: vec![1],
// This is larger than the maximum allowed value, so Postgres should return an error.
scale: 0x4000,
weight: 0,
sign: sign_to_pg(self.sign()),
}
})
.encode(buf);
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
PgNumeric::try_from(self)?.encode(buf);
IsNull::No
Ok(IsNull::No)
}
fn size_hint(&self) -> usize {

View file

@ -30,11 +30,11 @@ impl PgHasArrayType for BitVec {
}
impl Encode<'_, Postgres> for BitVec {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
buf.extend(&(self.len() as i32).to_be_bytes());
buf.extend(self.to_bytes());
IsNull::No
Ok(IsNull::No)
}
fn size_hint(&self) -> usize {

View file

@ -17,10 +17,10 @@ impl PgHasArrayType for bool {
}
impl Encode<'_, Postgres> for bool {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
buf.push(*self as u8);
IsNull::No
Ok(IsNull::No)
}
}

View file

@ -35,27 +35,27 @@ impl<const N: usize> PgHasArrayType for [u8; N] {
}
impl Encode<'_, Postgres> for &'_ [u8] {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
buf.extend_from_slice(self);
IsNull::No
Ok(IsNull::No)
}
}
impl Encode<'_, Postgres> for Box<[u8]> {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
<&[u8] as Encode<Postgres>>::encode(self.as_ref(), buf)
}
}
impl Encode<'_, Postgres> for Vec<u8> {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
<&[u8] as Encode<Postgres>>::encode(self, buf)
}
}
impl<const N: usize> Encode<'_, Postgres> for [u8; N] {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
<&[u8] as Encode<Postgres>>::encode(self.as_slice(), buf)
}
}

View file

@ -21,7 +21,7 @@ impl PgHasArrayType for NaiveDate {
}
impl Encode<'_, Postgres> for NaiveDate {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
// DATE is encoded as the days since epoch
let days = (*self - postgres_epoch_date()).num_days() as i32;
Encode::<Postgres>::encode(&days, buf)

View file

@ -33,12 +33,11 @@ impl<Tz: TimeZone> PgHasArrayType for DateTime<Tz> {
}
impl Encode<'_, Postgres> for NaiveDateTime {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull {
// FIXME: We should *really* be returning an error, Encode needs to be fallible
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
// TIMESTAMP is encoded as the microseconds since the epoch
let us = (*self - postgres_epoch_datetime())
.num_microseconds()
.unwrap_or_else(|| panic!("NaiveDateTime out of range for Postgres: {self:?}"));
.ok_or_else(|| format!("NaiveDateTime out of range for Postgres: {self:?}"))?;
Encode::<Postgres>::encode(&us, buf)
}
@ -76,7 +75,7 @@ impl<'r> Decode<'r, Postgres> for NaiveDateTime {
}
impl<Tz: TimeZone> Encode<'_, Postgres> for DateTime<Tz> {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
Encode::<Postgres>::encode(self.naive_utc(), buf)
}

View file

@ -19,10 +19,11 @@ impl PgHasArrayType for NaiveTime {
}
impl Encode<'_, Postgres> for NaiveTime {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
// TIME is encoded as the microseconds since midnight
// NOTE: panic! is on overflow and 1 day does not have enough micros to overflow
let us = (*self - NaiveTime::default()).num_microseconds().unwrap();
let us = (*self - NaiveTime::default())
.num_microseconds()
.ok_or_else(|| format!("Time out of range for PostgreSQL: {self}"))?;
Encode::<Postgres>::encode(&us, buf)
}

View file

@ -94,7 +94,7 @@ impl PgHasArrayType for PgCiText {
}
impl Encode<'_, Postgres> for PgCiText {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
<&str as Encode<Postgres>>::encode(&**self, buf)
}
}

View file

@ -19,10 +19,10 @@ impl PgHasArrayType for f32 {
}
impl Encode<'_, Postgres> for f32 {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
buf.extend(&self.to_be_bytes());
IsNull::No
Ok(IsNull::No)
}
}
@ -48,10 +48,10 @@ impl PgHasArrayType for f64 {
}
impl Encode<'_, Postgres> for f64 {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
buf.extend(&self.to_be_bytes());
IsNull::No
Ok(IsNull::No)
}
}

View file

@ -44,10 +44,10 @@ impl PgHasArrayType for i8 {
}
impl Encode<'_, Postgres> for i8 {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
buf.extend(&self.to_be_bytes());
IsNull::No
Ok(IsNull::No)
}
}
@ -89,10 +89,10 @@ impl PgHasArrayType for i16 {
}
impl Encode<'_, Postgres> for i16 {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
buf.extend(&self.to_be_bytes());
IsNull::No
Ok(IsNull::No)
}
}
@ -115,10 +115,10 @@ impl PgHasArrayType for i32 {
}
impl Encode<'_, Postgres> for i32 {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
buf.extend(&self.to_be_bytes());
IsNull::No
Ok(IsNull::No)
}
}
@ -141,10 +141,10 @@ impl PgHasArrayType for i64 {
}
impl Encode<'_, Postgres> for i64 {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
buf.extend(&self.to_be_bytes());
IsNull::No
Ok(IsNull::No)
}
}

View file

@ -54,12 +54,12 @@ impl<'de> Decode<'de, Postgres> for PgInterval {
}
impl Encode<'_, Postgres> for PgInterval {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
buf.extend(&self.microseconds.to_be_bytes());
buf.extend(&self.days.to_be_bytes());
buf.extend(&self.months.to_be_bytes());
IsNull::No
Ok(IsNull::No)
}
fn size_hint(&self) -> usize {
@ -83,10 +83,8 @@ impl PgHasArrayType for std::time::Duration {
}
impl Encode<'_, Postgres> for std::time::Duration {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull {
PgInterval::try_from(*self)
.expect("failed to encode `std::time::Duration`")
.encode_by_ref(buf)
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
PgInterval::try_from(*self)?.encode_by_ref(buf)
}
fn size_hint(&self) -> usize {
@ -130,8 +128,8 @@ impl PgHasArrayType for chrono::Duration {
#[cfg(feature = "chrono")]
impl Encode<'_, Postgres> for chrono::Duration {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull {
let pg_interval = PgInterval::try_from(*self).expect("Failed to encode chrono::Duration");
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
let pg_interval = PgInterval::try_from(*self)?;
pg_interval.encode_by_ref(buf)
}
@ -192,8 +190,8 @@ impl PgHasArrayType for time::Duration {
#[cfg(feature = "time")]
impl Encode<'_, Postgres> for time::Duration {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull {
let pg_interval = PgInterval::try_from(*self).expect("Failed to encode time::Duration");
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
let pg_interval = PgInterval::try_from(*self)?;
pg_interval.encode_by_ref(buf)
}
@ -234,7 +232,7 @@ fn test_encode_interval() {
};
assert!(matches!(
Encode::<Postgres>::encode(&interval, &mut buf),
IsNull::No
Ok(IsNull::No)
));
assert_eq!(&**buf, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
buf.clear();
@ -246,7 +244,7 @@ fn test_encode_interval() {
};
assert!(matches!(
Encode::<Postgres>::encode(&interval, &mut buf),
IsNull::No
Ok(IsNull::No)
));
assert_eq!(&**buf, [0, 0, 0, 0, 0, 0, 3, 232, 0, 0, 0, 0, 0, 0, 0, 0]);
buf.clear();
@ -258,7 +256,7 @@ fn test_encode_interval() {
};
assert!(matches!(
Encode::<Postgres>::encode(&interval, &mut buf),
IsNull::No
Ok(IsNull::No)
));
assert_eq!(&**buf, [0, 0, 0, 0, 0, 15, 66, 64, 0, 0, 0, 0, 0, 0, 0, 0]);
buf.clear();
@ -270,7 +268,7 @@ fn test_encode_interval() {
};
assert!(matches!(
Encode::<Postgres>::encode(&interval, &mut buf),
IsNull::No
Ok(IsNull::No)
));
assert_eq!(
&**buf,
@ -285,7 +283,7 @@ fn test_encode_interval() {
};
assert!(matches!(
Encode::<Postgres>::encode(&interval, &mut buf),
IsNull::No
Ok(IsNull::No)
));
assert_eq!(&**buf, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0]);
buf.clear();
@ -297,7 +295,7 @@ fn test_encode_interval() {
};
assert!(matches!(
Encode::<Postgres>::encode(&interval, &mut buf),
IsNull::No
Ok(IsNull::No)
));
assert_eq!(&**buf, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);
buf.clear();

View file

@ -35,7 +35,7 @@ impl<'db> Encode<'db, Postgres> for IpAddr
where
IpNetwork: Encode<'db, Postgres>,
{
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
IpNetwork::from(*self).encode_by_ref(buf)
}

View file

@ -36,7 +36,7 @@ impl PgHasArrayType for IpNetwork {
}
impl Encode<'_, Postgres> for IpNetwork {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
// https://github.com/postgres/postgres/blob/574925bfd0a8175f6e161936ea11d9695677ba09/src/backend/utils/adt/network.c#L293
// https://github.com/postgres/postgres/blob/574925bfd0a8175f6e161936ea11d9695677ba09/src/backend/utils/adt/network.c#L271
@ -58,7 +58,7 @@ impl Encode<'_, Postgres> for IpNetwork {
}
}
IsNull::No
Ok(IsNull::No)
}
fn size_hint(&self) -> usize {

View file

@ -58,7 +58,7 @@ impl<'q, T> Encode<'q, Postgres> for Json<T>
where
T: Serialize,
{
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
// we have a tiny amount of dynamic behavior depending if we are resolved to be JSON
// instead of JSONB
buf.patch(|buf, ty: &PgTypeInfo| {
@ -71,10 +71,9 @@ where
buf.push(1);
// the JSON data written to the buffer is the same regardless of parameter type
serde_json::to_writer(&mut **buf, &self.0)
.expect("failed to serialize to JSON for encoding on transmission to the database");
serde_json::to_writer(&mut **buf, &self.0)?;
IsNull::No
Ok(IsNull::No)
}
}

View file

@ -139,12 +139,11 @@ impl Type<Postgres> for PgLQuery {
}
impl Encode<'_, Postgres> for PgLQuery {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
buf.extend(1i8.to_le_bytes());
write!(buf, "{self}")
.expect("Display implementation panicked while writing to PgArgumentBuffer");
write!(buf, "{self}")?;
IsNull::No
Ok(IsNull::No)
}
}

View file

@ -181,12 +181,11 @@ impl PgHasArrayType for PgLTree {
}
impl Encode<'_, Postgres> for PgLTree {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
buf.extend(1i8.to_le_bytes());
write!(buf, "{self}")
.expect("Display implementation panicked while writing to PgArgumentBuffer");
write!(buf, "{self}")?;
IsNull::No
Ok(IsNull::No)
}
}

View file

@ -23,9 +23,9 @@ impl PgHasArrayType for MacAddress {
}
impl Encode<'_, Postgres> for MacAddress {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
buf.extend_from_slice(&self.bytes()); // write just the address
IsNull::No
Ok(IsNull::No)
}
fn size_hint(&self) -> usize {

View file

@ -165,10 +165,10 @@ where
}
impl Encode<'_, Postgres> for PgMoney {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
buf.extend(&self.0.to_be_bytes());
IsNull::No
Ok(IsNull::No)
}
}

View file

@ -36,10 +36,10 @@ impl PgHasArrayType for Oid {
}
impl Encode<'_, Postgres> for Oid {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
buf.extend(&self.0.to_be_bytes());
IsNull::No
Ok(IsNull::No)
}
}

View file

@ -292,7 +292,7 @@ impl<'q, T> Encode<'q, Postgres> for PgRange<T>
where
T: Encode<'q, Postgres>,
{
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
// https://github.com/postgres/postgres/blob/2f48ede080f42b97b594fb14102c82ca1001b80c/src/backend/utils/adt/rangetypes.c#L245
let mut flags = RangeFlags::empty();
@ -312,15 +312,15 @@ where
buf.push(flags.bits());
if let Bound::Included(v) | Bound::Excluded(v) = &self.start {
buf.encode(v);
buf.encode(v)?;
}
if let Bound::Included(v) | Bound::Excluded(v) = &self.end {
buf.encode(v);
buf.encode(v)?;
}
// ranges are themselves never null
IsNull::No
Ok(IsNull::No)
}
}

View file

@ -34,7 +34,7 @@ impl<'a> PgRecordEncoder<'a> {
}
#[doc(hidden)]
pub fn encode<'q, T>(&mut self, value: T) -> &mut Self
pub fn encode<'q, T>(&mut self, value: T) -> Result<&mut Self, BoxDynError>
where
'a: 'q,
T: Encode<'q, Postgres> + Type<Postgres>,
@ -50,10 +50,10 @@ impl<'a> PgRecordEncoder<'a> {
self.buf.extend(&ty.0.oid().0.to_be_bytes());
}
self.buf.encode(value);
self.buf.encode(value)?;
self.num += 1;
self
Ok(self)
}
}

View file

@ -72,18 +72,16 @@ impl TryFrom<PgNumeric> for Decimal {
}
// This impl is effectively infallible because `NUMERIC` has a greater range than `Decimal`.
impl TryFrom<&'_ Decimal> for PgNumeric {
type Error = BoxDynError;
fn try_from(decimal: &Decimal) -> Result<Self, BoxDynError> {
impl From<&'_ Decimal> for PgNumeric {
fn from(decimal: &Decimal) -> Self {
// `Decimal` added `is_zero()` as an inherent method in a more recent version
if Zero::is_zero(decimal) {
return Ok(PgNumeric::Number {
PgNumeric::Number {
sign: PgNumericSign::Positive,
scale: 0,
weight: 0,
digits: vec![],
});
};
}
let scale = decimal.scale() as u16;
@ -131,7 +129,7 @@ impl TryFrom<&'_ Decimal> for PgNumeric {
digits.pop();
}
Ok(PgNumeric::Number {
PgNumeric::Number {
sign: match decimal.is_sign_negative() {
false => PgNumericSign::Positive,
true => PgNumericSign::Negative,
@ -139,17 +137,15 @@ impl TryFrom<&'_ Decimal> for PgNumeric {
scale: scale as i16,
weight,
digits,
})
}
}
}
impl Encode<'_, Postgres> for Decimal {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull {
PgNumeric::try_from(self)
.expect("BUG: `Decimal` to `PgNumeric` conversion should be infallible")
.encode(buf);
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
PgNumeric::from(self).encode(buf);
IsNull::No
Ok(IsNull::No)
}
}

View file

@ -95,15 +95,15 @@ impl PgHasArrayType for String {
}
impl Encode<'_, Postgres> for &'_ str {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
buf.extend(self.as_bytes());
IsNull::No
Ok(IsNull::No)
}
}
impl Encode<'_, Postgres> for Cow<'_, str> {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
match self {
Cow::Borrowed(str) => <&str as Encode<Postgres>>::encode(*str, buf),
Cow::Owned(str) => <&str as Encode<Postgres>>::encode(&**str, buf),
@ -112,13 +112,13 @@ impl Encode<'_, Postgres> for Cow<'_, str> {
}
impl Encode<'_, Postgres> for Box<str> {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
<&str as Encode<Postgres>>::encode(&**self, buf)
}
}
impl Encode<'_, Postgres> for String {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
<&str as Encode<Postgres>>::encode(&**self, buf)
}
}

View file

@ -22,19 +22,9 @@ impl<'q, T> Encode<'q, Postgres> for Text<T>
where
T: Display,
{
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull {
// Unfortunately, our API design doesn't give us a way to bubble up the error here.
//
// Fortunately, writing to `Vec<u8>` is infallible so the only possible source of
// errors is from the implementation of `Display::fmt()` itself,
// where the onus is on the user.
//
// The blanket impl of `ToString` also panics if there's an error, so this is not
// unprecedented.
//
// However, the panic should be documented anyway.
write!(**buf, "{}", self.0).expect("unexpected error from `Display::fmt()`");
IsNull::No
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
write!(**buf, "{}", self.0)?;
Ok(IsNull::No)
}
}

View file

@ -21,7 +21,7 @@ impl PgHasArrayType for Date {
}
impl Encode<'_, Postgres> for Date {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
// DATE is encoded as the days since epoch
let days = (*self - PG_EPOCH).whole_days() as i32;
Encode::<Postgres>::encode(&days, buf)

View file

@ -35,7 +35,7 @@ impl PgHasArrayType for OffsetDateTime {
}
impl Encode<'_, Postgres> for PrimitiveDateTime {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
// TIMESTAMP is encoded as the microseconds since the epoch
let us = (*self - PG_EPOCH.midnight()).whole_microseconds() as i64;
Encode::<Postgres>::encode(&us, buf)
@ -84,7 +84,7 @@ impl<'r> Decode<'r, Postgres> for PrimitiveDateTime {
}
impl Encode<'_, Postgres> for OffsetDateTime {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
let utc = self.to_offset(offset!(UTC));
let primitive = PrimitiveDateTime::new(utc.date(), utc.time());

View file

@ -20,7 +20,7 @@ impl PgHasArrayType for Time {
}
impl Encode<'_, Postgres> for Time {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
// TIME is encoded as the microseconds since midnight
let us = (*self - Time::MIDNIGHT).whole_microseconds() as i64;
Encode::<Postgres>::encode(&us, buf)

View file

@ -51,11 +51,12 @@ mod chrono {
}
impl Encode<'_, Postgres> for PgTimeTz<NaiveTime, FixedOffset> {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull {
let _ = <NaiveTime as Encode<'_, Postgres>>::encode(self.time, buf);
let _ = <i32 as Encode<'_, Postgres>>::encode(self.offset.utc_minus_local(), buf);
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
let _: IsNull = <NaiveTime as Encode<'_, Postgres>>::encode(self.time, buf)?;
let _: IsNull =
<i32 as Encode<'_, Postgres>>::encode(self.offset.utc_minus_local(), buf)?;
IsNull::No
Ok(IsNull::No)
}
fn size_hint(&self) -> usize {
@ -134,11 +135,12 @@ mod time {
}
impl Encode<'_, Postgres> for PgTimeTz<Time, UtcOffset> {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull {
let _ = <Time as Encode<'_, Postgres>>::encode(self.time, buf);
let _ = <i32 as Encode<'_, Postgres>>::encode(-self.offset.whole_seconds(), buf);
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
let _: IsNull = <Time as Encode<'_, Postgres>>::encode(self.time, buf)?;
let _: IsNull =
<i32 as Encode<'_, Postgres>>::encode(-self.offset.whole_seconds(), buf)?;
IsNull::No
Ok(IsNull::No)
}
fn size_hint(&self) -> usize {

View file

@ -19,10 +19,10 @@ impl PgHasArrayType for Uuid {
}
impl Encode<'_, Postgres> for Uuid {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
buf.extend_from_slice(self.as_bytes());
IsNull::No
Ok(IsNull::No)
}
}

View file

@ -7,6 +7,7 @@ use libsqlite3_sys::SQLITE_OK;
use std::borrow::Cow;
pub(crate) use sqlx_core::arguments::*;
use sqlx_core::error::BoxDynError;
#[derive(Debug, Clone)]
pub enum SqliteArgumentValue<'q> {
@ -24,13 +25,23 @@ pub struct SqliteArguments<'q> {
}
impl<'q> SqliteArguments<'q> {
pub(crate) fn add<T>(&mut self, value: T)
pub(crate) fn add<T>(&mut self, value: T) -> Result<(), BoxDynError>
where
T: Encode<'q, Sqlite>,
{
if let IsNull::Yes = value.encode(&mut self.values) {
self.values.push(SqliteArgumentValue::Null);
}
let value_length_before_encoding = self.values.len();
match value.encode(&mut self.values) {
Ok(IsNull::Yes) => self.values.push(SqliteArgumentValue::Null),
Ok(IsNull::No) => {}
Err(error) => {
// reset the value buffer to its previous value if encoding failed so we don't leave a half-encoded value behind
self.values.truncate(value_length_before_encoding);
return Err(error);
}
};
Ok(())
}
pub(crate) fn into_static(self) -> SqliteArguments<'static> {
@ -51,12 +62,16 @@ impl<'q> Arguments<'q> for SqliteArguments<'q> {
self.values.reserve(len);
}
fn add<T>(&mut self, value: T)
fn add<T>(&mut self, value: T) -> Result<(), BoxDynError>
where
T: Encode<'q, Self::Database>,
{
self.add(value)
}
fn len(&self) -> usize {
self.values.len()
}
}
impl SqliteArguments<'_> {

View file

@ -3,11 +3,12 @@ use crate::{
};
use futures_core::future::BoxFuture;
use futures_core::stream::BoxStream;
use futures_util::{TryFutureExt, TryStreamExt};
use futures_util::{stream, FutureExt, StreamExt, TryFutureExt, TryStreamExt};
use sqlx_core::describe::Describe;
use sqlx_core::error::Error;
use sqlx_core::executor::{Execute, Executor};
use sqlx_core::Either;
use std::future;
impl<'c> Executor<'c> for &'c mut SqliteConnection {
type Database = Sqlite;
@ -21,7 +22,10 @@ impl<'c> Executor<'c> for &'c mut SqliteConnection {
E: Execute<'q, Self::Database>,
{
let sql = query.sql();
let arguments = query.take_arguments();
let arguments = match query.take_arguments().map_err(Error::Encode) {
Ok(arguments) => arguments,
Err(error) => return stream::once(future::ready(Err(error))).boxed(),
};
let persistent = query.persistent() && arguments.is_some();
Box::pin(
@ -41,7 +45,10 @@ impl<'c> Executor<'c> for &'c mut SqliteConnection {
E: Execute<'q, Self::Database>,
{
let sql = query.sql();
let arguments = query.take_arguments();
let arguments = match query.take_arguments().map_err(Error::Encode) {
Ok(arguments) => arguments,
Err(error) => return future::ready(Err(error)).boxed(),
};
let persistent = query.persistent() && arguments.is_some();
Box::pin(async move {

View file

@ -16,10 +16,13 @@ impl Type<Sqlite> for bool {
}
impl<'q> Encode<'q, Sqlite> for bool {
fn encode_by_ref(&self, args: &mut Vec<SqliteArgumentValue<'q>>) -> IsNull {
fn encode_by_ref(
&self,
args: &mut Vec<SqliteArgumentValue<'q>>,
) -> Result<IsNull, BoxDynError> {
args.push(SqliteArgumentValue::Int((*self).into()));
IsNull::No
Ok(IsNull::No)
}
}

View file

@ -18,10 +18,13 @@ impl Type<Sqlite> for [u8] {
}
impl<'q> Encode<'q, Sqlite> for &'q [u8] {
fn encode_by_ref(&self, args: &mut Vec<SqliteArgumentValue<'q>>) -> IsNull {
fn encode_by_ref(
&self,
args: &mut Vec<SqliteArgumentValue<'q>>,
) -> Result<IsNull, BoxDynError> {
args.push(SqliteArgumentValue::Blob(Cow::Borrowed(self)));
IsNull::No
Ok(IsNull::No)
}
}
@ -42,18 +45,21 @@ impl Type<Sqlite> for Box<[u8]> {
}
impl Encode<'_, Sqlite> for Box<[u8]> {
fn encode(self, args: &mut Vec<SqliteArgumentValue<'_>>) -> IsNull {
fn encode(self, args: &mut Vec<SqliteArgumentValue<'_>>) -> Result<IsNull, BoxDynError> {
args.push(SqliteArgumentValue::Blob(Cow::Owned(self.into_vec())));
IsNull::No
Ok(IsNull::No)
}
fn encode_by_ref(&self, args: &mut Vec<SqliteArgumentValue<'_>>) -> IsNull {
fn encode_by_ref(
&self,
args: &mut Vec<SqliteArgumentValue<'_>>,
) -> Result<IsNull, BoxDynError> {
args.push(SqliteArgumentValue::Blob(Cow::Owned(
self.clone().into_vec(),
)));
IsNull::No
Ok(IsNull::No)
}
}
@ -74,16 +80,19 @@ impl Type<Sqlite> for Vec<u8> {
}
impl<'q> Encode<'q, Sqlite> for Vec<u8> {
fn encode(self, args: &mut Vec<SqliteArgumentValue<'q>>) -> IsNull {
fn encode(self, args: &mut Vec<SqliteArgumentValue<'q>>) -> Result<IsNull, BoxDynError> {
args.push(SqliteArgumentValue::Blob(Cow::Owned(self)));
IsNull::No
Ok(IsNull::No)
}
fn encode_by_ref(&self, args: &mut Vec<SqliteArgumentValue<'q>>) -> IsNull {
fn encode_by_ref(
&self,
args: &mut Vec<SqliteArgumentValue<'q>>,
) -> Result<IsNull, BoxDynError> {
args.push(SqliteArgumentValue::Blob(Cow::Owned(self.clone())));
IsNull::No
Ok(IsNull::No)
}
}

View file

@ -65,25 +65,25 @@ impl<Tz: TimeZone> Encode<'_, Sqlite> for DateTime<Tz>
where
Tz::Offset: Display,
{
fn encode_by_ref(&self, buf: &mut Vec<SqliteArgumentValue<'_>>) -> IsNull {
fn encode_by_ref(&self, buf: &mut Vec<SqliteArgumentValue<'_>>) -> Result<IsNull, BoxDynError> {
Encode::<Sqlite>::encode(self.to_rfc3339_opts(SecondsFormat::AutoSi, false), buf)
}
}
impl Encode<'_, Sqlite> for NaiveDateTime {
fn encode_by_ref(&self, buf: &mut Vec<SqliteArgumentValue<'_>>) -> IsNull {
fn encode_by_ref(&self, buf: &mut Vec<SqliteArgumentValue<'_>>) -> Result<IsNull, BoxDynError> {
Encode::<Sqlite>::encode(self.format("%F %T%.f").to_string(), buf)
}
}
impl Encode<'_, Sqlite> for NaiveDate {
fn encode_by_ref(&self, buf: &mut Vec<SqliteArgumentValue<'_>>) -> IsNull {
fn encode_by_ref(&self, buf: &mut Vec<SqliteArgumentValue<'_>>) -> Result<IsNull, BoxDynError> {
Encode::<Sqlite>::encode(self.format("%F").to_string(), buf)
}
}
impl Encode<'_, Sqlite> for NaiveTime {
fn encode_by_ref(&self, buf: &mut Vec<SqliteArgumentValue<'_>>) -> IsNull {
fn encode_by_ref(&self, buf: &mut Vec<SqliteArgumentValue<'_>>) -> Result<IsNull, BoxDynError> {
Encode::<Sqlite>::encode(self.format("%T%.f").to_string(), buf)
}
}

View file

@ -12,10 +12,13 @@ impl Type<Sqlite> for f32 {
}
impl<'q> Encode<'q, Sqlite> for f32 {
fn encode_by_ref(&self, args: &mut Vec<SqliteArgumentValue<'q>>) -> IsNull {
fn encode_by_ref(
&self,
args: &mut Vec<SqliteArgumentValue<'q>>,
) -> Result<IsNull, BoxDynError> {
args.push(SqliteArgumentValue::Double((*self).into()));
IsNull::No
Ok(IsNull::No)
}
}
@ -32,10 +35,13 @@ impl Type<Sqlite> for f64 {
}
impl<'q> Encode<'q, Sqlite> for f64 {
fn encode_by_ref(&self, args: &mut Vec<SqliteArgumentValue<'q>>) -> IsNull {
fn encode_by_ref(
&self,
args: &mut Vec<SqliteArgumentValue<'q>>,
) -> Result<IsNull, BoxDynError> {
args.push(SqliteArgumentValue::Double(*self));
IsNull::No
Ok(IsNull::No)
}
}

View file

@ -16,10 +16,13 @@ impl Type<Sqlite> for i8 {
}
impl<'q> Encode<'q, Sqlite> for i8 {
fn encode_by_ref(&self, args: &mut Vec<SqliteArgumentValue<'q>>) -> IsNull {
fn encode_by_ref(
&self,
args: &mut Vec<SqliteArgumentValue<'q>>,
) -> Result<IsNull, BoxDynError> {
args.push(SqliteArgumentValue::Int(*self as i32));
IsNull::No
Ok(IsNull::No)
}
}
@ -44,10 +47,13 @@ impl Type<Sqlite> for i16 {
}
impl<'q> Encode<'q, Sqlite> for i16 {
fn encode_by_ref(&self, args: &mut Vec<SqliteArgumentValue<'q>>) -> IsNull {
fn encode_by_ref(
&self,
args: &mut Vec<SqliteArgumentValue<'q>>,
) -> Result<IsNull, BoxDynError> {
args.push(SqliteArgumentValue::Int(*self as i32));
IsNull::No
Ok(IsNull::No)
}
}
@ -68,10 +74,13 @@ impl Type<Sqlite> for i32 {
}
impl<'q> Encode<'q, Sqlite> for i32 {
fn encode_by_ref(&self, args: &mut Vec<SqliteArgumentValue<'q>>) -> IsNull {
fn encode_by_ref(
&self,
args: &mut Vec<SqliteArgumentValue<'q>>,
) -> Result<IsNull, BoxDynError> {
args.push(SqliteArgumentValue::Int(*self));
IsNull::No
Ok(IsNull::No)
}
}
@ -92,10 +101,13 @@ impl Type<Sqlite> for i64 {
}
impl<'q> Encode<'q, Sqlite> for i64 {
fn encode_by_ref(&self, args: &mut Vec<SqliteArgumentValue<'q>>) -> IsNull {
fn encode_by_ref(
&self,
args: &mut Vec<SqliteArgumentValue<'q>>,
) -> Result<IsNull, BoxDynError> {
args.push(SqliteArgumentValue::Int64(*self));
IsNull::No
Ok(IsNull::No)
}
}

View file

@ -20,8 +20,8 @@ impl<T> Encode<'_, Sqlite> for Json<T>
where
T: Serialize,
{
fn encode_by_ref(&self, buf: &mut Vec<SqliteArgumentValue<'_>>) -> IsNull {
Encode::<Sqlite>::encode(self.encode_to_string(), buf)
fn encode_by_ref(&self, buf: &mut Vec<SqliteArgumentValue<'_>>) -> Result<IsNull, BoxDynError> {
Encode::<Sqlite>::encode(self.encode_to_string()?, buf)
}
}

View file

@ -14,10 +14,13 @@ impl Type<Sqlite> for str {
}
impl<'q> Encode<'q, Sqlite> for &'q str {
fn encode_by_ref(&self, args: &mut Vec<SqliteArgumentValue<'q>>) -> IsNull {
fn encode_by_ref(
&self,
args: &mut Vec<SqliteArgumentValue<'q>>,
) -> Result<IsNull, BoxDynError> {
args.push(SqliteArgumentValue::Text(Cow::Borrowed(*self)));
IsNull::No
Ok(IsNull::No)
}
}
@ -34,18 +37,21 @@ impl Type<Sqlite> for Box<str> {
}
impl Encode<'_, Sqlite> for Box<str> {
fn encode(self, args: &mut Vec<SqliteArgumentValue<'_>>) -> IsNull {
fn encode(self, args: &mut Vec<SqliteArgumentValue<'_>>) -> Result<IsNull, BoxDynError> {
args.push(SqliteArgumentValue::Text(Cow::Owned(self.into_string())));
IsNull::No
Ok(IsNull::No)
}
fn encode_by_ref(&self, args: &mut Vec<SqliteArgumentValue<'_>>) -> IsNull {
fn encode_by_ref(
&self,
args: &mut Vec<SqliteArgumentValue<'_>>,
) -> Result<IsNull, BoxDynError> {
args.push(SqliteArgumentValue::Text(Cow::Owned(
self.clone().into_string(),
)));
IsNull::No
Ok(IsNull::No)
}
}
@ -62,16 +68,19 @@ impl Type<Sqlite> for String {
}
impl<'q> Encode<'q, Sqlite> for String {
fn encode(self, args: &mut Vec<SqliteArgumentValue<'q>>) -> IsNull {
fn encode(self, args: &mut Vec<SqliteArgumentValue<'q>>) -> Result<IsNull, BoxDynError> {
args.push(SqliteArgumentValue::Text(Cow::Owned(self)));
IsNull::No
Ok(IsNull::No)
}
fn encode_by_ref(&self, args: &mut Vec<SqliteArgumentValue<'q>>) -> IsNull {
fn encode_by_ref(
&self,
args: &mut Vec<SqliteArgumentValue<'q>>,
) -> Result<IsNull, BoxDynError> {
args.push(SqliteArgumentValue::Text(Cow::Owned(self.clone())));
IsNull::No
Ok(IsNull::No)
}
}
@ -92,16 +101,19 @@ impl Type<Sqlite> for Cow<'_, str> {
}
impl<'q> Encode<'q, Sqlite> for Cow<'q, str> {
fn encode(self, args: &mut Vec<SqliteArgumentValue<'q>>) -> IsNull {
fn encode(self, args: &mut Vec<SqliteArgumentValue<'q>>) -> Result<IsNull, BoxDynError> {
args.push(SqliteArgumentValue::Text(self));
IsNull::No
Ok(IsNull::No)
}
fn encode_by_ref(&self, args: &mut Vec<SqliteArgumentValue<'q>>) -> IsNull {
fn encode_by_ref(
&self,
args: &mut Vec<SqliteArgumentValue<'q>>,
) -> Result<IsNull, BoxDynError> {
args.push(SqliteArgumentValue::Text(self.clone()));
IsNull::No
Ok(IsNull::No)
}
}

View file

@ -20,7 +20,7 @@ impl<'q, T> Encode<'q, Sqlite> for Text<T>
where
T: Display,
{
fn encode_by_ref(&self, buf: &mut Vec<SqliteArgumentValue<'q>>) -> IsNull {
fn encode_by_ref(&self, buf: &mut Vec<SqliteArgumentValue<'q>>) -> Result<IsNull, BoxDynError> {
Encode::<Sqlite>::encode(self.0.to_string(), buf)
}
}

View file

@ -55,29 +55,29 @@ impl Type<Sqlite> for Time {
}
impl Encode<'_, Sqlite> for OffsetDateTime {
fn encode_by_ref(&self, buf: &mut Vec<SqliteArgumentValue<'_>>) -> IsNull {
Encode::<Sqlite>::encode(self.format(&Rfc3339).unwrap(), buf)
fn encode_by_ref(&self, buf: &mut Vec<SqliteArgumentValue<'_>>) -> Result<IsNull, BoxDynError> {
Encode::<Sqlite>::encode(self.format(&Rfc3339)?, buf)
}
}
impl Encode<'_, Sqlite> for PrimitiveDateTime {
fn encode_by_ref(&self, buf: &mut Vec<SqliteArgumentValue<'_>>) -> IsNull {
fn encode_by_ref(&self, buf: &mut Vec<SqliteArgumentValue<'_>>) -> Result<IsNull, BoxDynError> {
let format = fd!("[year]-[month]-[day] [hour]:[minute]:[second].[subsecond]");
Encode::<Sqlite>::encode(self.format(&format).unwrap(), buf)
Encode::<Sqlite>::encode(self.format(&format)?, buf)
}
}
impl Encode<'_, Sqlite> for Date {
fn encode_by_ref(&self, buf: &mut Vec<SqliteArgumentValue<'_>>) -> IsNull {
fn encode_by_ref(&self, buf: &mut Vec<SqliteArgumentValue<'_>>) -> Result<IsNull, BoxDynError> {
let format = fd!("[year]-[month]-[day]");
Encode::<Sqlite>::encode(self.format(&format).unwrap(), buf)
Encode::<Sqlite>::encode(self.format(&format)?, buf)
}
}
impl Encode<'_, Sqlite> for Time {
fn encode_by_ref(&self, buf: &mut Vec<SqliteArgumentValue<'_>>) -> IsNull {
fn encode_by_ref(&self, buf: &mut Vec<SqliteArgumentValue<'_>>) -> Result<IsNull, BoxDynError> {
let format = fd!("[hour]:[minute]:[second].[subsecond]");
Encode::<Sqlite>::encode(self.format(&format).unwrap(), buf)
Encode::<Sqlite>::encode(self.format(&format)?, buf)
}
}

View file

@ -16,10 +16,13 @@ impl Type<Sqlite> for u8 {
}
impl<'q> Encode<'q, Sqlite> for u8 {
fn encode_by_ref(&self, args: &mut Vec<SqliteArgumentValue<'q>>) -> IsNull {
fn encode_by_ref(
&self,
args: &mut Vec<SqliteArgumentValue<'q>>,
) -> Result<IsNull, BoxDynError> {
args.push(SqliteArgumentValue::Int(*self as i32));
IsNull::No
Ok(IsNull::No)
}
}
@ -44,10 +47,13 @@ impl Type<Sqlite> for u16 {
}
impl<'q> Encode<'q, Sqlite> for u16 {
fn encode_by_ref(&self, args: &mut Vec<SqliteArgumentValue<'q>>) -> IsNull {
fn encode_by_ref(
&self,
args: &mut Vec<SqliteArgumentValue<'q>>,
) -> Result<IsNull, BoxDynError> {
args.push(SqliteArgumentValue::Int(*self as i32));
IsNull::No
Ok(IsNull::No)
}
}
@ -68,10 +74,13 @@ impl Type<Sqlite> for u32 {
}
impl<'q> Encode<'q, Sqlite> for u32 {
fn encode_by_ref(&self, args: &mut Vec<SqliteArgumentValue<'q>>) -> IsNull {
fn encode_by_ref(
&self,
args: &mut Vec<SqliteArgumentValue<'q>>,
) -> Result<IsNull, BoxDynError> {
args.push(SqliteArgumentValue::Int64(*self as i64));
IsNull::No
Ok(IsNull::No)
}
}

View file

@ -21,12 +21,15 @@ impl Type<Sqlite> for Uuid {
}
impl<'q> Encode<'q, Sqlite> for Uuid {
fn encode_by_ref(&self, args: &mut Vec<SqliteArgumentValue<'q>>) -> IsNull {
fn encode_by_ref(
&self,
args: &mut Vec<SqliteArgumentValue<'q>>,
) -> Result<IsNull, BoxDynError> {
args.push(SqliteArgumentValue::Blob(Cow::Owned(
self.as_bytes().to_vec(),
)));
IsNull::No
Ok(IsNull::No)
}
}
@ -44,10 +47,13 @@ impl Type<Sqlite> for Hyphenated {
}
impl<'q> Encode<'q, Sqlite> for Hyphenated {
fn encode_by_ref(&self, args: &mut Vec<SqliteArgumentValue<'q>>) -> IsNull {
fn encode_by_ref(
&self,
args: &mut Vec<SqliteArgumentValue<'q>>,
) -> Result<IsNull, BoxDynError> {
args.push(SqliteArgumentValue::Text(Cow::Owned(self.to_string())));
IsNull::No
Ok(IsNull::No)
}
}
@ -67,10 +73,13 @@ impl Type<Sqlite> for Simple {
}
impl<'q> Encode<'q, Sqlite> for Simple {
fn encode_by_ref(&self, args: &mut Vec<SqliteArgumentValue<'q>>) -> IsNull {
fn encode_by_ref(
&self,
args: &mut Vec<SqliteArgumentValue<'q>>,
) -> Result<IsNull, BoxDynError> {
args.push(SqliteArgumentValue::Text(Cow::Owned(self.to_string())));
IsNull::No
Ok(IsNull::No)
}
}

View file

@ -11,9 +11,13 @@ pub use sqlx_core::describe::Describe;
pub use sqlx_core::executor::{Execute, Executor};
pub use sqlx_core::from_row::FromRow;
pub use sqlx_core::pool::{self, Pool};
#[doc(hidden)]
pub use sqlx_core::query::query_with_result as __query_with_result;
pub use sqlx_core::query::{query, query_with};
pub use sqlx_core::query_as::{query_as, query_as_with};
pub use sqlx_core::query_builder::{self, QueryBuilder};
#[doc(hidden)]
pub use sqlx_core::query_scalar::query_scalar_with_result as __query_scalar_with_result;
pub use sqlx_core::query_scalar::{query_scalar, query_scalar_with};
pub use sqlx_core::raw_sql::{raw_sql, RawSql};
pub use sqlx_core::row::Row;

View file

@ -6,7 +6,7 @@ use sqlx::postgres::{
PgPoolOptions, PgRow, PgSeverity, Postgres,
};
use sqlx::{Column, Connection, Executor, Row, Statement, TypeInfo};
use sqlx_core::bytes::Bytes;
use sqlx_core::{bytes::Bytes, error::BoxDynError};
use sqlx_test::{new, pool, setup_if_needed};
use std::env;
use std::pin::Pin;
@ -1124,7 +1124,7 @@ CREATE TABLE heating_bills (
fn encode_by_ref(
&self,
buf: &mut sqlx::postgres::PgArgumentBuffer,
) -> sqlx::encode::IsNull {
) -> Result<sqlx::encode::IsNull, BoxDynError> {
<i16 as sqlx::Encode<Postgres>>::encode(self.0, buf)
}
}
@ -1162,12 +1162,12 @@ CREATE TABLE heating_bills (
fn encode_by_ref(
&self,
buf: &mut sqlx::postgres::PgArgumentBuffer,
) -> sqlx::encode::IsNull {
) -> Result<sqlx::encode::IsNull, sqlx::error::BoxDynError> {
let mut encoder = sqlx::postgres::types::PgRecordEncoder::new(buf);
encoder.encode(self.year);
encoder.encode(self.month);
encoder.encode(self.year)?;
encoder.encode(self.month)?;
encoder.finish();
sqlx::encode::IsNull::No
Ok(sqlx::encode::IsNull::No)
}
}
let mut conn = new::<Postgres>().await?;
@ -1484,7 +1484,7 @@ CREATE TYPE another.some_enum_type AS ENUM ('d', 'e', 'f');
fn encode_by_ref(
&self,
buf: &mut sqlx::postgres::PgArgumentBuffer,
) -> sqlx::encode::IsNull {
) -> Result<sqlx::encode::IsNull, BoxDynError> {
<String as sqlx::Encode<Postgres>>::encode_by_ref(&self.0, buf)
}
}
@ -1673,7 +1673,7 @@ async fn it_encodes_custom_array_issue_1504() -> anyhow::Result<()> {
}
}
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
match self {
Value::String(s) => <String as Encode<'_, Postgres>>::encode_by_ref(s, buf),
Value::Number(n) => <i32 as Encode<'_, Postgres>>::encode_by_ref(n, buf),
@ -1912,31 +1912,24 @@ async fn test_issue_3052() {
let mut conn = new::<Postgres>().await.unwrap();
let too_small_res = sqlx::query_scalar::<_, BigDecimal>("SELECT $1::numeric")
let too_small_error = sqlx::query_scalar::<_, BigDecimal>("SELECT $1::numeric")
.bind(&too_small)
.fetch_one(&mut conn)
.await;
.await
.expect_err("Too small number should have failed");
assert!(
matches!(&too_small_error, sqlx::Error::Encode(_)),
"expected encode error, got {too_small_error:?}"
);
match too_small_res {
Err(sqlx::Error::Database(dbe)) => {
let dbe = dbe.downcast::<PgDatabaseError>();
assert_eq!(dbe.code(), "22P03");
}
other => panic!("expected Err(DatabaseError), got {other:?}"),
}
let too_large_res = sqlx::query_scalar::<_, BigDecimal>("SELECT $1::numeric")
let too_large_error = sqlx::query_scalar::<_, BigDecimal>("SELECT $1::numeric")
.bind(&too_large)
.fetch_one(&mut conn)
.await;
.await
.expect_err("Too large number should have failed");
match too_large_res {
Err(sqlx::Error::Database(dbe)) => {
let dbe = dbe.downcast::<PgDatabaseError>();
assert_eq!(dbe.code(), "22P03");
}
other => panic!("expected Err(DatabaseError), got {other:?}"),
}
assert!(
matches!(&too_large_error, sqlx::Error::Encode(_)),
"expected encode error, got {too_large_error:?}",
);
}