mirror of
https://github.com/launchbadge/sqlx.git
synced 2026-03-19 08:39:44 +00:00
feat: add ipnet support (#3710)
* feat: add ipnet support * fix: ipnet not decoding IP address strings * fix: prefer ipnetwork to ipnet for compatibility * fix: unnecessary cfg
This commit is contained in:
@@ -19,6 +19,7 @@ offline = ["sqlx-core/offline"]
|
||||
bigdecimal = ["dep:bigdecimal", "dep:num-bigint", "sqlx-core/bigdecimal"]
|
||||
bit-vec = ["dep:bit-vec", "sqlx-core/bit-vec"]
|
||||
chrono = ["dep:chrono", "sqlx-core/chrono"]
|
||||
ipnet = ["dep:ipnet", "sqlx-core/ipnet"]
|
||||
ipnetwork = ["dep:ipnetwork", "sqlx-core/ipnetwork"]
|
||||
mac_address = ["dep:mac_address", "sqlx-core/mac_address"]
|
||||
rust_decimal = ["dep:rust_decimal", "rust_decimal/maths", "sqlx-core/rust_decimal"]
|
||||
@@ -43,6 +44,7 @@ sha2 = { version = "0.10.0", default-features = false }
|
||||
bigdecimal = { workspace = true, optional = true }
|
||||
bit-vec = { workspace = true, optional = true }
|
||||
chrono = { workspace = true, optional = true }
|
||||
ipnet = { workspace = true, optional = true }
|
||||
ipnetwork = { workspace = true, optional = true }
|
||||
mac_address = { workspace = true, optional = true }
|
||||
rust_decimal = { workspace = true, optional = true }
|
||||
|
||||
@@ -88,6 +88,9 @@ impl_type_checking!(
|
||||
#[cfg(feature = "ipnetwork")]
|
||||
sqlx::types::ipnetwork::IpNetwork,
|
||||
|
||||
#[cfg(feature = "ipnet")]
|
||||
sqlx::types::ipnet::IpNet,
|
||||
|
||||
#[cfg(feature = "mac_address")]
|
||||
sqlx::types::mac_address::MacAddress,
|
||||
|
||||
@@ -149,6 +152,9 @@ impl_type_checking!(
|
||||
#[cfg(feature = "ipnetwork")]
|
||||
Vec<sqlx::types::ipnetwork::IpNetwork> | &[sqlx::types::ipnetwork::IpNetwork],
|
||||
|
||||
#[cfg(feature = "ipnet")]
|
||||
Vec<sqlx::types::ipnet::IpNet> | &[sqlx::types::ipnet::IpNet],
|
||||
|
||||
#[cfg(feature = "mac_address")]
|
||||
Vec<sqlx::types::mac_address::MacAddress> | &[sqlx::types::mac_address::MacAddress],
|
||||
|
||||
|
||||
62
sqlx-postgres/src/types/ipnet/ipaddr.rs
Normal file
62
sqlx-postgres/src/types/ipnet/ipaddr.rs
Normal file
@@ -0,0 +1,62 @@
|
||||
use std::net::IpAddr;
|
||||
|
||||
use ipnet::IpNet;
|
||||
|
||||
use crate::decode::Decode;
|
||||
use crate::encode::{Encode, IsNull};
|
||||
use crate::error::BoxDynError;
|
||||
use crate::types::Type;
|
||||
use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueRef, Postgres};
|
||||
|
||||
impl Type<Postgres> for IpAddr
|
||||
where
|
||||
IpNet: Type<Postgres>,
|
||||
{
|
||||
fn type_info() -> PgTypeInfo {
|
||||
IpNet::type_info()
|
||||
}
|
||||
|
||||
fn compatible(ty: &PgTypeInfo) -> bool {
|
||||
IpNet::compatible(ty)
|
||||
}
|
||||
}
|
||||
|
||||
impl PgHasArrayType for IpAddr {
|
||||
fn array_type_info() -> PgTypeInfo {
|
||||
<IpNet as PgHasArrayType>::array_type_info()
|
||||
}
|
||||
|
||||
fn array_compatible(ty: &PgTypeInfo) -> bool {
|
||||
<IpNet as PgHasArrayType>::array_compatible(ty)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'db> Encode<'db, Postgres> for IpAddr
|
||||
where
|
||||
IpNet: Encode<'db, Postgres>,
|
||||
{
|
||||
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
|
||||
IpNet::from(*self).encode_by_ref(buf)
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> usize {
|
||||
IpNet::from(*self).size_hint()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'db> Decode<'db, Postgres> for IpAddr
|
||||
where
|
||||
IpNet: Decode<'db, Postgres>,
|
||||
{
|
||||
fn decode(value: PgValueRef<'db>) -> Result<Self, BoxDynError> {
|
||||
let ipnet = IpNet::decode(value)?;
|
||||
|
||||
if matches!(ipnet, IpNet::V4(net) if net.prefix_len() != 32)
|
||||
|| matches!(ipnet, IpNet::V6(net) if net.prefix_len() != 128)
|
||||
{
|
||||
Err("lossy decode from inet/cidr")?
|
||||
}
|
||||
|
||||
Ok(ipnet.addr())
|
||||
}
|
||||
}
|
||||
130
sqlx-postgres/src/types/ipnet/ipnet.rs
Normal file
130
sqlx-postgres/src/types/ipnet/ipnet.rs
Normal file
@@ -0,0 +1,130 @@
|
||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
|
||||
|
||||
#[cfg(feature = "ipnet")]
|
||||
use ipnet::{IpNet, Ipv4Net, Ipv6Net};
|
||||
|
||||
use crate::decode::Decode;
|
||||
use crate::encode::{Encode, IsNull};
|
||||
use crate::error::BoxDynError;
|
||||
use crate::types::Type;
|
||||
use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres};
|
||||
|
||||
// https://github.com/postgres/postgres/blob/574925bfd0a8175f6e161936ea11d9695677ba09/src/include/utils/inet.h#L39
|
||||
|
||||
// Technically this is a magic number here but it doesn't make sense to drag in the whole of `libc`
|
||||
// just for one constant.
|
||||
const PGSQL_AF_INET: u8 = 2; // AF_INET
|
||||
const PGSQL_AF_INET6: u8 = PGSQL_AF_INET + 1;
|
||||
|
||||
impl Type<Postgres> for IpNet {
|
||||
fn type_info() -> PgTypeInfo {
|
||||
PgTypeInfo::INET
|
||||
}
|
||||
|
||||
fn compatible(ty: &PgTypeInfo) -> bool {
|
||||
*ty == PgTypeInfo::CIDR || *ty == PgTypeInfo::INET
|
||||
}
|
||||
}
|
||||
|
||||
impl PgHasArrayType for IpNet {
|
||||
fn array_type_info() -> PgTypeInfo {
|
||||
PgTypeInfo::INET_ARRAY
|
||||
}
|
||||
|
||||
fn array_compatible(ty: &PgTypeInfo) -> bool {
|
||||
*ty == PgTypeInfo::CIDR_ARRAY || *ty == PgTypeInfo::INET_ARRAY
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode<'_, Postgres> for IpNet {
|
||||
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
|
||||
// https://github.com/postgres/postgres/blob/574925bfd0a8175f6e161936ea11d9695677ba09/src/backend/utils/adt/network.c#L293
|
||||
// https://github.com/postgres/postgres/blob/574925bfd0a8175f6e161936ea11d9695677ba09/src/backend/utils/adt/network.c#L271
|
||||
|
||||
match self {
|
||||
IpNet::V4(net) => {
|
||||
buf.push(PGSQL_AF_INET); // ip_family
|
||||
buf.push(net.prefix_len()); // ip_bits
|
||||
buf.push(0); // is_cidr
|
||||
buf.push(4); // nb (number of bytes)
|
||||
buf.extend_from_slice(&net.addr().octets()) // address
|
||||
}
|
||||
|
||||
IpNet::V6(net) => {
|
||||
buf.push(PGSQL_AF_INET6); // ip_family
|
||||
buf.push(net.prefix_len()); // ip_bits
|
||||
buf.push(0); // is_cidr
|
||||
buf.push(16); // nb (number of bytes)
|
||||
buf.extend_from_slice(&net.addr().octets()); // address
|
||||
}
|
||||
}
|
||||
|
||||
Ok(IsNull::No)
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> usize {
|
||||
match self {
|
||||
IpNet::V4(_) => 8,
|
||||
IpNet::V6(_) => 20,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode<'_, Postgres> for IpNet {
|
||||
fn decode(value: PgValueRef<'_>) -> Result<Self, BoxDynError> {
|
||||
let bytes = match value.format() {
|
||||
PgValueFormat::Binary => value.as_bytes()?,
|
||||
PgValueFormat::Text => {
|
||||
let s = value.as_str()?;
|
||||
println!("{s}");
|
||||
if s.contains('/') {
|
||||
return Ok(s.parse()?);
|
||||
}
|
||||
// IpNet::from_str doesn't handle conversion from IpAddr to IpNet
|
||||
let addr: IpAddr = s.parse()?;
|
||||
return Ok(addr.into());
|
||||
}
|
||||
};
|
||||
|
||||
if bytes.len() >= 8 {
|
||||
let family = bytes[0];
|
||||
let prefix = bytes[1];
|
||||
let _is_cidr = bytes[2] != 0;
|
||||
let len = bytes[3];
|
||||
|
||||
match family {
|
||||
PGSQL_AF_INET => {
|
||||
if bytes.len() == 8 && len == 4 {
|
||||
let inet = Ipv4Net::new(
|
||||
Ipv4Addr::new(bytes[4], bytes[5], bytes[6], bytes[7]),
|
||||
prefix,
|
||||
)?;
|
||||
|
||||
return Ok(IpNet::V4(inet));
|
||||
}
|
||||
}
|
||||
|
||||
PGSQL_AF_INET6 => {
|
||||
if bytes.len() == 20 && len == 16 {
|
||||
let inet = Ipv6Net::new(
|
||||
Ipv6Addr::from([
|
||||
bytes[4], bytes[5], bytes[6], bytes[7], bytes[8], bytes[9],
|
||||
bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15],
|
||||
bytes[16], bytes[17], bytes[18], bytes[19],
|
||||
]),
|
||||
prefix,
|
||||
)?;
|
||||
|
||||
return Ok(IpNet::V6(inet));
|
||||
}
|
||||
}
|
||||
|
||||
_ => {
|
||||
return Err(format!("unknown ip family {family}").into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err("invalid data received when expecting an INET".into())
|
||||
}
|
||||
}
|
||||
7
sqlx-postgres/src/types/ipnet/mod.rs
Normal file
7
sqlx-postgres/src/types/ipnet/mod.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
// Prefer `ipnetwork` over `ipnet` because it was implemented first (want to avoid breaking change).
|
||||
#[cfg(not(feature = "ipnetwork"))]
|
||||
mod ipaddr;
|
||||
|
||||
// Parent module is named after the `ipnet` crate, this is named after the `IpNet` type.
|
||||
#[allow(clippy::module_inception)]
|
||||
mod ipnet;
|
||||
5
sqlx-postgres/src/types/ipnetwork/mod.rs
Normal file
5
sqlx-postgres/src/types/ipnetwork/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
mod ipaddr;
|
||||
|
||||
// Parent module is named after the `ipnetwork` crate, this is named after the `IpNetwork` type.
|
||||
#[allow(clippy::module_inception)]
|
||||
mod ipnetwork;
|
||||
@@ -87,7 +87,7 @@
|
||||
//!
|
||||
//! ### [`ipnetwork`](https://crates.io/crates/ipnetwork)
|
||||
//!
|
||||
//! Requires the `ipnetwork` Cargo feature flag.
|
||||
//! Requires the `ipnetwork` Cargo feature flag (takes precedence over `ipnet` if both are used).
|
||||
//!
|
||||
//! | Rust type | Postgres type(s) |
|
||||
//! |---------------------------------------|------------------------------------------------------|
|
||||
@@ -100,6 +100,17 @@
|
||||
//!
|
||||
//! `IpNetwork` does not have this limitation.
|
||||
//!
|
||||
//! ### [`ipnet`](https://crates.io/crates/ipnet)
|
||||
//!
|
||||
//! Requires the `ipnet` Cargo feature flag.
|
||||
//!
|
||||
//! | Rust type | Postgres type(s) |
|
||||
//! |---------------------------------------|------------------------------------------------------|
|
||||
//! | `ipnet::IpNet` | INET, CIDR |
|
||||
//! | `std::net::IpAddr` | INET, CIDR |
|
||||
//!
|
||||
//! The same `IpAddr` limitation for smaller network prefixes applies as with `ipnet`.
|
||||
//!
|
||||
//! ### [`mac_address`](https://crates.io/crates/mac_address)
|
||||
//!
|
||||
//! Requires the `mac_address` Cargo feature flag.
|
||||
@@ -248,11 +259,11 @@ mod time;
|
||||
#[cfg(feature = "uuid")]
|
||||
mod uuid;
|
||||
|
||||
#[cfg(feature = "ipnetwork")]
|
||||
mod ipnetwork;
|
||||
#[cfg(feature = "ipnet")]
|
||||
mod ipnet;
|
||||
|
||||
#[cfg(feature = "ipnetwork")]
|
||||
mod ipaddr;
|
||||
mod ipnetwork;
|
||||
|
||||
#[cfg(feature = "mac_address")]
|
||||
mod mac_address;
|
||||
|
||||
Reference in New Issue
Block a user