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