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
92 changed files with 791 additions and 455 deletions

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)
}
}