Skip to content
This repository has been archived by the owner on Apr 25, 2023. It is now read-only.

fix(pg): error out on overflowing integer #415

Merged
merged 3 commits into from
Oct 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
79 changes: 75 additions & 4 deletions src/connector/postgres/conversion.rs
Expand Up @@ -632,11 +632,53 @@ impl<'a> ToSql for Value<'a> {
) -> Result<IsNull, Box<dyn StdError + 'static + Send + Sync>> {
let res =
match (self, ty) {
(Value::Int32(integer), &PostgresType::INT2) => integer.map(|integer| (integer as i16).to_sql(ty, out)),
(Value::Int32(integer), &PostgresType::INT2) => match integer {
Some(i) => {
let integer = i16::try_from(*i).map_err(|_| {
let kind = ErrorKind::conversion(format!(
"Unable to fit integer value '{}' into an INT2 (16-bit signed integer).",
i
));

Error::builder(kind).build()
})?;

Some(integer.to_sql(ty, out))
}
_ => None,
},
(Value::Int32(integer), &PostgresType::INT4) => integer.map(|integer| (integer as i32).to_sql(ty, out)),
(Value::Int32(integer), &PostgresType::INT8) => integer.map(|integer| (integer as i64).to_sql(ty, out)),
(Value::Int64(integer), &PostgresType::INT2) => integer.map(|integer| (integer as i16).to_sql(ty, out)),
(Value::Int64(integer), &PostgresType::INT4) => integer.map(|integer| (integer as i32).to_sql(ty, out)),
(Value::Int64(integer), &PostgresType::INT2) => match integer {
Some(i) => {
let integer = i16::try_from(*i).map_err(|_| {
let kind = ErrorKind::conversion(format!(
"Unable to fit integer value '{}' into an INT2 (16-bit signed integer).",
i
));

Error::builder(kind).build()
})?;

Some(integer.to_sql(ty, out))
}
_ => None,
},
(Value::Int64(integer), &PostgresType::INT4) => match integer {
Some(i) => {
let integer = i32::try_from(*i).map_err(|_| {
let kind = ErrorKind::conversion(format!(
"Unable to fit integer value '{}' into an INT4 (32-bit signed integer).",
i
));

Error::builder(kind).build()
})?;

Some(integer.to_sql(ty, out))
}
_ => None,
},
(Value::Int64(integer), &PostgresType::INT8) => integer.map(|integer| (integer as i64).to_sql(ty, out)),
#[cfg(feature = "bigdecimal")]
(Value::Int32(integer), &PostgresType::NUMERIC) => integer
Expand All @@ -654,7 +696,36 @@ impl<'a> ToSql for Value<'a> {
(Value::Int64(integer), &PostgresType::TEXT) => {
integer.map(|integer| format!("{}", integer).to_sql(ty, out))
}
(Value::Int64(integer), &PostgresType::OID) => integer.map(|integer| (integer as u32).to_sql(ty, out)),
(Value::Int32(integer), &PostgresType::OID) => match integer {
Some(i) => {
let integer = u32::try_from(*i).map_err(|_| {
let kind = ErrorKind::conversion(format!(
"Unable to fit integer value '{}' into an OID (32-bit unsigned integer).",
i
));

Error::builder(kind).build()
})?;

Some(integer.to_sql(ty, out))
}
_ => None,
},
(Value::Int64(integer), &PostgresType::OID) => match integer {
Some(i) => {
let integer = u32::try_from(*i).map_err(|_| {
let kind = ErrorKind::conversion(format!(
"Unable to fit integer value '{}' into an OID (32-bit unsigned integer).",
i
));

Error::builder(kind).build()
})?;

Some(integer.to_sql(ty, out))
}
_ => None,
},
(Value::Int32(integer), _) => integer.map(|integer| integer.to_sql(ty, out)),
(Value::Int64(integer), _) => integer.map(|integer| integer.to_sql(ty, out)),
(Value::Float(float), &PostgresType::FLOAT8) => float.map(|float| (float as f64).to_sql(ty, out)),
Expand Down
43 changes: 43 additions & 0 deletions src/tests/query.rs
Expand Up @@ -3526,3 +3526,46 @@ async fn double_commit_error(api: &mut dyn TestApi) -> crate::Result<()> {

Ok(())
}

#[test_each_connector(tags("postgresql"))]
async fn overflowing_int_errors_out(api: &mut dyn TestApi) -> crate::Result<()> {
let table = api.create_temp_table("smallint int2, int int4, oid oid").await?;

let insert = Insert::single_into(&table).value("smallint", (i16::MAX as i64) + 1);
let err = api.conn().insert(insert.into()).await.unwrap_err();
assert!(err
.to_string()
.contains("Unable to fit integer value '32768' into an INT2 (16-bit signed integer)."));

let insert = Insert::single_into(&table).value("smallint", (i16::MIN as i64) - 1);
let err = api.conn().insert(insert.into()).await.unwrap_err();
assert!(err
.to_string()
.contains("Unable to fit integer value '-32769' into an INT2 (16-bit signed integer)."));

let insert = Insert::single_into(&table).value("int", (i32::MAX as i64) + 1);
let err = api.conn().insert(insert.into()).await.unwrap_err();
assert!(err
.to_string()
.contains("Unable to fit integer value '2147483648' into an INT4 (32-bit signed integer)."));

let insert = Insert::single_into(&table).value("int", (i32::MIN as i64) - 1);
let err = api.conn().insert(insert.into()).await.unwrap_err();
assert!(err
.to_string()
.contains("Unable to fit integer value '-2147483649' into an INT4 (32-bit signed integer)."));

let insert = Insert::single_into(&table).value("oid", (u32::MAX as i64) + 1);
let err = api.conn().insert(insert.into()).await.unwrap_err();
assert!(err
.to_string()
.contains("Unable to fit integer value '4294967296' into an OID (32-bit unsigned integer)."));

let insert = Insert::single_into(&table).value("oid", -1);
let err = api.conn().insert(insert.into()).await.unwrap_err();
assert!(err
.to_string()
.contains("Unable to fit integer value '-1' into an OID (32-bit unsigned integer)."));

Ok(())
}
61 changes: 52 additions & 9 deletions src/tests/types/postgres.rs
Expand Up @@ -33,6 +33,14 @@ test_type!(int2(
Value::int32(i16::MAX),
));

test_type!(int2_with_int64(
postgresql,
"int2",
(Value::Int64(None), Value::Int32(None)),
(Value::int64(i16::MIN), Value::int32(i16::MIN)),
(Value::int64(i16::MAX), Value::int32(i16::MAX))
));

test_type!(int2_array(
postgresql,
"int2[]",
Expand All @@ -45,6 +53,15 @@ test_type!(int2_array(
]),
));

test_type!(int2_array_with_i64(
postgresql,
"int2[]",
(
Value::array(vec![Value::int64(i16::MIN), Value::int64(i16::MAX), Value::Int64(None)]),
Value::array(vec![Value::int32(i16::MIN), Value::int32(i16::MAX), Value::Int32(None)])
)
));

test_type!(int4(
postgresql,
"int4",
Expand All @@ -53,16 +70,28 @@ test_type!(int4(
Value::int32(i32::MAX),
));

test_type!(int4_with_i64(
postgresql,
"int4",
(Value::Int64(None), Value::Int32(None)),
(Value::int64(i32::MIN), Value::int32(i32::MIN)),
(Value::int64(i32::MAX), Value::int32(i32::MAX))
));

test_type!(int4_array(
postgresql,
"int4[]",
Value::Array(None),
Value::array(vec![
Value::int32(1),
Value::int32(2),
Value::int32(3),
Value::Int32(None)
]),
Value::array(vec![Value::int32(i32::MIN), Value::int32(i32::MAX), Value::Int32(None)]),
));

test_type!(int4_array_with_i64(
postgresql,
"int4[]",
(
Value::array(vec![Value::int64(i32::MIN), Value::int64(i32::MAX), Value::Int64(None)]),
Value::array(vec![Value::int32(i32::MIN), Value::int32(i32::MAX), Value::Int32(None)])
)
));

test_type!(int8(
Expand Down Expand Up @@ -108,9 +137,23 @@ test_type!(float8_array(
Value::array(vec![Value::double(1.1234), Value::double(4.321), Value::Double(None)])
));

// NOTE: OIDs are unsigned 4 byte integers, see https://www.postgresql.org/docs/9.4/datatype-oid.html
// but a u32 cannot fit in i32, so we use i64
test_type!(oid(postgresql, "oid", Value::Int64(None), Value::int64(10000)));
// NOTE: OIDs are unsigned 32-bit integers (see https://www.postgresql.org/docs/9.4/datatype-oid.html)
// but a u32 cannot fit in an i32, so we always read OIDs back from the database as i64s.
test_type!(oid_with_i32(
postgresql,
"oid",
(Value::Int32(None), Value::Int64(None)),
(Value::int32(i32::MAX), Value::int64(i32::MAX)),
(Value::int32(u32::MIN as i32), Value::int64(u32::MIN)),
));

test_type!(oid_with_i64(
postgresql,
"oid",
Value::Int64(None),
Value::int64(u32::MAX),
Value::int64(u32::MIN),
));

test_type!(oid_array(
postgresql,
Expand Down