Skip to content

Commit f6fedb8

Browse files
authored
Merge pull request sfackler#726 from jeff-davis/pg-lsn
PgLsn type.
2 parents 77aa702 + f3cbc8c commit f6fedb8

File tree

5 files changed

+117
-2
lines changed

5 files changed

+117
-2
lines changed

postgres-protocol/src/lib.rs

+3
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ pub mod types;
2424
/// A Postgres OID.
2525
pub type Oid = u32;
2626

27+
/// A Postgres Log Sequence Number (LSN).
28+
pub type Lsn = u64;
29+
2730
/// An enum indicating if a value is `NULL` or not.
2831
pub enum IsNull {
2932
/// The value is `NULL`.

postgres-protocol/src/types/mod.rs

+17-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use std::io::Read;
88
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
99
use std::str;
1010

11-
use crate::{write_nullable, FromUsize, IsNull, Oid};
11+
use crate::{write_nullable, FromUsize, IsNull, Lsn, Oid};
1212

1313
#[cfg(test)]
1414
mod test;
@@ -142,6 +142,22 @@ pub fn int8_from_sql(mut buf: &[u8]) -> Result<i64, StdBox<dyn Error + Sync + Se
142142
Ok(v)
143143
}
144144

145+
/// Serializes a `PG_LSN` value.
146+
#[inline]
147+
pub fn lsn_to_sql(v: Lsn, buf: &mut BytesMut) {
148+
buf.put_u64(v);
149+
}
150+
151+
/// Deserializes a `PG_LSN` value.
152+
#[inline]
153+
pub fn lsn_from_sql(mut buf: &[u8]) -> Result<Lsn, StdBox<dyn Error + Sync + Send>> {
154+
let v = buf.read_u64::<BigEndian>()?;
155+
if !buf.is_empty() {
156+
return Err("invalid buffer size".into());
157+
}
158+
Ok(v)
159+
}
160+
145161
/// Serializes a `FLOAT4` value.
146162
#[inline]
147163
pub fn float4_to_sql(v: f32, buf: &mut BytesMut) {

postgres-types/src/lib.rs

+4
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,9 @@ use crate::type_gen::{Inner, Other};
130130
#[doc(inline)]
131131
pub use postgres_protocol::Oid;
132132

133+
#[doc(inline)]
134+
pub use pg_lsn::PgLsn;
135+
133136
pub use crate::special::{Date, Timestamp};
134137
use bytes::BytesMut;
135138

@@ -204,6 +207,7 @@ mod uuid_08;
204207
#[cfg(feature = "with-time-0_2")]
205208
extern crate time_02 as time;
206209

210+
mod pg_lsn;
207211
#[doc(hidden)]
208212
pub mod private;
209213
mod special;

postgres-types/src/pg_lsn.rs

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
//! Log Sequence Number (LSN) type for PostgreSQL Write-Ahead Log
2+
//! (WAL), also known as the transaction log.
3+
4+
use bytes::BytesMut;
5+
use postgres_protocol::types;
6+
use std::error::Error;
7+
use std::fmt;
8+
use std::str::FromStr;
9+
10+
use crate::{FromSql, IsNull, ToSql, Type};
11+
12+
/// Postgres `PG_LSN` type.
13+
#[derive(Clone, Copy, Eq, Ord, PartialEq, PartialOrd)]
14+
pub struct PgLsn(u64);
15+
16+
/// Error parsing LSN.
17+
#[derive(Debug)]
18+
pub struct ParseLsnError(());
19+
20+
impl From<u64> for PgLsn {
21+
fn from(lsn_u64: u64) -> Self {
22+
PgLsn(lsn_u64)
23+
}
24+
}
25+
26+
impl From<PgLsn> for u64 {
27+
fn from(lsn: PgLsn) -> u64 {
28+
lsn.0
29+
}
30+
}
31+
32+
impl FromStr for PgLsn {
33+
type Err = ParseLsnError;
34+
35+
fn from_str(lsn_str: &str) -> Result<Self, Self::Err> {
36+
let split: Vec<&str> = lsn_str.split('/').collect();
37+
if split.len() == 2 {
38+
let (hi, lo) = (
39+
u64::from_str_radix(split[0], 16).map_err(|_| ParseLsnError(()))?,
40+
u64::from_str_radix(split[1], 16).map_err(|_| ParseLsnError(()))?,
41+
);
42+
Ok(PgLsn((hi << 32) | lo))
43+
} else {
44+
Err(ParseLsnError(()))
45+
}
46+
}
47+
}
48+
49+
impl fmt::Display for PgLsn {
50+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51+
write!(f, "{:X}/{:X}", self.0 >> 32, self.0 & 0x00000000ffffffff)
52+
}
53+
}
54+
55+
impl fmt::Debug for PgLsn {
56+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57+
f.write_fmt(format_args!("{}", self))
58+
}
59+
}
60+
61+
impl<'a> FromSql<'a> for PgLsn {
62+
fn from_sql(_: &Type, raw: &'a [u8]) -> Result<Self, Box<dyn Error + Sync + Send>> {
63+
let v = types::lsn_from_sql(raw)?;
64+
Ok(v.into())
65+
}
66+
67+
accepts!(PG_LSN);
68+
}
69+
70+
impl ToSql for PgLsn {
71+
fn to_sql(&self, _: &Type, out: &mut BytesMut) -> Result<IsNull, Box<dyn Error + Sync + Send>> {
72+
types::lsn_to_sql((*self).into(), out);
73+
Ok(IsNull::No)
74+
}
75+
76+
accepts!(PG_LSN);
77+
78+
to_sql_checked!();
79+
}

tokio-postgres/tests/test/types/mod.rs

+14-1
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ use std::f64;
66
use std::fmt;
77
use std::net::IpAddr;
88
use std::result;
9+
use std::str::FromStr;
910
use std::time::{Duration, UNIX_EPOCH};
10-
use tokio_postgres::types::{FromSql, FromSqlOwned, IsNull, Kind, ToSql, Type, WrongType};
11+
use tokio_postgres::types::{FromSql, FromSqlOwned, IsNull, Kind, PgLsn, ToSql, Type, WrongType};
1112

1213
use crate::connect;
1314
use bytes::BytesMut;
@@ -135,6 +136,18 @@ async fn test_i64_params() {
135136
.await;
136137
}
137138

139+
#[tokio::test]
140+
async fn test_lsn_params() {
141+
test_type(
142+
"PG_LSN",
143+
&[
144+
(Some(PgLsn::from_str("2B/1757980").unwrap()), "'2B/1757980'"),
145+
(None, "NULL"),
146+
],
147+
)
148+
.await
149+
}
150+
138151
#[tokio::test]
139152
async fn test_f32_params() {
140153
test_type(

0 commit comments

Comments
 (0)