1use std::collections::HashMap;
10
11#[derive(Debug, Clone, PartialEq, thiserror::Error)]
13pub enum DecodeError {
14 #[error("pgoutput: message truncated")]
15 Truncated,
16 #[error("pgoutput: {0} unexpected trailing bytes")]
17 Trailing(usize),
18 #[error("pgoutput: unknown message type {0:?}")]
19 UnknownMessage(char),
20 #[error("pgoutput: unknown tuple column kind {0:?}")]
21 UnknownColumn(char),
22 #[error("pgoutput: unexpected tuple marker {0:?}")]
23 BadMarker(char),
24 #[error("pgoutput: string is not NUL-terminated UTF-8")]
25 BadString,
26 #[error("pgoutput: change for relation {0} before its Relation message")]
27 UnknownRelation(u32),
28 #[error("pgoutput: {0} outside a transaction")]
29 OutsideTxn(&'static str),
30}
31
32pub type Result<T> = std::result::Result<T, DecodeError>;
34
35#[derive(Debug, Clone, PartialEq)]
37pub enum Datum {
38 Null,
40 Unchanged,
42 Text(String),
44}
45
46pub type Tuple = Vec<Datum>;
48
49#[derive(Debug, Clone, PartialEq)]
51pub struct Column {
52 pub key: bool,
54 pub name: String,
56 pub type_oid: u32,
58 pub type_mod: i32,
60}
61
62#[derive(Debug, Clone, PartialEq)]
64pub struct Relation {
65 pub id: u32,
67 pub namespace: String,
69 pub name: String,
71 pub replica_identity: u8,
73 pub columns: Vec<Column>,
75}
76
77impl Relation {
78 pub fn get<'t>(&self, tuple: &'t Tuple, name: &str) -> Option<&'t Datum> {
80 let i = self.columns.iter().position(|c| c.name == name)?;
81 tuple.get(i)
82 }
83}
84
85#[allow(missing_docs)]
87#[derive(Debug, Clone, PartialEq)]
88pub enum Message {
89 Begin {
90 final_lsn: u64,
91 commit_ts: i64,
92 xid: u32,
93 },
94 Commit {
95 flags: u8,
96 commit_lsn: u64,
97 end_lsn: u64,
99 commit_ts: i64,
100 },
101 Origin {
102 lsn: u64,
103 name: String,
104 },
105 Relation(Relation),
106 Type {
107 id: u32,
108 namespace: String,
109 name: String,
110 },
111 Insert {
112 rel: u32,
113 new: Tuple,
114 },
115 Update {
116 rel: u32,
117 old: Option<Tuple>,
119 new: Tuple,
120 },
121 Delete {
122 rel: u32,
123 old: Tuple,
124 },
125 Truncate {
126 options: u8,
127 rels: Vec<u32>,
128 },
129}
130
131struct Reader<'a>(&'a [u8]);
132
133impl<'a> Reader<'a> {
134 fn take(&mut self, n: usize) -> Result<&'a [u8]> {
135 if self.0.len() < n {
136 return Err(DecodeError::Truncated);
137 }
138 let (head, tail) = self.0.split_at(n);
139 self.0 = tail;
140 Ok(head)
141 }
142
143 fn array<const N: usize>(&mut self) -> Result<[u8; N]> {
144 let mut a = [0; N];
145 a.copy_from_slice(self.take(N)?);
146 Ok(a)
147 }
148
149 fn u8(&mut self) -> Result<u8> {
150 Ok(self.take(1)?[0])
151 }
152
153 fn u16(&mut self) -> Result<u16> {
154 self.array().map(u16::from_be_bytes)
155 }
156
157 fn u32(&mut self) -> Result<u32> {
158 self.array().map(u32::from_be_bytes)
159 }
160
161 fn i32(&mut self) -> Result<i32> {
162 self.array().map(i32::from_be_bytes)
163 }
164
165 fn u64(&mut self) -> Result<u64> {
166 self.array().map(u64::from_be_bytes)
167 }
168
169 fn i64(&mut self) -> Result<i64> {
170 self.array().map(i64::from_be_bytes)
171 }
172
173 fn string(&mut self) -> Result<String> {
174 let n = self
175 .0
176 .iter()
177 .position(|&c| c == 0)
178 .ok_or(DecodeError::BadString)?;
179 let s = std::str::from_utf8(&self.0[..n]).map_err(|_| DecodeError::BadString)?;
180 self.0 = &self.0[n + 1..];
181 Ok(s.to_string())
182 }
183
184 fn marker(&mut self, ok: &[u8]) -> Result<u8> {
185 let m = self.u8()?;
186 if ok.contains(&m) {
187 Ok(m)
188 } else {
189 Err(DecodeError::BadMarker(m as char))
190 }
191 }
192
193 fn tuple(&mut self) -> Result<Tuple> {
194 let n = self.u16()?;
195 let mut t = Vec::with_capacity(usize::from(n));
196 for _ in 0..n {
197 t.push(match self.u8()? {
198 b'n' => Datum::Null,
199 b'u' => Datum::Unchanged,
200 b't' => {
201 let len = self.u32()? as usize;
202 let bytes = self.take(len)?;
203 let s = std::str::from_utf8(bytes).map_err(|_| DecodeError::BadString)?;
204 Datum::Text(s.to_string())
205 }
206 c => return Err(DecodeError::UnknownColumn(c as char)),
207 });
208 }
209 Ok(t)
210 }
211}
212
213pub fn decode(buf: &[u8]) -> Result<Message> {
215 let mut r = Reader(buf);
216 let m = match r.u8()? {
218 b'B' => Message::Begin {
219 final_lsn: r.u64()?,
220 commit_ts: r.i64()?,
221 xid: r.u32()?,
222 },
223 b'C' => Message::Commit {
224 flags: r.u8()?,
225 commit_lsn: r.u64()?,
226 end_lsn: r.u64()?,
227 commit_ts: r.i64()?,
228 },
229 b'O' => Message::Origin {
230 lsn: r.u64()?,
231 name: r.string()?,
232 },
233 b'R' => {
234 let id = r.u32()?;
235 let namespace = r.string()?;
236 let name = r.string()?;
237 let replica_identity = r.u8()?;
238 let n = r.u16()?;
239 let mut columns = Vec::with_capacity(usize::from(n));
240 for _ in 0..n {
241 columns.push(Column {
242 key: r.u8()? & 1 == 1,
243 name: r.string()?,
244 type_oid: r.u32()?,
245 type_mod: r.i32()?,
246 });
247 }
248 Message::Relation(Relation {
249 id,
250 namespace,
251 name,
252 replica_identity,
253 columns,
254 })
255 }
256 b'Y' => Message::Type {
257 id: r.u32()?,
258 namespace: r.string()?,
259 name: r.string()?,
260 },
261 b'I' => {
262 let rel = r.u32()?;
263 r.marker(b"N")?;
264 Message::Insert {
265 rel,
266 new: r.tuple()?,
267 }
268 }
269 b'U' => {
270 let rel = r.u32()?;
271 let old = match r.marker(b"KON")? {
272 b'N' => None,
273 _ => {
274 let old = r.tuple()?;
275 r.marker(b"N")?;
276 Some(old)
277 }
278 };
279 Message::Update {
280 rel,
281 old,
282 new: r.tuple()?,
283 }
284 }
285 b'D' => {
286 let rel = r.u32()?;
287 r.marker(b"KO")?;
288 Message::Delete {
289 rel,
290 old: r.tuple()?,
291 }
292 }
293 b'T' => {
294 let n = r.u32()?;
295 let options = r.u8()?;
296 let rels = (0..n).map(|_| r.u32()).collect::<Result<_>>()?;
298 Message::Truncate { options, rels }
299 }
300 c => return Err(DecodeError::UnknownMessage(c as char)),
301 };
302 if r.0.is_empty() {
303 Ok(m)
304 } else {
305 Err(DecodeError::Trailing(r.0.len()))
306 }
307}
308
309#[derive(Debug, Clone, PartialEq, Default)]
312pub struct Txn {
313 pub xid: u32,
315 pub end_lsn: u64,
317 pub changes: Vec<Message>,
319}
320
321#[derive(Debug, Default)]
324pub struct Stream {
325 pub relations: HashMap<u32, Relation>,
327 open: Option<Txn>,
328 pub done: Vec<Txn>,
330}
331
332impl Stream {
333 pub fn feed(&mut self, buf: &[u8]) -> Result<()> {
335 match decode(buf)? {
336 Message::Begin { xid, .. } => {
337 if self.open.is_some() {
338 return Err(DecodeError::OutsideTxn("Begin"));
339 }
340 self.open = Some(Txn {
341 xid,
342 ..Txn::default()
343 });
344 }
345 Message::Commit { end_lsn, .. } => {
346 let mut t = self.open.take().ok_or(DecodeError::OutsideTxn("Commit"))?;
347 t.end_lsn = end_lsn;
348 self.done.push(t);
349 }
350 Message::Relation(rel) => {
351 self.relations.insert(rel.id, rel);
352 }
353 Message::Origin { .. } | Message::Type { .. } => {}
354 change => {
355 let rels = match &change {
356 Message::Insert { rel, .. }
357 | Message::Update { rel, .. }
358 | Message::Delete { rel, .. } => std::slice::from_ref(rel),
359 Message::Truncate { rels, .. } => rels.as_slice(),
360 _ => unreachable!("every other message is matched above"),
361 };
362 if let Some(&id) = rels.iter().find(|id| !self.relations.contains_key(id)) {
363 return Err(DecodeError::UnknownRelation(id));
364 }
365 let t = self
366 .open
367 .as_mut()
368 .ok_or(DecodeError::OutsideTxn("row change"))?;
369 t.changes.push(change);
370 }
371 }
372 Ok(())
373 }
374}
375
376pub fn format_lsn(lsn: u64) -> String {
378 format!("{:X}/{:X}", lsn >> 32, lsn & 0xFFFF_FFFF)
379}
380
381pub fn parse_lsn(s: &str) -> Option<u64> {
383 let (hi, lo) = s.split_once('/')?;
384 let hi = u32::from_str_radix(hi, 16).ok()?;
385 let lo = u32::from_str_radix(lo, 16).ok()?;
386 Some((u64::from(hi) << 32) | u64::from(lo))
387}
388
389#[cfg(test)]
390#[cfg_attr(coverage_nightly, coverage(off))]
391mod tests {
392 use super::*;
393
394 fn s(b: &mut Vec<u8>, x: &str) {
395 b.extend_from_slice(x.as_bytes());
396 b.push(0);
397 }
398
399 fn tuple(b: &mut Vec<u8>, t: &[Datum]) {
400 b.extend_from_slice(&(t.len() as u16).to_be_bytes());
401 for d in t {
402 match d {
403 Datum::Null => b.push(b'n'),
404 Datum::Unchanged => b.push(b'u'),
405 Datum::Text(v) => {
406 b.push(b't');
407 b.extend_from_slice(&(v.len() as u32).to_be_bytes());
408 b.extend_from_slice(v.as_bytes());
409 }
410 }
411 }
412 }
413
414 fn txt(v: &str) -> Datum {
415 Datum::Text(v.to_string())
416 }
417
418 fn begin(xid: u32) -> Vec<u8> {
419 let mut b = vec![b'B'];
420 b.extend_from_slice(&0x0000_0001_0000_0100u64.to_be_bytes());
421 b.extend_from_slice(&(-5i64).to_be_bytes());
422 b.extend_from_slice(&xid.to_be_bytes());
423 b
424 }
425
426 fn commit(end: u64) -> Vec<u8> {
427 let mut b = vec![b'C', 0];
428 b.extend_from_slice(&(end - 8).to_be_bytes());
429 b.extend_from_slice(&end.to_be_bytes());
430 b.extend_from_slice(&7i64.to_be_bytes());
431 b
432 }
433
434 fn relation(id: u32) -> Vec<u8> {
436 let mut b = vec![b'R'];
437 b.extend_from_slice(&id.to_be_bytes());
438 s(&mut b, "public");
439 s(&mut b, "sales");
440 b.push(b'f');
441 b.extend_from_slice(&3u16.to_be_bytes());
442 for (key, name, oid) in [
443 (1u8, "quarter", 25u32),
444 (0, "product", 25),
445 (0, "price", 701),
446 ] {
447 b.push(key);
448 s(&mut b, name);
449 b.extend_from_slice(&oid.to_be_bytes());
450 b.extend_from_slice(&(-1i32).to_be_bytes());
451 }
452 b
453 }
454
455 fn insert(rel: u32, t: &[Datum]) -> Vec<u8> {
456 let mut b = vec![b'I'];
457 b.extend_from_slice(&rel.to_be_bytes());
458 b.push(b'N');
459 tuple(&mut b, t);
460 b
461 }
462
463 fn update(rel: u32, old: Option<(u8, &[Datum])>, new: &[Datum]) -> Vec<u8> {
464 let mut b = vec![b'U'];
465 b.extend_from_slice(&rel.to_be_bytes());
466 if let Some((m, t)) = old {
467 b.push(m);
468 tuple(&mut b, t);
469 }
470 b.push(b'N');
471 tuple(&mut b, new);
472 b
473 }
474
475 #[test]
476 fn begin_and_commit() {
477 assert_eq!(
478 decode(&begin(42)),
479 Ok(Message::Begin {
480 final_lsn: 0x1_0000_0100,
481 commit_ts: -5,
482 xid: 42
483 })
484 );
485 assert_eq!(
486 decode(&commit(0x200)),
487 Ok(Message::Commit {
488 flags: 0,
489 commit_lsn: 0x1F8,
490 end_lsn: 0x200,
491 commit_ts: 7
492 })
493 );
494 }
495
496 #[test]
497 fn relation_columns_and_lookup() {
498 let Ok(Message::Relation(r)) = decode(&relation(16384)) else {
499 panic!("not a relation")
500 };
501 assert_eq!((r.id, &*r.namespace, &*r.name), (16384, "public", "sales"));
502 assert_eq!(r.replica_identity, b'f');
503 let cols: Vec<_> = r
504 .columns
505 .iter()
506 .map(|c| (c.key, &*c.name, c.type_oid))
507 .collect();
508 assert_eq!(
509 cols,
510 [
511 (true, "quarter", 25),
512 (false, "product", 25),
513 (false, "price", 701)
514 ]
515 );
516 assert_eq!(r.columns[2].type_mod, -1);
517 let t = vec![txt("Q1"), Datum::Null, txt("10")];
518 assert_eq!(r.get(&t, "price"), Some(&txt("10")));
519 assert_eq!(r.get(&t, "product"), Some(&Datum::Null));
520 assert_eq!(r.get(&t, "nope"), None);
521 }
522
523 #[test]
524 fn insert_text_null_and_unchanged_toast() {
525 let t = [txt("Q1"), Datum::Null, Datum::Unchanged, txt("")];
526 assert_eq!(
527 decode(&insert(7, &t)),
528 Ok(Message::Insert {
529 rel: 7,
530 new: t.to_vec()
531 })
532 );
533 let t = [txt("Größe €")];
535 assert_eq!(
536 decode(&insert(7, &t)),
537 Ok(Message::Insert {
538 rel: 7,
539 new: t.to_vec()
540 })
541 );
542 }
543
544 #[test]
545 fn update_without_with_old_row_and_with_key() {
546 let new = [txt("Q1"), txt("Widget"), txt("12")];
547 assert_eq!(
548 decode(&update(7, None, &new)),
549 Ok(Message::Update {
550 rel: 7,
551 old: None,
552 new: new.to_vec()
553 })
554 );
555 let old = [txt("Q1"), txt("Widget"), txt("10")];
556 for marker in *b"OK" {
557 assert_eq!(
558 decode(&update(7, Some((marker, &old)), &new)),
559 Ok(Message::Update {
560 rel: 7,
561 old: Some(old.to_vec()),
562 new: new.to_vec()
563 })
564 );
565 }
566 }
567
568 #[test]
569 fn delete_truncate_origin_type() {
570 let old = [txt("Q4"), Datum::Null, Datum::Null];
571 let mut b = vec![b'D'];
572 b.extend_from_slice(&9u32.to_be_bytes());
573 b.push(b'K');
574 tuple(&mut b, &old);
575 assert_eq!(
576 decode(&b),
577 Ok(Message::Delete {
578 rel: 9,
579 old: old.to_vec()
580 })
581 );
582
583 let mut b = vec![b'T'];
584 b.extend_from_slice(&2u32.to_be_bytes());
585 b.push(1);
586 b.extend_from_slice(&5u32.to_be_bytes());
587 b.extend_from_slice(&6u32.to_be_bytes());
588 assert_eq!(
589 decode(&b),
590 Ok(Message::Truncate {
591 options: 1,
592 rels: vec![5, 6]
593 })
594 );
595
596 let mut b = vec![b'O'];
597 b.extend_from_slice(&3u64.to_be_bytes());
598 s(&mut b, "node1");
599 assert_eq!(
600 decode(&b),
601 Ok(Message::Origin {
602 lsn: 3,
603 name: "node1".into()
604 })
605 );
606
607 let mut b = vec![b'Y'];
608 b.extend_from_slice(&600u32.to_be_bytes());
609 s(&mut b, "public");
610 s(&mut b, "mood");
611 assert_eq!(
612 decode(&b),
613 Ok(Message::Type {
614 id: 600,
615 namespace: "public".into(),
616 name: "mood".into()
617 })
618 );
619 }
620
621 #[test]
622 fn malformed_messages_are_errors_not_panics() {
623 let msgs = [
624 begin(1),
625 commit(16),
626 relation(3),
627 insert(3, &[txt("a"), Datum::Null]),
628 update(3, Some((b'O', &[txt("a")])), &[txt("b")]),
629 ];
630 for m in &msgs {
631 for n in 0..m.len() {
632 assert!(decode(&m[..n]).is_err(), "prefix {n} of {m:?}");
633 }
634 let mut long = m.clone();
635 long.push(0);
636 assert_eq!(decode(&long), Err(DecodeError::Trailing(1)));
637 }
638 assert_eq!(decode(b"Z"), Err(DecodeError::UnknownMessage('Z')));
639 let mut b = insert(3, &[txt("a")]);
640 b[5] = b'X';
641 assert_eq!(decode(&b), Err(DecodeError::BadMarker('X')));
642 let mut b = insert(3, &[txt("a")]);
643 b[8] = b'b'; assert_eq!(decode(&b), Err(DecodeError::UnknownColumn('b')));
645 let mut b = insert(3, &[txt("a")]);
646 *b.last_mut().unwrap() = 0xFF;
647 assert_eq!(decode(&b), Err(DecodeError::BadString));
648 let mut b = vec![b'D'];
649 b.extend_from_slice(&3u32.to_be_bytes());
650 b.push(b'N');
651 assert_eq!(decode(&b), Err(DecodeError::BadMarker('N')));
652 }
653
654 #[test]
655 fn stream_groups_committed_transactions() {
656 let row = |q: &str| [txt(q), txt("Widget"), txt("10")];
657 let mut st = Stream::default();
658 for m in [
659 begin(10),
660 relation(3),
661 insert(3, &row("Q1")),
662 update(3, None, &row("Q2")),
663 commit(0x100),
664 begin(11),
665 commit(0x200),
666 begin(12),
667 insert(3, &row("Q3")),
668 ] {
669 st.feed(&m).unwrap();
670 }
671 assert_eq!(st.done.len(), 2, "the open transaction 12 is not done");
672 assert_eq!((st.done[0].xid, st.done[0].end_lsn), (10, 0x100));
673 assert_eq!(st.done[0].changes.len(), 2);
674 assert!(matches!(
675 st.done[0].changes[1],
676 Message::Update { rel: 3, .. }
677 ));
678 assert_eq!((st.done[1].xid, st.done[1].end_lsn), (11, 0x200));
679 assert!(st.done[1].changes.is_empty());
680 assert_eq!(st.relations[&3].name, "sales");
681 assert_eq!(st.feed(&begin(13)), Err(DecodeError::OutsideTxn("Begin")));
682 }
683
684 #[test]
685 fn stream_rejects_out_of_order_messages() {
686 let mut st = Stream::default();
687 assert_eq!(st.feed(&commit(8)), Err(DecodeError::OutsideTxn("Commit")));
688 assert_eq!(
689 st.feed(&insert(3, &[])),
690 Err(DecodeError::UnknownRelation(3))
691 );
692 st.feed(&relation(3)).unwrap();
693 assert_eq!(
694 st.feed(&insert(3, &[])),
695 Err(DecodeError::OutsideTxn("row change"))
696 );
697 let mut b = vec![b'T'];
698 b.extend_from_slice(&1u32.to_be_bytes());
699 b.push(0);
700 b.extend_from_slice(&4u32.to_be_bytes());
701 st.feed(&begin(1)).unwrap();
702 assert_eq!(st.feed(&b), Err(DecodeError::UnknownRelation(4)));
703 }
704
705 #[test]
706 fn lsn_text_round_trip() {
707 for lsn in [0, 1, 0x1_6B37_4D48, u64::MAX] {
708 assert_eq!(parse_lsn(&format_lsn(lsn)), Some(lsn));
709 }
710 assert_eq!(format_lsn(0x1_6B37_4D48), "1/6B374D48");
711 assert_eq!(parse_lsn("0/16B374D8"), Some(0x16B3_74D8));
712 for bad in ["", "1", "1/", "/1", "g/1", "1/2/3", "100000000/0"] {
713 assert_eq!(parse_lsn(bad), None, "{bad}");
714 }
715 }
716}