mirror of
https://github.com/launchbadge/sqlx.git
synced 2026-04-10 22:10:36 +00:00
feat(postgres): add support for SQL NULL to Option<_> and sqlx::Null
This commit is contained in:
@@ -6,6 +6,22 @@ use crate::{decode, encode, Database, Decode, Encode, RawValue, Type};
|
||||
#[derive(Debug)]
|
||||
pub struct Null;
|
||||
|
||||
impl<Db: Database> Encode<Db> for Null {
|
||||
fn encode(
|
||||
&self,
|
||||
_: &<Db as Database>::TypeInfo,
|
||||
_: &mut <Db as HasOutput<'_>>::Output,
|
||||
) -> encode::Result {
|
||||
Ok(encode::IsNull::Yes)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r, Db: Database> Decode<'r, Db> for Null {
|
||||
fn decode(_: <Db as HasRawValue<'r>>::RawValue) -> decode::Result<Self> {
|
||||
Ok(Self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Db: Database, T: Type<Db>> Type<Db> for Option<T>
|
||||
where
|
||||
Null: Type<Db>,
|
||||
|
||||
@@ -47,7 +47,9 @@ impl Serialize<'_> for Execute<'_, '_> {
|
||||
for param in self.parameters {
|
||||
if let Some(argument) = args.next() {
|
||||
if let IsNull::Yes = argument.encode(param, &mut out)? {
|
||||
// no data *should* have been written to the buffer if we were told the expression is NULL
|
||||
debug_assert!(out.buffer().len() != prev_len);
|
||||
|
||||
out.null();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use sqlx_core::database::{HasOutput, HasRawValue};
|
||||
use sqlx_core::{decode, encode, Database, Decode, Encode, Null, Type};
|
||||
use sqlx_core::{Null, Type};
|
||||
|
||||
use crate::{MySql, MySqlOutput, MySqlRawValue, MySqlTypeId, MySqlTypeInfo};
|
||||
use crate::{MySql, MySqlTypeId};
|
||||
|
||||
impl Type<MySql> for Null {
|
||||
fn type_id() -> MySqlTypeId
|
||||
@@ -11,15 +10,3 @@ impl Type<MySql> for Null {
|
||||
MySqlTypeId::NULL
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode<MySql> for Null {
|
||||
fn encode(&self, _: &MySqlTypeInfo, _: &mut MySqlOutput<'_>) -> encode::Result {
|
||||
Ok(encode::IsNull::Yes)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r> Decode<'r, MySql> for Null {
|
||||
fn decode(_: MySqlRawValue<'r>) -> decode::Result<Self> {
|
||||
Ok(Self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ use sqlx_core::Result;
|
||||
use crate::io::PgWriteExt;
|
||||
use crate::protocol::frontend::{PortalRef, StatementRef};
|
||||
use crate::{PgArguments, PgOutput, PgRawValueFormat, PgTypeInfo};
|
||||
use sqlx_core::encode::IsNull;
|
||||
|
||||
pub(crate) struct Bind<'a> {
|
||||
pub(crate) portal: PortalRef,
|
||||
@@ -50,16 +51,28 @@ impl Serialize<'_> for Bind<'_> {
|
||||
let offset = out.buffer().len();
|
||||
out.buffer().extend_from_slice(&[0; 4]);
|
||||
|
||||
let len = if let Some(argument) = args.next() {
|
||||
argument.encode(param, &mut out)?;
|
||||
|
||||
// prefixed length does not include the length in the length
|
||||
// unlike the regular "prefixed length" bytes protocol type
|
||||
(out.buffer().len() - offset - 4) as i32
|
||||
} else {
|
||||
let prev_len = out.buffer().len();
|
||||
let null = args
|
||||
.next()
|
||||
.map(|argument| argument.encode(param, &mut out))
|
||||
.transpose()?
|
||||
// if we run out of values, start sending NULL for
|
||||
.unwrap_or(IsNull::Yes);
|
||||
|
||||
let len = match null {
|
||||
// NULL is encoded as a -1 for the length
|
||||
-1_i32
|
||||
IsNull::Yes => {
|
||||
// no data *should* have been written to the buffer if we were told the expression is NULL
|
||||
debug_assert_eq!(prev_len, out.buffer().len());
|
||||
|
||||
-1_i32
|
||||
}
|
||||
|
||||
IsNull::No => {
|
||||
// prefixed length does not include the length in the length
|
||||
// unlike the regular "prefixed length" bytes protocol type
|
||||
(out.buffer().len() - offset - 4) as i32
|
||||
}
|
||||
};
|
||||
|
||||
// write the len to the beginning of the value
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
mod bool;
|
||||
mod int;
|
||||
mod null;
|
||||
|
||||
// https://www.postgresql.org/docs/current/datatype.html
|
||||
|
||||
@@ -44,6 +44,30 @@ fn ensure_not_too_large_or_too_small(value: i128, ty: &PgTypeInfo) -> Result<(),
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn ensure_not_too_large(value: u128, ty: &PgTypeInfo) -> Result<(), encode::Error> {
|
||||
let max: u128 = match ty.id() {
|
||||
PgTypeId::SMALLINT => i16::MAX as _,
|
||||
PgTypeId::INTEGER => i32::MAX as _,
|
||||
PgTypeId::BIGINT => i64::MAX as _,
|
||||
|
||||
_ => {
|
||||
// for non-integer types, ignore the check
|
||||
// if we got this far its because someone asked for and `_unchecked` bind
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
if value > max {
|
||||
return Err(encode::Error::msg(format!(
|
||||
"number `{}` too large to fit in SQL type `{}`",
|
||||
value,
|
||||
ty.name()
|
||||
)));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn decode_int<T>(value: &PgRawValue<'_>) -> decode::Result<T>
|
||||
where
|
||||
T: TryFrom<i64> + TryFrom<u64> + FromStr,
|
||||
@@ -97,8 +121,38 @@ impl_type_int! { i32 => INTEGER }
|
||||
impl_type_int! { i64 => BIGINT }
|
||||
impl_type_int! { i128 => BIGINT }
|
||||
|
||||
impl_type_int! { u8 => SMALLINT }
|
||||
impl_type_int! { u16 => SMALLINT }
|
||||
impl_type_int! { u32 => INTEGER }
|
||||
impl_type_int! { u64 => BIGINT }
|
||||
impl_type_int! { u128 => BIGINT }
|
||||
macro_rules! impl_type_uint {
|
||||
($ty:ty $(: $real:ty)? => $sql:ident) => {
|
||||
impl Type<Postgres> for $ty {
|
||||
fn type_id() -> PgTypeId {
|
||||
PgTypeId::$sql
|
||||
}
|
||||
|
||||
fn compatible(ty: &PgTypeInfo) -> bool {
|
||||
ty.id().is_integer()
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode<Postgres> for $ty {
|
||||
fn encode(&self, ty: &PgTypeInfo, out: &mut PgOutput<'_>) -> encode::Result {
|
||||
ensure_not_too_large((*self $(as $real)?).into(), ty)?;
|
||||
|
||||
out.buffer().extend_from_slice(&self.to_be_bytes());
|
||||
|
||||
Ok(encode::IsNull::No)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r> Decode<'r, Postgres> for $ty {
|
||||
fn decode(value: PgRawValue<'r>) -> decode::Result<Self> {
|
||||
decode_int(&value)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_type_uint! { u8 => SMALLINT }
|
||||
impl_type_uint! { u16 => SMALLINT }
|
||||
impl_type_uint! { u32 => INTEGER }
|
||||
impl_type_uint! { u64 => BIGINT }
|
||||
impl_type_uint! { u128 => BIGINT }
|
||||
|
||||
19
sqlx-postgres/src/types/null.rs
Normal file
19
sqlx-postgres/src/types/null.rs
Normal file
@@ -0,0 +1,19 @@
|
||||
use sqlx_core::{Null, Type};
|
||||
|
||||
use crate::{PgTypeId, PgTypeInfo, Postgres};
|
||||
|
||||
impl Type<Postgres> for Null {
|
||||
fn type_id() -> PgTypeId
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
PgTypeId::Oid(0)
|
||||
}
|
||||
|
||||
fn compatible(_: &PgTypeInfo) -> bool
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
true
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user