Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * proto.c
4 : * logical replication protocol functions
5 : *
6 : * Copyright (c) 2015-2020, PostgreSQL Global Development Group
7 : *
8 : * IDENTIFICATION
9 : * src/backend/replication/logical/proto.c
10 : *
11 : *-------------------------------------------------------------------------
12 : */
13 : #include "postgres.h"
14 :
15 : #include "access/sysattr.h"
16 : #include "catalog/pg_namespace.h"
17 : #include "catalog/pg_type.h"
18 : #include "libpq/pqformat.h"
19 : #include "replication/logicalproto.h"
20 : #include "utils/lsyscache.h"
21 : #include "utils/syscache.h"
22 :
23 : /*
24 : * Protocol message flags.
25 : */
26 : #define LOGICALREP_IS_REPLICA_IDENTITY 1
27 :
28 : #define TRUNCATE_CASCADE (1<<0)
29 : #define TRUNCATE_RESTART_SEQS (1<<1)
30 :
31 : static void logicalrep_write_attrs(StringInfo out, Relation rel);
32 : static void logicalrep_write_tuple(StringInfo out, Relation rel,
33 : HeapTuple tuple, bool binary);
34 :
35 : static void logicalrep_read_attrs(StringInfo in, LogicalRepRelation *rel);
36 : static void logicalrep_read_tuple(StringInfo in, LogicalRepTupleData *tuple);
37 :
38 : static void logicalrep_write_namespace(StringInfo out, Oid nspid);
39 : static const char *logicalrep_read_namespace(StringInfo in);
40 :
41 : /*
42 : * Write BEGIN to the output stream.
43 : */
44 : void
45 352 : logicalrep_write_begin(StringInfo out, ReorderBufferTXN *txn)
46 : {
47 352 : pq_sendbyte(out, 'B'); /* BEGIN */
48 :
49 : /* fixed fields */
50 352 : pq_sendint64(out, txn->final_lsn);
51 352 : pq_sendint64(out, txn->commit_time);
52 352 : pq_sendint32(out, txn->xid);
53 352 : }
54 :
55 : /*
56 : * Read transaction BEGIN from the stream.
57 : */
58 : void
59 498 : logicalrep_read_begin(StringInfo in, LogicalRepBeginData *begin_data)
60 : {
61 : /* read fields */
62 498 : begin_data->final_lsn = pq_getmsgint64(in);
63 498 : if (begin_data->final_lsn == InvalidXLogRecPtr)
64 0 : elog(ERROR, "final_lsn not set in begin message");
65 498 : begin_data->committime = pq_getmsgint64(in);
66 498 : begin_data->xid = pq_getmsgint(in, 4);
67 498 : }
68 :
69 :
70 : /*
71 : * Write COMMIT to the output stream.
72 : */
73 : void
74 338 : logicalrep_write_commit(StringInfo out, ReorderBufferTXN *txn,
75 : XLogRecPtr commit_lsn)
76 : {
77 338 : uint8 flags = 0;
78 :
79 338 : pq_sendbyte(out, 'C'); /* sending COMMIT */
80 :
81 : /* send the flags field */
82 338 : pq_sendbyte(out, flags);
83 :
84 : /* send fields */
85 338 : pq_sendint64(out, commit_lsn);
86 338 : pq_sendint64(out, txn->end_lsn);
87 338 : pq_sendint64(out, txn->commit_time);
88 338 : }
89 :
90 : /*
91 : * Read transaction COMMIT from the stream.
92 : */
93 : void
94 486 : logicalrep_read_commit(StringInfo in, LogicalRepCommitData *commit_data)
95 : {
96 : /* read flags (unused for now) */
97 486 : uint8 flags = pq_getmsgbyte(in);
98 :
99 486 : if (flags != 0)
100 0 : elog(ERROR, "unrecognized flags %u in commit message", flags);
101 :
102 :
103 : /* read fields */
104 486 : commit_data->commit_lsn = pq_getmsgint64(in);
105 486 : commit_data->end_lsn = pq_getmsgint64(in);
106 486 : commit_data->committime = pq_getmsgint64(in);
107 486 : }
108 :
109 : /*
110 : * Write PREPARE to the output stream.
111 : */
112 : void
113 50 : logicalrep_write_prepare(StringInfo out, ReorderBufferTXN *txn,
114 : XLogRecPtr prepare_lsn)
115 : {
116 50 : uint8 flags = 0;
117 :
118 50 : pq_sendbyte(out, 'P'); /* sending PREPARE protocol */
119 :
120 : /*
121 : * This should only ever happen for two-phase commit transactions. In which case we
122 : * expect to have a valid GID.
123 : */
124 50 : Assert(rbtxn_prepared(txn));
125 50 : Assert(txn->gid != NULL);
126 :
127 : /*
128 : * Flags are determined from the state of the transaction. We know we
129 : * always get PREPARE first and then [COMMIT|ROLLBACK] PREPARED, so if
130 : * it's already marked as committed then it has to be COMMIT PREPARED (and
131 : * likewise for abort / ROLLBACK PREPARED).
132 : */
133 50 : if (rbtxn_commit_prepared(txn))
134 20 : flags = LOGICALREP_IS_COMMIT_PREPARED;
135 30 : else if (rbtxn_rollback_prepared(txn))
136 16 : flags = LOGICALREP_IS_ROLLBACK_PREPARED;
137 : else
138 14 : flags = LOGICALREP_IS_PREPARE;
139 :
140 : /* Make sure exactly one of the expected flags is set. */
141 50 : if (!PrepareFlagsAreValid(flags))
142 0 : elog(ERROR, "unrecognized flags %u in prepare message", flags);
143 :
144 : /* send the flags field */
145 50 : pq_sendbyte(out, flags);
146 :
147 : /* send fields */
148 50 : pq_sendint64(out, prepare_lsn);
149 50 : pq_sendint64(out, txn->end_lsn);
150 50 : pq_sendint64(out, txn->commit_time);
151 :
152 : /* send gid */
153 50 : pq_sendstring(out, txn->gid);
154 50 : }
155 :
156 : /*
157 : * Read transaction PREPARE from the stream.
158 : */
159 : void
160 26 : logicalrep_read_prepare(StringInfo in, LogicalRepPrepareData * prepare_data)
161 : {
162 : /* read flags */
163 26 : uint8 flags = pq_getmsgbyte(in);
164 :
165 26 : if (!PrepareFlagsAreValid(flags))
166 0 : elog(ERROR, "unrecognized flags %u in prepare message", flags);
167 :
168 : /* set the action (reuse the constants used for the flags) */
169 26 : prepare_data->prepare_type = flags;
170 :
171 : /* read fields */
172 26 : prepare_data->prepare_lsn = pq_getmsgint64(in);
173 26 : prepare_data->end_lsn = pq_getmsgint64(in);
174 26 : prepare_data->preparetime = pq_getmsgint64(in);
175 :
176 : /* read gid (copy it into a pre-allocated buffer) */
177 26 : strcpy(prepare_data->gid, pq_getmsgstring(in));
178 26 : }
179 :
180 : /*
181 : * Write STREAM PREPARE to the output stream.
182 : * (For stream PREPARE, stream COMMIT PREPARED, stream ROLLBACK PREPARED)
183 : */
184 : void
185 18 : logicalrep_write_stream_prepare(StringInfo out,
186 : ReorderBufferTXN *txn,
187 : XLogRecPtr prepare_lsn)
188 : {
189 18 : uint8 flags = 0;
190 :
191 18 : pq_sendbyte(out, 'p'); /* sending STREAM PREPARE protocol */
192 :
193 : /*
194 : * This should only ever happen for two-phase transactions. In which case we
195 : * expect to have a valid GID.
196 : */
197 18 : Assert(rbtxn_prepared(txn));
198 18 : Assert(txn->gid != NULL);
199 :
200 : /*
201 : * For streaming APIs only PREPARE is supported. [COMMIT|ROLLBACK] PREPARED
202 : * uses non-streaming APIs
203 : */
204 18 : flags = LOGICALREP_IS_PREPARE;
205 :
206 : /* transaction ID */
207 18 : Assert(TransactionIdIsValid(txn->xid));
208 18 : pq_sendint32(out, txn->xid);
209 :
210 : /* send the flags field */
211 18 : pq_sendbyte(out, flags);
212 :
213 : /* send fields */
214 18 : pq_sendint64(out, prepare_lsn);
215 18 : pq_sendint64(out, txn->end_lsn);
216 18 : pq_sendint64(out, txn->commit_time);
217 :
218 : /* send gid */
219 18 : pq_sendstring(out, txn->gid);
220 18 : }
221 :
222 : /*
223 : * Read STREAM PREPARE from the output stream.
224 : * (For stream PREPARE, stream COMMIT PREPARED, stream ROLLBACK PREPARED)
225 : */
226 : TransactionId
227 12 : logicalrep_read_stream_prepare(StringInfo in, LogicalRepPrepareData *prepare_data)
228 : {
229 : TransactionId xid;
230 : uint8 flags;
231 :
232 12 : xid = pq_getmsgint(in, 4);
233 :
234 : /* read flags */
235 12 : flags = pq_getmsgbyte(in);
236 :
237 12 : if (!PrepareFlagsAreValid(flags))
238 0 : elog(ERROR, "unrecognized flags %u in prepare message", flags);
239 :
240 : /* set the action (reuse the constants used for the flags) */
241 12 : prepare_data->prepare_type = flags;
242 :
243 : /* read fields */
244 12 : prepare_data->prepare_lsn = pq_getmsgint64(in);
245 12 : prepare_data->end_lsn = pq_getmsgint64(in);
246 12 : prepare_data->preparetime = pq_getmsgint64(in);
247 :
248 : /* read gid (copy it into a pre-allocated buffer) */
249 12 : strcpy(prepare_data->gid, pq_getmsgstring(in));
250 :
251 12 : return xid;
252 : }
253 :
254 : /*
255 : * Write ORIGIN to the output stream.
256 : */
257 : void
258 0 : logicalrep_write_origin(StringInfo out, const char *origin,
259 : XLogRecPtr origin_lsn)
260 : {
261 0 : pq_sendbyte(out, 'O'); /* ORIGIN */
262 :
263 : /* fixed fields */
264 0 : pq_sendint64(out, origin_lsn);
265 :
266 : /* origin string */
267 0 : pq_sendstring(out, origin);
268 0 : }
269 :
270 : /*
271 : * Read ORIGIN from the output stream.
272 : */
273 : char *
274 0 : logicalrep_read_origin(StringInfo in, XLogRecPtr *origin_lsn)
275 : {
276 : /* fixed fields */
277 0 : *origin_lsn = pq_getmsgint64(in);
278 :
279 : /* return origin */
280 0 : return pstrdup(pq_getmsgstring(in));
281 : }
282 :
283 : /*
284 : * Write INSERT to the output stream.
285 : */
286 : void
287 162714 : logicalrep_write_insert(StringInfo out, TransactionId xid, Relation rel,
288 : HeapTuple newtuple, bool binary)
289 : {
290 162714 : pq_sendbyte(out, 'I'); /* action INSERT */
291 :
292 : /* transaction ID (if not valid, we're not streaming) */
293 162714 : if (TransactionIdIsValid(xid))
294 161872 : pq_sendint32(out, xid);
295 :
296 : /* use Oid as relation identifier */
297 162714 : pq_sendint32(out, RelationGetRelid(rel));
298 :
299 162714 : pq_sendbyte(out, 'N'); /* new tuple follows */
300 162714 : logicalrep_write_tuple(out, rel, newtuple, binary);
301 162714 : }
302 :
303 : /*
304 : * Read INSERT from stream.
305 : *
306 : * Fills the new tuple.
307 : */
308 : LogicalRepRelId
309 102852 : logicalrep_read_insert(StringInfo in, LogicalRepTupleData *newtup)
310 : {
311 : char action;
312 : LogicalRepRelId relid;
313 :
314 : /* read the relation id */
315 102852 : relid = pq_getmsgint(in, 4);
316 :
317 102852 : action = pq_getmsgbyte(in);
318 102852 : if (action != 'N')
319 0 : elog(ERROR, "expected new tuple but got %d",
320 : action);
321 :
322 102852 : logicalrep_read_tuple(in, newtup);
323 :
324 102852 : return relid;
325 : }
326 :
327 : /*
328 : * Write UPDATE to the output stream.
329 : */
330 : void
331 85468 : logicalrep_write_update(StringInfo out, TransactionId xid, Relation rel,
332 : HeapTuple oldtuple, HeapTuple newtuple, bool binary)
333 : {
334 85468 : pq_sendbyte(out, 'U'); /* action UPDATE */
335 :
336 85468 : Assert(rel->rd_rel->relreplident == REPLICA_IDENTITY_DEFAULT ||
337 : rel->rd_rel->relreplident == REPLICA_IDENTITY_FULL ||
338 : rel->rd_rel->relreplident == REPLICA_IDENTITY_INDEX);
339 :
340 : /* transaction ID (if not valid, we're not streaming) */
341 85468 : if (TransactionIdIsValid(xid))
342 85220 : pq_sendint32(out, xid);
343 :
344 : /* use Oid as relation identifier */
345 85468 : pq_sendint32(out, RelationGetRelid(rel));
346 :
347 85468 : if (oldtuple != NULL)
348 : {
349 136 : if (rel->rd_rel->relreplident == REPLICA_IDENTITY_FULL)
350 46 : pq_sendbyte(out, 'O'); /* old tuple follows */
351 : else
352 90 : pq_sendbyte(out, 'K'); /* old key follows */
353 136 : logicalrep_write_tuple(out, rel, oldtuple, binary);
354 : }
355 :
356 85468 : pq_sendbyte(out, 'N'); /* new tuple follows */
357 85468 : logicalrep_write_tuple(out, rel, newtuple, binary);
358 85468 : }
359 :
360 : /*
361 : * Read UPDATE from stream.
362 : */
363 : LogicalRepRelId
364 62776 : logicalrep_read_update(StringInfo in, bool *has_oldtuple,
365 : LogicalRepTupleData *oldtup,
366 : LogicalRepTupleData *newtup)
367 : {
368 : char action;
369 : LogicalRepRelId relid;
370 :
371 : /* read the relation id */
372 62776 : relid = pq_getmsgint(in, 4);
373 :
374 : /* read and verify action */
375 62776 : action = pq_getmsgbyte(in);
376 62776 : if (action != 'K' && action != 'O' && action != 'N')
377 0 : elog(ERROR, "expected action 'N', 'O' or 'K', got %c",
378 : action);
379 :
380 : /* check for old tuple */
381 62776 : if (action == 'K' || action == 'O')
382 : {
383 160 : logicalrep_read_tuple(in, oldtup);
384 160 : *has_oldtuple = true;
385 :
386 160 : action = pq_getmsgbyte(in);
387 : }
388 : else
389 62616 : *has_oldtuple = false;
390 :
391 : /* check for new tuple */
392 62776 : if (action != 'N')
393 0 : elog(ERROR, "expected action 'N', got %c",
394 : action);
395 :
396 62776 : logicalrep_read_tuple(in, newtup);
397 :
398 62776 : return relid;
399 : }
400 :
401 : /*
402 : * Write DELETE to the output stream.
403 : */
404 : void
405 71586 : logicalrep_write_delete(StringInfo out, TransactionId xid, Relation rel,
406 : HeapTuple oldtuple, bool binary)
407 : {
408 71586 : Assert(rel->rd_rel->relreplident == REPLICA_IDENTITY_DEFAULT ||
409 : rel->rd_rel->relreplident == REPLICA_IDENTITY_FULL ||
410 : rel->rd_rel->relreplident == REPLICA_IDENTITY_INDEX);
411 :
412 71586 : pq_sendbyte(out, 'D'); /* action DELETE */
413 :
414 : /* transaction ID (if not valid, we're not streaming) */
415 71586 : if (TransactionIdIsValid(xid))
416 71190 : pq_sendint32(out, xid);
417 :
418 : /* use Oid as relation identifier */
419 71586 : pq_sendint32(out, RelationGetRelid(rel));
420 :
421 71586 : if (rel->rd_rel->relreplident == REPLICA_IDENTITY_FULL)
422 200 : pq_sendbyte(out, 'O'); /* old tuple follows */
423 : else
424 71386 : pq_sendbyte(out, 'K'); /* old key follows */
425 :
426 71586 : logicalrep_write_tuple(out, rel, oldtuple, binary);
427 71586 : }
428 :
429 : /*
430 : * Read DELETE from stream.
431 : *
432 : * Fills the old tuple.
433 : */
434 : LogicalRepRelId
435 62100 : logicalrep_read_delete(StringInfo in, LogicalRepTupleData *oldtup)
436 : {
437 : char action;
438 : LogicalRepRelId relid;
439 :
440 : /* read the relation id */
441 62100 : relid = pq_getmsgint(in, 4);
442 :
443 : /* read and verify action */
444 62100 : action = pq_getmsgbyte(in);
445 62100 : if (action != 'K' && action != 'O')
446 0 : elog(ERROR, "expected action 'O' or 'K', got %c", action);
447 :
448 62100 : logicalrep_read_tuple(in, oldtup);
449 :
450 62100 : return relid;
451 : }
452 :
453 : /*
454 : * Write TRUNCATE to the output stream.
455 : */
456 : void
457 6 : logicalrep_write_truncate(StringInfo out,
458 : TransactionId xid,
459 : int nrelids,
460 : Oid relids[],
461 : bool cascade, bool restart_seqs)
462 : {
463 : int i;
464 6 : uint8 flags = 0;
465 :
466 6 : pq_sendbyte(out, 'T'); /* action TRUNCATE */
467 :
468 : /* transaction ID (if not valid, we're not streaming) */
469 6 : if (TransactionIdIsValid(xid))
470 0 : pq_sendint32(out, xid);
471 :
472 6 : pq_sendint32(out, nrelids);
473 :
474 : /* encode and send truncate flags */
475 6 : if (cascade)
476 0 : flags |= TRUNCATE_CASCADE;
477 6 : if (restart_seqs)
478 0 : flags |= TRUNCATE_RESTART_SEQS;
479 6 : pq_sendint8(out, flags);
480 :
481 16 : for (i = 0; i < nrelids; i++)
482 10 : pq_sendint32(out, relids[i]);
483 6 : }
484 :
485 : /*
486 : * Read TRUNCATE from stream.
487 : */
488 : List *
489 24 : logicalrep_read_truncate(StringInfo in,
490 : bool *cascade, bool *restart_seqs)
491 : {
492 : int i;
493 : int nrelids;
494 24 : List *relids = NIL;
495 : uint8 flags;
496 :
497 24 : nrelids = pq_getmsgint(in, 4);
498 :
499 : /* read and decode truncate flags */
500 24 : flags = pq_getmsgint(in, 1);
501 24 : *cascade = (flags & TRUNCATE_CASCADE) > 0;
502 24 : *restart_seqs = (flags & TRUNCATE_RESTART_SEQS) > 0;
503 :
504 64 : for (i = 0; i < nrelids; i++)
505 40 : relids = lappend_oid(relids, pq_getmsgint(in, 4));
506 :
507 24 : return relids;
508 : }
509 :
510 : /*
511 : * Write relation description to the output stream.
512 : */
513 : void
514 194 : logicalrep_write_rel(StringInfo out, TransactionId xid, Relation rel)
515 : {
516 : char *relname;
517 :
518 194 : pq_sendbyte(out, 'R'); /* sending RELATION */
519 :
520 : /* transaction ID (if not valid, we're not streaming) */
521 194 : if (TransactionIdIsValid(xid))
522 68 : pq_sendint32(out, xid);
523 :
524 : /* use Oid as relation identifier */
525 194 : pq_sendint32(out, RelationGetRelid(rel));
526 :
527 : /* send qualified relation name */
528 194 : logicalrep_write_namespace(out, RelationGetNamespace(rel));
529 194 : relname = RelationGetRelationName(rel);
530 194 : pq_sendstring(out, relname);
531 :
532 : /* send replica identity */
533 194 : pq_sendbyte(out, rel->rd_rel->relreplident);
534 :
535 : /* send the attribute info */
536 194 : logicalrep_write_attrs(out, rel);
537 194 : }
538 :
539 : /*
540 : * Read the relation info from stream and return as LogicalRepRelation.
541 : */
542 : LogicalRepRelation *
543 248 : logicalrep_read_rel(StringInfo in)
544 : {
545 248 : LogicalRepRelation *rel = palloc(sizeof(LogicalRepRelation));
546 :
547 248 : rel->remoteid = pq_getmsgint(in, 4);
548 :
549 : /* Read relation name from stream */
550 248 : rel->nspname = pstrdup(logicalrep_read_namespace(in));
551 248 : rel->relname = pstrdup(pq_getmsgstring(in));
552 :
553 : /* Read the replica identity. */
554 248 : rel->replident = pq_getmsgbyte(in);
555 :
556 : /* Get attribute description */
557 248 : logicalrep_read_attrs(in, rel);
558 :
559 248 : return rel;
560 : }
561 :
562 : /*
563 : * Write type info to the output stream.
564 : *
565 : * This function will always write base type info.
566 : */
567 : void
568 32 : logicalrep_write_typ(StringInfo out, TransactionId xid, Oid typoid)
569 : {
570 32 : Oid basetypoid = getBaseType(typoid);
571 : HeapTuple tup;
572 : Form_pg_type typtup;
573 :
574 32 : pq_sendbyte(out, 'Y'); /* sending TYPE */
575 :
576 : /* transaction ID (if not valid, we're not streaming) */
577 32 : if (TransactionIdIsValid(xid))
578 0 : pq_sendint32(out, xid);
579 :
580 32 : tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(basetypoid));
581 32 : if (!HeapTupleIsValid(tup))
582 0 : elog(ERROR, "cache lookup failed for type %u", basetypoid);
583 32 : typtup = (Form_pg_type) GETSTRUCT(tup);
584 :
585 : /* use Oid as relation identifier */
586 32 : pq_sendint32(out, typoid);
587 :
588 : /* send qualified type name */
589 32 : logicalrep_write_namespace(out, typtup->typnamespace);
590 32 : pq_sendstring(out, NameStr(typtup->typname));
591 :
592 32 : ReleaseSysCache(tup);
593 32 : }
594 :
595 : /*
596 : * Read type info from the output stream.
597 : */
598 : void
599 32 : logicalrep_read_typ(StringInfo in, LogicalRepTyp *ltyp)
600 : {
601 32 : ltyp->remoteid = pq_getmsgint(in, 4);
602 :
603 : /* Read type name from stream */
604 32 : ltyp->nspname = pstrdup(logicalrep_read_namespace(in));
605 32 : ltyp->typname = pstrdup(pq_getmsgstring(in));
606 32 : }
607 :
608 : /*
609 : * Write a tuple to the outputstream, in the most efficient format possible.
610 : */
611 : static void
612 319904 : logicalrep_write_tuple(StringInfo out, Relation rel, HeapTuple tuple, bool binary)
613 : {
614 : TupleDesc desc;
615 : Datum values[MaxTupleAttributeNumber];
616 : bool isnull[MaxTupleAttributeNumber];
617 : int i;
618 319904 : uint16 nliveatts = 0;
619 :
620 319904 : desc = RelationGetDescr(rel);
621 :
622 983920 : for (i = 0; i < desc->natts; i++)
623 : {
624 664016 : if (TupleDescAttr(desc, i)->attisdropped || TupleDescAttr(desc, i)->attgenerated)
625 4 : continue;
626 664012 : nliveatts++;
627 : }
628 319904 : pq_sendint16(out, nliveatts);
629 :
630 : /* try to allocate enough memory from the get-go */
631 639808 : enlargeStringInfo(out, tuple->t_len +
632 319904 : nliveatts * (1 + 4));
633 :
634 319904 : heap_deform_tuple(tuple, desc, values, isnull);
635 :
636 : /* Write the values */
637 983920 : for (i = 0; i < desc->natts; i++)
638 : {
639 : HeapTuple typtup;
640 : Form_pg_type typclass;
641 664016 : Form_pg_attribute att = TupleDescAttr(desc, i);
642 : char *outputstr;
643 :
644 664016 : if (att->attisdropped || att->attgenerated)
645 4 : continue;
646 :
647 664012 : if (isnull[i])
648 : {
649 71576 : pq_sendbyte(out, LOGICALREP_COLUMN_NULL);
650 71576 : continue;
651 : }
652 :
653 592436 : if (att->attlen == -1 && VARATT_IS_EXTERNAL_ONDISK(values[i]))
654 : {
655 : /*
656 : * Unchanged toasted datum. (Note that we don't promise to detect
657 : * unchanged data in general; this is just a cheap check to avoid
658 : * sending large values unnecessarily.)
659 : */
660 6 : pq_sendbyte(out, LOGICALREP_COLUMN_UNCHANGED);
661 6 : continue;
662 : }
663 :
664 592430 : typtup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(att->atttypid));
665 592430 : if (!HeapTupleIsValid(typtup))
666 0 : elog(ERROR, "cache lookup failed for type %u", att->atttypid);
667 592430 : typclass = (Form_pg_type) GETSTRUCT(typtup);
668 :
669 : /*
670 : * Send in binary if requested and type has suitable send function.
671 : */
672 592430 : if (binary && OidIsValid(typclass->typsend))
673 66746 : {
674 : bytea *outputbytes;
675 : int len;
676 :
677 66746 : pq_sendbyte(out, LOGICALREP_COLUMN_BINARY);
678 66746 : outputbytes = OidSendFunctionCall(typclass->typsend, values[i]);
679 66746 : len = VARSIZE(outputbytes) - VARHDRSZ;
680 66746 : pq_sendint(out, len, 4); /* length */
681 66746 : pq_sendbytes(out, VARDATA(outputbytes), len); /* data */
682 66746 : pfree(outputbytes);
683 : }
684 : else
685 : {
686 525684 : pq_sendbyte(out, LOGICALREP_COLUMN_TEXT);
687 525684 : outputstr = OidOutputFunctionCall(typclass->typoutput, values[i]);
688 525684 : pq_sendcountedtext(out, outputstr, strlen(outputstr), false);
689 525684 : pfree(outputstr);
690 : }
691 :
692 592430 : ReleaseSysCache(typtup);
693 : }
694 319904 : }
695 :
696 : /*
697 : * Read tuple in logical replication format from stream.
698 : */
699 : static void
700 227888 : logicalrep_read_tuple(StringInfo in, LogicalRepTupleData *tuple)
701 : {
702 : int i;
703 : int natts;
704 :
705 : /* Get number of attributes */
706 227888 : natts = pq_getmsgint(in, 2);
707 :
708 : /* Allocate space for per-column values; zero out unused StringInfoDatas */
709 227888 : tuple->colvalues = (StringInfoData *) palloc0(natts * sizeof(StringInfoData));
710 227888 : tuple->colstatus = (char *) palloc(natts * sizeof(char));
711 227888 : tuple->ncols = natts;
712 :
713 : /* Read the data */
714 707866 : for (i = 0; i < natts; i++)
715 : {
716 : char kind;
717 : int len;
718 479978 : StringInfo value = &tuple->colvalues[i];
719 :
720 479978 : kind = pq_getmsgbyte(in);
721 479978 : tuple->colstatus[i] = kind;
722 :
723 479978 : switch (kind)
724 : {
725 : case LOGICALREP_COLUMN_NULL:
726 : /* nothing more to do */
727 62210 : break;
728 : case LOGICALREP_COLUMN_UNCHANGED:
729 : /* we don't receive the value of an unchanged column */
730 6 : break;
731 : case LOGICALREP_COLUMN_TEXT:
732 351024 : len = pq_getmsgint(in, 4); /* read length */
733 :
734 : /* and data */
735 351024 : value->data = palloc(len + 1);
736 351024 : pq_copymsgbytes(in, value->data, len);
737 351024 : value->data[len] = '\0';
738 : /* make StringInfo fully valid */
739 351024 : value->len = len;
740 351024 : value->cursor = 0;
741 351024 : value->maxlen = len;
742 351024 : break;
743 : case LOGICALREP_COLUMN_BINARY:
744 66738 : len = pq_getmsgint(in, 4); /* read length */
745 :
746 : /* and data */
747 66738 : value->data = palloc(len + 1);
748 66738 : pq_copymsgbytes(in, value->data, len);
749 : /* not strictly necessary but per StringInfo practice */
750 66738 : value->data[len] = '\0';
751 : /* make StringInfo fully valid */
752 66738 : value->len = len;
753 66738 : value->cursor = 0;
754 66738 : value->maxlen = len;
755 66738 : break;
756 : default:
757 0 : elog(ERROR, "unrecognized data representation type '%c'", kind);
758 : }
759 : }
760 227888 : }
761 :
762 : /*
763 : * Write relation attribute metadata to the stream.
764 : */
765 : static void
766 194 : logicalrep_write_attrs(StringInfo out, Relation rel)
767 : {
768 : TupleDesc desc;
769 : int i;
770 194 : uint16 nliveatts = 0;
771 194 : Bitmapset *idattrs = NULL;
772 : bool replidentfull;
773 :
774 194 : desc = RelationGetDescr(rel);
775 :
776 : /* send number of live attributes */
777 622 : for (i = 0; i < desc->natts; i++)
778 : {
779 428 : if (TupleDescAttr(desc, i)->attisdropped || TupleDescAttr(desc, i)->attgenerated)
780 2 : continue;
781 426 : nliveatts++;
782 : }
783 194 : pq_sendint16(out, nliveatts);
784 :
785 : /* fetch bitmap of REPLICATION IDENTITY attributes */
786 194 : replidentfull = (rel->rd_rel->relreplident == REPLICA_IDENTITY_FULL);
787 194 : if (!replidentfull)
788 182 : idattrs = RelationGetIndexAttrBitmap(rel,
789 : INDEX_ATTR_BITMAP_IDENTITY_KEY);
790 :
791 : /* send the attributes */
792 622 : for (i = 0; i < desc->natts; i++)
793 : {
794 428 : Form_pg_attribute att = TupleDescAttr(desc, i);
795 428 : uint8 flags = 0;
796 :
797 428 : if (att->attisdropped || att->attgenerated)
798 2 : continue;
799 :
800 : /* REPLICA IDENTITY FULL means all columns are sent as part of key. */
801 838 : if (replidentfull ||
802 412 : bms_is_member(att->attnum - FirstLowInvalidHeapAttributeNumber,
803 : idattrs))
804 176 : flags |= LOGICALREP_IS_REPLICA_IDENTITY;
805 :
806 426 : pq_sendbyte(out, flags);
807 :
808 : /* attribute name */
809 426 : pq_sendstring(out, NameStr(att->attname));
810 :
811 : /* attribute type id */
812 426 : pq_sendint32(out, (int) att->atttypid);
813 :
814 : /* attribute mode */
815 426 : pq_sendint32(out, att->atttypmod);
816 : }
817 :
818 194 : bms_free(idattrs);
819 194 : }
820 :
821 : /*
822 : * Read relation attribute metadata from the stream.
823 : */
824 : static void
825 248 : logicalrep_read_attrs(StringInfo in, LogicalRepRelation *rel)
826 : {
827 : int i;
828 : int natts;
829 : char **attnames;
830 : Oid *atttyps;
831 248 : Bitmapset *attkeys = NULL;
832 :
833 248 : natts = pq_getmsgint(in, 2);
834 248 : attnames = palloc(natts * sizeof(char *));
835 248 : atttyps = palloc(natts * sizeof(Oid));
836 :
837 : /* read the attributes */
838 776 : for (i = 0; i < natts; i++)
839 : {
840 : uint8 flags;
841 :
842 : /* Check for replica identity column */
843 528 : flags = pq_getmsgbyte(in);
844 528 : if (flags & LOGICALREP_IS_REPLICA_IDENTITY)
845 230 : attkeys = bms_add_member(attkeys, i);
846 :
847 : /* attribute name */
848 528 : attnames[i] = pstrdup(pq_getmsgstring(in));
849 :
850 : /* attribute type id */
851 528 : atttyps[i] = (Oid) pq_getmsgint(in, 4);
852 :
853 : /* we ignore attribute mode for now */
854 528 : (void) pq_getmsgint(in, 4);
855 : }
856 :
857 248 : rel->attnames = attnames;
858 248 : rel->atttyps = atttyps;
859 248 : rel->attkeys = attkeys;
860 248 : rel->natts = natts;
861 248 : }
862 :
863 : /*
864 : * Write the namespace name or empty string for pg_catalog (to save space).
865 : */
866 : static void
867 226 : logicalrep_write_namespace(StringInfo out, Oid nspid)
868 : {
869 226 : if (nspid == PG_CATALOG_NAMESPACE)
870 2 : pq_sendbyte(out, '\0');
871 : else
872 : {
873 224 : char *nspname = get_namespace_name(nspid);
874 :
875 224 : if (nspname == NULL)
876 0 : elog(ERROR, "cache lookup failed for namespace %u",
877 : nspid);
878 :
879 224 : pq_sendstring(out, nspname);
880 : }
881 226 : }
882 :
883 : /*
884 : * Read the namespace name while treating empty string as pg_catalog.
885 : */
886 : static const char *
887 280 : logicalrep_read_namespace(StringInfo in)
888 : {
889 280 : const char *nspname = pq_getmsgstring(in);
890 :
891 280 : if (nspname[0] == '\0')
892 2 : nspname = "pg_catalog";
893 :
894 280 : return nspname;
895 : }
896 :
897 : /*
898 : * Write the information for the start stream message to the output stream.
899 : */
900 : void
901 802 : logicalrep_write_stream_start(StringInfo out,
902 : TransactionId xid, bool first_segment)
903 : {
904 802 : pq_sendbyte(out, 'S'); /* action STREAM START */
905 :
906 802 : Assert(TransactionIdIsValid(xid));
907 :
908 : /* transaction ID (we're starting to stream, so must be valid) */
909 802 : pq_sendint32(out, xid);
910 :
911 : /* 1 if this is the first streaming segment for this xid */
912 802 : pq_sendbyte(out, first_segment ? 1 : 0);
913 802 : }
914 :
915 : /*
916 : * Read the information about the start stream message from output stream.
917 : */
918 : TransactionId
919 614 : logicalrep_read_stream_start(StringInfo in, bool *first_segment)
920 : {
921 : TransactionId xid;
922 :
923 614 : Assert(first_segment);
924 :
925 614 : xid = pq_getmsgint(in, 4);
926 614 : *first_segment = (pq_getmsgbyte(in) == 1);
927 :
928 614 : return xid;
929 : }
930 :
931 : /*
932 : * Write the stop stream message to the output stream.
933 : */
934 : void
935 796 : logicalrep_write_stream_stop(StringInfo out)
936 : {
937 796 : pq_sendbyte(out, 'E'); /* action STREAM END */
938 796 : }
939 :
940 : /*
941 : * Write STREAM COMMIT to the output stream.
942 : */
943 : void
944 28 : logicalrep_write_stream_commit(StringInfo out, ReorderBufferTXN *txn,
945 : XLogRecPtr commit_lsn)
946 : {
947 28 : uint8 flags = 0;
948 :
949 28 : pq_sendbyte(out, 'c'); /* action STREAM COMMIT */
950 :
951 28 : Assert(TransactionIdIsValid(txn->xid));
952 :
953 : /* transaction ID */
954 28 : pq_sendint32(out, txn->xid);
955 :
956 : /* send the flags field (unused for now) */
957 28 : pq_sendbyte(out, flags);
958 :
959 : /* send fields */
960 28 : pq_sendint64(out, commit_lsn);
961 28 : pq_sendint64(out, txn->end_lsn);
962 28 : pq_sendint64(out, txn->commit_time);
963 28 : }
964 :
965 : /*
966 : * Read STREAM COMMIT from the output stream.
967 : */
968 : TransactionId
969 28 : logicalrep_read_stream_commit(StringInfo in, LogicalRepCommitData *commit_data)
970 : {
971 : TransactionId xid;
972 : uint8 flags;
973 :
974 28 : xid = pq_getmsgint(in, 4);
975 :
976 : /* read flags (unused for now) */
977 28 : flags = pq_getmsgbyte(in);
978 :
979 28 : if (flags != 0)
980 0 : elog(ERROR, "unrecognized flags %u in commit message", flags);
981 :
982 : /* read fields */
983 28 : commit_data->commit_lsn = pq_getmsgint64(in);
984 28 : commit_data->end_lsn = pq_getmsgint64(in);
985 28 : commit_data->committime = pq_getmsgint64(in);
986 :
987 28 : return xid;
988 : }
989 :
990 : /*
991 : * Write STREAM ABORT to the output stream. Note that xid and subxid will be
992 : * same for the top-level transaction abort.
993 : */
994 : void
995 26 : logicalrep_write_stream_abort(StringInfo out, TransactionId xid,
996 : TransactionId subxid)
997 : {
998 26 : pq_sendbyte(out, 'A'); /* action STREAM ABORT */
999 :
1000 26 : Assert(TransactionIdIsValid(xid) && TransactionIdIsValid(subxid));
1001 :
1002 : /* transaction ID */
1003 26 : pq_sendint32(out, xid);
1004 26 : pq_sendint32(out, subxid);
1005 26 : }
1006 :
1007 : /*
1008 : * Read STREAM ABORT from the output stream.
1009 : */
1010 : void
1011 26 : logicalrep_read_stream_abort(StringInfo in, TransactionId *xid,
1012 : TransactionId *subxid)
1013 : {
1014 26 : Assert(xid && subxid);
1015 :
1016 26 : *xid = pq_getmsgint(in, 4);
1017 26 : *subxid = pq_getmsgint(in, 4);
1018 26 : }
|