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 *
9 michael@paquier.xyz 56 :GNC 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;
9 michael@paquier.xyz 65 :UNC 0 : case RECOVERY_PAUSE_REQUESTED:
66 : 0 : statestr = "pause requested";
67 : 0 : break;
9 michael@paquier.xyz 68 :GNC 3 : case RECOVERY_PAUSED:
69 : 3 : statestr = "paused";
70 : 3 : break;
71 : : }
72 : :
73 [ - + ]: 6 : 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
1439 sfrost@snowman.net 87 :CBC 4 : pg_backup_start(PG_FUNCTION_ARGS)
88 : : {
3290 noah@leadboat.com 89 : 4 : text *backupid = PG_GETARG_TEXT_PP(0);
5245 simon@2ndQuadrant.co 90 : 4 : bool fast = PG_GETARG_BOOL(1);
91 : : char *backupidstr;
3278 teodor@sigaev.ru 92 : 4 : SessionBackupState status = get_backup_status();
93 : : MemoryContext oldcontext;
94 : :
5245 simon@2ndQuadrant.co 95 : 4 : backupidstr = text_to_cstring(backupid);
96 : :
1439 sfrost@snowman.net 97 [ - + ]: 4 : if (status == SESSION_BACKUP_RUNNING)
3631 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 : : */
1240 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 : : {
1240 michael@paquier.xyz 117 :UBC 0 : backup_state = NULL;
1266 118 : 0 : tablespace_map = NULL;
1240 119 : 0 : MemoryContextReset(backupcontext);
120 : : }
121 : :
1240 michael@paquier.xyz 122 :CBC 4 : oldcontext = MemoryContextSwitchTo(backupcontext);
95 michael@paquier.xyz 123 :GNC 4 : backup_state = palloc0_object(BackupState);
1266 michael@paquier.xyz 124 :CBC 4 : tablespace_map = makeStringInfo();
1439 sfrost@snowman.net 125 : 4 : MemoryContextSwitchTo(oldcontext);
126 : :
127 : 4 : register_persistent_abort_backup_handler();
1266 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
1439 sfrost@snowman.net 154 : 2 : pg_backup_stop(PG_FUNCTION_ARGS)
155 : : {
156 : : #define PG_BACKUP_STOP_V2_COLS 3
157 : : TupleDesc tupdesc;
1279 michael@paquier.xyz 158 : 2 : Datum values[PG_BACKUP_STOP_V2_COLS] = {0};
159 : 2 : bool nulls[PG_BACKUP_STOP_V2_COLS] = {0};
1439 sfrost@snowman.net 160 : 2 : bool waitforarchive = PG_GETARG_BOOL(0);
161 : : char *backup_label;
3278 teodor@sigaev.ru 162 : 2 : SessionBackupState status = get_backup_status();
163 : :
164 : : /* Initialize attributes information in the tuple descriptor */
3631 magnus@hagander.net 165 [ - + ]: 2 : if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
3631 magnus@hagander.net 166 [ # # ]:UBC 0 : elog(ERROR, "return type must be a row type");
167 : :
1439 sfrost@snowman.net 168 [ - + ]:CBC 2 : if (status != SESSION_BACKUP_RUNNING)
1439 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 : :
1266 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);
1265 184 : 2 : values[1] = CStringGetTextDatum(backup_label);
1266 185 : 2 : values[2] = CStringGetTextDatum(tablespace_map->data);
186 : :
187 : : /* Deallocate backup-related variables */
1240 188 : 2 : pfree(backup_label);
189 : :
190 : : /* Clean up the session-level state and its memory context */
1266 191 : 2 : backup_state = NULL;
192 : 2 : tablespace_map = NULL;
1240 193 : 2 : MemoryContextDelete(backupcontext);
194 : 2 : backupcontext = NULL;
195 : :
196 : : /* Returns the record as Datum */
1473 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
3321 rhaas@postgresql.org 207 : 468 : pg_switch_wal(PG_FUNCTION_ARGS)
208 : : {
209 : : XLogRecPtr switchpoint;
210 : :
5245 simon@2ndQuadrant.co 211 [ - + ]: 468 : if (RecoveryInProgress())
5245 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 : :
3370 andres@anarazel.de 217 :CBC 468 : switchpoint = RequestXLogSwitch(false);
218 : :
219 : : /*
220 : : * As a convenience, return the WAL location of the switch record
221 : : */
4407 rhaas@postgresql.org 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
1072 andres@anarazel.de 232 : 29 : pg_log_standby_snapshot(PG_FUNCTION_ARGS)
233 : : {
234 : : XLogRecPtr recptr;
235 : :
236 [ - + ]: 29 : if (RecoveryInProgress())
1072 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 : :
1072 andres@anarazel.de 243 [ - + ]:CBC 29 : if (!XLogStandbyInfoActive())
1072 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 : :
1072 andres@anarazel.de 248 :CBC 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
5245 simon@2ndQuadrant.co 263 : 3 : pg_create_restore_point(PG_FUNCTION_ARGS)
264 : : {
3290 noah@leadboat.com 265 : 3 : text *restore_name = PG_GETARG_TEXT_PP(0);
266 : : char *restore_name_str;
267 : : XLogRecPtr restorepoint;
268 : :
5245 simon@2ndQuadrant.co 269 [ - + ]: 3 : if (RecoveryInProgress())
5245 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 : :
5245 simon@2ndQuadrant.co 275 [ - + ]:CBC 3 : if (!XLogIsNeeded())
5245 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 : :
5245 simon@2ndQuadrant.co 281 :CBC 3 : restore_name_str = text_to_cstring(restore_name);
282 : :
283 [ - + ]: 3 : if (strlen(restore_name_str) >= MAXFNAMELEN)
5245 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 : :
5245 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 : : */
4407 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
3230 tgl@sss.pgh.pa.us 304 : 531 : pg_current_wal_lsn(PG_FUNCTION_ARGS)
305 : : {
306 : : XLogRecPtr current_recptr;
307 : :
5245 simon@2ndQuadrant.co 308 [ - + ]: 531 : if (RecoveryInProgress())
5245 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 : :
5245 simon@2ndQuadrant.co 314 :CBC 531 : current_recptr = GetXLogWriteRecPtr();
315 : :
4407 rhaas@postgresql.org 316 : 531 : 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
3230 tgl@sss.pgh.pa.us 325 : 1520 : pg_current_wal_insert_lsn(PG_FUNCTION_ARGS)
326 : : {
327 : : XLogRecPtr current_recptr;
328 : :
5245 simon@2ndQuadrant.co 329 [ - + ]: 1520 : if (RecoveryInProgress())
5245 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 : :
5177 heikki.linnakangas@i 335 :CBC 1520 : current_recptr = GetXLogInsertRecPtr();
336 : :
4407 rhaas@postgresql.org 337 : 1520 : 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
3230 tgl@sss.pgh.pa.us 346 : 90 : pg_current_wal_flush_lsn(PG_FUNCTION_ARGS)
347 : : {
348 : : XLogRecPtr current_recptr;
349 : :
3715 simon@2ndQuadrant.co 350 [ - + ]: 90 : if (RecoveryInProgress())
3715 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 : :
1591 rhaas@postgresql.org 356 :CBC 90 : current_recptr = GetFlushRecPtr(NULL);
357 : :
3715 simon@2ndQuadrant.co 358 : 90 : 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
3230 tgl@sss.pgh.pa.us 368 : 5 : pg_last_wal_receive_lsn(PG_FUNCTION_ARGS)
369 : : {
370 : : XLogRecPtr recptr;
371 : :
2167 tmunro@postgresql.or 372 : 5 : recptr = GetWalRcvFlushRecPtr(NULL, NULL);
373 : :
129 alvherre@kurilemu.de 374 [ - + ]:GNC 5 : if (!XLogRecPtrIsValid(recptr))
5245 simon@2ndQuadrant.co 375 :UBC 0 : PG_RETURN_NULL();
376 : :
4407 rhaas@postgresql.org 377 :CBC 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
3230 tgl@sss.pgh.pa.us 387 : 42 : pg_last_wal_replay_lsn(PG_FUNCTION_ARGS)
388 : : {
389 : : XLogRecPtr recptr;
390 : :
4833 heikki.linnakangas@i 391 : 42 : recptr = GetXLogReplayRecPtr(NULL);
392 : :
129 alvherre@kurilemu.de 393 [ - + ]:GNC 42 : if (!XLogRecPtrIsValid(recptr))
5245 simon@2ndQuadrant.co 394 :UBC 0 : PG_RETURN_NULL();
395 : :
4407 rhaas@postgresql.org 396 :CBC 42 : 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
3321 404 : 9 : pg_walfile_name_offset(PG_FUNCTION_ARGS)
405 : : {
406 : : XLogSegNo xlogsegno;
407 : : uint32 xrecoff;
4407 408 : 9 : 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 : :
5245 simon@2ndQuadrant.co 416 [ - + ]: 9 : if (RecoveryInProgress())
5245 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 : : */
2672 andres@anarazel.de 427 :CBC 9 : resultTupleDesc = CreateTemplateTupleDesc(2);
5245 simon@2ndQuadrant.co 428 : 9 : TupleDescInitEntry(resultTupleDesc, (AttrNumber) 1, "file_name",
429 : : TEXTOID, -1, 0);
430 : 9 : TupleDescInitEntry(resultTupleDesc, (AttrNumber) 2, "file_offset",
431 : : INT4OID, -1, 0);
432 : :
433 : 9 : resultTupleDesc = BlessTupleDesc(resultTupleDesc);
434 : :
435 : : /*
436 : : * xlogfilename
437 : : */
842 bruce@momjian.us 438 : 9 : XLByteToSeg(locationpoint, xlogsegno, wal_segment_size);
1591 rhaas@postgresql.org 439 : 9 : XLogFileName(xlogfilename, GetWALInsertionTimeLine(), xlogsegno,
440 : : wal_segment_size);
441 : :
5245 simon@2ndQuadrant.co 442 : 9 : values[0] = CStringGetTextDatum(xlogfilename);
443 : 9 : isnull[0] = false;
444 : :
445 : : /*
446 : : * offset
447 : : */
3099 andres@anarazel.de 448 : 9 : xrecoff = XLogSegmentOffset(locationpoint, wal_segment_size);
449 : :
5245 simon@2ndQuadrant.co 450 : 9 : values[1] = UInt32GetDatum(xrecoff);
451 : 9 : isnull[1] = false;
452 : :
453 : : /*
454 : : * Tuple jam: Having first prepared your Datums, then squash together
455 : : */
456 : 9 : resultHeapTuple = heap_form_tuple(resultTupleDesc, values, isnull);
457 : :
458 : 9 : result = HeapTupleGetDatum(resultHeapTuple);
459 : :
460 : 9 : PG_RETURN_DATUM(result);
461 : : }
462 : :
463 : : /*
464 : : * Compute an xlog file name given a WAL location,
465 : : * such as is returned by pg_backup_stop() or pg_switch_wal().
466 : : */
467 : : Datum
3321 rhaas@postgresql.org 468 : 24 : pg_walfile_name(PG_FUNCTION_ARGS)
469 : : {
470 : : XLogSegNo xlogsegno;
4407 471 : 24 : XLogRecPtr locationpoint = PG_GETARG_LSN(0);
472 : : char xlogfilename[MAXFNAMELEN];
473 : :
5245 simon@2ndQuadrant.co 474 [ - + ]: 24 : if (RecoveryInProgress())
5245 simon@2ndQuadrant.co 475 [ # # ]:UBC 0 : ereport(ERROR,
476 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
477 : : errmsg("recovery is in progress"),
478 : : errhint("%s cannot be executed during recovery.",
479 : : "pg_walfile_name()")));
480 : :
842 bruce@momjian.us 481 :CBC 24 : XLByteToSeg(locationpoint, xlogsegno, wal_segment_size);
1591 rhaas@postgresql.org 482 : 24 : XLogFileName(xlogfilename, GetWALInsertionTimeLine(), xlogsegno,
483 : : wal_segment_size);
484 : :
5245 simon@2ndQuadrant.co 485 : 24 : PG_RETURN_TEXT_P(cstring_to_text(xlogfilename));
486 : : }
487 : :
488 : : /*
489 : : * Extract the sequence number and the timeline ID from given a WAL file
490 : : * name.
491 : : */
492 : : Datum
1178 michael@paquier.xyz 493 : 18 : pg_split_walfile_name(PG_FUNCTION_ARGS)
494 : : {
495 : : #define PG_SPLIT_WALFILE_NAME_COLS 2
1181 496 : 18 : char *fname = text_to_cstring(PG_GETARG_TEXT_PP(0));
497 : : char *fname_upper;
498 : : char *p;
499 : : TimeLineID tli;
500 : : XLogSegNo segno;
1178 501 : 18 : Datum values[PG_SPLIT_WALFILE_NAME_COLS] = {0};
502 : 18 : bool isnull[PG_SPLIT_WALFILE_NAME_COLS] = {0};
503 : : TupleDesc tupdesc;
504 : : HeapTuple tuple;
505 : : char buf[256];
506 : : Datum result;
507 : :
1181 508 : 18 : fname_upper = pstrdup(fname);
509 : :
510 : : /* Capitalize WAL file name. */
511 [ + + ]: 399 : for (p = fname_upper; *p; p++)
104 jdavis@postgresql.or 512 :GNC 381 : *p = pg_ascii_toupper((unsigned char) *p);
513 : :
1181 michael@paquier.xyz 514 [ + + ]:CBC 18 : if (!IsXLogFileName(fname_upper))
515 [ + - ]: 3 : ereport(ERROR,
516 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
517 : : errmsg("invalid WAL file name \"%s\"", fname)));
518 : :
519 : 15 : XLogFromFileName(fname_upper, &tli, &segno, wal_segment_size);
520 : :
521 [ - + ]: 15 : if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
1181 michael@paquier.xyz 522 [ # # ]:UBC 0 : elog(ERROR, "return type must be a row type");
523 : :
524 : : /* Convert to numeric. */
1181 michael@paquier.xyz 525 :CBC 15 : snprintf(buf, sizeof buf, UINT64_FORMAT, segno);
526 : 15 : values[0] = DirectFunctionCall3(numeric_in,
527 : : CStringGetDatum(buf),
528 : : ObjectIdGetDatum(0),
529 : : Int32GetDatum(-1));
530 : :
531 : 15 : values[1] = Int64GetDatum(tli);
532 : :
533 : 15 : tuple = heap_form_tuple(tupdesc, values, isnull);
534 : 15 : result = HeapTupleGetDatum(tuple);
535 : :
536 : 15 : PG_RETURN_DATUM(result);
537 : :
538 : : #undef PG_SPLIT_WALFILE_NAME_COLS
539 : : }
540 : :
541 : : /*
542 : : * pg_wal_replay_pause - Request to pause recovery
543 : : *
544 : : * Permission checking for this function is managed through the normal
545 : : * GRANT system.
546 : : */
547 : : Datum
3321 rhaas@postgresql.org 548 : 4 : pg_wal_replay_pause(PG_FUNCTION_ARGS)
549 : : {
5245 simon@2ndQuadrant.co 550 [ - + ]: 4 : if (!RecoveryInProgress())
5245 simon@2ndQuadrant.co 551 [ # # ]:UBC 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 : :
2182 fujii@postgresql.org 556 [ - + ]:CBC 4 : if (PromoteIsTriggered())
2182 fujii@postgresql.org 557 [ # # ]:UBC 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_pause()")));
562 : :
5245 simon@2ndQuadrant.co 563 :CBC 4 : SetRecoveryPause(true);
564 : :
565 : : /* wake up the recovery process so that it can process the pause request */
1830 rhaas@postgresql.org 566 : 4 : WakeupRecovery();
567 : :
5245 simon@2ndQuadrant.co 568 : 4 : PG_RETURN_VOID();
569 : : }
570 : :
571 : : /*
572 : : * pg_wal_replay_resume - resume recovery now
573 : : *
574 : : * Permission checking for this function is managed through the normal
575 : : * GRANT system.
576 : : */
577 : : Datum
3321 rhaas@postgresql.org 578 : 3 : pg_wal_replay_resume(PG_FUNCTION_ARGS)
579 : : {
5245 simon@2ndQuadrant.co 580 [ - + ]: 3 : if (!RecoveryInProgress())
5245 simon@2ndQuadrant.co 581 [ # # ]:UBC 0 : ereport(ERROR,
582 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
583 : : errmsg("recovery is not in progress"),
584 : : errhint("Recovery control functions can only be executed during recovery.")));
585 : :
2182 fujii@postgresql.org 586 [ - + ]:CBC 3 : if (PromoteIsTriggered())
2182 fujii@postgresql.org 587 [ # # ]:UBC 0 : ereport(ERROR,
588 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
589 : : errmsg("standby promotion is ongoing"),
590 : : errhint("%s cannot be executed after promotion is triggered.",
591 : : "pg_wal_replay_resume()")));
592 : :
5245 simon@2ndQuadrant.co 593 :CBC 3 : SetRecoveryPause(false);
594 : :
595 : 3 : PG_RETURN_VOID();
596 : : }
597 : :
598 : : /*
599 : : * pg_is_wal_replay_paused
600 : : */
601 : : Datum
3321 rhaas@postgresql.org 602 :GBC 1 : pg_is_wal_replay_paused(PG_FUNCTION_ARGS)
603 : : {
5245 simon@2ndQuadrant.co 604 [ - + ]: 1 : if (!RecoveryInProgress())
5245 simon@2ndQuadrant.co 605 [ # # ]:UBC 0 : ereport(ERROR,
606 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
607 : : errmsg("recovery is not in progress"),
608 : : errhint("Recovery control functions can only be executed during recovery.")));
609 : :
1830 rhaas@postgresql.org 610 :GBC 1 : PG_RETURN_BOOL(GetRecoveryPauseState() != RECOVERY_NOT_PAUSED);
611 : : }
612 : :
613 : : /*
614 : : * pg_get_wal_replay_pause_state - Returns the recovery pause state.
615 : : *
616 : : * Returned values:
617 : : *
618 : : * 'not paused' - if pause is not requested
619 : : * 'pause requested' - if pause is requested but recovery is not yet paused
620 : : * 'paused' - if recovery is paused
621 : : */
622 : : Datum
1830 rhaas@postgresql.org 623 :CBC 5 : pg_get_wal_replay_pause_state(PG_FUNCTION_ARGS)
624 : : {
625 : : RecoveryPauseState state;
626 : :
627 [ - + ]: 5 : if (!RecoveryInProgress())
1830 rhaas@postgresql.org 628 [ # # ]:UBC 0 : ereport(ERROR,
629 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
630 : : errmsg("recovery is not in progress"),
631 : : errhint("Recovery control functions can only be executed during recovery.")));
632 : :
9 michael@paquier.xyz 633 :GNC 5 : state = GetRecoveryPauseState();
634 : :
635 : : /* get the recovery pause state */
636 : 5 : PG_RETURN_TEXT_P(cstring_to_text(GetRecoveryPauseStateString(state)));
637 : : }
638 : :
639 : : /*
640 : : * Returns timestamp of latest processed commit/abort record.
641 : : *
642 : : * When the server has been started normally without recovery the function
643 : : * returns NULL.
644 : : */
645 : : Datum
5245 simon@2ndQuadrant.co 646 :UBC 0 : pg_last_xact_replay_timestamp(PG_FUNCTION_ARGS)
647 : : {
648 : : TimestampTz xtime;
649 : :
650 : 0 : xtime = GetLatestXTime();
651 [ # # ]: 0 : if (xtime == 0)
652 : 0 : PG_RETURN_NULL();
653 : :
654 : 0 : PG_RETURN_TIMESTAMPTZ(xtime);
655 : : }
656 : :
657 : : /*
658 : : * Returns bool with current recovery mode, a global state.
659 : : */
660 : : Datum
5245 simon@2ndQuadrant.co 661 :CBC 1054 : pg_is_in_recovery(PG_FUNCTION_ARGS)
662 : : {
663 : 1054 : PG_RETURN_BOOL(RecoveryInProgress());
664 : : }
665 : :
666 : : /*
667 : : * Compute the difference in bytes between two WAL locations.
668 : : */
669 : : Datum
3230 tgl@sss.pgh.pa.us 670 : 3 : pg_wal_lsn_diff(PG_FUNCTION_ARGS)
671 : : {
672 : : Datum result;
673 : :
4407 rhaas@postgresql.org 674 : 3 : result = DirectFunctionCall2(pg_lsn_mi,
675 : : PG_GETARG_DATUM(0),
676 : : PG_GETARG_DATUM(1));
677 : :
1295 peter@eisentraut.org 678 : 3 : PG_RETURN_DATUM(result);
679 : : }
680 : :
681 : : /*
682 : : * Promotes a standby server.
683 : : *
684 : : * A result of "true" means that promotion has been completed if "wait" is
685 : : * "true", or initiated if "wait" is false.
686 : : */
687 : : Datum
2698 michael@paquier.xyz 688 : 3 : pg_promote(PG_FUNCTION_ARGS)
689 : : {
690 : 3 : bool wait = PG_GETARG_BOOL(0);
691 : 3 : int wait_seconds = PG_GETARG_INT32(1);
692 : : FILE *promote_file;
693 : : int i;
694 : :
695 [ - + ]: 3 : if (!RecoveryInProgress())
2698 michael@paquier.xyz 696 [ # # ]:UBC 0 : ereport(ERROR,
697 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
698 : : errmsg("recovery is not in progress"),
699 : : errhint("Recovery control functions can only be executed during recovery.")));
700 : :
2698 michael@paquier.xyz 701 [ - + ]:CBC 3 : if (wait_seconds <= 0)
2698 michael@paquier.xyz 702 [ # # ]:UBC 0 : ereport(ERROR,
703 : : (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
704 : : errmsg("\"wait_seconds\" must not be negative or zero")));
705 : :
706 : : /* create the promote signal file */
2698 michael@paquier.xyz 707 :CBC 3 : promote_file = AllocateFile(PROMOTE_SIGNAL_FILE, "w");
708 [ - + ]: 3 : if (!promote_file)
2698 michael@paquier.xyz 709 [ # # ]:UBC 0 : ereport(ERROR,
710 : : (errcode_for_file_access(),
711 : : errmsg("could not create file \"%s\": %m",
712 : : PROMOTE_SIGNAL_FILE)));
713 : :
2698 michael@paquier.xyz 714 [ - + ]:CBC 3 : if (FreeFile(promote_file))
2698 michael@paquier.xyz 715 [ # # ]:UBC 0 : ereport(ERROR,
716 : : (errcode_for_file_access(),
717 : : errmsg("could not write file \"%s\": %m",
718 : : PROMOTE_SIGNAL_FILE)));
719 : :
720 : : /* signal the postmaster */
2698 michael@paquier.xyz 721 [ - + ]:CBC 3 : if (kill(PostmasterPid, SIGUSR1) != 0)
722 : : {
2698 michael@paquier.xyz 723 :UBC 0 : (void) unlink(PROMOTE_SIGNAL_FILE);
929 724 [ # # ]: 0 : ereport(ERROR,
725 : : (errcode(ERRCODE_SYSTEM_ERROR),
726 : : errmsg("failed to send signal to postmaster: %m")));
727 : : }
728 : :
729 : : /* return immediately if waiting was not requested */
2698 michael@paquier.xyz 730 [ + + ]:CBC 3 : if (!wait)
2698 michael@paquier.xyz 731 :GBC 1 : PG_RETURN_BOOL(true);
732 : :
733 : : /* wait for the amount of time wanted until promotion */
734 : : #define WAITS_PER_SECOND 10
2698 michael@paquier.xyz 735 [ + - ]:CBC 21 : for (i = 0; i < WAITS_PER_SECOND * wait_seconds; i++)
736 : : {
737 : : int rc;
738 : :
739 : 21 : ResetLatch(MyLatch);
740 : :
741 [ + + ]: 21 : if (!RecoveryInProgress())
742 : 2 : PG_RETURN_BOOL(true);
743 : :
744 [ + + ]: 19 : CHECK_FOR_INTERRUPTS();
745 : :
2382 fujii@postgresql.org 746 : 19 : rc = WaitLatch(MyLatch,
747 : : WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,
748 : : 1000L / WAITS_PER_SECOND,
749 : : WAIT_EVENT_PROMOTE);
750 : :
751 : : /*
752 : : * Emergency bailout if postmaster has died. This is to avoid the
753 : : * necessity for manual cleanup of all postmaster children.
754 : : */
755 [ - + ]: 19 : if (rc & WL_POSTMASTER_DEATH)
929 michael@paquier.xyz 756 [ # # ]:UBC 0 : ereport(FATAL,
757 : : (errcode(ERRCODE_ADMIN_SHUTDOWN),
758 : : errmsg("terminating connection due to unexpected postmaster exit"),
759 : : errcontext("while waiting on promotion")));
760 : : }
761 : :
2698 762 [ # # ]: 0 : ereport(WARNING,
763 : : (errmsg_plural("server did not promote within %d second",
764 : : "server did not promote within %d seconds",
765 : : wait_seconds,
766 : : wait_seconds)));
767 : 0 : PG_RETURN_BOOL(false);
768 : : }
769 : :
770 : : /*
771 : : * pg_stat_get_recovery - returns information about WAL recovery state
772 : : *
773 : : * Returns NULL when not in recovery or when the caller lacks
774 : : * pg_read_all_stats privileges; one row otherwise.
775 : : */
776 : : Datum
9 michael@paquier.xyz 777 :GNC 4 : pg_stat_get_recovery(PG_FUNCTION_ARGS)
778 : : {
779 : : TupleDesc tupdesc;
780 : : Datum *values;
781 : : bool *nulls;
782 : :
783 : : /* Local copies of shared state */
784 : : bool promote_triggered;
785 : : XLogRecPtr last_replayed_read_lsn;
786 : : XLogRecPtr last_replayed_end_lsn;
787 : : TimeLineID last_replayed_tli;
788 : : XLogRecPtr replay_end_lsn;
789 : : TimeLineID replay_end_tli;
790 : : TimestampTz recovery_last_xact_time;
791 : : TimestampTz current_chunk_start_time;
792 : : RecoveryPauseState pause_state;
793 : :
794 [ + + ]: 4 : if (!RecoveryInProgress())
795 : 3 : PG_RETURN_NULL();
796 : :
797 [ - + ]: 1 : if (!has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS))
9 michael@paquier.xyz 798 :UNC 0 : PG_RETURN_NULL();
799 : :
800 : : /* Take a lock to ensure value consistency */
9 michael@paquier.xyz 801 :GNC 1 : SpinLockAcquire(&XLogRecoveryCtl->info_lck);
802 : 1 : promote_triggered = XLogRecoveryCtl->SharedPromoteIsTriggered;
803 : 1 : last_replayed_read_lsn = XLogRecoveryCtl->lastReplayedReadRecPtr;
804 : 1 : last_replayed_end_lsn = XLogRecoveryCtl->lastReplayedEndRecPtr;
805 : 1 : last_replayed_tli = XLogRecoveryCtl->lastReplayedTLI;
806 : 1 : replay_end_lsn = XLogRecoveryCtl->replayEndRecPtr;
807 : 1 : replay_end_tli = XLogRecoveryCtl->replayEndTLI;
808 : 1 : recovery_last_xact_time = XLogRecoveryCtl->recoveryLastXTime;
809 : 1 : current_chunk_start_time = XLogRecoveryCtl->currentChunkStartTime;
810 : 1 : pause_state = XLogRecoveryCtl->recoveryPauseState;
811 : 1 : SpinLockRelease(&XLogRecoveryCtl->info_lck);
812 : :
813 [ - + ]: 1 : if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
9 michael@paquier.xyz 814 [ # # ]:UNC 0 : elog(ERROR, "return type must be a row type");
815 : :
9 michael@paquier.xyz 816 :GNC 1 : values = palloc0_array(Datum, tupdesc->natts);
817 : 1 : nulls = palloc0_array(bool, tupdesc->natts);
818 : :
819 : 1 : values[0] = BoolGetDatum(promote_triggered);
820 : :
821 [ + - ]: 1 : if (XLogRecPtrIsValid(last_replayed_read_lsn))
822 : 1 : values[1] = LSNGetDatum(last_replayed_read_lsn);
823 : : else
9 michael@paquier.xyz 824 :UNC 0 : nulls[1] = true;
825 : :
9 michael@paquier.xyz 826 [ + - ]:GNC 1 : if (XLogRecPtrIsValid(last_replayed_end_lsn))
827 : 1 : values[2] = LSNGetDatum(last_replayed_end_lsn);
828 : : else
9 michael@paquier.xyz 829 :UNC 0 : nulls[2] = true;
830 : :
9 michael@paquier.xyz 831 [ + - ]:GNC 1 : if (XLogRecPtrIsValid(last_replayed_end_lsn))
832 : 1 : values[3] = Int32GetDatum(last_replayed_tli);
833 : : else
9 michael@paquier.xyz 834 :UNC 0 : nulls[3] = true;
835 : :
9 michael@paquier.xyz 836 [ + - ]:GNC 1 : if (XLogRecPtrIsValid(replay_end_lsn))
837 : 1 : values[4] = LSNGetDatum(replay_end_lsn);
838 : : else
9 michael@paquier.xyz 839 :UNC 0 : nulls[4] = true;
840 : :
9 michael@paquier.xyz 841 [ + - ]:GNC 1 : if (XLogRecPtrIsValid(replay_end_lsn))
842 : 1 : values[5] = Int32GetDatum(replay_end_tli);
843 : : else
9 michael@paquier.xyz 844 :UNC 0 : nulls[5] = true;
845 : :
9 michael@paquier.xyz 846 [ + - ]:GNC 1 : if (recovery_last_xact_time != 0)
847 : 1 : values[6] = TimestampTzGetDatum(recovery_last_xact_time);
848 : : else
9 michael@paquier.xyz 849 :UNC 0 : nulls[6] = true;
850 : :
9 michael@paquier.xyz 851 [ + - ]:GNC 1 : if (current_chunk_start_time != 0)
852 : 1 : values[7] = TimestampTzGetDatum(current_chunk_start_time);
853 : : else
9 michael@paquier.xyz 854 :UNC 0 : nulls[7] = true;
855 : :
9 michael@paquier.xyz 856 :GNC 1 : values[8] = CStringGetTextDatum(GetRecoveryPauseStateString(pause_state));
857 : :
858 : 1 : PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
859 : : }
|