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 *
56 6 : GetRecoveryPauseStateString(RecoveryPauseState pause_state)
57 : {
58 6 : const char *statestr = NULL;
59 :
60 6 : switch (pause_state)
61 : {
62 3 : case RECOVERY_NOT_PAUSED:
63 3 : statestr = "not paused";
64 3 : break;
65 0 : case RECOVERY_PAUSE_REQUESTED:
66 0 : statestr = "pause requested";
67 0 : break;
68 3 : case RECOVERY_PAUSED:
69 3 : statestr = "paused";
70 3 : break;
71 : }
72 :
73 : Assert(statestr != NULL);
74 6 : 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
87 5 : pg_backup_start(PG_FUNCTION_ARGS)
88 : {
89 5 : text *backupid = PG_GETARG_TEXT_PP(0);
90 5 : bool fast = PG_GETARG_BOOL(1);
91 : char *backupidstr;
92 5 : SessionBackupState status = get_backup_status();
93 : MemoryContext oldcontext;
94 :
95 5 : backupidstr = text_to_cstring(backupid);
96 :
97 5 : if (status == SESSION_BACKUP_RUNNING)
98 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 : */
109 5 : if (backupcontext == NULL)
110 : {
111 5 : backupcontext = AllocSetContextCreate(TopMemoryContext,
112 : "on-line backup context",
113 : ALLOCSET_START_SMALL_SIZES);
114 : }
115 : else
116 : {
117 0 : backup_state = NULL;
118 0 : tablespace_map = NULL;
119 0 : MemoryContextReset(backupcontext);
120 : }
121 :
122 5 : oldcontext = MemoryContextSwitchTo(backupcontext);
123 5 : backup_state = palloc0_object(BackupState);
124 5 : tablespace_map = makeStringInfo();
125 5 : MemoryContextSwitchTo(oldcontext);
126 :
127 5 : register_persistent_abort_backup_handler();
128 5 : do_pg_backup_start(backupidstr, fast, NULL, backup_state, tablespace_map);
129 :
130 4 : 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
154 3 : pg_backup_stop(PG_FUNCTION_ARGS)
155 : {
156 : #define PG_BACKUP_STOP_V2_COLS 3
157 : TupleDesc tupdesc;
158 3 : Datum values[PG_BACKUP_STOP_V2_COLS] = {0};
159 3 : bool nulls[PG_BACKUP_STOP_V2_COLS] = {0};
160 3 : bool waitforarchive = PG_GETARG_BOOL(0);
161 : char *backup_label;
162 3 : SessionBackupState status = get_backup_status();
163 :
164 : /* Initialize attributes information in the tuple descriptor */
165 3 : if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
166 0 : elog(ERROR, "return type must be a row type");
167 :
168 3 : if (status != SESSION_BACKUP_RUNNING)
169 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 :
174 : Assert(backup_state != NULL);
175 : Assert(tablespace_map != NULL);
176 :
177 : /* Stop the backup */
178 3 : do_pg_backup_stop(backup_state, waitforarchive);
179 :
180 : /* Build the contents of backup_label */
181 3 : backup_label = build_backup_content(backup_state, false);
182 :
183 3 : values[0] = LSNGetDatum(backup_state->stoppoint);
184 3 : values[1] = CStringGetTextDatum(backup_label);
185 3 : values[2] = CStringGetTextDatum(tablespace_map->data);
186 :
187 : /* Deallocate backup-related variables */
188 3 : pfree(backup_label);
189 :
190 : /* Clean up the session-level state and its memory context */
191 3 : backup_state = NULL;
192 3 : tablespace_map = NULL;
193 3 : MemoryContextDelete(backupcontext);
194 3 : backupcontext = NULL;
195 :
196 : /* Returns the record as Datum */
197 3 : 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
207 468 : pg_switch_wal(PG_FUNCTION_ARGS)
208 : {
209 : XLogRecPtr switchpoint;
210 :
211 468 : if (RecoveryInProgress())
212 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 :
217 468 : switchpoint = RequestXLogSwitch(false);
218 :
219 : /*
220 : * As a convenience, return the WAL location of the switch record
221 : */
222 468 : 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
232 29 : pg_log_standby_snapshot(PG_FUNCTION_ARGS)
233 : {
234 : XLogRecPtr recptr;
235 :
236 29 : if (RecoveryInProgress())
237 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 :
243 29 : if (!XLogStandbyInfoActive())
244 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 :
248 29 : recptr = LogStandbySnapshot();
249 :
250 : /*
251 : * As a convenience, return the WAL location of the last inserted record
252 : */
253 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
263 3 : pg_create_restore_point(PG_FUNCTION_ARGS)
264 : {
265 3 : text *restore_name = PG_GETARG_TEXT_PP(0);
266 : char *restore_name_str;
267 : XLogRecPtr restorepoint;
268 :
269 3 : if (RecoveryInProgress())
270 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 :
275 3 : if (!XLogIsNeeded())
276 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 :
281 3 : restore_name_str = text_to_cstring(restore_name);
282 :
283 3 : if (strlen(restore_name_str) >= MAXFNAMELEN)
284 0 : ereport(ERROR,
285 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
286 : errmsg("value too long for restore point (maximum %d characters)", MAXFNAMELEN - 1)));
287 :
288 3 : restorepoint = XLogRestorePoint(restore_name_str);
289 :
290 : /*
291 : * As a convenience, return the WAL location of the restore point record
292 : */
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
304 536 : pg_current_wal_lsn(PG_FUNCTION_ARGS)
305 : {
306 : XLogRecPtr current_recptr;
307 :
308 536 : if (RecoveryInProgress())
309 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 :
314 536 : current_recptr = GetXLogWriteRecPtr();
315 :
316 536 : 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
325 1638 : pg_current_wal_insert_lsn(PG_FUNCTION_ARGS)
326 : {
327 : XLogRecPtr current_recptr;
328 :
329 1638 : if (RecoveryInProgress())
330 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 :
335 1638 : current_recptr = GetXLogInsertRecPtr();
336 :
337 1638 : 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
346 91 : pg_current_wal_flush_lsn(PG_FUNCTION_ARGS)
347 : {
348 : XLogRecPtr current_recptr;
349 :
350 91 : if (RecoveryInProgress())
351 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 :
356 91 : current_recptr = GetFlushRecPtr(NULL);
357 :
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
368 5 : pg_last_wal_receive_lsn(PG_FUNCTION_ARGS)
369 : {
370 : XLogRecPtr recptr;
371 :
372 5 : recptr = GetWalRcvFlushRecPtr(NULL, NULL);
373 :
374 5 : if (!XLogRecPtrIsValid(recptr))
375 0 : PG_RETURN_NULL();
376 :
377 5 : 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
387 45 : pg_last_wal_replay_lsn(PG_FUNCTION_ARGS)
388 : {
389 : XLogRecPtr recptr;
390 :
391 45 : recptr = GetXLogReplayRecPtr(NULL);
392 :
393 45 : if (!XLogRecPtrIsValid(recptr))
394 0 : PG_RETURN_NULL();
395 :
396 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
404 12 : pg_walfile_name_offset(PG_FUNCTION_ARGS)
405 : {
406 : XLogSegNo xlogsegno;
407 : uint32 xrecoff;
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 :
416 12 : if (RecoveryInProgress())
417 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 : */
427 12 : resultTupleDesc = CreateTemplateTupleDesc(2);
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 :
433 12 : TupleDescFinalize(resultTupleDesc);
434 12 : resultTupleDesc = BlessTupleDesc(resultTupleDesc);
435 :
436 : /*
437 : * xlogfilename
438 : */
439 12 : XLByteToSeg(locationpoint, xlogsegno, wal_segment_size);
440 12 : XLogFileName(xlogfilename, GetWALInsertionTimeLine(), xlogsegno,
441 : wal_segment_size);
442 :
443 12 : values[0] = CStringGetTextDatum(xlogfilename);
444 12 : isnull[0] = false;
445 :
446 : /*
447 : * offset
448 : */
449 12 : xrecoff = XLogSegmentOffset(locationpoint, wal_segment_size);
450 :
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
469 24 : pg_walfile_name(PG_FUNCTION_ARGS)
470 : {
471 : XLogSegNo xlogsegno;
472 24 : XLogRecPtr locationpoint = PG_GETARG_LSN(0);
473 : char xlogfilename[MAXFNAMELEN];
474 :
475 24 : if (RecoveryInProgress())
476 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 :
482 24 : XLByteToSeg(locationpoint, xlogsegno, wal_segment_size);
483 24 : XLogFileName(xlogfilename, GetWALInsertionTimeLine(), xlogsegno,
484 : wal_segment_size);
485 :
486 24 : 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
494 24 : pg_split_walfile_name(PG_FUNCTION_ARGS)
495 : {
496 : #define PG_SPLIT_WALFILE_NAME_COLS 2
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;
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 :
509 24 : fname_upper = pstrdup(fname);
510 :
511 : /* Capitalize WAL file name. */
512 532 : for (p = fname_upper; *p; p++)
513 508 : *p = pg_ascii_toupper((unsigned char) *p);
514 :
515 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)
523 0 : elog(ERROR, "return type must be a row type");
524 :
525 : /* Convert to numeric. */
526 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
549 4 : pg_wal_replay_pause(PG_FUNCTION_ARGS)
550 : {
551 4 : if (!RecoveryInProgress())
552 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 :
557 4 : if (PromoteIsTriggered())
558 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 :
564 4 : SetRecoveryPause(true);
565 :
566 : /* wake up the recovery process so that it can process the pause request */
567 4 : WakeupRecovery();
568 :
569 4 : 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
579 3 : pg_wal_replay_resume(PG_FUNCTION_ARGS)
580 : {
581 3 : if (!RecoveryInProgress())
582 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 :
587 3 : if (PromoteIsTriggered())
588 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 :
594 3 : SetRecoveryPause(false);
595 :
596 3 : PG_RETURN_VOID();
597 : }
598 :
599 : /*
600 : * pg_is_wal_replay_paused
601 : */
602 : Datum
603 1 : pg_is_wal_replay_paused(PG_FUNCTION_ARGS)
604 : {
605 1 : if (!RecoveryInProgress())
606 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 :
611 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
624 5 : pg_get_wal_replay_pause_state(PG_FUNCTION_ARGS)
625 : {
626 : RecoveryPauseState state;
627 :
628 5 : if (!RecoveryInProgress())
629 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 :
634 5 : state = GetRecoveryPauseState();
635 :
636 : /* get the recovery pause state */
637 5 : 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
647 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
662 1065 : pg_is_in_recovery(PG_FUNCTION_ARGS)
663 : {
664 1065 : PG_RETURN_BOOL(RecoveryInProgress());
665 : }
666 :
667 : /*
668 : * Compute the difference in bytes between two WAL locations.
669 : */
670 : Datum
671 3 : pg_wal_lsn_diff(PG_FUNCTION_ARGS)
672 : {
673 : Datum result;
674 :
675 3 : result = DirectFunctionCall2(pg_lsn_mi,
676 : PG_GETARG_DATUM(0),
677 : PG_GETARG_DATUM(1));
678 :
679 3 : 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
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 : int i;
695 :
696 3 : if (!RecoveryInProgress())
697 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 :
702 3 : if (wait_seconds <= 0)
703 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 */
708 3 : promote_file = AllocateFile(PROMOTE_SIGNAL_FILE, "w");
709 3 : if (!promote_file)
710 0 : ereport(ERROR,
711 : (errcode_for_file_access(),
712 : errmsg("could not create file \"%s\": %m",
713 : PROMOTE_SIGNAL_FILE)));
714 :
715 3 : if (FreeFile(promote_file))
716 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 */
722 3 : if (kill(PostmasterPid, SIGUSR1) != 0)
723 : {
724 0 : (void) unlink(PROMOTE_SIGNAL_FILE);
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 */
731 3 : if (!wait)
732 1 : PG_RETURN_BOOL(true);
733 :
734 : /* wait for the amount of time wanted until promotion */
735 : #define WAITS_PER_SECOND 10
736 22 : for (i = 0; i < WAITS_PER_SECOND * wait_seconds; i++)
737 : {
738 : int rc;
739 :
740 22 : ResetLatch(MyLatch);
741 :
742 22 : if (!RecoveryInProgress())
743 2 : PG_RETURN_BOOL(true);
744 :
745 20 : CHECK_FOR_INTERRUPTS();
746 :
747 20 : rc = WaitLatch(MyLatch,
748 : WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,
749 : 1000L / WAITS_PER_SECOND,
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)
757 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 :
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
778 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))
799 0 : PG_RETURN_NULL();
800 :
801 : /* Take a lock to ensure value consistency */
802 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)
815 0 : elog(ERROR, "return type must be a row type");
816 :
817 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
825 0 : nulls[1] = true;
826 :
827 1 : if (XLogRecPtrIsValid(last_replayed_end_lsn))
828 1 : values[2] = LSNGetDatum(last_replayed_end_lsn);
829 : else
830 0 : nulls[2] = true;
831 :
832 1 : if (XLogRecPtrIsValid(last_replayed_end_lsn))
833 1 : values[3] = Int32GetDatum(last_replayed_tli);
834 : else
835 0 : nulls[3] = true;
836 :
837 1 : if (XLogRecPtrIsValid(replay_end_lsn))
838 1 : values[4] = LSNGetDatum(replay_end_lsn);
839 : else
840 0 : nulls[4] = true;
841 :
842 1 : if (XLogRecPtrIsValid(replay_end_lsn))
843 1 : values[5] = Int32GetDatum(replay_end_tli);
844 : else
845 0 : nulls[5] = true;
846 :
847 1 : if (recovery_last_xact_time != 0)
848 1 : values[6] = TimestampTzGetDatum(recovery_last_xact_time);
849 : else
850 0 : nulls[6] = true;
851 :
852 1 : if (current_chunk_start_time != 0)
853 1 : values[7] = TimestampTzGetDatum(current_chunk_start_time);
854 : else
855 0 : nulls[7] = true;
856 :
857 1 : values[8] = CStringGetTextDatum(GetRecoveryPauseStateString(pause_state));
858 :
859 1 : PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
860 : }
|