1use bytes::{Buf, Bytes};
8use pgsync_core::event::{Column, Relation, ReplicaIdentity, Tuple, Value};
9use pgsync_core::{Error, Lsn, Result};
10
11#[derive(Debug, Clone, PartialEq, Eq)]
13pub enum ReplicationMessage {
14 XLogData {
15 wal_start: Lsn,
16 wal_end: Lsn,
17 server_clock: i64,
18 body: LogicalMessage,
19 },
20 PrimaryKeepalive {
21 wal_end: Lsn,
22 server_clock: i64,
23 reply_requested: bool,
24 },
25}
26
27#[derive(Debug, Clone, PartialEq, Eq)]
29pub enum LogicalMessage {
30 Begin {
31 final_lsn: Lsn,
32 commit_ts: i64,
33 xid: u32,
34 },
35 Commit {
36 flags: u8,
37 commit_lsn: Lsn,
38 end_lsn: Lsn,
39 commit_ts: i64,
40 },
41 Origin {
42 commit_lsn: Lsn,
43 name: String,
44 },
45 Relation(Relation),
46 Type {
47 oid: u32,
48 schema: String,
49 name: String,
50 },
51 Insert {
52 relation: u32,
53 new: Tuple,
54 },
55 Update {
56 relation: u32,
57 key: Option<Tuple>,
58 old: Option<Tuple>,
59 new: Tuple,
60 },
61 Delete {
62 relation: u32,
63 key: Option<Tuple>,
64 old: Option<Tuple>,
65 },
66 Truncate {
67 relations: Vec<u32>,
68 cascade: bool,
69 restart_identity: bool,
70 },
71 Message {
72 transactional: bool,
73 lsn: Lsn,
74 prefix: String,
75 content: Bytes,
76 },
77 Unsupported {
79 tag: u8,
80 },
81}
82
83pub fn decode(mut buf: Bytes) -> Result<ReplicationMessage> {
84 let tag = take_u8(&mut buf, "replication message tag")?;
85 match tag {
86 b'w' => {
87 let wal_start = Lsn(take_u64(&mut buf, "wal_start")?);
88 let wal_end = Lsn(take_u64(&mut buf, "wal_end")?);
89 let server_clock = take_i64(&mut buf, "server_clock")?;
90 let body = decode_logical(buf)?;
91 Ok(ReplicationMessage::XLogData {
92 wal_start,
93 wal_end,
94 server_clock,
95 body,
96 })
97 }
98 b'k' => {
99 let wal_end = Lsn(take_u64(&mut buf, "wal_end")?);
100 let server_clock = take_i64(&mut buf, "server_clock")?;
101 let reply_requested = take_u8(&mut buf, "reply")? != 0;
102 Ok(ReplicationMessage::PrimaryKeepalive {
103 wal_end,
104 server_clock,
105 reply_requested,
106 })
107 }
108 other => Err(Error::internal(format!(
109 "unknown replication message tag {:?}",
110 other as char
111 ))),
112 }
113}
114
115pub fn decode_logical(mut buf: Bytes) -> Result<LogicalMessage> {
116 let tag = take_u8(&mut buf, "pgoutput tag")?;
117 let msg = match tag {
118 b'B' => LogicalMessage::Begin {
119 final_lsn: Lsn(take_u64(&mut buf, "final_lsn")?),
120 commit_ts: take_i64(&mut buf, "commit_ts")?,
121 xid: take_u32(&mut buf, "xid")?,
122 },
123 b'C' => LogicalMessage::Commit {
124 flags: take_u8(&mut buf, "flags")?,
125 commit_lsn: Lsn(take_u64(&mut buf, "commit_lsn")?),
126 end_lsn: Lsn(take_u64(&mut buf, "end_lsn")?),
127 commit_ts: take_i64(&mut buf, "commit_ts")?,
128 },
129 b'O' => LogicalMessage::Origin {
130 commit_lsn: Lsn(take_u64(&mut buf, "origin lsn")?),
131 name: take_cstr(&mut buf, "origin name")?,
132 },
133 b'R' => {
134 let id = take_u32(&mut buf, "relation id")?;
135 let schema = take_cstr(&mut buf, "namespace")?;
136 let name = take_cstr(&mut buf, "relation name")?;
137 let ident = take_u8(&mut buf, "replica identity")?;
138 let replica_identity = ReplicaIdentity::from_char(ident).ok_or_else(|| {
139 Error::internal(format!("unknown replica identity {:?}", ident as char))
140 })?;
141 let ncols = take_u16(&mut buf, "column count")? as usize;
142 let mut columns = Vec::with_capacity(ncols);
143 for _ in 0..ncols {
144 let flags = take_u8(&mut buf, "column flags")?;
145 let name = take_cstr(&mut buf, "column name")?;
146 let type_oid = take_u32(&mut buf, "column type")?;
147 let type_modifier = take_i32(&mut buf, "column typmod")?;
148 columns.push(Column {
149 name,
150 type_oid,
151 type_modifier,
152 is_key: flags & 1 == 1,
153 });
154 }
155 LogicalMessage::Relation(Relation {
156 id,
157 schema: if schema.is_empty() {
158 "pg_catalog".to_owned()
159 } else {
160 schema
161 },
162 name,
163 replica_identity,
164 columns,
165 })
166 }
167 b'Y' => LogicalMessage::Type {
168 oid: take_u32(&mut buf, "type oid")?,
169 schema: take_cstr(&mut buf, "type namespace")?,
170 name: take_cstr(&mut buf, "type name")?,
171 },
172 b'I' => {
173 let relation = take_u32(&mut buf, "relation id")?;
174 let kind = take_u8(&mut buf, "tuple kind")?;
175 if kind != b'N' {
176 return Err(Error::internal(format!(
177 "insert: expected 'N' tuple, got {:?}",
178 kind as char
179 )));
180 }
181 LogicalMessage::Insert {
182 relation,
183 new: take_tuple(&mut buf)?,
184 }
185 }
186 b'U' => {
187 let relation = take_u32(&mut buf, "relation id")?;
188 let mut key = None;
189 let mut old = None;
190 let mut kind = take_u8(&mut buf, "tuple kind")?;
191 if kind == b'K' {
192 key = Some(take_tuple(&mut buf)?);
193 kind = take_u8(&mut buf, "tuple kind")?;
194 } else if kind == b'O' {
195 old = Some(take_tuple(&mut buf)?);
196 kind = take_u8(&mut buf, "tuple kind")?;
197 }
198 if kind != b'N' {
199 return Err(Error::internal(format!(
200 "update: expected 'N' tuple, got {:?}",
201 kind as char
202 )));
203 }
204 LogicalMessage::Update {
205 relation,
206 key,
207 old,
208 new: take_tuple(&mut buf)?,
209 }
210 }
211 b'D' => {
212 let relation = take_u32(&mut buf, "relation id")?;
213 let kind = take_u8(&mut buf, "tuple kind")?;
214 let tuple = take_tuple(&mut buf)?;
215 match kind {
216 b'K' => LogicalMessage::Delete {
217 relation,
218 key: Some(tuple),
219 old: None,
220 },
221 b'O' => LogicalMessage::Delete {
222 relation,
223 key: None,
224 old: Some(tuple),
225 },
226 other => {
227 return Err(Error::internal(format!(
228 "delete: unexpected tuple kind {:?}",
229 other as char
230 )));
231 }
232 }
233 }
234 b'T' => {
235 let n = take_u32(&mut buf, "relation count")? as usize;
236 let options = take_u8(&mut buf, "truncate options")?;
237 let mut relations = Vec::with_capacity(n);
238 for _ in 0..n {
239 relations.push(take_u32(&mut buf, "relation id")?);
240 }
241 LogicalMessage::Truncate {
242 relations,
243 cascade: options & 1 != 0,
244 restart_identity: options & 2 != 0,
245 }
246 }
247 b'M' => {
248 let flags = take_u8(&mut buf, "message flags")?;
249 let lsn = Lsn(take_u64(&mut buf, "message lsn")?);
250 let prefix = take_cstr(&mut buf, "message prefix")?;
251 let len = take_u32(&mut buf, "message length")? as usize;
252 if buf.len() < len {
253 return Err(truncated("message content"));
254 }
255 let content = buf.split_to(len);
256 LogicalMessage::Message {
257 transactional: flags & 1 != 0,
258 lsn,
259 prefix,
260 content,
261 }
262 }
263 other => LogicalMessage::Unsupported { tag: other },
264 };
265 Ok(msg)
266}
267
268fn take_tuple(buf: &mut Bytes) -> Result<Tuple> {
269 let n = take_u16(buf, "tuple column count")? as usize;
270 let mut values = Vec::with_capacity(n);
271 for _ in 0..n {
272 let kind = take_u8(buf, "tuple value kind")?;
273 let v = match kind {
274 b'n' => Value::Null,
275 b'u' => Value::Unchanged,
276 b't' | b'b' => {
277 let len = take_u32(buf, "value length")? as usize;
278 if buf.len() < len {
279 return Err(truncated("tuple value"));
280 }
281 let data = buf.split_to(len);
282 if kind == b't' {
283 Value::Text(data)
284 } else {
285 Value::Binary(data)
286 }
287 }
288 other => {
289 return Err(Error::internal(format!(
290 "unknown tuple value kind {:?}",
291 other as char
292 )));
293 }
294 };
295 values.push(v);
296 }
297 Ok(Tuple { values })
298}
299
300fn truncated(what: &str) -> Error {
301 Error::internal(format!("truncated pgoutput message while reading {what}"))
302}
303
304fn take_u8(buf: &mut Bytes, what: &str) -> Result<u8> {
305 if buf.remaining() < 1 {
306 return Err(truncated(what));
307 }
308 Ok(buf.get_u8())
309}
310fn take_u16(buf: &mut Bytes, what: &str) -> Result<u16> {
311 if buf.remaining() < 2 {
312 return Err(truncated(what));
313 }
314 Ok(buf.get_u16())
315}
316fn take_u32(buf: &mut Bytes, what: &str) -> Result<u32> {
317 if buf.remaining() < 4 {
318 return Err(truncated(what));
319 }
320 Ok(buf.get_u32())
321}
322fn take_i32(buf: &mut Bytes, what: &str) -> Result<i32> {
323 Ok(take_u32(buf, what)? as i32)
324}
325fn take_u64(buf: &mut Bytes, what: &str) -> Result<u64> {
326 if buf.remaining() < 8 {
327 return Err(truncated(what));
328 }
329 Ok(buf.get_u64())
330}
331fn take_i64(buf: &mut Bytes, what: &str) -> Result<i64> {
332 Ok(take_u64(buf, what)? as i64)
333}
334fn take_cstr(buf: &mut Bytes, what: &str) -> Result<String> {
335 let end = buf
336 .iter()
337 .position(|b| *b == 0)
338 .ok_or_else(|| truncated(what))?;
339 let s = buf.split_to(end);
340 buf.advance(1);
341 String::from_utf8(s.to_vec()).map_err(|_| Error::internal(format!("{what}: invalid UTF-8")))
342}
343
344pub fn encode_standby_status(
346 written: Lsn,
347 flushed: Lsn,
348 applied: Lsn,
349 clock: i64,
350 reply: bool,
351) -> [u8; 34] {
352 let mut out = [0u8; 34];
353 out[0] = b'r';
354 out[1..9].copy_from_slice(&written.0.to_be_bytes());
355 out[9..17].copy_from_slice(&flushed.0.to_be_bytes());
356 out[17..25].copy_from_slice(&applied.0.to_be_bytes());
357 out[25..33].copy_from_slice(&clock.to_be_bytes());
358 out[33] = reply as u8;
359 out
360}
361
362#[cfg(test)]
363mod tests {
364 use super::*;
365
366 fn b(v: &[&[u8]]) -> Bytes {
367 Bytes::from(v.concat())
368 }
369 fn u64be(v: u64) -> Vec<u8> {
370 v.to_be_bytes().to_vec()
371 }
372 fn u32be(v: u32) -> Vec<u8> {
373 v.to_be_bytes().to_vec()
374 }
375 fn u16be(v: u16) -> Vec<u8> {
376 v.to_be_bytes().to_vec()
377 }
378 fn cstr(s: &str) -> Vec<u8> {
379 let mut v = s.as_bytes().to_vec();
380 v.push(0);
381 v
382 }
383 fn text(s: &str) -> Vec<u8> {
384 let mut v = vec![b't'];
385 v.extend(u32be(s.len() as u32));
386 v.extend(s.as_bytes());
387 v
388 }
389
390 #[test]
391 fn keepalive() {
392 let m = decode(b(&[b"k", &u64be(0x16B3748), &u64be(123), &[1]])).unwrap();
393 assert_eq!(
394 m,
395 ReplicationMessage::PrimaryKeepalive {
396 wal_end: Lsn(0x16B3748),
397 server_clock: 123,
398 reply_requested: true
399 }
400 );
401 assert!(decode(b(&[b"k", &u64be(1)])).is_err());
402 assert!(decode(b(&[b"x"])).is_err());
403 }
404
405 #[test]
406 fn begin_and_commit() {
407 let m = decode(b(&[
408 b"w",
409 &u64be(10),
410 &u64be(20),
411 &u64be(30),
412 b"B",
413 &u64be(100),
414 &u64be(777),
415 &u32be(42),
416 ]))
417 .unwrap();
418 match m {
419 ReplicationMessage::XLogData {
420 wal_start,
421 wal_end,
422 server_clock,
423 body,
424 } => {
425 assert_eq!((wal_start, wal_end, server_clock), (Lsn(10), Lsn(20), 30));
426 assert_eq!(
427 body,
428 LogicalMessage::Begin {
429 final_lsn: Lsn(100),
430 commit_ts: 777,
431 xid: 42
432 }
433 );
434 }
435 _ => panic!(),
436 }
437 let c = decode_logical(b(&[b"C", &[0], &u64be(100), &u64be(108), &u64be(777)])).unwrap();
438 assert_eq!(
439 c,
440 LogicalMessage::Commit {
441 flags: 0,
442 commit_lsn: Lsn(100),
443 end_lsn: Lsn(108),
444 commit_ts: 777
445 }
446 );
447 }
448
449 #[test]
450 fn relation() {
451 let m = decode_logical(b(&[
452 b"R",
453 &u32be(16385),
454 &cstr("public"),
455 &cstr("users"),
456 b"d",
457 &u16be(2),
458 &[1],
459 &cstr("id"),
460 &u32be(20),
461 &u32be(0xFFFF_FFFF),
462 &[0],
463 &cstr("email"),
464 &u32be(25),
465 &u32be(0xFFFF_FFFF),
466 ]))
467 .unwrap();
468 let LogicalMessage::Relation(r) = m else {
469 panic!()
470 };
471 assert_eq!(r.id, 16385);
472 assert_eq!(r.qualified_name(), "public.users");
473 assert_eq!(r.replica_identity, ReplicaIdentity::Default);
474 assert_eq!(r.columns.len(), 2);
475 assert!(r.columns[0].is_key && !r.columns[1].is_key);
476 assert_eq!(r.columns[0].type_oid, 20);
477 assert_eq!(r.columns[1].type_modifier, -1);
478 assert_eq!(r.key_columns().map(|(i, _)| i).collect::<Vec<_>>(), vec![0]);
479
480 let m = decode_logical(b(&[
482 b"R",
483 &u32be(1),
484 &cstr(""),
485 &cstr("t"),
486 b"n",
487 &u16be(0),
488 ]))
489 .unwrap();
490 let LogicalMessage::Relation(r) = m else {
491 panic!()
492 };
493 assert_eq!(r.schema, "pg_catalog");
494 }
495
496 #[test]
497 fn insert_update_delete_tuples() {
498 let ins = decode_logical(b(&[
499 b"I",
500 &u32be(7),
501 b"N",
502 &u16be(3),
503 &text("1"),
504 b"n",
505 b"u",
506 ]))
507 .unwrap();
508 assert_eq!(
509 ins,
510 LogicalMessage::Insert {
511 relation: 7,
512 new: Tuple {
513 values: vec![Value::text("1"), Value::Null, Value::Unchanged]
514 }
515 }
516 );
517
518 let upd = decode_logical(b(&[
520 b"U",
521 &u32be(7),
522 b"K",
523 &u16be(1),
524 &text("1"),
525 b"N",
526 &u16be(1),
527 &text("2"),
528 ]))
529 .unwrap();
530 assert_eq!(
531 upd,
532 LogicalMessage::Update {
533 relation: 7,
534 key: Some(Tuple {
535 values: vec![Value::text("1")]
536 }),
537 old: None,
538 new: Tuple {
539 values: vec![Value::text("2")]
540 }
541 }
542 );
543 let upd = decode_logical(b(&[
545 b"U",
546 &u32be(7),
547 b"O",
548 &u16be(1),
549 &text("1"),
550 b"N",
551 &u16be(1),
552 &text("2"),
553 ]))
554 .unwrap();
555 assert!(matches!(
556 upd,
557 LogicalMessage::Update {
558 old: Some(_),
559 key: None,
560 ..
561 }
562 ));
563 let upd = decode_logical(b(&[b"U", &u32be(7), b"N", &u16be(1), &text("2")])).unwrap();
565 assert!(matches!(
566 upd,
567 LogicalMessage::Update {
568 old: None,
569 key: None,
570 ..
571 }
572 ));
573
574 let del = decode_logical(b(&[b"D", &u32be(7), b"K", &u16be(1), &text("1")])).unwrap();
575 assert_eq!(
576 del,
577 LogicalMessage::Delete {
578 relation: 7,
579 key: Some(Tuple {
580 values: vec![Value::text("1")]
581 }),
582 old: None
583 }
584 );
585 let del = decode_logical(b(&[b"D", &u32be(7), b"O", &u16be(1), &text("1")])).unwrap();
586 assert!(matches!(
587 del,
588 LogicalMessage::Delete {
589 old: Some(_),
590 key: None,
591 ..
592 }
593 ));
594 assert!(decode_logical(b(&[b"D", &u32be(7), b"N", &u16be(0)])).is_err());
595 }
596
597 #[test]
598 fn binary_values_and_truncation() {
599 let ins = decode_logical(b(&[
600 b"I",
601 &u32be(7),
602 b"N",
603 &u16be(1),
604 b"b",
605 &u32be(2),
606 &[0xAB, 0xCD],
607 ]))
608 .unwrap();
609 assert_eq!(
610 ins,
611 LogicalMessage::Insert {
612 relation: 7,
613 new: Tuple {
614 values: vec![Value::Binary(Bytes::from_static(&[0xAB, 0xCD]))]
615 }
616 }
617 );
618 assert!(
620 decode_logical(b(&[
621 b"I",
622 &u32be(7),
623 b"N",
624 &u16be(1),
625 b"t",
626 &u32be(5),
627 b"ab"
628 ]))
629 .is_err()
630 );
631 assert!(decode_logical(b(&[b"I", &u32be(7), b"N", &u16be(1), b"z"])).is_err());
633 }
634
635 #[test]
636 fn truncate_origin_type_message_unsupported() {
637 let t = decode_logical(b(&[b"T", &u32be(2), &[3], &u32be(1), &u32be(2)])).unwrap();
638 assert_eq!(
639 t,
640 LogicalMessage::Truncate {
641 relations: vec![1, 2],
642 cascade: true,
643 restart_identity: true
644 }
645 );
646 let o = decode_logical(b(&[b"O", &u64be(5), &cstr("node1")])).unwrap();
647 assert_eq!(
648 o,
649 LogicalMessage::Origin {
650 commit_lsn: Lsn(5),
651 name: "node1".into()
652 }
653 );
654 let y = decode_logical(b(&[b"Y", &u32be(99), &cstr("public"), &cstr("mood")])).unwrap();
655 assert_eq!(
656 y,
657 LogicalMessage::Type {
658 oid: 99,
659 schema: "public".into(),
660 name: "mood".into()
661 }
662 );
663 let m =
664 decode_logical(b(&[b"M", &[1], &u64be(9), &cstr("pfx"), &u32be(3), b"abc"])).unwrap();
665 assert_eq!(
666 m,
667 LogicalMessage::Message {
668 transactional: true,
669 lsn: Lsn(9),
670 prefix: "pfx".into(),
671 content: Bytes::from_static(b"abc")
672 }
673 );
674 for tag in [b'S', b'E', b'c', b'A', b'b', b'P', b'K', b'r', b'p'] {
675 assert_eq!(
676 decode_logical(b(&[&[tag], &[0; 40]])).unwrap(),
677 LogicalMessage::Unsupported { tag }
678 );
679 }
680 }
681
682 #[test]
683 fn standby_status_layout() {
684 let s = encode_standby_status(Lsn(1), Lsn(2), Lsn(3), 4, true);
685 assert_eq!(s[0], b'r');
686 assert_eq!(&s[1..9], &u64be(1)[..]);
687 assert_eq!(&s[9..17], &u64be(2)[..]);
688 assert_eq!(&s[17..25], &u64be(3)[..]);
689 assert_eq!(&s[25..33], &u64be(4)[..]);
690 assert_eq!(s[33], 1);
691 }
692}