Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * xlogfuncs.c
4 : : *
5 : : * PostgreSQL write-ahead log manager user interface functions
6 : : *
7 : : * This file contains WAL control and information functions.
8 : : *
9 : : *
10 : : * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
11 : : * Portions Copyright (c) 1994, Regents of the University of California
12 : : *
13 : : * src/backend/access/transam/xlogfuncs.c
14 : : *
15 : : *-------------------------------------------------------------------------
16 : : */
17 : : #include "postgres.h"
18 : :
19 : : #include <unistd.h>
20 : :
21 : : #include "access/htup_details.h"
22 : : #include "access/xlog_internal.h"
23 : : #include "access/xlogbackup.h"
24 : : #include "access/xlogrecovery.h"
25 : : #include "catalog/pg_authid.h"
26 : : #include "catalog/pg_type.h"
27 : : #include "funcapi.h"
28 : : #include "miscadmin.h"
29 : : #include "pgstat.h"
30 : : #include "utils/acl.h"
31 : : #include "replication/walreceiver.h"
32 : : #include "storage/fd.h"
33 : : #include "storage/latch.h"
34 : : #include "storage/standby.h"
35 : : #include "utils/builtins.h"
36 : : #include "utils/memutils.h"
37 : : #include "utils/pg_lsn.h"
38 : : #include "utils/timestamp.h"
39 : : #include "utils/wait_event.h"
40 : :
41 : : /*
42 : : * Backup-related variables.
43 : : */
44 : : static BackupState *backup_state = NULL;
45 : : static StringInfo tablespace_map = NULL;
46 : :
47 : : /* Session-level context for the SQL-callable backup functions */
48 : : static MemoryContext backupcontext = NULL;
49 : :
50 : :
51 : : /*
52 : : * Return a string constant representing the recovery pause state. This is
53 : : * used in system functions and views, and should *not* be translated.
54 : : */
55 : : static const char *
60 michael@paquier.xyz 56 :GNC 9 : GetRecoveryPauseStateString(RecoveryPauseState pause_state)
57 : : {
58 : 9 : const char *statestr = NULL;
59 : :
60 [ + - + - ]: 9 : switch (pause_state)
61 : : {
62 : 3 : case RECOVERY_NOT_PAUSED:
63 : 3 : statestr = "not paused";
64 : 3 : break;
60 michael@paquier.xyz 65 :UNC 0 : case RECOVERY_PAUSE_REQUESTED:
66 : 0 : statestr = "pause requested";
67 : 0 : break;
60 michael@paquier.xyz 68 :GNC 6 : case RECOVERY_PAUSED:
69 : 6 : statestr = "paused";
70 : 6 : break;
71 : : }
72 : :
73 [ - + ]: 9 : Assert(statestr != NULL);
74 : 9 : return statestr;
75 : : }
76 : :
77 : : /*
78 : : * pg_backup_start: set up for taking an on-line backup dump
79 : : *
80 : : * Essentially what this does is to create the contents required for the
81 : : * backup_label file and the tablespace map.
82 : : *
83 : : * Permission checking for this function is managed through the normal
84 : : * GRANT system.
85 : : */
86 : : Datum
1490 sfrost@snowman.net 87 :CBC 4 : pg_backup_start(PG_FUNCTION_ARGS)
88 : : {
3341 noah@leadboat.com 89 : 4 : text *backupid = PG_GETARG_TEXT_PP(0);
5296 simon@2ndQuadrant.co 90 : 4 : bool fast = PG_GETARG_BOOL(1);
91 : : char *backupidstr;
3329 teodor@sigaev.ru 92 : 4 : SessionBackupState status = get_backup_status();
93 : : MemoryContext oldcontext;
94 : :
5296 simon@2ndQuadrant.co 95 : 4 : backupidstr = text_to_cstring(backupid);
96 : :
1490 sfrost@snowman.net 97 [ - + ]: 4 : if (status == SESSION_BACKUP_RUNNING)
3682 magnus@hagander.net 98 [ # # ]:UBC 0 : ereport(ERROR,
99 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
100 : : errmsg("a backup is already in progress in this session")));
101 : :
102 : : /*
103 : : * backup_state and tablespace_map need to be long-lived as they are used
104 : : * in pg_backup_stop(). These are allocated in a dedicated memory context
105 : : * child of TopMemoryContext, deleted at the end of pg_backup_stop(). If
106 : : * an error happens before ending the backup, memory would be leaked in
107 : : * this context until pg_backup_start() is called again.
108 : : */
1291 michael@paquier.xyz 109 [ + - ]:CBC 4 : if (backupcontext == NULL)
110 : : {
111 : 4 : backupcontext = AllocSetContextCreate(TopMemoryContext,
112 : : "on-line backup context",
113 : : ALLOCSET_START_SMALL_SIZES);
114 : : }
115 : : else
116 : : {
1291 michael@paquier.xyz 117 :UBC 0 : backup_state = NULL;
1317 118 : 0 : tablespace_map = NULL;
1291 119 : 0 : MemoryContextReset(backupcontext);
120 : : }
121 : :
1291 michael@paquier.xyz 122 :CBC 4 : oldcontext = MemoryContextSwitchTo(backupcontext);
146 michael@paquier.xyz 123 :GNC 4 : backup_state = palloc0_object(BackupState);
1317 michael@paquier.xyz 124 :CBC 4 : tablespace_map = makeStringInfo();
1490 sfrost@snowman.net 125 : 4 : MemoryContextSwitchTo(oldcontext);
126 : :
127 : 4 : register_persistent_abort_backup_handler();
1317 michael@paquier.xyz 128 : 4 : do_pg_backup_start(backupidstr, fast, NULL, backup_state, tablespace_map);
129 : :
130 : 3 : PG_RETURN_LSN(backup_state->startpoint);
131 : : }
132 : :
133 : :
134 : : /*
135 : : * pg_backup_stop: finish taking an on-line backup.
136 : : *
137 : : * The first parameter (variable 'waitforarchive'), which is optional,
138 : : * allows the user to choose if they want to wait for the WAL to be archived
139 : : * or if we should just return as soon as the WAL record is written.
140 : : *
141 : : * This function stops an in-progress backup, creates backup_label contents and
142 : : * it returns the backup stop LSN, backup_label and tablespace_map contents.
143 : : *
144 : : * The backup_label contains the user-supplied label string (typically this
145 : : * would be used to tell where the backup dump will be stored), the starting
146 : : * time, starting WAL location for the dump and so on. It is the caller's
147 : : * responsibility to write the backup_label and tablespace_map files in the
148 : : * data folder that will be restored from this backup.
149 : : *
150 : : * Permission checking for this function is managed through the normal
151 : : * GRANT system.
152 : : */
153 : : Datum
1490 sfrost@snowman.net 154 : 2 : pg_backup_stop(PG_FUNCTION_ARGS)
155 : : {
156 : : #define PG_BACKUP_STOP_V2_COLS 3
157 : : TupleDesc tupdesc;
1330 michael@paquier.xyz 158 : 2 : Datum values[PG_BACKUP_STOP_V2_COLS] = {0};
159 : 2 : bool nulls[PG_BACKUP_STOP_V2_COLS] = {0};
1490 sfrost@snowman.net 160 : 2 : bool waitforarchive = PG_GETARG_BOOL(0);
161 : : char *backup_label;
3329 teodor@sigaev.ru 162 : 2 : SessionBackupState status = get_backup_status();
163 : :
164 : : /* Initialize attributes information in the tuple descriptor */
3682 magnus@hagander.net 165 [ - + ]: 2 : if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
3682 magnus@hagander.net 166 [ # # ]:UBC 0 : elog(ERROR, "return type must be a row type");
167 : :
1490 sfrost@snowman.net 168 [ - + ]:CBC 2 : if (status != SESSION_BACKUP_RUNNING)
1490 sfrost@snowman.net 169 [ # # ]:UBC 0 : ereport(ERROR,
170 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
171 : : errmsg("backup is not in progress"),
172 : : errhint("Did you call pg_backup_start()?")));
173 : :
1317 michael@paquier.xyz 174 [ - + ]:CBC 2 : Assert(backup_state != NULL);
175 [ - + ]: 2 : Assert(tablespace_map != NULL);
176 : :
177 : : /* Stop the backup */
178 : 2 : do_pg_backup_stop(backup_state, waitforarchive);
179 : :
180 : : /* Build the contents of backup_label */
181 : 2 : backup_label = build_backup_content(backup_state, false);
182 : :
183 : 2 : values[0] = LSNGetDatum(backup_state->stoppoint);
1316 184 : 2 : values[1] = CStringGetTextDatum(backup_label);
1317 185 : 2 : values[2] = CStringGetTextDatum(tablespace_map->data);
186 : :
187 : : /* Deallocate backup-related variables */
1291 188 : 2 : pfree(backup_label);
189 : :
190 : : /* Clean up the session-level state and its memory context */
1317 191 : 2 : backup_state = NULL;
192 : 2 : tablespace_map = NULL;
1291 193 : 2 : MemoryContextDelete(backupcontext);
194 : 2 : backupcontext = NULL;
195 : :
196 : : /* Returns the record as Datum */
1524 197 : 2 : PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
198 : : }
199 : :
200 : : /*
201 : : * pg_switch_wal: switch to next xlog file
202 : : *
203 : : * Permission checking for this function is managed through the normal
204 : : * GRANT system.
205 : : */
206 : : Datum
3372 rhaas@postgresql.org 207 : 472 : pg_switch_wal(PG_FUNCTION_ARGS)
208 : : {
209 : : XLogRecPtr switchpoint;
210 : :
5296 simon@2ndQuadrant.co 211 [ - + ]: 472 : if (RecoveryInProgress())
5296 simon@2ndQuadrant.co 212 [ # # ]:UBC 0 : ereport(ERROR,
213 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
214 : : errmsg("recovery is in progress"),
215 : : errhint("WAL control functions cannot be executed during recovery.")));
216 : :
3421 andres@anarazel.de 217 :CBC 472 : switchpoint = RequestXLogSwitch(false);
218 : :
219 : : /*
220 : : * As a convenience, return the WAL location of the switch record
221 : : */
4458 rhaas@postgresql.org 222 : 472 : PG_RETURN_LSN(switchpoint);
223 : : }
224 : :
225 : : /*
226 : : * pg_log_standby_snapshot: call LogStandbySnapshot()
227 : : *
228 : : * Permission checking for this function is managed through the normal
229 : : * GRANT system.
230 : : */
231 : : Datum
1123 andres@anarazel.de 232 : 29 : pg_log_standby_snapshot(PG_FUNCTION_ARGS)
233 : : {
234 : : XLogRecPtr recptr;
235 : :
236 [ - + ]: 29 : if (RecoveryInProgress())
1123 andres@anarazel.de 237 [ # # ]:UBC 0 : ereport(ERROR,
238 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
239 : : errmsg("recovery is in progress"),
240 : : errhint("%s cannot be executed during recovery.",
241 : : "pg_log_standby_snapshot()")));
242 : :
1123 andres@anarazel.de 243 [ - + ]:CBC 29 : if (!XLogStandbyInfoActive())
1123 andres@anarazel.de 244 [ # # ]:UBC 0 : ereport(ERROR,
245 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
246 : : errmsg("pg_log_standby_snapshot() can only be used if \"wal_level\" >= \"replica\"")));
247 : :
28 alvherre@kurilemu.de 248 :GNC 29 : recptr = LogStandbySnapshot(InvalidOid);
249 : :
250 : : /*
251 : : * As a convenience, return the WAL location of the last inserted record
252 : : */
1123 andres@anarazel.de 253 :CBC 29 : PG_RETURN_LSN(recptr);
254 : : }
255 : :
256 : : /*
257 : : * pg_create_restore_point: a named point for restore
258 : : *
259 : : * Permission checking for this function is managed through the normal
260 : : * GRANT system.
261 : : */
262 : : Datum
5296 simon@2ndQuadrant.co 263 : 3 : pg_create_restore_point(PG_FUNCTION_ARGS)
264 : : {
3341 noah@leadboat.com 265 : 3 : text *restore_name = PG_GETARG_TEXT_PP(0);
266 : : char *restore_name_str;
267 : : XLogRecPtr restorepoint;
268 : :
5296 simon@2ndQuadrant.co 269 [ - + ]: 3 : if (RecoveryInProgress())
5296 simon@2ndQuadrant.co 270 [ # # ]:UBC 0 : ereport(ERROR,
271 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
272 : : errmsg("recovery is in progress"),
273 : : errhint("WAL control functions cannot be executed during recovery.")));
274 : :
5296 simon@2ndQuadrant.co 275 [ - + ]:CBC 3 : if (!XLogIsNeeded())
5296 simon@2ndQuadrant.co 276 [ # # ]:UBC 0 : ereport(ERROR,
277 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
278 : : errmsg("WAL level not sufficient for creating a restore point"),
279 : : errhint("\"wal_level\" must be set to \"replica\" or \"logical\" at server start.")));
280 : :
5296 simon@2ndQuadrant.co 281 :CBC 3 : restore_name_str = text_to_cstring(restore_name);
282 : :
283 [ - + ]: 3 : if (strlen(restore_name_str) >= MAXFNAMELEN)
5296 simon@2ndQuadrant.co 284 [ # # ]:UBC 0 : ereport(ERROR,
285 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
286 : : errmsg("value too long for restore point (maximum %d characters)", MAXFNAMELEN - 1)));
287 : :
5296 simon@2ndQuadrant.co 288 :CBC 3 : restorepoint = XLogRestorePoint(restore_name_str);
289 : :
290 : : /*
291 : : * As a convenience, return the WAL location of the restore point record
292 : : */
4458 rhaas@postgresql.org 293 : 3 : PG_RETURN_LSN(restorepoint);
294 : : }
295 : :
296 : : /*
297 : : * Report the current WAL write location (same format as pg_backup_start etc)
298 : : *
299 : : * This is useful for determining how much of WAL is visible to an external
300 : : * archiving process. Note that the data before this point is written out
301 : : * to the kernel, but is not necessarily synced to disk.
302 : : */
303 : : Datum
3281 tgl@sss.pgh.pa.us 304 : 543 : pg_current_wal_lsn(PG_FUNCTION_ARGS)
305 : : {
306 : : XLogRecPtr current_recptr;
307 : :
5296 simon@2ndQuadrant.co 308 [ - + ]: 543 : if (RecoveryInProgress())
5296 simon@2ndQuadrant.co 309 [ # # ]:UBC 0 : ereport(ERROR,
310 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
311 : : errmsg("recovery is in progress"),
312 : : errhint("WAL control functions cannot be executed during recovery.")));
313 : :
5296 simon@2ndQuadrant.co 314 :CBC 543 : current_recptr = GetXLogWriteRecPtr();
315 : :
4458 rhaas@postgresql.org 316 : 543 : PG_RETURN_LSN(current_recptr);
317 : : }
318 : :
319 : : /*
320 : : * Report the current WAL insert location (same format as pg_backup_start etc)
321 : : *
322 : : * This function is mostly for debugging purposes.
323 : : */
324 : : Datum
3281 tgl@sss.pgh.pa.us 325 : 1539 : pg_current_wal_insert_lsn(PG_FUNCTION_ARGS)
326 : : {
327 : : XLogRecPtr current_recptr;
328 : :
5296 simon@2ndQuadrant.co 329 [ - + ]: 1539 : if (RecoveryInProgress())
5296 simon@2ndQuadrant.co 330 [ # # ]:UBC 0 : ereport(ERROR,
331 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
332 : : errmsg("recovery is in progress"),
333 : : errhint("WAL control functions cannot be executed during recovery.")));
334 : :
5228 heikki.linnakangas@i 335 :CBC 1539 : current_recptr = GetXLogInsertRecPtr();
336 : :
4458 rhaas@postgresql.org 337 : 1539 : PG_RETURN_LSN(current_recptr);
338 : : }
339 : :
340 : : /*
341 : : * Report the current WAL flush location (same format as pg_backup_start etc)
342 : : *
343 : : * This function is mostly for debugging purposes.
344 : : */
345 : : Datum
3281 tgl@sss.pgh.pa.us 346 : 91 : pg_current_wal_flush_lsn(PG_FUNCTION_ARGS)
347 : : {
348 : : XLogRecPtr current_recptr;
349 : :
3766 simon@2ndQuadrant.co 350 [ - + ]: 91 : if (RecoveryInProgress())
3766 simon@2ndQuadrant.co 351 [ # # ]:UBC 0 : ereport(ERROR,
352 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
353 : : errmsg("recovery is in progress"),
354 : : errhint("WAL control functions cannot be executed during recovery.")));
355 : :
1642 rhaas@postgresql.org 356 :CBC 91 : current_recptr = GetFlushRecPtr(NULL);
357 : :
3766 simon@2ndQuadrant.co 358 : 91 : PG_RETURN_LSN(current_recptr);
359 : : }
360 : :
361 : : /*
362 : : * Report the last WAL receive location (same format as pg_backup_start etc)
363 : : *
364 : : * This is useful for determining how much of WAL is guaranteed to be received
365 : : * and synced to disk by walreceiver.
366 : : */
367 : : Datum
3281 tgl@sss.pgh.pa.us 368 : 8 : pg_last_wal_receive_lsn(PG_FUNCTION_ARGS)
369 : : {
370 : : XLogRecPtr recptr;
371 : :
2218 tmunro@postgresql.or 372 : 8 : recptr = GetWalRcvFlushRecPtr(NULL, NULL);
373 : :
180 alvherre@kurilemu.de 374 [ - + ]:GNC 8 : if (!XLogRecPtrIsValid(recptr))
5296 simon@2ndQuadrant.co 375 :UBC 0 : PG_RETURN_NULL();
376 : :
4458 rhaas@postgresql.org 377 :CBC 8 : PG_RETURN_LSN(recptr);
378 : : }
379 : :
380 : : /*
381 : : * Report the last WAL replay location (same format as pg_backup_start etc)
382 : : *
383 : : * This is useful for determining how much of WAL is visible to read-only
384 : : * connections during recovery.
385 : : */
386 : : Datum
3281 tgl@sss.pgh.pa.us 387 : 45 : pg_last_wal_replay_lsn(PG_FUNCTION_ARGS)
388 : : {
389 : : XLogRecPtr recptr;
390 : :
4884 heikki.linnakangas@i 391 : 45 : recptr = GetXLogReplayRecPtr(NULL);
392 : :
180 alvherre@kurilemu.de 393 [ - + ]:GNC 45 : if (!XLogRecPtrIsValid(recptr))
5296 simon@2ndQuadrant.co 394 :UBC 0 : PG_RETURN_NULL();
395 : :
4458 rhaas@postgresql.org 396 :CBC 45 : PG_RETURN_LSN(recptr);
397 : : }
398 : :
399 : : /*
400 : : * Compute an xlog file name and decimal byte offset given a WAL location,
401 : : * such as is returned by pg_backup_stop() or pg_switch_wal().
402 : : */
403 : : Datum
3372 404 : 12 : pg_walfile_name_offset(PG_FUNCTION_ARGS)
405 : : {
406 : : XLogSegNo xlogsegno;
407 : : uint32 xrecoff;
4458 408 : 12 : XLogRecPtr locationpoint = PG_GETARG_LSN(0);
409 : : char xlogfilename[MAXFNAMELEN];
410 : : Datum values[2];
411 : : bool isnull[2];
412 : : TupleDesc resultTupleDesc;
413 : : HeapTuple resultHeapTuple;
414 : : Datum result;
415 : :
5296 simon@2ndQuadrant.co 416 [ - + ]: 12 : if (RecoveryInProgress())
5296 simon@2ndQuadrant.co 417 [ # # ]:UBC 0 : ereport(ERROR,
418 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
419 : : errmsg("recovery is in progress"),
420 : : errhint("%s cannot be executed during recovery.",
421 : : "pg_walfile_name_offset()")));
422 : :
423 : : /*
424 : : * Construct a tuple descriptor for the result row. This must match this
425 : : * function's pg_proc entry!
426 : : */
2723 andres@anarazel.de 427 :CBC 12 : resultTupleDesc = CreateTemplateTupleDesc(2);
5296 simon@2ndQuadrant.co 428 : 12 : TupleDescInitEntry(resultTupleDesc, (AttrNumber) 1, "file_name",
429 : : TEXTOID, -1, 0);
430 : 12 : TupleDescInitEntry(resultTupleDesc, (AttrNumber) 2, "file_offset",
431 : : INT4OID, -1, 0);
432 : :
50 drowley@postgresql.o 433 :GNC 12 : TupleDescFinalize(resultTupleDesc);
5296 simon@2ndQuadrant.co 434 :CBC 12 : resultTupleDesc = BlessTupleDesc(resultTupleDesc);
435 : :
436 : : /*
437 : : * xlogfilename
438 : : */
893 bruce@momjian.us 439 : 12 : XLByteToSeg(locationpoint, xlogsegno, wal_segment_size);
1642 rhaas@postgresql.org 440 : 12 : XLogFileName(xlogfilename, GetWALInsertionTimeLine(), xlogsegno,
441 : : wal_segment_size);
442 : :
5296 simon@2ndQuadrant.co 443 : 12 : values[0] = CStringGetTextDatum(xlogfilename);
444 : 12 : isnull[0] = false;
445 : :
446 : : /*
447 : : * offset
448 : : */
3150 andres@anarazel.de 449 : 12 : xrecoff = XLogSegmentOffset(locationpoint, wal_segment_size);
450 : :
5296 simon@2ndQuadrant.co 451 : 12 : values[1] = UInt32GetDatum(xrecoff);
452 : 12 : isnull[1] = false;
453 : :
454 : : /*
455 : : * Tuple jam: Having first prepared your Datums, then squash together
456 : : */
457 : 12 : resultHeapTuple = heap_form_tuple(resultTupleDesc, values, isnull);
458 : :
459 : 12 : result = HeapTupleGetDatum(resultHeapTuple);
460 : :
461 : 12 : PG_RETURN_DATUM(result);
462 : : }
463 : :
464 : : /*
465 : : * Compute an xlog file name given a WAL location,
466 : : * such as is returned by pg_backup_stop() or pg_switch_wal().
467 : : */
468 : : Datum
3372 rhaas@postgresql.org 469 : 25 : pg_walfile_name(PG_FUNCTION_ARGS)
470 : : {
471 : : XLogSegNo xlogsegno;
4458 472 : 25 : XLogRecPtr locationpoint = PG_GETARG_LSN(0);
473 : : char xlogfilename[MAXFNAMELEN];
474 : :
5296 simon@2ndQuadrant.co 475 [ - + ]: 25 : if (RecoveryInProgress())
5296 simon@2ndQuadrant.co 476 [ # # ]:UBC 0 : ereport(ERROR,
477 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
478 : : errmsg("recovery is in progress"),
479 : : errhint("%s cannot be executed during recovery.",
480 : : "pg_walfile_name()")));
481 : :
893 bruce@momjian.us 482 :CBC 25 : XLByteToSeg(locationpoint, xlogsegno, wal_segment_size);
1642 rhaas@postgresql.org 483 : 25 : XLogFileName(xlogfilename, GetWALInsertionTimeLine(), xlogsegno,
484 : : wal_segment_size);
485 : :
5296 simon@2ndQuadrant.co 486 : 25 : PG_RETURN_TEXT_P(cstring_to_text(xlogfilename));
487 : : }
488 : :
489 : : /*
490 : : * Extract the sequence number and the timeline ID from given a WAL file
491 : : * name.
492 : : */
493 : : Datum
1229 michael@paquier.xyz 494 : 24 : pg_split_walfile_name(PG_FUNCTION_ARGS)
495 : : {
496 : : #define PG_SPLIT_WALFILE_NAME_COLS 2
1232 497 : 24 : char *fname = text_to_cstring(PG_GETARG_TEXT_PP(0));
498 : : char *fname_upper;
499 : : char *p;
500 : : TimeLineID tli;
501 : : XLogSegNo segno;
1229 502 : 24 : Datum values[PG_SPLIT_WALFILE_NAME_COLS] = {0};
503 : 24 : bool isnull[PG_SPLIT_WALFILE_NAME_COLS] = {0};
504 : : TupleDesc tupdesc;
505 : : HeapTuple tuple;
506 : : char buf[256];
507 : : Datum result;
508 : :
1232 509 : 24 : fname_upper = pstrdup(fname);
510 : :
511 : : /* Capitalize WAL file name. */
512 [ + + ]: 532 : for (p = fname_upper; *p; p++)
155 jdavis@postgresql.or 513 :GNC 508 : *p = pg_ascii_toupper((unsigned char) *p);
514 : :
1232 michael@paquier.xyz 515 [ + + ]:CBC 24 : if (!IsXLogFileName(fname_upper))
516 [ + - ]: 4 : ereport(ERROR,
517 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
518 : : errmsg("invalid WAL file name \"%s\"", fname)));
519 : :
520 : 20 : XLogFromFileName(fname_upper, &tli, &segno, wal_segment_size);
521 : :
522 [ - + ]: 20 : if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
1232 michael@paquier.xyz 523 [ # # ]:UBC 0 : elog(ERROR, "return type must be a row type");
524 : :
525 : : /* Convert to numeric. */
1232 michael@paquier.xyz 526 :CBC 20 : snprintf(buf, sizeof buf, UINT64_FORMAT, segno);
527 : 20 : values[0] = DirectFunctionCall3(numeric_in,
528 : : CStringGetDatum(buf),
529 : : ObjectIdGetDatum(0),
530 : : Int32GetDatum(-1));
531 : :
532 : 20 : values[1] = Int64GetDatum(tli);
533 : :
534 : 20 : tuple = heap_form_tuple(tupdesc, values, isnull);
535 : 20 : result = HeapTupleGetDatum(tuple);
536 : :
537 : 20 : PG_RETURN_DATUM(result);
538 : :
539 : : #undef PG_SPLIT_WALFILE_NAME_COLS
540 : : }
541 : :
542 : : /*
543 : : * pg_wal_replay_pause - Request to pause recovery
544 : : *
545 : : * Permission checking for this function is managed through the normal
546 : : * GRANT system.
547 : : */
548 : : Datum
3372 rhaas@postgresql.org 549 : 8 : pg_wal_replay_pause(PG_FUNCTION_ARGS)
550 : : {
5296 simon@2ndQuadrant.co 551 [ - + ]: 8 : if (!RecoveryInProgress())
5296 simon@2ndQuadrant.co 552 [ # # ]:UBC 0 : ereport(ERROR,
553 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
554 : : errmsg("recovery is not in progress"),
555 : : errhint("Recovery control functions can only be executed during recovery.")));
556 : :
2233 fujii@postgresql.org 557 [ - + ]:CBC 8 : if (PromoteIsTriggered())
2233 fujii@postgresql.org 558 [ # # ]:UBC 0 : ereport(ERROR,
559 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
560 : : errmsg("standby promotion is ongoing"),
561 : : errhint("%s cannot be executed after promotion is triggered.",
562 : : "pg_wal_replay_pause()")));
563 : :
5296 simon@2ndQuadrant.co 564 :CBC 8 : SetRecoveryPause(true);
565 : :
566 : : /* wake up the recovery process so that it can process the pause request */
1881 rhaas@postgresql.org 567 : 8 : WakeupRecovery();
568 : :
5296 simon@2ndQuadrant.co 569 : 8 : PG_RETURN_VOID();
570 : : }
571 : :
572 : : /*
573 : : * pg_wal_replay_resume - resume recovery now
574 : : *
575 : : * Permission checking for this function is managed through the normal
576 : : * GRANT system.
577 : : */
578 : : Datum
3372 rhaas@postgresql.org 579 : 7 : pg_wal_replay_resume(PG_FUNCTION_ARGS)
580 : : {
5296 simon@2ndQuadrant.co 581 [ - + ]: 7 : if (!RecoveryInProgress())
5296 simon@2ndQuadrant.co 582 [ # # ]:UBC 0 : ereport(ERROR,
583 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
584 : : errmsg("recovery is not in progress"),
585 : : errhint("Recovery control functions can only be executed during recovery.")));
586 : :
2233 fujii@postgresql.org 587 [ - + ]:CBC 7 : if (PromoteIsTriggered())
2233 fujii@postgresql.org 588 [ # # ]:UBC 0 : ereport(ERROR,
589 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
590 : : errmsg("standby promotion is ongoing"),
591 : : errhint("%s cannot be executed after promotion is triggered.",
592 : : "pg_wal_replay_resume()")));
593 : :
5296 simon@2ndQuadrant.co 594 :CBC 7 : SetRecoveryPause(false);
595 : :
596 : 7 : PG_RETURN_VOID();
597 : : }
598 : :
599 : : /*
600 : : * pg_is_wal_replay_paused
601 : : */
602 : : Datum
3372 rhaas@postgresql.org 603 :GBC 1 : pg_is_wal_replay_paused(PG_FUNCTION_ARGS)
604 : : {
5296 simon@2ndQuadrant.co 605 [ - + ]: 1 : if (!RecoveryInProgress())
5296 simon@2ndQuadrant.co 606 [ # # ]:UBC 0 : ereport(ERROR,
607 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
608 : : errmsg("recovery is not in progress"),
609 : : errhint("Recovery control functions can only be executed during recovery.")));
610 : :
1881 rhaas@postgresql.org 611 :GBC 1 : PG_RETURN_BOOL(GetRecoveryPauseState() != RECOVERY_NOT_PAUSED);
612 : : }
613 : :
614 : : /*
615 : : * pg_get_wal_replay_pause_state - Returns the recovery pause state.
616 : : *
617 : : * Returned values:
618 : : *
619 : : * 'not paused' - if pause is not requested
620 : : * 'pause requested' - if pause is requested but recovery is not yet paused
621 : : * 'paused' - if recovery is paused
622 : : */
623 : : Datum
1881 rhaas@postgresql.org 624 :CBC 8 : pg_get_wal_replay_pause_state(PG_FUNCTION_ARGS)
625 : : {
626 : : RecoveryPauseState state;
627 : :
628 [ - + ]: 8 : if (!RecoveryInProgress())
1881 rhaas@postgresql.org 629 [ # # ]:UBC 0 : ereport(ERROR,
630 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
631 : : errmsg("recovery is not in progress"),
632 : : errhint("Recovery control functions can only be executed during recovery.")));
633 : :
60 michael@paquier.xyz 634 :GNC 8 : state = GetRecoveryPauseState();
635 : :
636 : : /* get the recovery pause state */
637 : 8 : PG_RETURN_TEXT_P(cstring_to_text(GetRecoveryPauseStateString(state)));
638 : : }
639 : :
640 : : /*
641 : : * Returns timestamp of latest processed commit/abort record.
642 : : *
643 : : * When the server has been started normally without recovery the function
644 : : * returns NULL.
645 : : */
646 : : Datum
5296 simon@2ndQuadrant.co 647 :UBC 0 : pg_last_xact_replay_timestamp(PG_FUNCTION_ARGS)
648 : : {
649 : : TimestampTz xtime;
650 : :
651 : 0 : xtime = GetLatestXTime();
652 [ # # ]: 0 : if (xtime == 0)
653 : 0 : PG_RETURN_NULL();
654 : :
655 : 0 : PG_RETURN_TIMESTAMPTZ(xtime);
656 : : }
657 : :
658 : : /*
659 : : * Returns bool with current recovery mode, a global state.
660 : : */
661 : : Datum
5296 simon@2ndQuadrant.co 662 :CBC 1075 : pg_is_in_recovery(PG_FUNCTION_ARGS)
663 : : {
664 : 1075 : PG_RETURN_BOOL(RecoveryInProgress());
665 : : }
666 : :
667 : : /*
668 : : * Compute the difference in bytes between two WAL locations.
669 : : */
670 : : Datum
3281 tgl@sss.pgh.pa.us 671 : 8 : pg_wal_lsn_diff(PG_FUNCTION_ARGS)
672 : : {
673 : : Datum result;
674 : :
4458 rhaas@postgresql.org 675 : 8 : result = DirectFunctionCall2(pg_lsn_mi,
676 : : PG_GETARG_DATUM(0),
677 : : PG_GETARG_DATUM(1));
678 : :
1346 peter@eisentraut.org 679 : 8 : PG_RETURN_DATUM(result);
680 : : }
681 : :
682 : : /*
683 : : * Promotes a standby server.
684 : : *
685 : : * A result of "true" means that promotion has been completed if "wait" is
686 : : * "true", or initiated if "wait" is false.
687 : : */
688 : : Datum
2749 michael@paquier.xyz 689 : 3 : pg_promote(PG_FUNCTION_ARGS)
690 : : {
691 : 3 : bool wait = PG_GETARG_BOOL(0);
692 : 3 : int wait_seconds = PG_GETARG_INT32(1);
693 : : FILE *promote_file;
694 : : TimestampTz end_time;
695 : :
696 [ - + ]: 3 : if (!RecoveryInProgress())
2749 michael@paquier.xyz 697 [ # # ]:UBC 0 : ereport(ERROR,
698 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
699 : : errmsg("recovery is not in progress"),
700 : : errhint("Recovery control functions can only be executed during recovery.")));
701 : :
2749 michael@paquier.xyz 702 [ - + ]:CBC 3 : if (wait_seconds <= 0)
2749 michael@paquier.xyz 703 [ # # ]:UBC 0 : ereport(ERROR,
704 : : (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
705 : : errmsg("\"wait_seconds\" must not be negative or zero")));
706 : :
707 : : /* create the promote signal file */
2749 michael@paquier.xyz 708 :CBC 3 : promote_file = AllocateFile(PROMOTE_SIGNAL_FILE, "w");
709 [ - + ]: 3 : if (!promote_file)
2749 michael@paquier.xyz 710 [ # # ]:UBC 0 : ereport(ERROR,
711 : : (errcode_for_file_access(),
712 : : errmsg("could not create file \"%s\": %m",
713 : : PROMOTE_SIGNAL_FILE)));
714 : :
2749 michael@paquier.xyz 715 [ - + ]:CBC 3 : if (FreeFile(promote_file))
2749 michael@paquier.xyz 716 [ # # ]:UBC 0 : ereport(ERROR,
717 : : (errcode_for_file_access(),
718 : : errmsg("could not write file \"%s\": %m",
719 : : PROMOTE_SIGNAL_FILE)));
720 : :
721 : : /* signal the postmaster */
2749 michael@paquier.xyz 722 [ - + ]:CBC 3 : if (kill(PostmasterPid, SIGUSR1) != 0)
723 : : {
2749 michael@paquier.xyz 724 :UBC 0 : (void) unlink(PROMOTE_SIGNAL_FILE);
980 725 [ # # ]: 0 : ereport(ERROR,
726 : : (errcode(ERRCODE_SYSTEM_ERROR),
727 : : errmsg("failed to send signal to postmaster: %m")));
728 : : }
729 : :
730 : : /* return immediately if waiting was not requested */
2749 michael@paquier.xyz 731 [ + + ]:CBC 3 : if (!wait)
2749 michael@paquier.xyz 732 :GBC 1 : PG_RETURN_BOOL(true);
733 : :
734 : : /* wait for the amount of time wanted until promotion */
40 michael@paquier.xyz 735 :GNC 2 : end_time = TimestampTzPlusSeconds(GetCurrentTimestamp(), wait_seconds);
736 [ + - ]: 22 : while (GetCurrentTimestamp() < end_time)
737 : : {
738 : : int rc;
739 : :
2749 michael@paquier.xyz 740 :CBC 22 : ResetLatch(MyLatch);
741 : :
742 [ + + ]: 22 : if (!RecoveryInProgress())
743 : 2 : PG_RETURN_BOOL(true);
744 : :
745 [ + + ]: 20 : CHECK_FOR_INTERRUPTS();
746 : :
2433 fujii@postgresql.org 747 : 20 : rc = WaitLatch(MyLatch,
748 : : WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,
749 : : 100L,
750 : : WAIT_EVENT_PROMOTE);
751 : :
752 : : /*
753 : : * Emergency bailout if postmaster has died. This is to avoid the
754 : : * necessity for manual cleanup of all postmaster children.
755 : : */
756 [ - + ]: 20 : if (rc & WL_POSTMASTER_DEATH)
980 michael@paquier.xyz 757 [ # # ]:UBC 0 : ereport(FATAL,
758 : : (errcode(ERRCODE_ADMIN_SHUTDOWN),
759 : : errmsg("terminating connection due to unexpected postmaster exit"),
760 : : errcontext("while waiting on promotion")));
761 : : }
762 : :
2749 763 [ # # ]: 0 : ereport(WARNING,
764 : : (errmsg_plural("server did not promote within %d second",
765 : : "server did not promote within %d seconds",
766 : : wait_seconds,
767 : : wait_seconds)));
768 : 0 : PG_RETURN_BOOL(false);
769 : : }
770 : :
771 : : /*
772 : : * pg_stat_get_recovery - returns information about WAL recovery state
773 : : *
774 : : * Returns NULL when not in recovery or when the caller lacks
775 : : * pg_read_all_stats privileges; one row otherwise.
776 : : */
777 : : Datum
60 michael@paquier.xyz 778 :GNC 5 : pg_stat_get_recovery(PG_FUNCTION_ARGS)
779 : : {
780 : : TupleDesc tupdesc;
781 : : Datum *values;
782 : : bool *nulls;
783 : :
784 : : /* Local copies of shared state */
785 : : bool promote_triggered;
786 : : XLogRecPtr last_replayed_read_lsn;
787 : : XLogRecPtr last_replayed_end_lsn;
788 : : TimeLineID last_replayed_tli;
789 : : XLogRecPtr replay_end_lsn;
790 : : TimeLineID replay_end_tli;
791 : : TimestampTz recovery_last_xact_time;
792 : : TimestampTz current_chunk_start_time;
793 : : RecoveryPauseState pause_state;
794 : :
795 [ + + ]: 5 : if (!RecoveryInProgress())
796 : 4 : PG_RETURN_NULL();
797 : :
798 [ - + ]: 1 : if (!has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS))
60 michael@paquier.xyz 799 :UNC 0 : PG_RETURN_NULL();
800 : :
801 : : /* Take a lock to ensure value consistency */
60 michael@paquier.xyz 802 :GNC 1 : SpinLockAcquire(&XLogRecoveryCtl->info_lck);
803 : 1 : promote_triggered = XLogRecoveryCtl->SharedPromoteIsTriggered;
804 : 1 : last_replayed_read_lsn = XLogRecoveryCtl->lastReplayedReadRecPtr;
805 : 1 : last_replayed_end_lsn = XLogRecoveryCtl->lastReplayedEndRecPtr;
806 : 1 : last_replayed_tli = XLogRecoveryCtl->lastReplayedTLI;
807 : 1 : replay_end_lsn = XLogRecoveryCtl->replayEndRecPtr;
808 : 1 : replay_end_tli = XLogRecoveryCtl->replayEndTLI;
809 : 1 : recovery_last_xact_time = XLogRecoveryCtl->recoveryLastXTime;
810 : 1 : current_chunk_start_time = XLogRecoveryCtl->currentChunkStartTime;
811 : 1 : pause_state = XLogRecoveryCtl->recoveryPauseState;
812 : 1 : SpinLockRelease(&XLogRecoveryCtl->info_lck);
813 : :
814 [ - + ]: 1 : if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
60 michael@paquier.xyz 815 [ # # ]:UNC 0 : elog(ERROR, "return type must be a row type");
816 : :
60 michael@paquier.xyz 817 :GNC 1 : values = palloc0_array(Datum, tupdesc->natts);
818 : 1 : nulls = palloc0_array(bool, tupdesc->natts);
819 : :
820 : 1 : values[0] = BoolGetDatum(promote_triggered);
821 : :
822 [ + - ]: 1 : if (XLogRecPtrIsValid(last_replayed_read_lsn))
823 : 1 : values[1] = LSNGetDatum(last_replayed_read_lsn);
824 : : else
60 michael@paquier.xyz 825 :UNC 0 : nulls[1] = true;
826 : :
60 michael@paquier.xyz 827 [ + - ]:GNC 1 : if (XLogRecPtrIsValid(last_replayed_end_lsn))
828 : 1 : values[2] = LSNGetDatum(last_replayed_end_lsn);
829 : : else
60 michael@paquier.xyz 830 :UNC 0 : nulls[2] = true;
831 : :
60 michael@paquier.xyz 832 [ + - ]:GNC 1 : if (XLogRecPtrIsValid(last_replayed_end_lsn))
833 : 1 : values[3] = Int32GetDatum(last_replayed_tli);
834 : : else
60 michael@paquier.xyz 835 :UNC 0 : nulls[3] = true;
836 : :
60 michael@paquier.xyz 837 [ + - ]:GNC 1 : if (XLogRecPtrIsValid(replay_end_lsn))
838 : 1 : values[4] = LSNGetDatum(replay_end_lsn);
839 : : else
60 michael@paquier.xyz 840 :UNC 0 : nulls[4] = true;
841 : :
60 michael@paquier.xyz 842 [ + - ]:GNC 1 : if (XLogRecPtrIsValid(replay_end_lsn))
843 : 1 : values[5] = Int32GetDatum(replay_end_tli);
844 : : else
60 michael@paquier.xyz 845 :UNC 0 : nulls[5] = true;
846 : :
60 michael@paquier.xyz 847 [ + - ]:GNC 1 : if (recovery_last_xact_time != 0)
848 : 1 : values[6] = TimestampTzGetDatum(recovery_last_xact_time);
849 : : else
60 michael@paquier.xyz 850 :UNC 0 : nulls[6] = true;
851 : :
60 michael@paquier.xyz 852 [ + - ]:GNC 1 : if (current_chunk_start_time != 0)
853 : 1 : values[7] = TimestampTzGetDatum(current_chunk_start_time);
854 : : else
60 michael@paquier.xyz 855 :UNC 0 : nulls[7] = true;
856 : :
60 michael@paquier.xyz 857 :GNC 1 : values[8] = CStringGetTextDatum(GetRecoveryPauseStateString(pause_state));
858 : :
859 : 1 : PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
860 : : }
|