pub use alloy::primitives::Uint;
pub use alloy::primitives::U128;
pub use alloy::primitives::U256;
use std::ops::Deref;
use std::str::FromStr;
use crate::error::SqlTypeError;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
mod convert;
mod operation;
mod primitive_ops;
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct SqlUint<const BITS: usize, const LIMBS: usize>(pub(crate) Uint<BITS, LIMBS>);
pub type SqlU128 = SqlUint<128, 2>;
pub type SqlU256 = SqlUint<256, 4>;
impl<const BITS: usize, const LIMBS: usize> SqlUint<BITS, LIMBS> {
pub const ZERO: Self = SqlUint(Uint::ZERO);
pub fn inner(&self) -> &Uint<BITS, LIMBS> {
&self.0
}
pub fn into_inner(self) -> Uint<BITS, LIMBS> {
self.0
}
pub fn from_be_slice(bytes: &[u8]) -> Self {
Self(Uint::<BITS, LIMBS>::from_be_slice(bytes))
}
pub fn as_u8(&self) -> Result<u8, crate::error::SqlTypeError> {
if self.0 > Uint::<BITS, LIMBS>::from(u8::MAX) {
Err(crate::error::SqlTypeError::Overflow)
} else {
Ok(self.0.to::<u8>())
}
}
pub fn as_u16(&self) -> Result<u16, crate::error::SqlTypeError> {
if self.0 > Uint::<BITS, LIMBS>::from(u16::MAX) {
Err(crate::error::SqlTypeError::Overflow)
} else {
Ok(self.0.to::<u16>())
}
}
pub fn as_u32(&self) -> Result<u32, crate::error::SqlTypeError> {
if self.0 > Uint::<BITS, LIMBS>::from(u32::MAX) {
Err(crate::error::SqlTypeError::Overflow)
} else {
Ok(self.0.to::<u32>())
}
}
pub fn as_u64(&self) -> Result<u64, crate::error::SqlTypeError> {
if self.0 > Uint::<BITS, LIMBS>::from(u64::MAX) {
Err(crate::error::SqlTypeError::Overflow)
} else {
Ok(self.0.to::<u64>())
}
}
pub fn as_u128(&self) -> Result<u128, crate::error::SqlTypeError> {
if self.0 > Uint::<BITS, LIMBS>::from(u128::MAX) {
Err(crate::error::SqlTypeError::Overflow)
} else {
Ok(self.0.to::<u128>())
}
}
}
impl SqlU256 {
pub const ETHER: Self = Self(U256::from_limbs([0x0, 0x8AC7230489E80000, 0, 0]));
}
impl<const BITS: usize, const LIMBS: usize> AsRef<Uint<BITS, LIMBS>> for SqlUint<BITS, LIMBS> {
fn as_ref(&self) -> &Uint<BITS, LIMBS> {
&self.0
}
}
impl<const BITS: usize, const LIMBS: usize> Deref for SqlUint<BITS, LIMBS> {
type Target = Uint<BITS, LIMBS>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<const BITS: usize, const LIMBS: usize> From<Uint<BITS, LIMBS>> for SqlUint<BITS, LIMBS> {
fn from(value: Uint<BITS, LIMBS>) -> Self {
SqlUint(value)
}
}
impl<const BITS: usize, const LIMBS: usize> From<SqlUint<BITS, LIMBS>> for Uint<BITS, LIMBS> {
fn from(value: SqlUint<BITS, LIMBS>) -> Self {
value.0
}
}
impl<const BITS: usize, const LIMBS: usize> FromStr for SqlUint<BITS, LIMBS> {
type Err = SqlTypeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Uint::from_str(s)
.map(SqlUint)
.map_err(|e| SqlTypeError::InvalidFormat(e.to_string()))
}
}
impl<const BITS: usize, const LIMBS: usize> std::fmt::Display for SqlUint<BITS, LIMBS> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "0x{:x}", self.0)
}
}
impl<const BITS: usize, const LIMBS: usize> std::fmt::LowerHex for SqlUint<BITS, LIMBS> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
impl<const BITS: usize, const LIMBS: usize> std::fmt::UpperHex for SqlUint<BITS, LIMBS> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
impl<const BITS: usize, const LIMBS: usize> Default for SqlUint<BITS, LIMBS> {
fn default() -> Self {
Self::ZERO
}
}
macro_rules! impl_cmp_with_primitives {
($sql_ty:ty) => {
impl PartialEq<u8> for $sql_ty {
fn eq(&self, other: &u8) -> bool { *self == <$sql_ty>::from(*other) }
}
impl PartialEq<u16> for $sql_ty {
fn eq(&self, other: &u16) -> bool { *self == <$sql_ty>::from(*other) }
}
impl PartialEq<u32> for $sql_ty {
fn eq(&self, other: &u32) -> bool { *self == <$sql_ty>::from(*other) }
}
impl PartialEq<u64> for $sql_ty {
fn eq(&self, other: &u64) -> bool { *self == <$sql_ty>::from(*other) }
}
impl PartialEq<u128> for $sql_ty {
fn eq(&self, other: &u128) -> bool { *self == <$sql_ty>::from(*other) }
}
impl PartialOrd<u8> for $sql_ty {
fn partial_cmp(&self, other: &u8) -> Option<std::cmp::Ordering> {
self.partial_cmp(&<$sql_ty>::from(*other))
}
}
impl PartialOrd<u16> for $sql_ty {
fn partial_cmp(&self, other: &u16) -> Option<std::cmp::Ordering> {
self.partial_cmp(&<$sql_ty>::from(*other))
}
}
impl PartialOrd<u32> for $sql_ty {
fn partial_cmp(&self, other: &u32) -> Option<std::cmp::Ordering> {
self.partial_cmp(&<$sql_ty>::from(*other))
}
}
impl PartialOrd<u64> for $sql_ty {
fn partial_cmp(&self, other: &u64) -> Option<std::cmp::Ordering> {
self.partial_cmp(&<$sql_ty>::from(*other))
}
}
impl PartialOrd<u128> for $sql_ty {
fn partial_cmp(&self, other: &u128) -> Option<std::cmp::Ordering> {
self.partial_cmp(&<$sql_ty>::from(*other))
}
}
impl PartialEq<$sql_ty> for u8 {
fn eq(&self, other: &$sql_ty) -> bool { <$sql_ty>::from(*self) == *other }
}
impl PartialEq<$sql_ty> for u16 {
fn eq(&self, other: &$sql_ty) -> bool { <$sql_ty>::from(*self) == *other }
}
impl PartialEq<$sql_ty> for u32 {
fn eq(&self, other: &$sql_ty) -> bool { <$sql_ty>::from(*self) == *other }
}
impl PartialEq<$sql_ty> for u64 {
fn eq(&self, other: &$sql_ty) -> bool { <$sql_ty>::from(*self) == *other }
}
impl PartialEq<$sql_ty> for u128 {
fn eq(&self, other: &$sql_ty) -> bool { <$sql_ty>::from(*self) == *other }
}
impl PartialOrd<$sql_ty> for u8 {
fn partial_cmp(&self, other: &$sql_ty) -> Option<std::cmp::Ordering> {
<$sql_ty>::from(*self).partial_cmp(other)
}
}
impl PartialOrd<$sql_ty> for u16 {
fn partial_cmp(&self, other: &$sql_ty) -> Option<std::cmp::Ordering> {
<$sql_ty>::from(*self).partial_cmp(other)
}
}
impl PartialOrd<$sql_ty> for u32 {
fn partial_cmp(&self, other: &$sql_ty) -> Option<std::cmp::Ordering> {
<$sql_ty>::from(*self).partial_cmp(other)
}
}
impl PartialOrd<$sql_ty> for u64 {
fn partial_cmp(&self, other: &$sql_ty) -> Option<std::cmp::Ordering> {
<$sql_ty>::from(*self).partial_cmp(other)
}
}
impl PartialOrd<$sql_ty> for u128 {
fn partial_cmp(&self, other: &$sql_ty) -> Option<std::cmp::Ordering> {
<$sql_ty>::from(*self).partial_cmp(other)
}
}
};
}
impl_cmp_with_primitives!(crate::SqlU128);
impl_cmp_with_primitives!(crate::SqlU256);
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "serde")]
#[test]
fn test_serde() {
let value = "0x12345678";
let s_value = SqlUint::<32, 1>::from_str(value).unwrap();
let json = serde_json::to_string(&s_value).unwrap();
assert_eq!(json, "\"0x12345678\"");
let de: SqlUint<32, 1> = serde_json::from_str(&json).unwrap();
assert_eq!(s_value, de);
}
#[test]
fn test_creation_and_constants() {
let zero = SqlU256::ZERO;
assert_eq!(zero, SqlU256::from(0u64));
let value = SqlU256::from(U256::from(42u64));
assert_eq!(value, SqlU256::from(42u64));
}
#[test]
fn test_from_conversions() {
let u256_val = U256::from(123456789u64);
let sql_u256 = SqlU256::from(u256_val);
assert_eq!(sql_u256.inner(), &u256_val);
let back_to_u256: U256 = sql_u256.into();
assert_eq!(back_to_u256, u256_val);
}
#[test]
fn test_inner_and_deref() {
let sql_u256 = SqlU256::from(42u64);
let inner_ref: &U256 = sql_u256.inner();
assert_eq!(*inner_ref, U256::from(42u64));
let as_ref: &U256 = sql_u256.as_ref();
assert_eq!(*as_ref, U256::from(42u64));
let deref_val: U256 = *sql_u256;
assert_eq!(deref_val, U256::from(42u64));
}
#[test]
fn test_from_str_parsing() {
let from_decimal = SqlU256::from_str("123456789").unwrap();
assert_eq!(from_decimal, SqlU256::from(123456789u64));
let from_hex = SqlU256::from_str("0x75bcd15").unwrap();
assert_eq!(from_hex, SqlU256::from(123456789u64));
assert_eq!(from_decimal, from_hex);
let zero_decimal = SqlU256::from_str("0").unwrap();
let zero_hex = SqlU256::from_str("0x0").unwrap();
assert_eq!(zero_decimal, zero_hex);
assert_eq!(zero_decimal, SqlU256::ZERO);
}
#[test]
fn test_from_str_edge_cases() {
let max_hex = format!("0x{:x}", U256::MAX);
let max_sql = SqlU256::from_str(&max_hex).unwrap();
assert_eq!(max_sql.inner(), &U256::MAX);
let zero_cases = [
("", "empty string"),
("0", "zero"),
("00", "double zero"),
("0x", "just 0x prefix"),
("0x0", "0x0"),
("0x00", "0x00"),
];
for (input, _desc) in zero_cases {
let result = SqlU256::from_str(input).unwrap();
assert_eq!(
result,
SqlU256::ZERO,
"Input '{}' should parse as zero",
input
);
}
assert!(SqlU256::from_str("not_a_number").is_err());
assert!(SqlU256::from_str("0xnot_hex").is_err());
assert!(SqlU256::from_str("123abc").is_err());
assert!(SqlU256::from_str("0x123xyz").is_err());
}
#[test]
fn test_display_formatting() {
let test_cases = [
(0u64, "0x0"),
(255u64, "0xff"),
(0xdeadbeef_u64, "0xdeadbeef"),
(123456789u64, "0x75bcd15"),
];
for (input, expected) in test_cases {
let sql_u256 = SqlU256::from(input);
let display_str = format!("{}", sql_u256);
assert_eq!(display_str, expected);
}
}
#[test]
fn test_round_trip_consistency() {
let test_values = [
U256::from(0u64),
U256::from(1u64),
U256::from(255u64),
U256::from(0xdeadbeef_u64),
U256::from(123456789u64),
U256::MAX,
];
for original_u256 in test_values {
let sql_u256 = SqlU256::from(original_u256);
let display_str = format!("{}", sql_u256);
let parsed_back = SqlU256::from_str(&display_str).unwrap();
assert_eq!(sql_u256, parsed_back);
let back_to_u256: U256 = sql_u256.into();
assert_eq!(back_to_u256, original_u256);
}
}
#[test]
fn test_equality_and_comparison() {
let a = SqlU256::from(100u64);
let b = SqlU256::from(100u64);
let c = SqlU256::from(200u64);
assert_eq!(a, b);
assert_ne!(a, c);
let zero1 = SqlU256::ZERO;
let zero2 = SqlU256::from(0u64);
assert_eq!(zero1, zero2);
}
#[test]
fn test_clone_and_copy() {
let original = SqlU256::from(42u64);
let cloned = original.clone();
assert_eq!(original, cloned);
let copied = original;
assert_eq!(original, copied);
assert_eq!(original, SqlU256::from(42u64));
}
#[test]
fn test_debug_formatting() {
let sql_u256 = SqlU256::from(42u64);
let debug_str = format!("{:?}", sql_u256);
assert!(debug_str.contains("SqlUint"));
}
#[test]
fn test_sql_u256_hash() {
use std::collections::{HashMap, HashSet};
let val1 = SqlU256::from(123u64);
let val2 = SqlU256::from(123u64);
let val3 = SqlU256::from(456u64);
use std::hash::{DefaultHasher, Hash, Hasher};
let mut hasher1 = DefaultHasher::new();
let mut hasher2 = DefaultHasher::new();
let mut hasher3 = DefaultHasher::new();
val1.hash(&mut hasher1);
val2.hash(&mut hasher2);
val3.hash(&mut hasher3);
assert_eq!(hasher1.finish(), hasher2.finish());
assert_ne!(hasher1.finish(), hasher3.finish());
let mut value_set = HashSet::new();
value_set.insert(val1);
value_set.insert(val2); value_set.insert(val3);
value_set.insert(SqlU256::ZERO);
assert_eq!(value_set.len(), 3);
assert!(value_set.contains(&val1));
assert!(value_set.contains(&val2));
assert!(value_set.contains(&val3));
assert!(value_set.contains(&SqlU256::ZERO));
let mut value_map = HashMap::new();
value_map.insert(val1, "First value");
value_map.insert(val2, "Same value"); value_map.insert(val3, "Different value");
value_map.insert(SqlU256::ZERO, "Zero value");
assert_eq!(value_map.len(), 3);
assert_eq!(value_map.get(&val1), Some(&"Same value"));
assert_eq!(value_map.get(&val2), Some(&"Same value"));
assert_eq!(value_map.get(&val3), Some(&"Different value"));
assert_eq!(value_map.get(&SqlU256::ZERO), Some(&"Zero value"));
let large1 =
SqlU256::from_str("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
.unwrap();
let large2 = SqlU256::from_str(
"115792089237316195423570985008687907853269984665640564039457584007913129639935",
)
.unwrap();
let mut large_hasher1 = DefaultHasher::new();
let mut large_hasher2 = DefaultHasher::new();
large1.hash(&mut large_hasher1);
large2.hash(&mut large_hasher2);
assert_eq!(large_hasher1.finish(), large_hasher2.finish());
assert_eq!(large1, large2);
}
#[test]
fn test_sql_u256_hash_consistency_with_alloy_u256() {
use std::hash::{DefaultHasher, Hash, Hasher};
fn calculate_hash<T: Hash>(t: &T) -> u64 {
let mut hasher = DefaultHasher::new();
t.hash(&mut hasher);
hasher.finish()
}
let test_values = [
"0",
"42",
"1000000000000000000",
"0x75bcd15",
"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
];
for value_str in &test_values {
let alloy_u256 = U256::from_str(value_str).unwrap();
let sql_u256 = SqlU256::from_str(value_str).unwrap();
let alloy_hash = calculate_hash(&alloy_u256);
let sql_hash = calculate_hash(&sql_u256);
assert_eq!(
alloy_hash, sql_hash,
"Hash mismatch for value {}: alloy={}, sql={}",
value_str, alloy_hash, sql_hash
);
}
let original = U256::from(12345u64);
let sql_wrapped = SqlU256::from(original);
let converted_back: U256 = sql_wrapped.into();
assert_eq!(calculate_hash(&original), calculate_hash(&sql_wrapped));
assert_eq!(calculate_hash(&original), calculate_hash(&converted_back));
assert_eq!(
calculate_hash(&sql_wrapped),
calculate_hash(&converted_back)
);
assert_eq!(calculate_hash(&U256::ZERO), calculate_hash(&SqlU256::ZERO));
let max_alloy = U256::MAX;
let max_sql = SqlU256::from(max_alloy);
assert_eq!(calculate_hash(&max_alloy), calculate_hash(&max_sql));
}
}