Coverage Report

Created: 2026-11-09 07:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/postgres/src/backend/access/transam/xloginsert.c
Line
Count
Source
1
/*-------------------------------------------------------------------------
2
 *
3
 * xloginsert.c
4
 *    Functions for constructing WAL records
5
 *
6
 * Constructing a WAL record begins with a call to XLogBeginInsert,
7
 * followed by a number of XLogRegister* calls. The registered data is
8
 * collected in private working memory, and finally assembled into a chain
9
 * of XLogRecData structs by a call to XLogRecordAssemble(). See
10
 * access/transam/README for details.
11
 *
12
 * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
13
 * Portions Copyright (c) 1994, Regents of the University of California
14
 *
15
 * src/backend/access/transam/xloginsert.c
16
 *
17
 *-------------------------------------------------------------------------
18
 */
19
20
#include "postgres.h"
21
22
#ifdef USE_LZ4
23
#include <lz4.h>
24
#endif
25
26
#ifdef USE_ZSTD
27
#include <zstd.h>
28
#endif
29
30
#include "access/xact.h"
31
#include "access/xlog.h"
32
#include "access/xlog_internal.h"
33
#include "access/xloginsert.h"
34
#include "catalog/pg_control.h"
35
#include "common/pg_lzcompress.h"
36
#include "executor/instrument.h"
37
#include "miscadmin.h"
38
#include "pg_trace.h"
39
#include "replication/origin.h"
40
#include "storage/bufmgr.h"
41
#include "storage/proc.h"
42
#include "utils/memutils.h"
43
#include "utils/pgstat_internal.h"
44
#include "utils/rel.h"
45
46
/*
47
 * Guess the maximum buffer size required to store a compressed version of
48
 * backup block image.
49
 */
50
#ifdef USE_LZ4
51
#define LZ4_MAX_BLCKSZ    LZ4_COMPRESSBOUND(BLCKSZ)
52
#else
53
#define LZ4_MAX_BLCKSZ    0
54
#endif
55
56
#ifdef USE_ZSTD
57
#define ZSTD_MAX_BLCKSZ   ZSTD_COMPRESSBOUND(BLCKSZ)
58
#else
59
#define ZSTD_MAX_BLCKSZ   0
60
#endif
61
62
#define PGLZ_MAX_BLCKSZ   PGLZ_MAX_OUTPUT(BLCKSZ)
63
64
/* Buffer size required to store a compressed version of backup block image */
65
#define COMPRESS_BUFSIZE  Max(Max(PGLZ_MAX_BLCKSZ, LZ4_MAX_BLCKSZ), ZSTD_MAX_BLCKSZ)
66
67
/*
68
 * For each block reference registered with XLogRegisterBuffer, we fill in
69
 * a registered_buffer struct.
70
 */
71
typedef struct
72
{
73
  bool    in_use;     /* is this slot in use? */
74
  uint8   flags;      /* REGBUF_* flags */
75
  RelFileLocator rlocator;  /* identifies the relation and block */
76
  ForkNumber  forkno;
77
  BlockNumber block;
78
  const PageData *page;   /* page content */
79
  uint32    rdata_len;    /* total length of data in rdata chain */
80
  XLogRecData *rdata_head;  /* head of the chain of data registered with
81
                 * this block */
82
  XLogRecData *rdata_tail;  /* last entry in the chain, or &rdata_head if
83
                 * empty */
84
85
  XLogRecData bkp_rdatas[2];  /* temporary rdatas used to hold references to
86
                 * backup block data in XLogRecordAssemble() */
87
88
  /* buffer to store a compressed version of backup block image */
89
  char    compressed_page[COMPRESS_BUFSIZE];
90
} registered_buffer;
91
92
static registered_buffer *registered_buffers;
93
static int  max_registered_buffers; /* allocated size */
94
static int  max_registered_block_id = 0;  /* highest block_id + 1 currently
95
                       * registered */
96
97
/*
98
 * A chain of XLogRecDatas to hold the "main data" of a WAL record, registered
99
 * with XLogRegisterData(...).
100
 */
101
static XLogRecData *mainrdata_head;
102
static XLogRecData *mainrdata_last = (XLogRecData *) &mainrdata_head;
103
static uint64 mainrdata_len;  /* total # of bytes in chain */
104
105
/* flags for the in-progress insertion */
106
static uint8 curinsert_flags = 0;
107
108
/*
109
 * These are used to hold the record header while constructing a record.
110
 * 'hdr_scratch' is not a plain variable, but is palloc'd at initialization,
111
 * because we want it to be MAXALIGNed and padding bytes zeroed.
112
 *
113
 * For simplicity, it's allocated large enough to hold the headers for any
114
 * WAL record.
115
 */
116
static XLogRecData hdr_rdt;
117
static char *hdr_scratch = NULL;
118
119
#ifdef USE_ZSTD
120
/* Compression context reused for zstd-compressed full-page images. */
121
static ZSTD_CCtx *zstd_cctx = NULL;
122
#endif
123
124
0
#define SizeOfXlogOrigin  (sizeof(ReplOriginId) + sizeof(char))
125
0
#define SizeOfXLogTransactionId (sizeof(TransactionId) + sizeof(char))
126
127
#define HEADER_SCRATCH_SIZE \
128
0
  (SizeOfXLogRecord + \
129
0
   MaxSizeOfXLogRecordBlockHeader * (XLR_MAX_BLOCK_ID + 1) + \
130
0
   SizeOfXLogRecordDataHeaderLong + SizeOfXlogOrigin + \
131
0
   SizeOfXLogTransactionId)
132
133
/*
134
 * An array of XLogRecData structs, to hold registered data.
135
 */
136
static XLogRecData *rdatas;
137
static int  num_rdatas;     /* entries currently used */
138
static int  max_rdatas;     /* allocated size */
139
140
static bool begininsert_called = false;
141
142
/* Memory context to hold the registered buffer and data references. */
143
static MemoryContext xloginsert_cxt;
144
145
static XLogRecData *XLogRecordAssemble(RmgrId rmid, uint8 info,
146
                     XLogRecPtr RedoRecPtr, bool doPageWrites,
147
                     XLogRecPtr *fpw_lsn, int *num_fpi,
148
                     uint64 *fpi_bytes,
149
                     bool *topxid_included);
150
static bool XLogCompressBackupBlock(const PageData *page, uint16 hole_offset,
151
                  uint16 hole_length, void *dest, uint16 *dlen);
152
153
/*
154
 * Begin constructing a WAL record. This must be called before the
155
 * XLogRegister* functions and XLogInsert().
156
 */
157
void
158
XLogBeginInsert(void)
159
0
{
160
0
  Assert(max_registered_block_id == 0);
161
0
  Assert(mainrdata_last == (XLogRecData *) &mainrdata_head);
162
0
  Assert(mainrdata_len == 0);
163
164
  /* cross-check on whether we should be here or not */
165
0
  if (!XLogInsertAllowed())
166
0
    elog(ERROR, "cannot make new WAL entries during recovery");
167
168
0
  if (begininsert_called)
169
0
    elog(ERROR, "XLogBeginInsert was already called");
170
171
0
  begininsert_called = true;
172
0
}
173
174
/*
175
 * Ensure that there are enough buffer and data slots in the working area,
176
 * for subsequent XLogRegisterBuffer, XLogRegisterData and XLogRegisterBufData
177
 * calls.
178
 *
179
 * There is always space for a small number of buffers and data chunks, enough
180
 * for most record types. This function is for the exceptional cases that need
181
 * more.
182
 */
183
void
184
XLogEnsureRecordSpace(int max_block_id, int ndatas)
185
0
{
186
0
  int     nbuffers;
187
188
  /*
189
   * This must be called before entering a critical section, because
190
   * allocating memory inside a critical section can fail. repalloc() will
191
   * check the same, but better to check it here too so that we fail
192
   * consistently even if the arrays happen to be large enough already.
193
   */
194
0
  Assert(CritSectionCount == 0);
195
196
  /* the minimum values can't be decreased */
197
0
  if (max_block_id < XLR_NORMAL_MAX_BLOCK_ID)
198
0
    max_block_id = XLR_NORMAL_MAX_BLOCK_ID;
199
0
  if (ndatas < XLR_NORMAL_RDATAS)
200
0
    ndatas = XLR_NORMAL_RDATAS;
201
202
0
  if (max_block_id > XLR_MAX_BLOCK_ID)
203
0
    elog(ERROR, "maximum number of WAL record block references exceeded");
204
0
  nbuffers = max_block_id + 1;
205
206
0
  if (nbuffers > max_registered_buffers)
207
0
  {
208
0
    registered_buffers = repalloc_array(registered_buffers, registered_buffer, nbuffers);
209
210
    /*
211
     * At least the padding bytes in the structs must be zeroed, because
212
     * they are included in WAL data, but initialize it all for tidiness.
213
     */
214
0
    MemSet(&registered_buffers[max_registered_buffers], 0,
215
0
         (nbuffers - max_registered_buffers) * sizeof(registered_buffer));
216
0
    max_registered_buffers = nbuffers;
217
0
  }
218
219
0
  if (ndatas > max_rdatas)
220
0
  {
221
0
    rdatas = repalloc_array(rdatas, XLogRecData, ndatas);
222
0
    max_rdatas = ndatas;
223
0
  }
224
0
}
225
226
/*
227
 * Reset WAL record construction buffers.
228
 */
229
void
230
XLogResetInsertion(void)
231
0
{
232
0
  int     i;
233
234
0
  for (i = 0; i < max_registered_block_id; i++)
235
0
    registered_buffers[i].in_use = false;
236
237
0
  num_rdatas = 0;
238
0
  max_registered_block_id = 0;
239
0
  mainrdata_len = 0;
240
0
  mainrdata_last = (XLogRecData *) &mainrdata_head;
241
0
  curinsert_flags = 0;
242
0
  begininsert_called = false;
243
0
}
244
245
/*
246
 * Register a reference to a buffer with the WAL record being constructed.
247
 * This must be called for every page that the WAL-logged operation modifies.
248
 */
249
void
250
XLogRegisterBuffer(uint8 block_id, Buffer buffer, uint8 flags)
251
0
{
252
0
  registered_buffer *regbuf;
253
254
  /* NO_IMAGE doesn't make sense with FORCE_IMAGE */
255
0
  Assert(!((flags & REGBUF_FORCE_IMAGE) && (flags & (REGBUF_NO_IMAGE))));
256
0
  Assert(begininsert_called);
257
258
  /*
259
   * Ordinarily, the buffer should be exclusive-locked (or share-exclusive
260
   * in case of hint bits) and marked dirty before we get here, otherwise we
261
   * could end up violating one of the rules in access/transam/README.
262
   *
263
   * Some callers intentionally register a clean page and never update that
264
   * page's LSN; in that case they can pass the flag REGBUF_NO_CHANGE to
265
   * bypass these checks.
266
   */
267
#ifdef USE_ASSERT_CHECKING
268
  if (!(flags & REGBUF_NO_CHANGE))
269
  {
270
    Assert(BufferIsDirty(buffer));
271
    Assert(BufferIsLockedByMeInMode(buffer, BUFFER_LOCK_EXCLUSIVE) ||
272
         BufferIsLockedByMeInMode(buffer, BUFFER_LOCK_SHARE_EXCLUSIVE));
273
  }
274
#endif
275
276
0
  if (block_id >= max_registered_block_id)
277
0
  {
278
0
    if (block_id >= max_registered_buffers)
279
0
      elog(ERROR, "too many registered buffers");
280
0
    max_registered_block_id = block_id + 1;
281
0
  }
282
283
0
  regbuf = &registered_buffers[block_id];
284
285
0
  BufferGetTag(buffer, &regbuf->rlocator, &regbuf->forkno, &regbuf->block);
286
0
  regbuf->page = BufferGetPage(buffer);
287
0
  regbuf->flags = flags;
288
0
  regbuf->rdata_tail = (XLogRecData *) &regbuf->rdata_head;
289
0
  regbuf->rdata_len = 0;
290
291
  /*
292
   * Check that this page hasn't already been registered with some other
293
   * block_id.
294
   */
295
#ifdef USE_ASSERT_CHECKING
296
  {
297
    int     i;
298
299
    for (i = 0; i < max_registered_block_id; i++)
300
    {
301
      registered_buffer *regbuf_old = &registered_buffers[i];
302
303
      if (i == block_id || !regbuf_old->in_use)
304
        continue;
305
306
      Assert(!RelFileLocatorEquals(regbuf_old->rlocator, regbuf->rlocator) ||
307
           regbuf_old->forkno != regbuf->forkno ||
308
           regbuf_old->block != regbuf->block);
309
    }
310
  }
311
#endif
312
313
0
  regbuf->in_use = true;
314
0
}
315
316
/*
317
 * Like XLogRegisterBuffer, but for registering a block that's not in the
318
 * shared buffer pool (i.e. when you don't have a Buffer for it).
319
 */
320
void
321
XLogRegisterBlock(uint8 block_id, RelFileLocator *rlocator, ForkNumber forknum,
322
          BlockNumber blknum, const PageData *page, uint8 flags)
323
0
{
324
0
  registered_buffer *regbuf;
325
326
0
  Assert(begininsert_called);
327
328
0
  if (block_id >= max_registered_block_id)
329
0
    max_registered_block_id = block_id + 1;
330
331
0
  if (block_id >= max_registered_buffers)
332
0
    elog(ERROR, "too many registered buffers");
333
334
0
  regbuf = &registered_buffers[block_id];
335
336
0
  regbuf->rlocator = *rlocator;
337
0
  regbuf->forkno = forknum;
338
0
  regbuf->block = blknum;
339
0
  regbuf->page = page;
340
0
  regbuf->flags = flags;
341
0
  regbuf->rdata_tail = (XLogRecData *) &regbuf->rdata_head;
342
0
  regbuf->rdata_len = 0;
343
344
  /*
345
   * Check that this page hasn't already been registered with some other
346
   * block_id.
347
   */
348
#ifdef USE_ASSERT_CHECKING
349
  {
350
    int     i;
351
352
    for (i = 0; i < max_registered_block_id; i++)
353
    {
354
      registered_buffer *regbuf_old = &registered_buffers[i];
355
356
      if (i == block_id || !regbuf_old->in_use)
357
        continue;
358
359
      Assert(!RelFileLocatorEquals(regbuf_old->rlocator, regbuf->rlocator) ||
360
           regbuf_old->forkno != regbuf->forkno ||
361
           regbuf_old->block != regbuf->block);
362
    }
363
  }
364
#endif
365
366
0
  regbuf->in_use = true;
367
0
}
368
369
/*
370
 * Add data to the WAL record that's being constructed.
371
 *
372
 * The data is appended to the "main chunk", available at replay with
373
 * XLogRecGetData().
374
 */
375
void
376
XLogRegisterData(const void *data, uint32 len)
377
0
{
378
0
  XLogRecData *rdata;
379
380
0
  Assert(begininsert_called);
381
382
0
  if (num_rdatas >= max_rdatas)
383
0
    ereport(ERROR,
384
0
        (errmsg_internal("too much WAL data"),
385
0
         errdetail_internal("%d out of %d data segments are already in use.",
386
0
                  num_rdatas, max_rdatas)));
387
0
  rdata = &rdatas[num_rdatas++];
388
389
0
  rdata->data = data;
390
0
  rdata->len = len;
391
392
  /*
393
   * we use the mainrdata_last pointer to track the end of the chain, so no
394
   * need to clear 'next' here.
395
   */
396
397
0
  mainrdata_last->next = rdata;
398
0
  mainrdata_last = rdata;
399
400
0
  mainrdata_len += len;
401
0
}
402
403
/*
404
 * Add buffer-specific data to the WAL record that's being constructed.
405
 *
406
 * Block_id must reference a block previously registered with
407
 * XLogRegisterBuffer(). If this is called more than once for the same
408
 * block_id, the data is appended.
409
 *
410
 * The maximum amount of data that can be registered per block is 65535
411
 * bytes. That should be plenty; if you need more than BLCKSZ bytes to
412
 * reconstruct the changes to the page, you might as well just log a full
413
 * copy of it. (the "main data" that's not associated with a block is not
414
 * limited)
415
 */
416
void
417
XLogRegisterBufData(uint8 block_id, const void *data, uint32 len)
418
0
{
419
0
  registered_buffer *regbuf;
420
0
  XLogRecData *rdata;
421
422
0
  Assert(begininsert_called);
423
424
  /* find the registered buffer struct */
425
0
  regbuf = &registered_buffers[block_id];
426
0
  if (!regbuf->in_use)
427
0
    elog(ERROR, "no block with id %d registered with WAL insertion",
428
0
       block_id);
429
430
  /*
431
   * Check against max_rdatas and ensure we do not register more data per
432
   * buffer than can be handled by the physical data format; i.e. that
433
   * regbuf->rdata_len does not grow beyond what
434
   * XLogRecordBlockHeader->data_length can hold.
435
   */
436
0
  if (num_rdatas >= max_rdatas)
437
0
    ereport(ERROR,
438
0
        (errmsg_internal("too much WAL data"),
439
0
         errdetail_internal("%d out of %d data segments are already in use.",
440
0
                  num_rdatas, max_rdatas)));
441
0
  if (regbuf->rdata_len + len > UINT16_MAX || len > UINT16_MAX)
442
0
    ereport(ERROR,
443
0
        (errmsg_internal("too much WAL data"),
444
0
         errdetail_internal("Registering more than maximum %u bytes allowed to block %u: current %u bytes, adding %u bytes.",
445
0
                  UINT16_MAX, block_id, regbuf->rdata_len, len)));
446
447
0
  rdata = &rdatas[num_rdatas++];
448
449
0
  rdata->data = data;
450
0
  rdata->len = len;
451
452
0
  regbuf->rdata_tail->next = rdata;
453
0
  regbuf->rdata_tail = rdata;
454
0
  regbuf->rdata_len += len;
455
0
}
456
457
/*
458
 * Set insert status flags for the upcoming WAL record.
459
 *
460
 * The flags that can be used here are:
461
 * - XLOG_INCLUDE_ORIGIN, to determine if the replication origin should be
462
 *   included in the record.
463
 * - XLOG_MARK_UNIMPORTANT, to signal that the record is not important for
464
 *   durability, which allows to avoid triggering WAL archiving and other
465
 *   background activity.
466
 */
467
void
468
XLogSetRecordFlags(uint8 flags)
469
0
{
470
0
  Assert(begininsert_called);
471
0
  curinsert_flags |= flags;
472
0
}
473
474
/*
475
 * Insert an XLOG record having the specified RMID and info bytes, with the
476
 * body of the record being the data and buffer references registered earlier
477
 * with XLogRegister* calls.
478
 *
479
 * Returns XLOG pointer to end of record (beginning of next record).
480
 * This can be used as LSN for data pages affected by the logged action.
481
 * (LSN is the XLOG point up to which the XLOG must be flushed to disk
482
 * before the data page can be written out.  This implements the basic
483
 * WAL rule "write the log before the data".)
484
 */
485
XLogRecPtr
486
XLogInsert(RmgrId rmid, uint8 info)
487
0
{
488
0
  XLogRecPtr  EndPos;
489
490
  /* XLogBeginInsert() must have been called. */
491
0
  if (!begininsert_called)
492
0
    elog(ERROR, "XLogBeginInsert was not called");
493
494
  /*
495
   * The caller can set rmgr bits, XLR_SPECIAL_REL_UPDATE and
496
   * XLR_CHECK_CONSISTENCY; the rest are reserved for use by me.
497
   */
498
0
  if ((info & ~(XLR_RMGR_INFO_MASK |
499
0
          XLR_SPECIAL_REL_UPDATE |
500
0
          XLR_CHECK_CONSISTENCY)) != 0)
501
0
    elog(PANIC, "invalid xlog info mask %02X", info);
502
503
0
  TRACE_POSTGRESQL_WAL_INSERT(rmid, info);
504
505
  /*
506
   * In bootstrap mode, we don't actually log anything but XLOG resources;
507
   * return a phony record pointer.
508
   */
509
0
  if (IsBootstrapProcessingMode() && rmid != RM_XLOG_ID)
510
0
  {
511
0
    XLogResetInsertion();
512
0
    EndPos = SizeOfXLogLongPHD; /* start of 1st chkpt record */
513
0
    return EndPos;
514
0
  }
515
516
0
  do
517
0
  {
518
0
    XLogRecPtr  RedoRecPtr;
519
0
    bool    doPageWrites;
520
0
    bool    topxid_included = false;
521
0
    XLogRecPtr  fpw_lsn;
522
0
    XLogRecData *rdt;
523
0
    int     num_fpi = 0;
524
0
    uint64    fpi_bytes = 0;
525
526
    /*
527
     * Get values needed to decide whether to do full-page writes. Since
528
     * we don't yet have an insertion lock, these could change under us,
529
     * but XLogInsertRecord will recheck them once it has a lock.
530
     */
531
0
    GetFullPageWriteInfo(&RedoRecPtr, &doPageWrites);
532
533
0
    rdt = XLogRecordAssemble(rmid, info, RedoRecPtr, doPageWrites,
534
0
                 &fpw_lsn, &num_fpi, &fpi_bytes,
535
0
                 &topxid_included);
536
537
0
    EndPos = XLogInsertRecord(rdt, fpw_lsn, curinsert_flags, num_fpi,
538
0
                  fpi_bytes, topxid_included);
539
0
  } while (!XLogRecPtrIsValid(EndPos));
540
541
0
  XLogResetInsertion();
542
543
0
  return EndPos;
544
0
}
545
546
/*
547
 * Simple wrapper to XLogInsert to insert a WAL record with elementary
548
 * contents (only an int64 is supported as value currently).
549
 */
550
XLogRecPtr
551
XLogSimpleInsertInt64(RmgrId rmid, uint8 info, int64 value)
552
0
{
553
0
  XLogBeginInsert();
554
0
  XLogRegisterData(&value, sizeof(value));
555
0
  return XLogInsert(rmid, info);
556
0
}
557
558
/*
559
 * XLogGetFakeLSN - get a fake LSN for an index page that isn't WAL-logged.
560
 *
561
 * Some index AMs use LSNs to detect concurrent page modifications, but not
562
 * all index pages are WAL-logged.  This function provides a sequence of fake
563
 * LSNs for that purpose.
564
 */
565
XLogRecPtr
566
XLogGetFakeLSN(Relation rel)
567
0
{
568
0
  if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
569
0
  {
570
    /*
571
     * Temporary relations are only accessible in our session, so a simple
572
     * backend-local counter will do.
573
     */
574
0
    static XLogRecPtr counter = FirstNormalUnloggedLSN;
575
576
0
    return counter++;
577
0
  }
578
0
  else if (rel->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED)
579
0
  {
580
    /*
581
     * Unlogged relations are accessible from other backends, and survive
582
     * (clean) restarts.  GetFakeLSNForUnloggedRel() handles that for us.
583
     */
584
0
    return GetFakeLSNForUnloggedRel();
585
0
  }
586
0
  else
587
0
  {
588
    /*
589
     * WAL-logging on this relation will start after commit, so its LSNs
590
     * must be distinct numbers smaller than the LSN at the next commit.
591
     * Emit a dummy WAL record if insert-LSN hasn't advanced after the
592
     * last call.
593
     */
594
0
    static XLogRecPtr lastlsn = InvalidXLogRecPtr;
595
0
    XLogRecPtr  currlsn = GetXLogInsertEndRecPtr();
596
597
0
    Assert(!RelationNeedsWAL(rel));
598
0
    Assert(RelationIsPermanent(rel));
599
600
    /* No need for an actual record if we already have a distinct LSN */
601
0
    if (XLogRecPtrIsValid(lastlsn) && lastlsn == currlsn)
602
0
      currlsn = XLogAssignLSN();
603
604
0
    lastlsn = currlsn;
605
0
    return currlsn;
606
0
  }
607
0
}
608
609
/*
610
 * Assemble a WAL record from the registered data and buffers into an
611
 * XLogRecData chain, ready for insertion with XLogInsertRecord().
612
 *
613
 * The record header fields are filled in, except for the xl_prev field. The
614
 * calculated CRC does not include the record header yet.
615
 *
616
 * If there are any registered buffers, and a full-page image was not taken
617
 * of all of them, *fpw_lsn is set to the lowest LSN among such pages. This
618
 * signals that the assembled record is only good for insertion on the
619
 * assumption that the RedoRecPtr and doPageWrites values were up-to-date.
620
 *
621
 * *topxid_included is set if the topmost transaction ID is logged with the
622
 * current subtransaction.
623
 */
624
static XLogRecData *
625
XLogRecordAssemble(RmgrId rmid, uint8 info,
626
           XLogRecPtr RedoRecPtr, bool doPageWrites,
627
           XLogRecPtr *fpw_lsn, int *num_fpi, uint64 *fpi_bytes,
628
           bool *topxid_included)
629
0
{
630
0
  XLogRecData *rdt;
631
0
  uint64    total_len = 0;
632
0
  int     block_id;
633
0
  pg_crc32c rdata_crc;
634
0
  registered_buffer *prev_regbuf = NULL;
635
0
  XLogRecData *rdt_datas_last;
636
0
  XLogRecord *rechdr;
637
0
  char     *scratch = hdr_scratch;
638
639
  /*
640
   * Note: this function can be called multiple times for the same record.
641
   * All the modifications we do to the rdata chains below must handle that.
642
   */
643
644
  /* The record begins with the fixed-size header */
645
0
  rechdr = (XLogRecord *) scratch;
646
0
  scratch += SizeOfXLogRecord;
647
648
0
  hdr_rdt.next = NULL;
649
0
  rdt_datas_last = &hdr_rdt;
650
0
  hdr_rdt.data = hdr_scratch;
651
652
  /*
653
   * Enforce consistency checks for this record if user is looking for it.
654
   * Do this before at the beginning of this routine to give the possibility
655
   * for callers of XLogInsert() to pass XLR_CHECK_CONSISTENCY directly for
656
   * a record.
657
   */
658
0
  if (wal_consistency_checking[rmid])
659
0
    info |= XLR_CHECK_CONSISTENCY;
660
661
  /*
662
   * Make an rdata chain containing all the data portions of all block
663
   * references. This includes the data for full-page images. Also append
664
   * the headers for the block references in the scratch buffer.
665
   */
666
0
  *fpw_lsn = InvalidXLogRecPtr;
667
0
  for (block_id = 0; block_id < max_registered_block_id; block_id++)
668
0
  {
669
0
    registered_buffer *regbuf = &registered_buffers[block_id];
670
0
    bool    needs_backup;
671
0
    bool    needs_data;
672
0
    XLogRecordBlockHeader bkpb;
673
0
    XLogRecordBlockImageHeader bimg;
674
0
    XLogRecordBlockCompressHeader cbimg = {0};
675
0
    bool    samerel;
676
0
    bool    is_compressed = false;
677
0
    bool    include_image;
678
679
0
    if (!regbuf->in_use)
680
0
      continue;
681
682
    /* Determine if this block needs to be backed up */
683
0
    if (regbuf->flags & REGBUF_FORCE_IMAGE)
684
0
      needs_backup = true;
685
0
    else if (regbuf->flags & REGBUF_NO_IMAGE)
686
0
      needs_backup = false;
687
0
    else if (!doPageWrites)
688
0
      needs_backup = false;
689
0
    else
690
0
    {
691
      /*
692
       * We assume page LSN is first data on *every* page that can be
693
       * passed to XLogInsert, whether it has the standard page layout
694
       * or not.
695
       */
696
0
      XLogRecPtr  page_lsn = PageGetLSN(regbuf->page);
697
698
0
      needs_backup = (page_lsn <= RedoRecPtr);
699
0
      if (!needs_backup)
700
0
      {
701
0
        if (!XLogRecPtrIsValid(*fpw_lsn) || page_lsn < *fpw_lsn)
702
0
          *fpw_lsn = page_lsn;
703
0
      }
704
0
    }
705
706
    /* Determine if the buffer data needs to included */
707
0
    if (regbuf->rdata_len == 0)
708
0
      needs_data = false;
709
0
    else if ((regbuf->flags & REGBUF_KEEP_DATA) != 0)
710
0
      needs_data = true;
711
0
    else
712
0
      needs_data = !needs_backup;
713
714
0
    bkpb.id = block_id;
715
0
    bkpb.fork_flags = regbuf->forkno;
716
0
    bkpb.data_length = 0;
717
718
0
    if ((regbuf->flags & REGBUF_WILL_INIT) == REGBUF_WILL_INIT)
719
0
      bkpb.fork_flags |= BKPBLOCK_WILL_INIT;
720
721
    /*
722
     * If needs_backup is true or WAL checking is enabled for current
723
     * resource manager, log a full-page write for the current block.
724
     *
725
     * Do not force an image for a REGBUF_NO_CHANGE buffer.
726
     */
727
0
    if (regbuf->flags & REGBUF_NO_CHANGE)
728
0
      include_image = needs_backup;
729
0
    else
730
0
      include_image = needs_backup || (info & XLR_CHECK_CONSISTENCY) != 0;
731
732
0
    if (include_image)
733
0
    {
734
0
      const PageData *page = regbuf->page;
735
0
      uint16    compressed_len = 0;
736
737
      /*
738
       * The page needs to be backed up, so calculate its hole length
739
       * and offset.
740
       */
741
0
      if (regbuf->flags & REGBUF_STANDARD)
742
0
      {
743
        /* Assume we can omit data between pd_lower and pd_upper */
744
0
        uint16    lower = ((const PageHeaderData *) page)->pd_lower;
745
0
        uint16    upper = ((const PageHeaderData *) page)->pd_upper;
746
747
0
        if (lower >= SizeOfPageHeaderData &&
748
0
          upper > lower &&
749
0
          upper <= BLCKSZ)
750
0
        {
751
0
          bimg.hole_offset = lower;
752
0
          cbimg.hole_length = upper - lower;
753
0
        }
754
0
        else
755
0
        {
756
          /* No "hole" to remove */
757
0
          bimg.hole_offset = 0;
758
0
          cbimg.hole_length = 0;
759
0
        }
760
0
      }
761
0
      else
762
0
      {
763
        /* Not a standard page header, don't try to eliminate "hole" */
764
0
        bimg.hole_offset = 0;
765
0
        cbimg.hole_length = 0;
766
0
      }
767
768
      /*
769
       * Try to compress a block image if wal_compression is enabled
770
       */
771
0
      if (wal_compression != WAL_COMPRESSION_NONE)
772
0
      {
773
0
        is_compressed =
774
0
          XLogCompressBackupBlock(page, bimg.hole_offset,
775
0
                      cbimg.hole_length,
776
0
                      regbuf->compressed_page,
777
0
                      &compressed_len);
778
0
      }
779
780
      /*
781
       * Fill in the remaining fields in the XLogRecordBlockHeader
782
       * struct
783
       */
784
0
      bkpb.fork_flags |= BKPBLOCK_HAS_IMAGE;
785
786
      /* Report a full page image constructed for the WAL record */
787
0
      *num_fpi += 1;
788
789
      /*
790
       * Construct XLogRecData entries for the page content.
791
       */
792
0
      rdt_datas_last->next = &regbuf->bkp_rdatas[0];
793
0
      rdt_datas_last = rdt_datas_last->next;
794
795
0
      bimg.bimg_info = (cbimg.hole_length == 0) ? 0 : BKPIMAGE_HAS_HOLE;
796
797
      /*
798
       * If WAL consistency checking is enabled for the resource manager
799
       * of this WAL record, a full-page image is included in the record
800
       * for the block modified. During redo, the full-page is replayed
801
       * only if BKPIMAGE_APPLY is set.
802
       */
803
0
      if (needs_backup)
804
0
        bimg.bimg_info |= BKPIMAGE_APPLY;
805
806
0
      if (is_compressed)
807
0
      {
808
        /* The current compression is stored in the WAL record */
809
0
        bimg.length = compressed_len;
810
811
        /* Set the compression method used for this block */
812
0
        switch ((WalCompression) wal_compression)
813
0
        {
814
0
          case WAL_COMPRESSION_PGLZ:
815
0
            bimg.bimg_info |= BKPIMAGE_COMPRESS_PGLZ;
816
0
            break;
817
818
0
          case WAL_COMPRESSION_LZ4:
819
#ifdef USE_LZ4
820
            bimg.bimg_info |= BKPIMAGE_COMPRESS_LZ4;
821
#else
822
0
            elog(ERROR, "LZ4 is not supported by this build");
823
0
#endif
824
0
            break;
825
826
0
          case WAL_COMPRESSION_ZSTD:
827
#ifdef USE_ZSTD
828
            bimg.bimg_info |= BKPIMAGE_COMPRESS_ZSTD;
829
#else
830
0
            elog(ERROR, "zstd is not supported by this build");
831
0
#endif
832
0
            break;
833
834
0
          case WAL_COMPRESSION_NONE:
835
0
            Assert(false);  /* cannot happen */
836
0
            break;
837
            /* no default case, so that compiler will warn */
838
0
        }
839
840
0
        rdt_datas_last->data = regbuf->compressed_page;
841
0
        rdt_datas_last->len = compressed_len;
842
0
      }
843
0
      else
844
0
      {
845
0
        bimg.length = BLCKSZ - cbimg.hole_length;
846
847
0
        if (cbimg.hole_length == 0)
848
0
        {
849
0
          rdt_datas_last->data = page;
850
0
          rdt_datas_last->len = BLCKSZ;
851
0
        }
852
0
        else
853
0
        {
854
          /* must skip the hole */
855
0
          rdt_datas_last->data = page;
856
0
          rdt_datas_last->len = bimg.hole_offset;
857
858
0
          rdt_datas_last->next = &regbuf->bkp_rdatas[1];
859
0
          rdt_datas_last = rdt_datas_last->next;
860
861
0
          rdt_datas_last->data =
862
0
            page + (bimg.hole_offset + cbimg.hole_length);
863
0
          rdt_datas_last->len =
864
0
            BLCKSZ - (bimg.hole_offset + cbimg.hole_length);
865
0
        }
866
0
      }
867
868
0
      total_len += bimg.length;
869
870
      /* Track the WAL full page images in bytes */
871
0
      *fpi_bytes += bimg.length;
872
0
    }
873
874
0
    if (needs_data)
875
0
    {
876
      /*
877
       * When copying to XLogRecordBlockHeader, the length is narrowed
878
       * to an uint16.  Double-check that it is still correct.
879
       */
880
0
      Assert(regbuf->rdata_len <= UINT16_MAX);
881
882
      /*
883
       * Link the caller-supplied rdata chain for this buffer to the
884
       * overall list.
885
       */
886
0
      bkpb.fork_flags |= BKPBLOCK_HAS_DATA;
887
0
      bkpb.data_length = (uint16) regbuf->rdata_len;
888
0
      total_len += regbuf->rdata_len;
889
890
0
      rdt_datas_last->next = regbuf->rdata_head;
891
0
      rdt_datas_last = regbuf->rdata_tail;
892
0
    }
893
894
0
    if (prev_regbuf && RelFileLocatorEquals(regbuf->rlocator, prev_regbuf->rlocator))
895
0
    {
896
0
      samerel = true;
897
0
      bkpb.fork_flags |= BKPBLOCK_SAME_REL;
898
0
    }
899
0
    else
900
0
      samerel = false;
901
0
    prev_regbuf = regbuf;
902
903
    /* Ok, copy the header to the scratch buffer */
904
0
    memcpy(scratch, &bkpb, SizeOfXLogRecordBlockHeader);
905
0
    scratch += SizeOfXLogRecordBlockHeader;
906
0
    if (include_image)
907
0
    {
908
0
      memcpy(scratch, &bimg, SizeOfXLogRecordBlockImageHeader);
909
0
      scratch += SizeOfXLogRecordBlockImageHeader;
910
0
      if (cbimg.hole_length != 0 && is_compressed)
911
0
      {
912
0
        memcpy(scratch, &cbimg,
913
0
             SizeOfXLogRecordBlockCompressHeader);
914
0
        scratch += SizeOfXLogRecordBlockCompressHeader;
915
0
      }
916
0
    }
917
0
    if (!samerel)
918
0
    {
919
0
      memcpy(scratch, &regbuf->rlocator, sizeof(RelFileLocator));
920
0
      scratch += sizeof(RelFileLocator);
921
0
    }
922
0
    memcpy(scratch, &regbuf->block, sizeof(BlockNumber));
923
0
    scratch += sizeof(BlockNumber);
924
0
  }
925
926
  /* followed by the record's origin, if any */
927
0
  if ((curinsert_flags & XLOG_INCLUDE_ORIGIN) &&
928
0
    replorigin_xact_state.origin != InvalidReplOriginId)
929
0
  {
930
0
    *(scratch++) = (char) XLR_BLOCK_ID_ORIGIN;
931
0
    memcpy(scratch, &replorigin_xact_state.origin, sizeof(replorigin_xact_state.origin));
932
0
    scratch += sizeof(replorigin_xact_state.origin);
933
0
  }
934
935
  /* followed by toplevel XID, if not already included in previous record */
936
0
  if (IsSubxactTopXidLogPending())
937
0
  {
938
0
    TransactionId xid = GetTopTransactionIdIfAny();
939
940
    /* Set the flag that the top xid is included in the WAL */
941
0
    *topxid_included = true;
942
943
0
    *(scratch++) = (char) XLR_BLOCK_ID_TOPLEVEL_XID;
944
0
    memcpy(scratch, &xid, sizeof(TransactionId));
945
0
    scratch += sizeof(TransactionId);
946
0
  }
947
948
  /* followed by main data, if any */
949
0
  if (mainrdata_len > 0)
950
0
  {
951
0
    if (mainrdata_len > 255)
952
0
    {
953
0
      uint32    mainrdata_len_4b;
954
955
0
      if (mainrdata_len > PG_UINT32_MAX)
956
0
        ereport(ERROR,
957
0
            (errmsg_internal("too much WAL data"),
958
0
             errdetail_internal("Main data length is %" PRIu64 " bytes for a maximum of %u bytes.",
959
0
                      mainrdata_len,
960
0
                      PG_UINT32_MAX)));
961
962
0
      mainrdata_len_4b = (uint32) mainrdata_len;
963
0
      *(scratch++) = (char) XLR_BLOCK_ID_DATA_LONG;
964
0
      memcpy(scratch, &mainrdata_len_4b, sizeof(uint32));
965
0
      scratch += sizeof(uint32);
966
0
    }
967
0
    else
968
0
    {
969
0
      *(scratch++) = (char) XLR_BLOCK_ID_DATA_SHORT;
970
0
      *(scratch++) = (uint8) mainrdata_len;
971
0
    }
972
0
    rdt_datas_last->next = mainrdata_head;
973
0
    rdt_datas_last = mainrdata_last;
974
0
    total_len += mainrdata_len;
975
0
  }
976
0
  rdt_datas_last->next = NULL;
977
978
0
  hdr_rdt.len = (scratch - hdr_scratch);
979
0
  total_len += hdr_rdt.len;
980
981
  /*
982
   * Calculate CRC of the data
983
   *
984
   * Note that the record header isn't added into the CRC initially since we
985
   * don't know the prev-link yet.  Thus, the CRC will represent the CRC of
986
   * the whole record in the order: rdata, then backup blocks, then record
987
   * header.
988
   */
989
0
  INIT_CRC32C(rdata_crc);
990
0
  COMP_CRC32C(rdata_crc, hdr_scratch + SizeOfXLogRecord, hdr_rdt.len - SizeOfXLogRecord);
991
0
  for (rdt = hdr_rdt.next; rdt != NULL; rdt = rdt->next)
992
0
    COMP_CRC32C(rdata_crc, rdt->data, rdt->len);
993
994
  /*
995
   * Ensure that the XLogRecord is not too large.
996
   *
997
   * XLogReader machinery is only able to handle records up to a certain
998
   * size (ignoring machine resource limitations), so make sure that we will
999
   * not emit records larger than the sizes advertised to be supported.
1000
   */
1001
0
  if (total_len > XLogRecordMaxSize)
1002
0
    ereport(ERROR,
1003
0
        (errmsg_internal("oversized WAL record"),
1004
0
         errdetail_internal("WAL record would be %" PRIu64 " bytes (of maximum %u bytes); rmid %u flags %u.",
1005
0
                  total_len, XLogRecordMaxSize, rmid, info)));
1006
1007
  /*
1008
   * Fill in the fields in the record header. Prev-link is filled in later,
1009
   * once we know where in the WAL the record will be inserted. The CRC does
1010
   * not include the record header yet.
1011
   */
1012
0
  rechdr->xl_xid = GetCurrentTransactionIdIfAny();
1013
0
  rechdr->xl_tot_len = (uint32) total_len;
1014
0
  rechdr->xl_info = info;
1015
0
  rechdr->xl_rmid = rmid;
1016
0
  rechdr->xl_prev = InvalidXLogRecPtr;
1017
0
  rechdr->xl_crc = rdata_crc;
1018
1019
0
  return &hdr_rdt;
1020
0
}
1021
1022
/*
1023
 * Create a compressed version of a backup block image.
1024
 *
1025
 * Returns false if compression fails (i.e., compressed result is actually
1026
 * bigger than original). Otherwise, returns true and sets 'dlen' to
1027
 * the length of compressed block image.
1028
 */
1029
static bool
1030
XLogCompressBackupBlock(const PageData *page, uint16 hole_offset, uint16 hole_length,
1031
            void *dest, uint16 *dlen)
1032
0
{
1033
0
  int32   orig_len = BLCKSZ - hole_length;
1034
0
  int32   len = -1;
1035
0
  int32   extra_bytes = 0;
1036
0
  const void *source;
1037
0
  PGAlignedBlock tmp;
1038
1039
0
  if (hole_length != 0)
1040
0
  {
1041
    /* must skip the hole */
1042
0
    memcpy(tmp.data, page, hole_offset);
1043
0
    memcpy(tmp.data + hole_offset,
1044
0
         page + (hole_offset + hole_length),
1045
0
         BLCKSZ - (hole_length + hole_offset));
1046
0
    source = tmp.data;
1047
1048
    /*
1049
     * Extra data needs to be stored in WAL record for the compressed
1050
     * version of block image if the hole exists.
1051
     */
1052
0
    extra_bytes = SizeOfXLogRecordBlockCompressHeader;
1053
0
  }
1054
0
  else
1055
0
    source = page;
1056
1057
0
  switch ((WalCompression) wal_compression)
1058
0
  {
1059
0
    case WAL_COMPRESSION_PGLZ:
1060
0
      len = pglz_compress(source, orig_len, dest, PGLZ_strategy_default);
1061
0
      break;
1062
1063
0
    case WAL_COMPRESSION_LZ4:
1064
#ifdef USE_LZ4
1065
      len = LZ4_compress_default(source, dest, orig_len,
1066
                     COMPRESS_BUFSIZE);
1067
      if (len <= 0)
1068
        len = -1;   /* failure */
1069
#else
1070
0
      elog(ERROR, "LZ4 is not supported by this build");
1071
0
#endif
1072
0
      break;
1073
1074
0
    case WAL_COMPRESSION_ZSTD:
1075
#ifdef USE_ZSTD
1076
      if (zstd_cctx == NULL)
1077
        zstd_cctx = ZSTD_createCCtx();
1078
1079
      /*
1080
       * We may be in a critical section, so skip if the context
1081
       * allocation failed.
1082
       */
1083
      if (zstd_cctx == NULL)
1084
      {
1085
        len = -1;
1086
        break;
1087
      }
1088
1089
      len = ZSTD_compressCCtx(zstd_cctx, dest, COMPRESS_BUFSIZE,
1090
                  source, orig_len, ZSTD_CLEVEL_DEFAULT);
1091
      if (ZSTD_isError(len))
1092
        len = -1;   /* failure */
1093
#else
1094
0
      elog(ERROR, "zstd is not supported by this build");
1095
0
#endif
1096
0
      break;
1097
1098
0
    case WAL_COMPRESSION_NONE:
1099
0
      Assert(false);    /* cannot happen */
1100
0
      break;
1101
      /* no default case, so that compiler will warn */
1102
0
  }
1103
1104
  /*
1105
   * We recheck the actual size even if compression reports success and see
1106
   * if the number of bytes saved by compression is larger than the length
1107
   * of extra data needed for the compressed version of block image.
1108
   */
1109
0
  if (len >= 0 &&
1110
0
    len + extra_bytes < orig_len)
1111
0
  {
1112
0
    *dlen = (uint16) len; /* successful compression */
1113
0
    return true;
1114
0
  }
1115
0
  return false;
1116
0
}
1117
1118
/*
1119
 * Determine whether the buffer referenced has to be backed up.
1120
 *
1121
 * Since we don't yet have the insert lock, fullPageWrites and runningBackups
1122
 * (which forces full-page writes) could change later, so the result should
1123
 * be used for optimization purposes only.
1124
 */
1125
bool
1126
XLogCheckBufferNeedsBackup(Buffer buffer)
1127
0
{
1128
0
  XLogRecPtr  RedoRecPtr;
1129
0
  bool    doPageWrites;
1130
0
  Page    page;
1131
1132
0
  GetFullPageWriteInfo(&RedoRecPtr, &doPageWrites);
1133
1134
0
  page = BufferGetPage(buffer);
1135
1136
0
  if (doPageWrites && PageGetLSN(page) <= RedoRecPtr)
1137
0
    return true;     /* buffer requires backup */
1138
1139
0
  return false;       /* buffer does not need to be backed up */
1140
0
}
1141
1142
/*
1143
 * Write a backup block if needed when we are setting a hint. Note that
1144
 * this may be called for a variety of page types, not just heaps.
1145
 *
1146
 * Callable while holding just a share-exclusive lock on the buffer
1147
 * content. That suffices to prevent concurrent modifications of the
1148
 * buffer. The buffer already needs to have been marked dirty by
1149
 * MarkBufferDirtyHint().
1150
 *
1151
 * We only need to do something if page has not yet been full page written in
1152
 * this checkpoint round. The LSN of the inserted wal record is returned if we
1153
 * had to write, InvalidXLogRecPtr otherwise.
1154
 */
1155
XLogRecPtr
1156
XLogSaveBufferForHint(Buffer buffer, bool buffer_std)
1157
0
{
1158
0
  XLogRecPtr  recptr = InvalidXLogRecPtr;
1159
0
  XLogRecPtr  lsn;
1160
0
  XLogRecPtr  RedoRecPtr;
1161
1162
  /* this also verifies that we hold an appropriate lock */
1163
0
  Assert(BufferIsDirty(buffer));
1164
1165
  /*
1166
   * Update RedoRecPtr so that we can make the right decision. It's possible
1167
   * that a new checkpoint will start just after GetRedoRecPtr(), but that
1168
   * is ok, as the buffer is already dirty, ensuring that any BufferSync()
1169
   * started after the buffer was marked dirty cannot complete without
1170
   * flushing this buffer.  If a checkpoint started between marking the
1171
   * buffer dirty and this check, we will emit an unnecessary WAL record (as
1172
   * the buffer will be written out as part of the checkpoint), but the
1173
   * window for that is not big.
1174
   */
1175
0
  RedoRecPtr = GetRedoRecPtr();
1176
1177
  /*
1178
   * We assume page LSN is first data on *every* page that can be passed to
1179
   * XLogInsert, whether it has the standard page layout or not.
1180
   */
1181
0
  lsn = PageGetLSN(BufferGetPage(buffer));
1182
1183
0
  if (lsn <= RedoRecPtr)
1184
0
  {
1185
0
    int     flags = 0;
1186
1187
0
    XLogBeginInsert();
1188
1189
0
    if (buffer_std)
1190
0
      flags |= REGBUF_STANDARD;
1191
1192
0
    XLogRegisterBuffer(0, buffer, flags);
1193
1194
0
    recptr = XLogInsert(RM_XLOG_ID, XLOG_FPI_FOR_HINT);
1195
0
  }
1196
1197
0
  return recptr;
1198
0
}
1199
1200
/*
1201
 * Write a WAL record containing a full image of a page. Caller is responsible
1202
 * for writing the page to disk after calling this routine.
1203
 *
1204
 * Note: If you're using this function, you should be building pages in private
1205
 * memory and writing them directly to smgr.  If you're using buffers, call
1206
 * log_newpage_buffer instead.
1207
 *
1208
 * If the page follows the standard page layout, with a PageHeader and unused
1209
 * space between pd_lower and pd_upper, set 'page_std' to true. That allows
1210
 * the unused space to be left out from the WAL record, making it smaller.
1211
 */
1212
XLogRecPtr
1213
log_newpage(RelFileLocator *rlocator, ForkNumber forknum, BlockNumber blkno,
1214
      Page page, bool page_std)
1215
0
{
1216
0
  int     flags;
1217
0
  XLogRecPtr  recptr;
1218
1219
0
  flags = REGBUF_FORCE_IMAGE;
1220
0
  if (page_std)
1221
0
    flags |= REGBUF_STANDARD;
1222
1223
0
  XLogBeginInsert();
1224
0
  XLogRegisterBlock(0, rlocator, forknum, blkno, page, flags);
1225
0
  recptr = XLogInsert(RM_XLOG_ID, XLOG_FPI);
1226
1227
  /*
1228
   * The page may be uninitialized. If so, we can't set the LSN because that
1229
   * would corrupt the page.
1230
   */
1231
0
  if (!PageIsNew(page))
1232
0
  {
1233
0
    PageSetLSN(page, recptr);
1234
0
  }
1235
1236
0
  return recptr;
1237
0
}
1238
1239
/*
1240
 * Like log_newpage(), but allows logging multiple pages in one operation.
1241
 * It is more efficient than calling log_newpage() for each page separately,
1242
 * because we can write multiple pages in a single WAL record.
1243
 */
1244
void
1245
log_newpages(RelFileLocator *rlocator, ForkNumber forknum, int num_pages,
1246
       BlockNumber *blknos, Page *pages, bool page_std)
1247
0
{
1248
0
  int     flags;
1249
0
  XLogRecPtr  recptr;
1250
0
  int     i;
1251
0
  int     j;
1252
1253
0
  flags = REGBUF_FORCE_IMAGE;
1254
0
  if (page_std)
1255
0
    flags |= REGBUF_STANDARD;
1256
1257
  /*
1258
   * Iterate over all the pages. They are collected into batches of
1259
   * XLR_MAX_BLOCK_ID pages, and a single WAL-record is written for each
1260
   * batch.
1261
   */
1262
0
  XLogEnsureRecordSpace(XLR_MAX_BLOCK_ID - 1, 0);
1263
1264
0
  i = 0;
1265
0
  while (i < num_pages)
1266
0
  {
1267
0
    int     batch_start = i;
1268
0
    int     nbatch;
1269
1270
0
    XLogBeginInsert();
1271
1272
0
    nbatch = 0;
1273
0
    while (nbatch < XLR_MAX_BLOCK_ID && i < num_pages)
1274
0
    {
1275
0
      XLogRegisterBlock(nbatch, rlocator, forknum, blknos[i], pages[i], flags);
1276
0
      i++;
1277
0
      nbatch++;
1278
0
    }
1279
1280
0
    recptr = XLogInsert(RM_XLOG_ID, XLOG_FPI);
1281
1282
0
    for (j = batch_start; j < i; j++)
1283
0
    {
1284
      /*
1285
       * The page may be uninitialized. If so, we can't set the LSN
1286
       * because that would corrupt the page.
1287
       */
1288
0
      if (!PageIsNew(pages[j]))
1289
0
      {
1290
0
        PageSetLSN(pages[j], recptr);
1291
0
      }
1292
0
    }
1293
0
  }
1294
0
}
1295
1296
/*
1297
 * Write a WAL record containing a full image of a page.
1298
 *
1299
 * Caller should initialize the buffer and mark it dirty before calling this
1300
 * function.  This function will set the page LSN.
1301
 *
1302
 * If the page follows the standard page layout, with a PageHeader and unused
1303
 * space between pd_lower and pd_upper, set 'page_std' to true. That allows
1304
 * the unused space to be left out from the WAL record, making it smaller.
1305
 */
1306
XLogRecPtr
1307
log_newpage_buffer(Buffer buffer, bool page_std)
1308
0
{
1309
0
  Page    page = BufferGetPage(buffer);
1310
0
  RelFileLocator rlocator;
1311
0
  ForkNumber  forknum;
1312
0
  BlockNumber blkno;
1313
1314
  /* Shared buffers should be modified in a critical section. */
1315
0
  Assert(CritSectionCount > 0);
1316
1317
0
  BufferGetTag(buffer, &rlocator, &forknum, &blkno);
1318
1319
0
  return log_newpage(&rlocator, forknum, blkno, page, page_std);
1320
0
}
1321
1322
/*
1323
 * WAL-log a range of blocks in a relation.
1324
 *
1325
 * An image of all pages with block numbers 'startblk' <= X < 'endblk' is
1326
 * written to the WAL. If the range is large, this is done in multiple WAL
1327
 * records.
1328
 *
1329
 * If all page follows the standard page layout, with a PageHeader and unused
1330
 * space between pd_lower and pd_upper, set 'page_std' to true. That allows
1331
 * the unused space to be left out from the WAL records, making them smaller.
1332
 *
1333
 * NOTE: This function acquires exclusive-locks on the pages. Typically, this
1334
 * is used on a newly-built relation, and the caller is holding a
1335
 * AccessExclusiveLock on it, so no other backend can be accessing it at the
1336
 * same time. If that's not the case, you must ensure that this does not
1337
 * cause a deadlock through some other means.
1338
 */
1339
void
1340
log_newpage_range(Relation rel, ForkNumber forknum,
1341
          BlockNumber startblk, BlockNumber endblk,
1342
          bool page_std)
1343
0
{
1344
0
  int     flags;
1345
0
  BlockNumber blkno;
1346
1347
0
  flags = REGBUF_FORCE_IMAGE;
1348
0
  if (page_std)
1349
0
    flags |= REGBUF_STANDARD;
1350
1351
  /*
1352
   * Iterate over all the pages in the range. They are collected into
1353
   * batches of XLR_MAX_BLOCK_ID pages, and a single WAL-record is written
1354
   * for each batch.
1355
   */
1356
0
  XLogEnsureRecordSpace(XLR_MAX_BLOCK_ID - 1, 0);
1357
1358
0
  blkno = startblk;
1359
0
  while (blkno < endblk)
1360
0
  {
1361
0
    Buffer    bufpack[XLR_MAX_BLOCK_ID];
1362
0
    XLogRecPtr  recptr;
1363
0
    int     nbufs;
1364
0
    int     i;
1365
1366
0
    CHECK_FOR_INTERRUPTS();
1367
1368
    /* Collect a batch of blocks. */
1369
0
    nbufs = 0;
1370
0
    while (nbufs < XLR_MAX_BLOCK_ID && blkno < endblk)
1371
0
    {
1372
0
      Buffer    buf = ReadBufferExtended(rel, forknum, blkno,
1373
0
                         RBM_NORMAL, NULL);
1374
1375
0
      LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
1376
1377
      /*
1378
       * Completely empty pages are not WAL-logged. Writing a WAL record
1379
       * would change the LSN, and we don't want that. We want the page
1380
       * to stay empty.
1381
       */
1382
0
      if (!PageIsNew(BufferGetPage(buf)))
1383
0
        bufpack[nbufs++] = buf;
1384
0
      else
1385
0
        UnlockReleaseBuffer(buf);
1386
0
      blkno++;
1387
0
    }
1388
1389
    /* Nothing more to do if all remaining blocks were empty. */
1390
0
    if (nbufs == 0)
1391
0
      break;
1392
1393
    /* Write WAL record for this batch. */
1394
0
    XLogBeginInsert();
1395
1396
0
    START_CRIT_SECTION();
1397
0
    for (i = 0; i < nbufs; i++)
1398
0
    {
1399
0
      MarkBufferDirty(bufpack[i]);
1400
0
      XLogRegisterBuffer(i, bufpack[i], flags);
1401
0
    }
1402
1403
0
    recptr = XLogInsert(RM_XLOG_ID, XLOG_FPI);
1404
1405
0
    for (i = 0; i < nbufs; i++)
1406
0
      PageSetLSN(BufferGetPage(bufpack[i]), recptr);
1407
1408
0
    END_CRIT_SECTION();
1409
1410
0
    for (i = 0; i < nbufs; i++)
1411
0
      UnlockReleaseBuffer(bufpack[i]);
1412
0
  }
1413
0
}
1414
1415
/*
1416
 * Allocate working buffers needed for WAL record construction.
1417
 */
1418
void
1419
InitXLogInsert(void)
1420
0
{
1421
#ifdef USE_ASSERT_CHECKING
1422
1423
  /*
1424
   * Check that any records assembled can be decoded.  This is capped based
1425
   * on what XLogReader would require at its maximum bound.  The XLOG_BLCKSZ
1426
   * addend covers the larger allocate_recordbuf() demand.  This code path
1427
   * is called once per backend, more than enough for this check.
1428
   */
1429
  size_t    max_required =
1430
    DecodeXLogRecordRequiredSpace(XLogRecordMaxSize + XLOG_BLCKSZ);
1431
1432
  Assert(AllocSizeIsValid(max_required));
1433
#endif
1434
1435
  /* Initialize the working areas */
1436
0
  if (xloginsert_cxt == NULL)
1437
0
  {
1438
0
    xloginsert_cxt = AllocSetContextCreate(TopMemoryContext,
1439
0
                         "WAL record construction",
1440
0
                         ALLOCSET_DEFAULT_SIZES);
1441
0
  }
1442
1443
0
  if (registered_buffers == NULL)
1444
0
  {
1445
0
    registered_buffers = (registered_buffer *)
1446
0
      MemoryContextAllocZero(xloginsert_cxt,
1447
0
                   sizeof(registered_buffer) * (XLR_NORMAL_MAX_BLOCK_ID + 1));
1448
0
    max_registered_buffers = XLR_NORMAL_MAX_BLOCK_ID + 1;
1449
0
  }
1450
0
  if (rdatas == NULL)
1451
0
  {
1452
0
    rdatas = MemoryContextAlloc(xloginsert_cxt,
1453
0
                  sizeof(XLogRecData) * XLR_NORMAL_RDATAS);
1454
0
    max_rdatas = XLR_NORMAL_RDATAS;
1455
0
  }
1456
1457
  /*
1458
   * Allocate a buffer to hold the header information for a WAL record.
1459
   */
1460
0
  if (hdr_scratch == NULL)
1461
0
    hdr_scratch = MemoryContextAllocZero(xloginsert_cxt,
1462
0
                       HEADER_SCRATCH_SIZE);
1463
1464
  /* Extra initialization for wal_debug */
1465
#ifdef WAL_DEBUG
1466
  InitWalDebug();
1467
#endif
1468
0
}