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

Commit

Permalink
fix(pg): error out on overflowing integer (#415)
Browse files Browse the repository at this point in the history
  • Loading branch information
Weakky committed Oct 20, 2022
1 parent e5eb328 commit 08831df
Show file tree
Hide file tree
Showing 3 changed files with 170 additions and 13 deletions.
79 changes: 75 additions & 4 deletions src/connector/postgres/conversion.rs
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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

0 comments on commit 08831df

Please sign in to comment.