/src/postgres/src/backend/access/transam/xlogfuncs.c
Line | Count | Source |
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 | | GetRecoveryPauseStateString(RecoveryPauseState pause_state) |
57 | 0 | { |
58 | 0 | const char *statestr = NULL; |
59 | |
|
60 | 0 | switch (pause_state) |
61 | 0 | { |
62 | 0 | case RECOVERY_NOT_PAUSED: |
63 | 0 | statestr = "not paused"; |
64 | 0 | break; |
65 | 0 | case RECOVERY_PAUSE_REQUESTED: |
66 | 0 | statestr = "pause requested"; |
67 | 0 | break; |
68 | 0 | case RECOVERY_PAUSED: |
69 | 0 | statestr = "paused"; |
70 | 0 | break; |
71 | 0 | } |
72 | | |
73 | 0 | Assert(statestr != NULL); |
74 | 0 | return statestr; |
75 | 0 | } |
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 | | pg_backup_start(PG_FUNCTION_ARGS) |
88 | 0 | { |
89 | 0 | text *backupid = PG_GETARG_TEXT_PP(0); |
90 | 0 | bool fast = PG_GETARG_BOOL(1); |
91 | 0 | char *backupidstr; |
92 | 0 | SessionBackupState status = get_backup_status(); |
93 | 0 | MemoryContext oldcontext; |
94 | |
|
95 | 0 | backupidstr = text_to_cstring(backupid); |
96 | |
|
97 | 0 | if (status == SESSION_BACKUP_RUNNING) |
98 | 0 | ereport(ERROR, |
99 | 0 | (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), |
100 | 0 | 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 | 0 | if (backupcontext == NULL) |
110 | 0 | { |
111 | 0 | backupcontext = AllocSetContextCreate(TopMemoryContext, |
112 | 0 | "on-line backup context", |
113 | 0 | ALLOCSET_START_SMALL_SIZES); |
114 | 0 | } |
115 | 0 | else |
116 | 0 | { |
117 | 0 | backup_state = NULL; |
118 | 0 | tablespace_map = NULL; |
119 | 0 | MemoryContextReset(backupcontext); |
120 | 0 | } |
121 | |
|
122 | 0 | oldcontext = MemoryContextSwitchTo(backupcontext); |
123 | 0 | backup_state = palloc0_object(BackupState); |
124 | 0 | tablespace_map = makeStringInfo(); |
125 | 0 | MemoryContextSwitchTo(oldcontext); |
126 | |
|
127 | 0 | register_persistent_abort_backup_handler(); |
128 | 0 | do_pg_backup_start(backupidstr, fast, NULL, backup_state, tablespace_map); |
129 | |
|
130 | 0 | PG_RETURN_LSN(backup_state->startpoint); |
131 | 0 | } |
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 | | pg_backup_stop(PG_FUNCTION_ARGS) |
155 | 0 | { |
156 | 0 | #define PG_BACKUP_STOP_V2_COLS 3 |
157 | 0 | TupleDesc tupdesc; |
158 | 0 | Datum values[PG_BACKUP_STOP_V2_COLS] = {0}; |
159 | 0 | bool nulls[PG_BACKUP_STOP_V2_COLS] = {0}; |
160 | 0 | bool waitforarchive = PG_GETARG_BOOL(0); |
161 | 0 | char *backup_label; |
162 | 0 | SessionBackupState status = get_backup_status(); |
163 | | |
164 | | /* Initialize attributes information in the tuple descriptor */ |
165 | 0 | if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) |
166 | 0 | elog(ERROR, "return type must be a row type"); |
167 | | |
168 | 0 | if (status != SESSION_BACKUP_RUNNING) |
169 | 0 | ereport(ERROR, |
170 | 0 | (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), |
171 | 0 | errmsg("backup is not in progress"), |
172 | 0 | errhint("Did you call pg_backup_start()?"))); |
173 | | |
174 | 0 | Assert(backup_state != NULL); |
175 | 0 | Assert(tablespace_map != NULL); |
176 | | |
177 | | /* Stop the backup */ |
178 | 0 | do_pg_backup_stop(backup_state, waitforarchive); |
179 | | |
180 | | /* Build the contents of backup_label */ |
181 | 0 | backup_label = build_backup_content(backup_state, false); |
182 | |
|
183 | 0 | values[0] = LSNGetDatum(backup_state->stoppoint); |
184 | 0 | values[1] = CStringGetTextDatum(backup_label); |
185 | 0 | values[2] = CStringGetTextDatum(tablespace_map->data); |
186 | | |
187 | | /* Deallocate backup-related variables */ |
188 | 0 | pfree(backup_label); |
189 | | |
190 | | /* Clean up the session-level state and its memory context */ |
191 | 0 | backup_state = NULL; |
192 | 0 | tablespace_map = NULL; |
193 | 0 | MemoryContextDelete(backupcontext); |
194 | 0 | backupcontext = NULL; |
195 | | |
196 | | /* Returns the record as Datum */ |
197 | 0 | PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls))); |
198 | 0 | } |
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 | | pg_switch_wal(PG_FUNCTION_ARGS) |
208 | 0 | { |
209 | 0 | XLogRecPtr switchpoint; |
210 | |
|
211 | 0 | if (RecoveryInProgress()) |
212 | 0 | ereport(ERROR, |
213 | 0 | (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), |
214 | 0 | errmsg("recovery is in progress"), |
215 | 0 | errhint("WAL control functions cannot be executed during recovery."))); |
216 | | |
217 | 0 | switchpoint = RequestXLogSwitch(false); |
218 | | |
219 | | /* |
220 | | * As a convenience, return the WAL location of the switch record |
221 | | */ |
222 | 0 | PG_RETURN_LSN(switchpoint); |
223 | 0 | } |
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 | | pg_log_standby_snapshot(PG_FUNCTION_ARGS) |
233 | 0 | { |
234 | 0 | XLogRecPtr recptr; |
235 | |
|
236 | 0 | if (RecoveryInProgress()) |
237 | 0 | ereport(ERROR, |
238 | 0 | (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), |
239 | 0 | errmsg("recovery is in progress"), |
240 | 0 | errhint("%s cannot be executed during recovery.", |
241 | 0 | "pg_log_standby_snapshot()"))); |
242 | | |
243 | 0 | if (!XLogStandbyInfoActive()) |
244 | 0 | ereport(ERROR, |
245 | 0 | (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), |
246 | 0 | errmsg("pg_log_standby_snapshot() can only be used if \"wal_level\" >= \"replica\""))); |
247 | | |
248 | 0 | recptr = LogStandbySnapshot(); |
249 | | |
250 | | /* |
251 | | * As a convenience, return the WAL location of the last inserted record |
252 | | */ |
253 | 0 | PG_RETURN_LSN(recptr); |
254 | 0 | } |
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 | | pg_create_restore_point(PG_FUNCTION_ARGS) |
264 | 0 | { |
265 | 0 | text *restore_name = PG_GETARG_TEXT_PP(0); |
266 | 0 | char *restore_name_str; |
267 | 0 | XLogRecPtr restorepoint; |
268 | |
|
269 | 0 | if (RecoveryInProgress()) |
270 | 0 | ereport(ERROR, |
271 | 0 | (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), |
272 | 0 | errmsg("recovery is in progress"), |
273 | 0 | errhint("WAL control functions cannot be executed during recovery."))); |
274 | | |
275 | 0 | if (!XLogIsNeeded()) |
276 | 0 | ereport(ERROR, |
277 | 0 | (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), |
278 | 0 | errmsg("WAL level not sufficient for creating a restore point"), |
279 | 0 | errhint("\"wal_level\" must be set to \"replica\" or \"logical\" at server start."))); |
280 | | |
281 | 0 | restore_name_str = text_to_cstring(restore_name); |
282 | |
|
283 | 0 | if (strlen(restore_name_str) >= MAXFNAMELEN) |
284 | 0 | ereport(ERROR, |
285 | 0 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
286 | 0 | errmsg("value too long for restore point (maximum %d characters)", MAXFNAMELEN - 1))); |
287 | | |
288 | 0 | restorepoint = XLogRestorePoint(restore_name_str); |
289 | | |
290 | | /* |
291 | | * As a convenience, return the WAL location of the restore point record |
292 | | */ |
293 | 0 | PG_RETURN_LSN(restorepoint); |
294 | 0 | } |
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 | | pg_current_wal_lsn(PG_FUNCTION_ARGS) |
305 | 0 | { |
306 | 0 | XLogRecPtr current_recptr; |
307 | |
|
308 | 0 | if (RecoveryInProgress()) |
309 | 0 | ereport(ERROR, |
310 | 0 | (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), |
311 | 0 | errmsg("recovery is in progress"), |
312 | 0 | errhint("WAL control functions cannot be executed during recovery."))); |
313 | | |
314 | 0 | current_recptr = GetXLogWriteRecPtr(); |
315 | |
|
316 | 0 | PG_RETURN_LSN(current_recptr); |
317 | 0 | } |
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 | | pg_current_wal_insert_lsn(PG_FUNCTION_ARGS) |
326 | 0 | { |
327 | 0 | XLogRecPtr current_recptr; |
328 | |
|
329 | 0 | if (RecoveryInProgress()) |
330 | 0 | ereport(ERROR, |
331 | 0 | (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), |
332 | 0 | errmsg("recovery is in progress"), |
333 | 0 | errhint("WAL control functions cannot be executed during recovery."))); |
334 | | |
335 | 0 | current_recptr = GetXLogInsertRecPtr(); |
336 | |
|
337 | 0 | PG_RETURN_LSN(current_recptr); |
338 | 0 | } |
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 | | pg_current_wal_flush_lsn(PG_FUNCTION_ARGS) |
347 | 0 | { |
348 | 0 | XLogRecPtr current_recptr; |
349 | |
|
350 | 0 | if (RecoveryInProgress()) |
351 | 0 | ereport(ERROR, |
352 | 0 | (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), |
353 | 0 | errmsg("recovery is in progress"), |
354 | 0 | errhint("WAL control functions cannot be executed during recovery."))); |
355 | | |
356 | 0 | current_recptr = GetFlushRecPtr(NULL); |
357 | |
|
358 | 0 | PG_RETURN_LSN(current_recptr); |
359 | 0 | } |
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 | | pg_last_wal_receive_lsn(PG_FUNCTION_ARGS) |
369 | 0 | { |
370 | 0 | XLogRecPtr recptr; |
371 | |
|
372 | 0 | recptr = GetWalRcvFlushRecPtr(NULL, NULL); |
373 | |
|
374 | 0 | if (!XLogRecPtrIsValid(recptr)) |
375 | 0 | PG_RETURN_NULL(); |
376 | | |
377 | 0 | PG_RETURN_LSN(recptr); |
378 | 0 | } |
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 | | pg_last_wal_replay_lsn(PG_FUNCTION_ARGS) |
388 | 0 | { |
389 | 0 | XLogRecPtr recptr; |
390 | |
|
391 | 0 | recptr = GetXLogReplayRecPtr(NULL); |
392 | |
|
393 | 0 | if (!XLogRecPtrIsValid(recptr)) |
394 | 0 | PG_RETURN_NULL(); |
395 | | |
396 | 0 | PG_RETURN_LSN(recptr); |
397 | 0 | } |
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 | | pg_walfile_name_offset(PG_FUNCTION_ARGS) |
405 | 0 | { |
406 | 0 | XLogSegNo xlogsegno; |
407 | 0 | uint32 xrecoff; |
408 | 0 | XLogRecPtr locationpoint = PG_GETARG_LSN(0); |
409 | 0 | char xlogfilename[MAXFNAMELEN]; |
410 | 0 | Datum values[2]; |
411 | 0 | bool isnull[2]; |
412 | 0 | TupleDesc resultTupleDesc; |
413 | 0 | HeapTuple resultHeapTuple; |
414 | 0 | Datum result; |
415 | |
|
416 | 0 | if (RecoveryInProgress()) |
417 | 0 | ereport(ERROR, |
418 | 0 | (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), |
419 | 0 | errmsg("recovery is in progress"), |
420 | 0 | errhint("%s cannot be executed during recovery.", |
421 | 0 | "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 | 0 | resultTupleDesc = CreateTemplateTupleDesc(2); |
428 | 0 | TupleDescInitEntry(resultTupleDesc, (AttrNumber) 1, "file_name", |
429 | 0 | TEXTOID, -1, 0); |
430 | 0 | TupleDescInitEntry(resultTupleDesc, (AttrNumber) 2, "file_offset", |
431 | 0 | INT4OID, -1, 0); |
432 | |
|
433 | 0 | TupleDescFinalize(resultTupleDesc); |
434 | 0 | resultTupleDesc = BlessTupleDesc(resultTupleDesc); |
435 | | |
436 | | /* |
437 | | * xlogfilename |
438 | | */ |
439 | 0 | XLByteToSeg(locationpoint, xlogsegno, wal_segment_size); |
440 | 0 | XLogFileName(xlogfilename, GetWALInsertionTimeLine(), xlogsegno, |
441 | 0 | wal_segment_size); |
442 | |
|
443 | 0 | values[0] = CStringGetTextDatum(xlogfilename); |
444 | 0 | isnull[0] = false; |
445 | | |
446 | | /* |
447 | | * offset |
448 | | */ |
449 | 0 | xrecoff = XLogSegmentOffset(locationpoint, wal_segment_size); |
450 | |
|
451 | 0 | values[1] = Int32GetDatum(xrecoff); |
452 | 0 | isnull[1] = false; |
453 | | |
454 | | /* |
455 | | * Tuple jam: Having first prepared your Datums, then squash together |
456 | | */ |
457 | 0 | resultHeapTuple = heap_form_tuple(resultTupleDesc, values, isnull); |
458 | |
|
459 | 0 | result = HeapTupleGetDatum(resultHeapTuple); |
460 | |
|
461 | 0 | PG_RETURN_DATUM(result); |
462 | 0 | } |
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 | | pg_walfile_name(PG_FUNCTION_ARGS) |
470 | 0 | { |
471 | 0 | XLogSegNo xlogsegno; |
472 | 0 | XLogRecPtr locationpoint = PG_GETARG_LSN(0); |
473 | 0 | char xlogfilename[MAXFNAMELEN]; |
474 | |
|
475 | 0 | if (RecoveryInProgress()) |
476 | 0 | ereport(ERROR, |
477 | 0 | (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), |
478 | 0 | errmsg("recovery is in progress"), |
479 | 0 | errhint("%s cannot be executed during recovery.", |
480 | 0 | "pg_walfile_name()"))); |
481 | | |
482 | 0 | XLByteToSeg(locationpoint, xlogsegno, wal_segment_size); |
483 | 0 | XLogFileName(xlogfilename, GetWALInsertionTimeLine(), xlogsegno, |
484 | 0 | wal_segment_size); |
485 | |
|
486 | 0 | PG_RETURN_TEXT_P(cstring_to_text(xlogfilename)); |
487 | 0 | } |
488 | | |
489 | | /* |
490 | | * Extract the sequence number and the timeline ID from given a WAL file |
491 | | * name. |
492 | | */ |
493 | | Datum |
494 | | pg_split_walfile_name(PG_FUNCTION_ARGS) |
495 | 0 | { |
496 | 0 | #define PG_SPLIT_WALFILE_NAME_COLS 2 |
497 | 0 | char *fname = text_to_cstring(PG_GETARG_TEXT_PP(0)); |
498 | 0 | char *fname_upper; |
499 | 0 | char *p; |
500 | 0 | TimeLineID tli; |
501 | 0 | XLogSegNo segno; |
502 | 0 | Datum values[PG_SPLIT_WALFILE_NAME_COLS] = {0}; |
503 | 0 | bool isnull[PG_SPLIT_WALFILE_NAME_COLS] = {0}; |
504 | 0 | TupleDesc tupdesc; |
505 | 0 | HeapTuple tuple; |
506 | 0 | char buf[256]; |
507 | 0 | Datum result; |
508 | |
|
509 | 0 | fname_upper = pstrdup(fname); |
510 | | |
511 | | /* Capitalize WAL file name. */ |
512 | 0 | for (p = fname_upper; *p; p++) |
513 | 0 | *p = pg_ascii_toupper((unsigned char) *p); |
514 | |
|
515 | 0 | if (!IsXLogFileName(fname_upper)) |
516 | 0 | ereport(ERROR, |
517 | 0 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
518 | 0 | errmsg("invalid WAL file name \"%s\"", fname))); |
519 | | |
520 | 0 | XLogFromFileName(fname_upper, &tli, &segno, wal_segment_size); |
521 | |
|
522 | 0 | 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 | 0 | snprintf(buf, sizeof buf, UINT64_FORMAT, segno); |
527 | 0 | values[0] = DirectFunctionCall3(numeric_in, |
528 | 0 | CStringGetDatum(buf), |
529 | 0 | ObjectIdGetDatum(0), |
530 | 0 | Int32GetDatum(-1)); |
531 | |
|
532 | 0 | values[1] = Int64GetDatum(tli); |
533 | |
|
534 | 0 | tuple = heap_form_tuple(tupdesc, values, isnull); |
535 | 0 | result = HeapTupleGetDatum(tuple); |
536 | |
|
537 | 0 | PG_RETURN_DATUM(result); |
538 | |
|
539 | 0 | #undef PG_SPLIT_WALFILE_NAME_COLS |
540 | 0 | } |
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 | | pg_wal_replay_pause(PG_FUNCTION_ARGS) |
550 | 0 | { |
551 | 0 | if (!RecoveryInProgress()) |
552 | 0 | ereport(ERROR, |
553 | 0 | (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), |
554 | 0 | errmsg("recovery is not in progress"), |
555 | 0 | errhint("Recovery control functions can only be executed during recovery."))); |
556 | | |
557 | 0 | if (PromoteIsTriggered()) |
558 | 0 | ereport(ERROR, |
559 | 0 | (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), |
560 | 0 | errmsg("standby promotion is ongoing"), |
561 | 0 | errhint("%s cannot be executed after promotion is triggered.", |
562 | 0 | "pg_wal_replay_pause()"))); |
563 | | |
564 | 0 | SetRecoveryPause(true); |
565 | | |
566 | | /* wake up the recovery process so that it can process the pause request */ |
567 | 0 | WakeupRecovery(); |
568 | |
|
569 | 0 | PG_RETURN_VOID(); |
570 | 0 | } |
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 | | pg_wal_replay_resume(PG_FUNCTION_ARGS) |
580 | 0 | { |
581 | 0 | if (!RecoveryInProgress()) |
582 | 0 | ereport(ERROR, |
583 | 0 | (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), |
584 | 0 | errmsg("recovery is not in progress"), |
585 | 0 | errhint("Recovery control functions can only be executed during recovery."))); |
586 | | |
587 | 0 | if (PromoteIsTriggered()) |
588 | 0 | ereport(ERROR, |
589 | 0 | (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), |
590 | 0 | errmsg("standby promotion is ongoing"), |
591 | 0 | errhint("%s cannot be executed after promotion is triggered.", |
592 | 0 | "pg_wal_replay_resume()"))); |
593 | | |
594 | 0 | SetRecoveryPause(false); |
595 | |
|
596 | 0 | PG_RETURN_VOID(); |
597 | 0 | } |
598 | | |
599 | | /* |
600 | | * pg_is_wal_replay_paused |
601 | | */ |
602 | | Datum |
603 | | pg_is_wal_replay_paused(PG_FUNCTION_ARGS) |
604 | 0 | { |
605 | 0 | if (!RecoveryInProgress()) |
606 | 0 | ereport(ERROR, |
607 | 0 | (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), |
608 | 0 | errmsg("recovery is not in progress"), |
609 | 0 | errhint("Recovery control functions can only be executed during recovery."))); |
610 | | |
611 | 0 | PG_RETURN_BOOL(GetRecoveryPauseState() != RECOVERY_NOT_PAUSED); |
612 | 0 | } |
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 | | pg_get_wal_replay_pause_state(PG_FUNCTION_ARGS) |
625 | 0 | { |
626 | 0 | RecoveryPauseState state; |
627 | |
|
628 | 0 | if (!RecoveryInProgress()) |
629 | 0 | ereport(ERROR, |
630 | 0 | (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), |
631 | 0 | errmsg("recovery is not in progress"), |
632 | 0 | errhint("Recovery control functions can only be executed during recovery."))); |
633 | | |
634 | 0 | state = GetRecoveryPauseState(); |
635 | | |
636 | | /* get the recovery pause state */ |
637 | 0 | PG_RETURN_TEXT_P(cstring_to_text(GetRecoveryPauseStateString(state))); |
638 | 0 | } |
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 | | pg_last_xact_replay_timestamp(PG_FUNCTION_ARGS) |
648 | 0 | { |
649 | 0 | 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 | 0 | } |
657 | | |
658 | | /* |
659 | | * Returns bool with current recovery mode, a global state. |
660 | | */ |
661 | | Datum |
662 | | pg_is_in_recovery(PG_FUNCTION_ARGS) |
663 | 0 | { |
664 | 0 | PG_RETURN_BOOL(RecoveryInProgress()); |
665 | 0 | } |
666 | | |
667 | | /* |
668 | | * Compute the difference in bytes between two WAL locations. |
669 | | */ |
670 | | Datum |
671 | | pg_wal_lsn_diff(PG_FUNCTION_ARGS) |
672 | 0 | { |
673 | 0 | Datum result; |
674 | |
|
675 | 0 | result = DirectFunctionCall2(pg_lsn_mi, |
676 | 0 | PG_GETARG_DATUM(0), |
677 | 0 | PG_GETARG_DATUM(1)); |
678 | |
|
679 | 0 | PG_RETURN_DATUM(result); |
680 | 0 | } |
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 | | pg_promote(PG_FUNCTION_ARGS) |
690 | | { |
691 | | bool wait = PG_GETARG_BOOL(0); |
692 | | int wait_seconds = PG_GETARG_INT32(1); |
693 | | FILE *promote_file; |
694 | | TimestampTz end_time; |
695 | | |
696 | | if (!RecoveryInProgress()) |
697 | | 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 | | if (wait_seconds <= 0) |
703 | | 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 | | promote_file = AllocateFile(PROMOTE_SIGNAL_FILE, "w"); |
709 | | if (!promote_file) |
710 | | ereport(ERROR, |
711 | | (errcode_for_file_access(), |
712 | | errmsg("could not create file \"%s\": %m", |
713 | | PROMOTE_SIGNAL_FILE))); |
714 | | |
715 | | if (FreeFile(promote_file)) |
716 | | 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 | | if (kill(PostmasterPid, SIGUSR1) != 0) |
723 | | { |
724 | | (void) unlink(PROMOTE_SIGNAL_FILE); |
725 | | 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 | | if (!wait) |
732 | | PG_RETURN_BOOL(true); |
733 | | |
734 | | /* wait for the amount of time wanted until promotion */ |
735 | | end_time = TimestampTzPlusSeconds(GetCurrentTimestamp(), wait_seconds); |
736 | | while (GetCurrentTimestamp() < end_time) |
737 | | { |
738 | | int rc; |
739 | | |
740 | | ResetLatch(MyLatch); |
741 | | |
742 | | if (!RecoveryInProgress()) |
743 | | PG_RETURN_BOOL(true); |
744 | | |
745 | | CHECK_FOR_INTERRUPTS(); |
746 | | |
747 | | rc = WaitLatch(MyLatch, |
748 | | WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH, |
749 | | 100L, |
750 | | WAIT_EVENT_PROMOTE); |
751 | | |
752 | | /* |
753 | | * Emergency bailout if postmaster has died. This is to avoid the |
754 | | * necessity for manual cleanup of all postmaster children. |
755 | | */ |
756 | | if (rc & WL_POSTMASTER_DEATH) |
757 | | 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 | | 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 | | 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 | | pg_stat_get_recovery(PG_FUNCTION_ARGS) |
779 | 0 | { |
780 | 0 | TupleDesc tupdesc; |
781 | 0 | Datum *values; |
782 | 0 | bool *nulls; |
783 | | |
784 | | /* Local copies of shared state */ |
785 | 0 | bool promote_triggered; |
786 | 0 | XLogRecPtr last_replayed_read_lsn; |
787 | 0 | XLogRecPtr last_replayed_end_lsn; |
788 | 0 | TimeLineID last_replayed_tli; |
789 | 0 | XLogRecPtr replay_end_lsn; |
790 | 0 | TimeLineID replay_end_tli; |
791 | 0 | TimestampTz recovery_last_xact_time; |
792 | 0 | TimestampTz current_chunk_start_time; |
793 | 0 | RecoveryPauseState pause_state; |
794 | |
|
795 | 0 | if (!RecoveryInProgress()) |
796 | 0 | PG_RETURN_NULL(); |
797 | | |
798 | 0 | 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 | 0 | SpinLockAcquire(&XLogRecoveryCtl->info_lck); |
803 | 0 | promote_triggered = XLogRecoveryCtl->SharedPromoteIsTriggered; |
804 | 0 | last_replayed_read_lsn = XLogRecoveryCtl->lastReplayedReadRecPtr; |
805 | 0 | last_replayed_end_lsn = XLogRecoveryCtl->lastReplayedEndRecPtr; |
806 | 0 | last_replayed_tli = XLogRecoveryCtl->lastReplayedTLI; |
807 | 0 | replay_end_lsn = XLogRecoveryCtl->replayEndRecPtr; |
808 | 0 | replay_end_tli = XLogRecoveryCtl->replayEndTLI; |
809 | 0 | recovery_last_xact_time = XLogRecoveryCtl->recoveryLastXTime; |
810 | 0 | current_chunk_start_time = XLogRecoveryCtl->currentChunkStartTime; |
811 | 0 | pause_state = XLogRecoveryCtl->recoveryPauseState; |
812 | 0 | SpinLockRelease(&XLogRecoveryCtl->info_lck); |
813 | |
|
814 | 0 | if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) |
815 | 0 | elog(ERROR, "return type must be a row type"); |
816 | | |
817 | 0 | values = palloc0_array(Datum, tupdesc->natts); |
818 | 0 | nulls = palloc0_array(bool, tupdesc->natts); |
819 | |
|
820 | 0 | values[0] = BoolGetDatum(promote_triggered); |
821 | |
|
822 | 0 | if (XLogRecPtrIsValid(last_replayed_read_lsn)) |
823 | 0 | values[1] = LSNGetDatum(last_replayed_read_lsn); |
824 | 0 | else |
825 | 0 | nulls[1] = true; |
826 | |
|
827 | 0 | if (XLogRecPtrIsValid(last_replayed_end_lsn)) |
828 | 0 | values[2] = LSNGetDatum(last_replayed_end_lsn); |
829 | 0 | else |
830 | 0 | nulls[2] = true; |
831 | |
|
832 | 0 | if (XLogRecPtrIsValid(last_replayed_end_lsn)) |
833 | 0 | values[3] = Int32GetDatum(last_replayed_tli); |
834 | 0 | else |
835 | 0 | nulls[3] = true; |
836 | |
|
837 | 0 | if (XLogRecPtrIsValid(replay_end_lsn)) |
838 | 0 | values[4] = LSNGetDatum(replay_end_lsn); |
839 | 0 | else |
840 | 0 | nulls[4] = true; |
841 | |
|
842 | 0 | if (XLogRecPtrIsValid(replay_end_lsn)) |
843 | 0 | values[5] = Int32GetDatum(replay_end_tli); |
844 | 0 | else |
845 | 0 | nulls[5] = true; |
846 | |
|
847 | 0 | if (recovery_last_xact_time != 0) |
848 | 0 | values[6] = TimestampTzGetDatum(recovery_last_xact_time); |
849 | 0 | else |
850 | 0 | nulls[6] = true; |
851 | |
|
852 | 0 | if (current_chunk_start_time != 0) |
853 | 0 | values[7] = TimestampTzGetDatum(current_chunk_start_time); |
854 | 0 | else |
855 | 0 | nulls[7] = true; |
856 | |
|
857 | 0 | values[8] = CStringGetTextDatum(GetRecoveryPauseStateString(pause_state)); |
858 | |
|
859 | 0 | PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls))); |
860 | 0 | } |