postgres: ipnetwork support

This commit is contained in:
PoiScript 2020-03-19 18:21:09 +08:00
parent 9be1512833
commit 060c5d2b66
5 changed files with 149 additions and 0 deletions

11
Cargo.lock generated
View File

@ -829,6 +829,15 @@ dependencies = [
"libc",
]
[[package]]
name = "ipnetwork"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8eca9f51da27bc908ef3dd85c21e1bbba794edaf94d7841e37356275b82d31e"
dependencies = [
"serde",
]
[[package]]
name = "itoa"
version = "0.4.5"
@ -1706,6 +1715,8 @@ dependencies = [
"generic-array",
"hex",
"hmac",
"ipnetwork",
"libc",
"libsqlite3-sys",
"log",
"matches",

View File

@ -18,6 +18,7 @@ unstable = []
# we need a feature which activates `num-bigint` as well because
# `bigdecimal` uses types from it but does not reexport (tsk tsk)
bigdecimal_bigint = ["bigdecimal", "num-bigint"]
network-address = [ "ipnetwork", "libc" ]
postgres = [ "md-5", "sha2", "base64", "sha-1", "rand", "hmac", "futures-channel/sink", "futures-util/sink" ]
mysql = [ "sha-1", "sha2", "generic-array", "num-bigint", "base64", "digest", "rand" ]
sqlite = [ "libsqlite3-sys" ]
@ -43,6 +44,8 @@ futures-util = { version = "0.3.4", default-features = false }
generic-array = { version = "0.12.3", default-features = false, optional = true }
hex = "0.4.2"
hmac = { version = "0.7.1", default-features = false, optional = true }
ipnetwork = { version = "0.16.0", default-feature = false, optional = true }
libc = { version = "0.2.68", default-feature = false, optional = true }
log = { version = "0.4.8", default-features = false }
md-5 = { version = "0.8.0", default-features = false, optional = true }
memchr = { version = "2.3.3", default-features = false }

View File

@ -33,6 +33,9 @@ impl TypeId {
pub(crate) const UUID: TypeId = TypeId(2950);
pub(crate) const CIDR: TypeId = TypeId(650);
pub(crate) const INET: TypeId = TypeId(869);
// Arrays
pub(crate) const ARRAY_BOOL: TypeId = TypeId(1000);
@ -56,4 +59,7 @@ impl TypeId {
pub(crate) const ARRAY_BYTEA: TypeId = TypeId(1001);
pub(crate) const ARRAY_UUID: TypeId = TypeId(2951);
pub(crate) const ARRAY_CIDR: TypeId = TypeId(651);
pub(crate) const ARRAY_INET: TypeId = TypeId(1041);
}

View File

@ -33,6 +33,14 @@
//! |---------------------------------------|------------------------------------------------------|
//! | `uuid::Uuid` | UUID |
//!
//! ### [`ipnetwork`](https://crates.io/crates/ipnetwork)
//!
//! Requires the `network-address` Cargo feature flag.
//!
//! | Rust type | Postgres type(s) |
//! |---------------------------------------|------------------------------------------------------|
//! | `ipnetwork::IpNetwork` | INET, CIDR |
//!
//! # Composite types
//!
//! Anonymous composite types are represented as tuples.
@ -70,6 +78,8 @@ mod chrono;
#[cfg(feature = "uuid")]
mod uuid;
mod network;
/// Type information for a Postgres SQL type.
#[derive(Debug, Clone)]
pub struct PgTypeInfo {

View File

@ -0,0 +1,119 @@
use std::convert::TryInto;
use std::net::{Ipv4Addr, Ipv6Addr};
use ipnetwork::{IpNetwork, Ipv4Network, Ipv6Network};
use crate::decode::Decode;
use crate::encode::Encode;
use crate::postgres::protocol::TypeId;
use crate::postgres::row::PgValue;
use crate::postgres::types::PgTypeInfo;
use crate::postgres::Postgres;
use crate::types::Type;
use crate::Error;
#[cfg(windows)]
const AF_INET: u8 = 2;
// Maybe not used, but defining to follow Rust's libstd/net/sys
#[cfg(redox)]
const AF_INET: u8 = 1;
#[cfg(not(any(windows, redox)))]
const AF_INET: u8 = libc::AF_INET as u8;
const PGSQL_AF_INET: u8 = AF_INET;
const PGSQL_AF_INET6: u8 = AF_INET + 1;
const INET_TYPE: u8 = 0;
const CIDR_TYPE: u8 = 1;
impl Type<Postgres> for IpNetwork {
fn type_info() -> PgTypeInfo {
PgTypeInfo::new(TypeId::INET, "INET")
}
}
impl Type<Postgres> for [IpNetwork] {
fn type_info() -> PgTypeInfo {
PgTypeInfo::new(TypeId::ARRAY_INET, "INET[]")
}
}
impl Encode<Postgres> for IpNetwork {
fn encode(&self, buf: &mut Vec<u8>) {
encode(self, INET_TYPE, buf)
}
fn size_hint(&self) -> usize {
match self {
IpNetwork::V4(_) => 8,
IpNetwork::V6(_) => 20,
}
}
}
impl<'de> Decode<'de, Postgres> for IpNetwork {
fn decode(value: Option<PgValue<'de>>) -> crate::Result<Self> {
match value.try_into()? {
PgValue::Binary(buf) => decode(buf, INET_TYPE),
PgValue::Text(s) => decode(s.as_bytes(), INET_TYPE),
}
}
}
fn encode(net: &IpNetwork, net_type: u8, buf: &mut Vec<u8>) {
match net {
IpNetwork::V4(net) => {
buf.push(PGSQL_AF_INET);
buf.push(net.prefix());
buf.push(net_type);
buf.push(4);
buf.extend_from_slice(&net.ip().octets());
}
IpNetwork::V6(net) => {
buf.push(PGSQL_AF_INET6);
buf.push(net.prefix());
buf.push(net_type);
buf.push(16);
buf.extend_from_slice(&net.ip().octets());
}
}
}
fn decode(bytes: &[u8], net_type: u8) -> crate::Result<IpNetwork> {
if bytes.len() <= 8 {
return Err(Error::Decode("Input too short".into()));
}
let af = bytes[0];
let prefix = bytes[1];
let type_ = bytes[2];
let len = bytes[3];
if type_ == net_type {
if af == PGSQL_AF_INET && bytes.len() == 8 && len == 4 {
let inet = Ipv4Network::new(
Ipv4Addr::new(bytes[4], bytes[5], bytes[6], bytes[7]),
prefix,
)
.map_err(Error::decode)?;
return Ok(IpNetwork::V4(inet));
}
if af == PGSQL_AF_INET6 && bytes.len() == 20 && len == 16 {
let inet = Ipv6Network::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,
)
.map_err(Error::decode)?;
return Ok(IpNetwork::V6(inet));
}
}
return Err(Error::Decode("Invalid inet_struct".into()));
}