opt out of compatible type check for null values

This commit is contained in:
Ryan Leckey
2020-03-25 04:25:38 -07:00
parent 7ab772ea80
commit 6049f976f9
10 changed files with 58 additions and 52 deletions

View File

@@ -121,42 +121,42 @@ jobs:
# -----------------------------------------------------
# integration test: async-std (chrono)
- run: cargo test --no-default-features --features 'runtime-async-std postgres macros uuid chrono bigdecimal ipnetwork tls'
- run: cargo test --no-default-features --features 'runtime-async-std postgres macros uuid chrono bigdecimal json ipnetwork tls'
env:
DATABASE_URL: postgres://postgres:postgres@localhost:${{ job.services.postgres.ports[5432] }}/postgres
# integration test: async-std (time)
- run: cargo test --no-default-features --features 'runtime-async-std postgres macros uuid time bigdecimal ipnetwork tls'
- run: cargo test --no-default-features --features 'runtime-async-std postgres macros uuid time bigdecimal json ipnetwork tls'
env:
DATABASE_URL: postgres://postgres:postgres@localhost:${{ job.services.postgres.ports[5432] }}/postgres
# integration test: async-std (time + chrono)
- run: cargo test --no-default-features --features 'runtime-async-std postgres macros uuid chrono time bigdecimal ipnetwork tls'
- run: cargo test --no-default-features --features 'runtime-async-std postgres macros uuid chrono time bigdecimal json ipnetwork tls'
env:
DATABASE_URL: postgres://postgres:postgres@localhost:${{ job.services.postgres.ports[5432] }}/postgres
# integration test: tokio (chrono)
- run: cargo test --no-default-features --features 'runtime-tokio postgres macros uuid chrono bigdecimal ipnetwork tls'
- run: cargo test --no-default-features --features 'runtime-tokio postgres macros uuid chrono bigdecimal json ipnetwork tls'
env:
DATABASE_URL: postgres://postgres:postgres@localhost:${{ job.services.postgres.ports[5432] }}/postgres
# integration test: tokio (time)
- run: cargo test --no-default-features --features 'runtime-tokio postgres macros uuid time bigdecimal ipnetwork tls'
- run: cargo test --no-default-features --features 'runtime-tokio postgres macros uuid time bigdecimal json ipnetwork tls'
env:
DATABASE_URL: postgres://postgres:postgres@localhost:${{ job.services.postgres.ports[5432] }}/postgres
# integration test: tokio (time + chrono)
- run: cargo test --no-default-features --features 'runtime-tokio postgres macros uuid chrono time bigdecimal ipnetwork tls'
- run: cargo test --no-default-features --features 'runtime-tokio postgres macros uuid chrono time bigdecimal json ipnetwork tls'
env:
DATABASE_URL: postgres://postgres:postgres@localhost:${{ job.services.postgres.ports[5432] }}/postgres
# UI feature gate tests: async-std
- run: cargo test --no-default-features --features 'runtime-async-std postgres macros bigdecimal ipnetwork tls'
- run: cargo test --no-default-features --features 'runtime-async-std postgres macros bigdecimal json ipnetwork tls'
env:
DATABASE_URL: postgres://postgres:postgres@localhost:${{ job.services.postgres.ports[5432] }}/postgres
# UI feature gate tests: tokio
- run: cargo test --no-default-features --features 'runtime-tokio postgres macros bigdecimal ipnetwork tls'
- run: cargo test --no-default-features --features 'runtime-tokio postgres macros bigdecimal json ipnetwork tls'
env:
DATABASE_URL: postgres://postgres:postgres@localhost:${{ job.services.postgres.ports[5432] }}/postgres

View File

@@ -28,7 +28,7 @@ impl<'c> Row<'c> for MySqlRow<'c> {
let column_ty = self.row.columns[index].clone();
let buffer = self.row.get(index);
let value = match (self.row.binary, buffer) {
(_, None) => MySqlValue::null(column_ty),
(_, None) => MySqlValue::null(),
(true, Some(buf)) => MySqlValue::binary(column_ty, buf),
(false, Some(buf)) => MySqlValue::text(column_ty, buf),
};

View File

@@ -10,7 +10,7 @@ pub enum MySqlData<'c> {
#[derive(Debug)]
pub struct MySqlValue<'c> {
type_info: MySqlTypeInfo,
type_info: Option<MySqlTypeInfo>,
data: Option<MySqlData<'c>>,
}
@@ -31,23 +31,23 @@ impl<'c> MySqlValue<'c> {
self.data
}
pub(crate) fn null(type_info: MySqlTypeInfo) -> Self {
pub(crate) fn null() -> Self {
Self {
type_info,
type_info: None,
data: None,
}
}
pub(crate) fn binary(type_info: MySqlTypeInfo, buf: &'c [u8]) -> Self {
Self {
type_info,
type_info: Some(type_info),
data: Some(MySqlData::Binary(buf)),
}
}
pub(crate) fn text(type_info: MySqlTypeInfo, buf: &'c [u8]) -> Self {
Self {
type_info,
type_info: Some(type_info),
data: Some(MySqlData::Text(buf)),
}
}
@@ -56,7 +56,7 @@ impl<'c> MySqlValue<'c> {
impl<'c> RawValue<'c> for MySqlValue<'c> {
type Database = MySql;
fn type_info(&self) -> MySqlTypeInfo {
fn type_info(&self) -> Option<MySqlTypeInfo> {
self.type_info.clone()
}
}

View File

@@ -62,7 +62,7 @@ where
T: Serialize,
{
fn encode(&self, buf: &mut Vec<u8>) {
// JSONB version (as of 2020-03-20)
// JSONB version (as of 2020-03-20 )
buf.put_u8(1);
serde_json::to_writer(buf, &self.0)
@@ -79,7 +79,7 @@ where
(match value.try_get()? {
PgData::Text(s) => serde_json::from_str(s),
PgData::Binary(mut buf) => {
if value.type_info().id == TypeId::JSONB {
if value.type_info().as_ref().map(|info| info.id) == Some(TypeId::JSONB) {
let version = buf.get_u8()?;
assert_eq!(

View File

@@ -65,7 +65,11 @@ impl<'c> PgValue<'c> {
impl<'c> RawValue<'c> for PgValue<'c> {
type Database = Postgres;
fn type_info(&self) -> PgTypeInfo {
PgTypeInfo::with_oid(self.type_id.0)
fn type_info(&self) -> Option<PgTypeInfo> {
if self.data.is_some() {
Some(PgTypeInfo::with_oid(self.type_id.0))
} else {
None
}
}
}

View File

@@ -134,10 +134,15 @@ where
T: Decode<'c, Self::Database>,
{
let value = self.try_get_raw(index)?;
let expected_ty = value.type_info();
if !expected_ty.compatible(&T::type_info()) {
return Err(crate::Error::mismatched_types::<T>(expected_ty));
if let Some(expected_ty) = value.type_info() {
// NOTE: If there is no type, the value is NULL. This is fine. If the user tries
// to get this into a non-Option we catch that elsewhere and report as
// UnexpectedNullError.
if !expected_ty.compatible(&T::type_info()) {
return Err(crate::Error::mismatched_types::<T>(expected_ty));
}
}
T::decode(value)

View File

@@ -35,7 +35,6 @@ mod str;
// https://www.sqlite.org/c3ref/c_blob.html
#[derive(Debug, PartialEq, Clone, Copy)]
pub(crate) enum SqliteType {
Null = 0,
Integer = 1,
Float = 2,
Text = 3,
@@ -78,7 +77,6 @@ impl Display for SqliteTypeInfo {
SqliteType::Integer => "INTEGER",
SqliteType::Float => "DOUBLE",
SqliteType::Blob => "BLOB",
SqliteType::Null => "NULL",
})
}
}

View File

@@ -27,20 +27,20 @@ pub struct SqliteValue<'c> {
impl<'c> SqliteValue<'c> {
/// Returns true if the value should be intrepreted as NULL.
pub(super) fn is_null(&self) -> bool {
self.r#type() == SqliteType::Null
self.r#type().is_none()
}
fn r#type(&self) -> SqliteType {
fn r#type(&self) -> Option<SqliteType> {
#[allow(unsafe_code)]
let type_code = unsafe { sqlite3_column_type(self.statement.handle(), self.index) };
// SQLITE_INTEGER, SQLITE_FLOAT, SQLITE_TEXT, SQLITE_BLOB, or SQLITE_NULL
match type_code {
SQLITE_INTEGER => SqliteType::Integer,
SQLITE_FLOAT => SqliteType::Float,
SQLITE_TEXT => SqliteType::Text,
SQLITE_BLOB => SqliteType::Blob,
SQLITE_NULL => SqliteType::Null,
SQLITE_INTEGER => Some(SqliteType::Integer),
SQLITE_FLOAT => Some(SqliteType::Float),
SQLITE_TEXT => Some(SqliteType::Text),
SQLITE_BLOB => Some(SqliteType::Blob),
SQLITE_NULL => None,
_ => unreachable!("received unexpected column type: {}", type_code),
}
@@ -111,10 +111,10 @@ impl<'c> SqliteValue<'c> {
impl<'c> RawValue<'c> for SqliteValue<'c> {
type Database = Sqlite;
fn type_info(&self) -> SqliteTypeInfo {
SqliteTypeInfo {
r#type: self.r#type(),
fn type_info(&self) -> Option<SqliteTypeInfo> {
Some(SqliteTypeInfo {
r#type: self.r#type()?,
affinity: None,
}
})
}
}

View File

@@ -19,5 +19,5 @@ pub trait HasRawValue<'c> {
pub trait RawValue<'c> {
type Database: Database;
fn type_info(&self) -> <Self::Database as Database>::TypeInfo;
fn type_info(&self) -> Option<<Self::Database as Database>::TypeInfo>;
}

View File

@@ -60,22 +60,21 @@ CREATE TEMPORARY TABLE users (id INTEGER PRIMARY KEY);
async fn it_can_return_interleaved_nulls_issue_104() -> anyhow::Result<()> {
let mut conn = new::<Postgres>().await?;
let tuple =
sqlx::query("SELECT NULL::INT, 10::INT, NULL, 20::INT, NULL, 40::INT, NULL, 80::INT")
.try_map(|row: PgRow| {
Ok((
row.get::<Option<i32>, _>(0),
row.get::<Option<i32>, _>(1),
row.get::<Option<i32>, _>(2),
row.get::<Option<i32>, _>(3),
row.get::<Option<i32>, _>(4),
row.get::<Option<i32>, _>(5),
row.get::<Option<i32>, _>(6),
row.get::<Option<i32>, _>(7),
))
})
.fetch_one(&mut conn)
.await?;
let tuple = sqlx::query("SELECT NULL, 10::INT, NULL, 20::INT, NULL, 40::INT, NULL, 80::INT")
.try_map(|row: PgRow| {
Ok((
row.get::<Option<i32>, _>(0),
row.get::<Option<i32>, _>(1),
row.get::<Option<i32>, _>(2),
row.get::<Option<i32>, _>(3),
row.get::<Option<i32>, _>(4),
row.get::<Option<i32>, _>(5),
row.get::<Option<i32>, _>(6),
row.get::<Option<i32>, _>(7),
))
})
.fetch_one(&mut conn)
.await?;
assert_eq!(tuple.0, None);
assert_eq!(tuple.1, Some(10));