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