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

feat: support unsigned int 32 #422

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions src/ast/values.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ pub enum Value<'a> {
Int32(Option<i32>),
/// 64-bit signed integer.
Int64(Option<i64>),
/// 32-bit unsigned integer.
UnsignedInt32(Option<u32>),
/// 32-bit floating point.
Float(Option<f32>),
/// 64-bit floating point.
Expand Down Expand Up @@ -112,6 +114,7 @@ impl<'a> fmt::Display for Value<'a> {
let res = match self {
Value::Int32(val) => val.map(|v| write!(f, "{}", v)),
Value::Int64(val) => val.map(|v| write!(f, "{}", v)),
Value::UnsignedInt32(val) => val.map(|v| write!(f, "{}", v)),
Value::Float(val) => val.map(|v| write!(f, "{}", v)),
Value::Double(val) => val.map(|v| write!(f, "{}", v)),
Value::Text(val) => val.as_ref().map(|v| write!(f, "\"{}\"", v)),
Expand Down Expand Up @@ -161,6 +164,7 @@ impl<'a> From<Value<'a>> for serde_json::Value {
let res = match pv {
Value::Int32(i) => i.map(|i| serde_json::Value::Number(Number::from(i))),
Value::Int64(i) => i.map(|i| serde_json::Value::Number(Number::from(i))),
Value::UnsignedInt32(u) => u.map(|u| serde_json::Value::Number(Number::from(u))),
Value::Float(f) => f.map(|f| match Number::from_f64(f as f64) {
Some(number) => serde_json::Value::Number(number),
None => serde_json::Value::Null,
Expand Down Expand Up @@ -222,6 +226,14 @@ impl<'a> Value<'a> {
Value::Int64(Some(value.into()))
}

/// Creates a new 32-bit signed integer.
pub fn uint32<I>(value: I) -> Self
where
I: Into<u32>,
{
Value::UnsignedInt32(Some(value.into()))
}

/// Creates a new 32-bit signed integer.
pub fn integer<I>(value: I) -> Self
where
Expand Down Expand Up @@ -344,6 +356,7 @@ impl<'a> Value<'a> {
match self {
Value::Int32(i) => i.is_none(),
Value::Int64(i) => i.is_none(),
Value::UnsignedInt32(u) => u.is_none(),
Value::Float(i) => i.is_none(),
Value::Double(i) => i.is_none(),
Value::Text(t) => t.is_none(),
Expand Down Expand Up @@ -442,6 +455,11 @@ impl<'a> Value<'a> {
matches!(self, Value::Int64(_))
}

/// `true` if the `Value` is a 64-bit signed integer.
pub const fn is_uint32(&self) -> bool {
matches!(self, Value::UnsignedInt32(_))
}

/// `true` if the `Value` is a signed integer.
pub const fn is_integer(&self) -> bool {
matches!(self, Value::Int32(_) | Value::Int64(_))
Expand All @@ -463,6 +481,14 @@ impl<'a> Value<'a> {
}
}

/// Returns an `i32` if the value is a 32-bit signed integer, otherwise `None`.
pub const fn as_uint32(&self) -> Option<u32> {
match self {
Value::UnsignedInt32(i) => *i,
_ => None,
}
}

/// Returns an `i64` if the value is a signed integer, otherwise `None`.
pub fn as_integer(&self) -> Option<i64> {
match self {
Expand Down Expand Up @@ -526,6 +552,7 @@ impl<'a> Value<'a> {
// For schemas which don't tag booleans
Value::Int32(Some(i)) if *i == 0 || *i == 1 => true,
Value::Int64(Some(i)) if *i == 0 || *i == 1 => true,
Value::UnsignedInt32(Some(i)) if *i == 0 || *i == 1 => true,
_ => false,
}
}
Expand All @@ -537,6 +564,7 @@ impl<'a> Value<'a> {
// For schemas which don't tag booleans
Value::Int32(Some(i)) if *i == 0 || *i == 1 => Some(*i == 1),
Value::Int64(Some(i)) if *i == 0 || *i == 1 => Some(*i == 1),
Value::UnsignedInt32(Some(i)) if *i == 0 || *i == 1 => Some(*i == 1),
_ => None,
}
}
Expand Down Expand Up @@ -662,6 +690,7 @@ impl<'a> Value<'a> {

value!(val: i64, Int64, val);
value!(val: i32, Int32, val);
value!(val: u32, UnsignedInt32, val);
value!(val: bool, Boolean, val);
value!(val: &'a str, Text, val.into());
value!(val: String, Text, val.into());
Expand Down Expand Up @@ -708,6 +737,16 @@ impl<'a> TryFrom<Value<'a>> for i32 {
}
}

impl<'a> TryFrom<Value<'a>> for u32 {
type Error = Error;

fn try_from(value: Value<'a>) -> Result<u32, Self::Error> {
value
.as_uint32()
.ok_or_else(|| Error::builder(ErrorKind::conversion("Not a u32")).build())
}
}

#[cfg(feature = "bigdecimal")]
impl<'a> TryFrom<Value<'a>> for BigDecimal {
type Error = Error;
Expand Down Expand Up @@ -931,6 +970,13 @@ mod tests {
assert_eq!(values, vec![1]);
}

#[test]
fn a_parameterized_value_of_uints32_can_be_converted_into_a_vec() {
let pv = Value::array(vec![1_u32]);
let values: Vec<u32> = pv.into_vec().expect("convert into Vec<i64>");
assert_eq!(values, vec![1]);
}

#[test]
fn a_parameterized_value_of_reals_can_be_converted_into_a_vec() {
let pv = Value::array(vec![1.0]);
Expand Down
1 change: 1 addition & 0 deletions src/connector/mssql/conversion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ impl<'a> IntoSql<'a> for &'a Value<'a> {
match self {
Value::Int32(val) => val.into_sql(),
Value::Int64(val) => val.into_sql(),
Value::UnsignedInt32(val) => val.map(|val| val as i64).into_sql(),
Value::Float(val) => val.into_sql(),
Value::Double(val) => val.into_sql(),
Value::Text(val) => val.as_deref().into_sql(),
Expand Down
24 changes: 16 additions & 8 deletions src/connector/mysql/conversion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ pub fn conv_params(params: &[Value<'_>]) -> crate::Result<my::Params> {
let res = match pv {
Value::Int32(i) => i.map(|i| my::Value::Int(i as i64)),
Value::Int64(i) => i.map(my::Value::Int),
Value::UnsignedInt32(u) => u.map(|u| my::Value::UInt(u as u64)),
Value::Float(f) => f.map(my::Value::Float),
Value::Double(f) => f.map(my::Value::Double),
Value::Text(s) => s.clone().map(|s| my::Value::Bytes((&*s).as_bytes().to_vec())),
Expand Down Expand Up @@ -110,7 +111,7 @@ impl TypeIdentifier for my::Column {

let is_unsigned = self.flags().intersects(ColumnFlags::UNSIGNED_FLAG);

// https://dev.mysql.com/doc/internals/en/binary-protocol-value.html#packet-ProtocolBinary
// https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_binary_resultset.html
// MYSQL_TYPE_TINY = i8
// MYSQL_TYPE_SHORT = i16
// MYSQL_TYPE_YEAR = i16
Expand All @@ -129,16 +130,20 @@ impl TypeIdentifier for my::Column {
fn is_int64(&self) -> bool {
use ColumnType::*;

// https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_binary_resultset.html
// MYSQL_TYPE_LONGLONG = i64
matches!(self.column_type(), MYSQL_TYPE_LONGLONG)
}

fn is_uint32(&self) -> bool {
use ColumnType::*;

let is_unsigned = self.flags().intersects(ColumnFlags::UNSIGNED_FLAG);

// https://dev.mysql.com/doc/internals/en/binary-protocol-value.html#packet-ProtocolBinary
// MYSQL_TYPE_LONGLONG = i64
// UNSIGNED MYSQL_TYPE_LONG = u32
// https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_binary_resultset.html
// UNSIGNED MYSQL_TYPE_LONG = u32
// UNSIGNED MYSQL_TYPE_INT24 = u32
matches!(
(self.column_type(), is_unsigned),
(MYSQL_TYPE_LONGLONG, _) | (MYSQL_TYPE_LONG, true) | (MYSQL_TYPE_INT24, true)
)
is_unsigned && matches!(self.column_type(), MYSQL_TYPE_LONG | MYSQL_TYPE_INT24)
}

fn is_datetime(&self) -> bool {
Expand Down Expand Up @@ -267,7 +272,9 @@ impl TakeRow for my::Row {
my::Value::Bytes(b) if column.character_set() == 63 => Value::bytes(b),
my::Value::Bytes(s) => Value::text(String::from_utf8(s)?),
my::Value::Int(i) if column.is_int64() => Value::int64(i),
my::Value::Int(i) if column.is_uint32() => Value::uint32(i as u32),
my::Value::Int(i) => Value::int32(i as i32),
my::Value::UInt(i) if column.is_uint32() => Value::uint32(u32::try_from(i)?),
my::Value::UInt(i) => Value::int64(i64::try_from(i).map_err(|_| {
let msg = "Unsigned integers larger than 9_223_372_036_854_775_807 are currently not handled.";
let kind = ErrorKind::value_out_of_range(msg);
Expand Down Expand Up @@ -315,6 +322,7 @@ impl TakeRow for my::Row {
t if t.is_enum() => Value::Enum(None),
t if t.is_null() => Value::Int32(None),
t if t.is_int64() => Value::Int64(None),
t if t.is_uint32() => Value::UnsignedInt32(None),
t if t.is_int32() => Value::Int32(None),
t if t.is_float() => Value::Float(None),
t if t.is_double() => Value::Double(None),
Expand Down
42 changes: 39 additions & 3 deletions src/connector/postgres/conversion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ pub(crate) fn params_to_types(params: &[Value<'_>]) -> Vec<PostgresType> {
match p {
Value::Int32(_) => PostgresType::INT4,
Value::Int64(_) => PostgresType::INT8,
Value::UnsignedInt32(_) => PostgresType::INT8,
Value::Float(_) => PostgresType::FLOAT4,
Value::Double(_) => PostgresType::FLOAT8,
Value::Text(_) => PostgresType::TEXT,
Expand Down Expand Up @@ -84,6 +85,7 @@ pub(crate) fn params_to_types(params: &[Value<'_>]) -> Vec<PostgresType> {
match first {
Value::Int32(_) => PostgresType::INT4_ARRAY,
Value::Int64(_) => PostgresType::INT8_ARRAY,
Value::UnsignedInt32(_) => PostgresType::INT8_ARRAY,
Value::Float(_) => PostgresType::FLOAT4_ARRAY,
Value::Double(_) => PostgresType::FLOAT8_ARRAY,
Value::Text(_) => PostgresType::TEXT_ARRAY,
Expand Down Expand Up @@ -406,7 +408,7 @@ impl GetRow for PostgresRow {
PostgresType::OID_ARRAY => match row.try_get(i)? {
Some(val) => {
let val: Vec<Option<u32>> = val;
let nums = val.into_iter().map(|oid| Value::Int64(oid.map(|oid| oid as i64)));
let nums = val.into_iter().map(Value::UnsignedInt32);

Value::array(nums)
}
Expand Down Expand Up @@ -475,9 +477,9 @@ impl GetRow for PostgresRow {
PostgresType::OID => match row.try_get(i)? {
Some(val) => {
let val: u32 = val;
Value::int64(val)
Value::uint32(val)
}
None => Value::Int64(None),
None => Value::UnsignedInt32(None),
},
PostgresType::CHAR => match row.try_get(i)? {
Some(val) => {
Expand Down Expand Up @@ -680,6 +682,39 @@ impl<'a> ToSql for Value<'a> {
_ => None,
},
(Value::Int64(integer), &PostgresType::INT8) => integer.map(|integer| (integer as i64).to_sql(ty, out)),
(Value::UnsignedInt32(integer), &PostgresType::INT2) => match integer {
Some(i) => {
let integer = i16::try_from(*i).map_err(|_| {
let kind = ErrorKind::conversion(format!(
"Unable to fit unsigned integer value '{}' into an INT2 (16-bit signed integer).",
i
));

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

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

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

Some(integer.to_sql(ty, out))
}
None => None,
},
(Value::UnsignedInt32(integer), &PostgresType::INT8) => {
integer.map(|integer| (integer as i64).to_sql(ty, out))
}
#[cfg(feature = "bigdecimal")]
(Value::Int32(integer), &PostgresType::NUMERIC) => integer
.map(|integer| BigDecimal::from_i32(integer).unwrap())
Expand Down Expand Up @@ -728,6 +763,7 @@ impl<'a> ToSql for Value<'a> {
},
(Value::Int32(integer), _) => integer.map(|integer| integer.to_sql(ty, out)),
(Value::Int64(integer), _) => integer.map(|integer| integer.to_sql(ty, out)),
(Value::UnsignedInt32(integer), _) => integer.map(|integer| integer.to_sql(ty, out)),
(Value::Float(float), &PostgresType::FLOAT8) => float.map(|float| (float as f64).to_sql(ty, out)),
#[cfg(feature = "bigdecimal")]
(Value::Float(float), &PostgresType::NUMERIC) => float
Expand Down
5 changes: 5 additions & 0 deletions src/connector/sqlite/conversion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ impl TypeIdentifier for Column<'_> {
)
}

fn is_uint32(&self) -> bool {
false
}

fn is_datetime(&self) -> bool {
matches!(
self.decl_type(),
Expand Down Expand Up @@ -253,6 +257,7 @@ impl<'a> ToSql for Value<'a> {
let value = match self {
Value::Int32(integer) => integer.map(ToSqlOutput::from),
Value::Int64(integer) => integer.map(ToSqlOutput::from),
Value::UnsignedInt32(integer) => integer.map(|u| ToSqlOutput::from(u as i64)),
Value::Float(float) => float.map(|f| f as f64).map(ToSqlOutput::from),
Value::Double(double) => double.map(ToSqlOutput::from),
Value::Text(cow) => cow.as_ref().map(|cow| ToSqlOutput::from(cow.as_ref())),
Expand Down
1 change: 1 addition & 0 deletions src/connector/type_identifier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pub(crate) trait TypeIdentifier {
fn is_double(&self) -> bool;
fn is_int32(&self) -> bool;
fn is_int64(&self) -> bool;
fn is_uint32(&self) -> bool;
fn is_datetime(&self) -> bool;
fn is_time(&self) -> bool;
fn is_date(&self) -> bool;
Expand Down
2 changes: 2 additions & 0 deletions src/serde.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ impl<'de> Deserializer<'de> for ValueDeserializer<'de> {
Value::Int32(None) => visitor.visit_none(),
Value::Int64(Some(i)) => visitor.visit_i64(i),
Value::Int64(None) => visitor.visit_none(),
Value::UnsignedInt32(Some(i)) => visitor.visit_u32(i),
Value::UnsignedInt32(None) => visitor.visit_none(),
Value::Boolean(Some(b)) => visitor.visit_bool(b),
Value::Boolean(None) => visitor.visit_none(),
Value::Char(Some(c)) => visitor.visit_char(c),
Expand Down
8 changes: 8 additions & 0 deletions src/tests/types/mssql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,14 @@ test_type!(bigint(
Value::int64(i64::MAX),
));

test_type!(bigint_from_u32(
mssql,
"bigint",
(Value::UnsignedInt32(None), Value::Int64(None)),
(Value::uint32(u32::MIN), Value::int64(u32::MIN as i64)),
(Value::uint32(u32::MAX), Value::int64(u32::MAX as i64)),
));

test_type!(float_24(mssql, "float(24)", Value::Float(None), Value::float(1.23456),));

test_type!(real(mssql, "real", Value::Float(None), Value::float(1.123456)));
Expand Down
20 changes: 10 additions & 10 deletions src/tests/types/mysql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,9 @@ test_type!(mediumint(
test_type!(mediumint_unsigned(
mysql,
"mediumint unsigned",
Value::Int64(None),
Value::int64(0),
Value::int64(16777215)
Value::UnsignedInt32(None),
Value::uint32(0u32),
Value::uint32(16777215u32)
));

test_type!(int(
Expand All @@ -81,18 +81,18 @@ test_type!(int(
test_type!(int_unsigned(
mysql,
"int unsigned",
Value::Int64(None),
Value::int64(0),
Value::int64(2173158296i64),
Value::int64(4294967295i64)
Value::UnsignedInt32(None),
Value::uint32(0u32),
Value::uint32(2173158296u32),
Value::uint32(4294967295u32)
));

test_type!(int_unsigned_not_null(
mysql,
"int unsigned not null",
Value::int64(0),
Value::int64(2173158296i64),
Value::int64(4294967295i64)
Value::uint32(0_u32),
Value::uint32(2173158296_u32),
Value::uint32(4294967295_u32)
));

test_type!(bigint(
Expand Down
Loading