From ecd578361158f4c6d90a1d983c2a2e055945c638 Mon Sep 17 00:00:00 2001 From: Bharath Rupireddy Date: Fri, 10 Mar 2023 05:07:18 +0000 Subject: [PATCH v4] Rework pg_walinspect functions --- contrib/pg_walinspect/Makefile | 2 +- .../pg_walinspect/expected/oldextversions.out | 64 +++++ .../pg_walinspect/expected/pg_walinspect.out | 85 ++++++- contrib/pg_walinspect/meson.build | 1 + .../pg_walinspect/pg_walinspect--1.0--1.1.sql | 57 ++++- contrib/pg_walinspect/pg_walinspect.c | 235 +++++++++++------- contrib/pg_walinspect/sql/oldextversions.sql | 27 ++ contrib/pg_walinspect/sql/pg_walinspect.sql | 70 +++--- doc/src/sgml/pgwalinspect.sgml | 73 +++--- 9 files changed, 428 insertions(+), 186 deletions(-) create mode 100644 contrib/pg_walinspect/expected/oldextversions.out create mode 100644 contrib/pg_walinspect/sql/oldextversions.sql diff --git a/contrib/pg_walinspect/Makefile b/contrib/pg_walinspect/Makefile index 7033878a79..22090f7716 100644 --- a/contrib/pg_walinspect/Makefile +++ b/contrib/pg_walinspect/Makefile @@ -9,7 +9,7 @@ PGFILEDESC = "pg_walinspect - functions to inspect contents of PostgreSQL Write- EXTENSION = pg_walinspect DATA = pg_walinspect--1.0.sql pg_walinspect--1.0--1.1.sql -REGRESS = pg_walinspect +REGRESS = pg_walinspect oldextversions REGRESS_OPTS = --temp-config $(top_srcdir)/contrib/pg_walinspect/walinspect.conf diff --git a/contrib/pg_walinspect/expected/oldextversions.out b/contrib/pg_walinspect/expected/oldextversions.out new file mode 100644 index 0000000000..0ac82dfeb1 --- /dev/null +++ b/contrib/pg_walinspect/expected/oldextversions.out @@ -0,0 +1,64 @@ +-- test old extension version entry points +DROP EXTENSION pg_walinspect; +CREATE EXTENSION pg_walinspect WITH VERSION '1.0'; +-- Move to new version 1.1 +ALTER EXTENSION pg_walinspect UPDATE TO '1.1'; +-- New function introduced in 1.1 +SELECT pg_get_functiondef('pg_get_wal_block_info'::regproc); + pg_get_functiondef +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + CREATE OR REPLACE FUNCTION public.pg_get_wal_block_info(start_lsn pg_lsn, end_lsn pg_lsn DEFAULT NULL::pg_lsn, OUT lsn pg_lsn, OUT blockid smallint, OUT reltablespace oid, OUT reldatabase oid, OUT relfilenode oid, OUT relblocknumber bigint, OUT forkname text, OUT blockdata bytea, OUT fpi bytea, OUT fpilen integer, OUT fpiinfo text[])+ + RETURNS SETOF record + + LANGUAGE c + + PARALLEL SAFE + + AS '$libdir/pg_walinspect', $function$pg_get_wal_block_info$function$ + + +(1 row) + +-- Redefined function +SELECT pg_get_functiondef('pg_get_wal_records_info'::regproc); + pg_get_functiondef +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + CREATE OR REPLACE FUNCTION public.pg_get_wal_records_info(start_lsn pg_lsn, end_lsn pg_lsn DEFAULT NULL::pg_lsn, OUT start_lsn pg_lsn, OUT end_lsn pg_lsn, OUT prev_lsn pg_lsn, OUT xid xid, OUT resource_manager text, OUT record_type text, OUT record_length integer, OUT main_data_length integer, OUT fpi_length integer, OUT description text, OUT block_ref text)+ + RETURNS SETOF record + + LANGUAGE c + + PARALLEL SAFE + + AS '$libdir/pg_walinspect', $function$pg_get_wal_records_info$function$ + + +(1 row) + +-- Removed function +SELECT pg_get_functiondef('pg_get_wal_records_info_till_end_of_wal'::regproc); +ERROR: function "pg_get_wal_records_info_till_end_of_wal" does not exist +LINE 1: SELECT pg_get_functiondef('pg_get_wal_records_info_till_end_... + ^ +-- Redefined function +SELECT pg_get_functiondef('pg_get_wal_stats'::regproc); + pg_get_functiondef +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + CREATE OR REPLACE FUNCTION public.pg_get_wal_stats(start_lsn pg_lsn, end_lsn pg_lsn DEFAULT NULL::pg_lsn, per_record boolean DEFAULT false, OUT "resource_manager/record_type" text, OUT count bigint, OUT count_percentage double precision, OUT record_size bigint, OUT record_size_percentage double precision, OUT fpi_size bigint, OUT fpi_size_percentage double precision, OUT combined_size bigint, OUT combined_size_percentage double precision)+ + RETURNS SETOF record + + LANGUAGE c + + PARALLEL SAFE + + AS '$libdir/pg_walinspect', $function$pg_get_wal_stats$function$ + + +(1 row) + +-- Removed function +SELECT pg_get_functiondef('pg_get_wal_stats_till_end_of_wal'::regproc); +ERROR: function "pg_get_wal_stats_till_end_of_wal" does not exist +LINE 1: SELECT pg_get_functiondef('pg_get_wal_stats_till_end_of_wal'... + ^ +-- Redefined function +SELECT pg_get_functiondef('pg_get_wal_block_info'::regproc); + pg_get_functiondef +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + CREATE OR REPLACE FUNCTION public.pg_get_wal_block_info(start_lsn pg_lsn, end_lsn pg_lsn DEFAULT NULL::pg_lsn, OUT lsn pg_lsn, OUT blockid smallint, OUT reltablespace oid, OUT reldatabase oid, OUT relfilenode oid, OUT relblocknumber bigint, OUT forkname text, OUT blockdata bytea, OUT fpi bytea, OUT fpilen integer, OUT fpiinfo text[])+ + RETURNS SETOF record + + LANGUAGE c + + PARALLEL SAFE + + AS '$libdir/pg_walinspect', $function$pg_get_wal_block_info$function$ + + +(1 row) + +DROP EXTENSION pg_walinspect; diff --git a/contrib/pg_walinspect/expected/pg_walinspect.out b/contrib/pg_walinspect/expected/pg_walinspect.out index e0eb7ca08f..31ef22a2cc 100644 --- a/contrib/pg_walinspect/expected/pg_walinspect.out +++ b/contrib/pg_walinspect/expected/pg_walinspect.out @@ -14,38 +14,89 @@ INSERT INTO sample_tbl SELECT * FROM generate_series(3, 4); -- =================================================================== -- Tests for input validation -- =================================================================== -SELECT COUNT(*) >= 0 AS ok FROM pg_get_wal_records_info(:'wal_lsn2', :'wal_lsn1'); -- ERROR -ERROR: WAL start LSN must be less than end LSN -SELECT COUNT(*) >= 0 AS ok FROM pg_get_wal_stats(:'wal_lsn2', :'wal_lsn1'); -- ERROR -ERROR: WAL start LSN must be less than end LSN +-- Invalid input LSNs +SELECT * FROM pg_get_wal_record_info('0/0'); -- ERROR +ERROR: invalid input LSN +SELECT * FROM pg_get_wal_records_info('0/0'); -- ERROR +ERROR: invalid start LSN +SELECT * FROM pg_get_wal_stats('0/0'); -- ERROR +ERROR: invalid start LSN +SELECT * FROM pg_get_wal_block_info('0/0'); -- ERROR +ERROR: invalid start LSN +-- Start LSN NULL +SELECT * FROM pg_get_wal_records_info(NULL); -- ERROR +ERROR: invalid start LSN +SELECT * FROM pg_get_wal_stats(NULL); -- ERROR +ERROR: invalid start LSN +SELECT * FROM pg_get_wal_block_info(NULL); -- ERROR +ERROR: invalid start LSN +-- Start LSN > End LSN +SELECT * FROM pg_get_wal_records_info(:'wal_lsn2', :'wal_lsn1'); -- NULL + start_lsn | end_lsn | prev_lsn | xid | resource_manager | record_type | record_length | main_data_length | fpi_length | description | block_ref +-----------+---------+----------+-----+------------------+-------------+---------------+------------------+------------+-------------+----------- + | | | | | | | | | | +(1 row) + +SELECT * FROM pg_get_wal_stats(:'wal_lsn2', :'wal_lsn1'); -- NULL + resource_manager/record_type | count | count_percentage | record_size | record_size_percentage | fpi_size | fpi_size_percentage | combined_size | combined_size_percentage +------------------------------+-------+------------------+-------------+------------------------+----------+---------------------+---------------+-------------------------- + | | | | | | | | +(1 row) + +SELECT * FROM pg_get_wal_block_info(:'wal_lsn2', :'wal_lsn1'); -- NULL + lsn | blockid | reltablespace | reldatabase | relfilenode | relblocknumber | forkname | blockdata | fpi | fpilen | fpiinfo +-----+---------+---------------+-------------+-------------+----------------+----------+-----------+-----+--------+--------- + | | | | | | | | | | +(1 row) + -- =================================================================== -- Tests for all function executions -- =================================================================== -SELECT COUNT(*) >= 0 AS ok FROM pg_get_wal_record_info(:'wal_lsn1'); +SELECT COUNT(*) >= 1 AS ok FROM pg_get_wal_record_info(:'wal_lsn1'); + ok +---- + t +(1 row) + +SELECT COUNT(*) >= 1 AS ok FROM pg_get_wal_records_info(:'wal_lsn1', :'wal_lsn2'); + ok +---- + t +(1 row) + +-- Get info till end of WAL +SELECT COUNT(*) >= 1 AS ok FROM pg_get_wal_records_info(:'wal_lsn1'); ok ---- t (1 row) -SELECT COUNT(*) >= 0 AS ok FROM pg_get_wal_records_info(:'wal_lsn1', :'wal_lsn2'); +SELECT COUNT(*) >= 1 AS ok FROM pg_get_wal_records_info(:'wal_lsn1', '0/0'); ok ---- t (1 row) -SELECT COUNT(*) >= 0 AS ok FROM pg_get_wal_records_info_till_end_of_wal(:'wal_lsn1'); +SELECT COUNT(*) >= 1 AS ok FROM pg_get_wal_records_info(:'wal_lsn1', NULL); ok ---- t (1 row) -SELECT COUNT(*) >= 0 AS ok FROM pg_get_wal_stats(:'wal_lsn1', :'wal_lsn2'); +-- Get stats till end of WAL +SELECT COUNT(*) >= 1 AS ok FROM pg_get_wal_stats(:'wal_lsn1'); ok ---- t (1 row) -SELECT COUNT(*) >= 0 AS ok FROM pg_get_wal_stats_till_end_of_wal(:'wal_lsn1'); +SELECT COUNT(*) >= 1 AS ok FROM pg_get_wal_stats(:'wal_lsn1', '0/0'); + ok +---- + t +(1 row) + +SELECT COUNT(*) >= 1 AS ok FROM pg_get_wal_stats(:'wal_lsn1', NULL); ok ---- t @@ -88,6 +139,14 @@ SELECT COUNT(*) >= 1 AS ok FROM pg_get_wal_block_info(:'wal_lsn3', :'wal_lsn4') t (1 row) +-- Check till end of WAL if we get block data from WAL record. +SELECT COUNT(*) >= 1 AS ok FROM pg_get_wal_block_info(:'wal_lsn3') + WHERE relfilenode = :'sample_tbl_oid'; + ok +---- + t +(1 row) + -- Force full-page image on the next update. SELECT pg_current_wal_lsn() AS wal_lsn5 \gset CHECKPOINT; @@ -101,6 +160,14 @@ SELECT COUNT(*) >= 1 AS ok FROM pg_get_wal_block_info(:'wal_lsn5', :'wal_lsn6') t (1 row) +-- Check till end of WAL if we get FPI from WAL record. +SELECT COUNT(*) >= 1 AS ok FROM pg_get_wal_block_info(:'wal_lsn5') + WHERE relfilenode = :'sample_tbl_oid'; + ok +---- + t +(1 row) + -- =================================================================== -- Tests for permissions -- =================================================================== diff --git a/contrib/pg_walinspect/meson.build b/contrib/pg_walinspect/meson.build index bf7b79b1b7..80059f6119 100644 --- a/contrib/pg_walinspect/meson.build +++ b/contrib/pg_walinspect/meson.build @@ -30,6 +30,7 @@ tests += { 'regress': { 'sql': [ 'pg_walinspect', + 'oldextversions', ], # Disabled because these tests require "wal_level=replica", which # some runningcheck users do not have (e.g. buildfarm clients). diff --git a/contrib/pg_walinspect/pg_walinspect--1.0--1.1.sql b/contrib/pg_walinspect/pg_walinspect--1.0--1.1.sql index e674ef25aa..8d9b82ead2 100644 --- a/contrib/pg_walinspect/pg_walinspect--1.0--1.1.sql +++ b/contrib/pg_walinspect/pg_walinspect--1.0--1.1.sql @@ -3,11 +3,64 @@ -- complain if script is sourced in psql, rather than via ALTER EXTENSION \echo Use "ALTER EXTENSION pg_walinspect UPDATE TO '1.1'" to load this file. \quit +/* Drop the unneeded functions and redefine needed ones */ +DROP FUNCTION pg_get_wal_records_info(pg_lsn, pg_lsn); +DROP FUNCTION pg_get_wal_records_info_till_end_of_wal(pg_lsn); +DROP FUNCTION pg_get_wal_stats(pg_lsn, pg_lsn, boolean); +DROP FUNCTION pg_get_wal_stats_till_end_of_wal(pg_lsn, boolean); + +-- +-- pg_get_wal_records_info() +-- +CREATE FUNCTION pg_get_wal_records_info(IN start_lsn pg_lsn, + IN end_lsn pg_lsn DEFAULT NULL, + OUT start_lsn pg_lsn, + OUT end_lsn pg_lsn, + OUT prev_lsn pg_lsn, + OUT xid xid, + OUT resource_manager text, + OUT record_type text, + OUT record_length int4, + OUT main_data_length int4, + OUT fpi_length int4, + OUT description text, + OUT block_ref text +) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'pg_get_wal_records_info' +LANGUAGE C CALLED ON NULL INPUT PARALLEL SAFE; + +REVOKE EXECUTE ON FUNCTION pg_get_wal_records_info(pg_lsn, pg_lsn) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pg_get_wal_records_info(pg_lsn, pg_lsn) TO pg_read_server_files; + +-- +-- pg_get_wal_stats() +-- +CREATE FUNCTION pg_get_wal_stats(IN start_lsn pg_lsn, + IN end_lsn pg_lsn DEFAULT NULL, + IN per_record boolean DEFAULT false, + OUT "resource_manager/record_type" text, + OUT count int8, + OUT count_percentage float8, + OUT record_size int8, + OUT record_size_percentage float8, + OUT fpi_size int8, + OUT fpi_size_percentage float8, + OUT combined_size int8, + OUT combined_size_percentage float8 +) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'pg_get_wal_stats' +LANGUAGE C CALLED ON NULL INPUT PARALLEL SAFE; + +REVOKE EXECUTE ON FUNCTION pg_get_wal_stats(pg_lsn, pg_lsn, boolean) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pg_get_wal_stats(pg_lsn, pg_lsn, boolean) TO pg_read_server_files; + -- -- pg_get_wal_block_info() -- CREATE FUNCTION pg_get_wal_block_info(IN start_lsn pg_lsn, - IN end_lsn pg_lsn, + IN end_lsn pg_lsn DEFAULT NULL, OUT lsn pg_lsn, OUT blockid int2, OUT reltablespace oid, @@ -22,7 +75,7 @@ CREATE FUNCTION pg_get_wal_block_info(IN start_lsn pg_lsn, ) RETURNS SETOF record AS 'MODULE_PATHNAME', 'pg_get_wal_block_info' -LANGUAGE C STRICT PARALLEL SAFE; +LANGUAGE C CALLED ON NULL INPUT PARALLEL SAFE; REVOKE EXECUTE ON FUNCTION pg_get_wal_block_info(pg_lsn, pg_lsn) FROM PUBLIC; GRANT EXECUTE ON FUNCTION pg_get_wal_block_info(pg_lsn, pg_lsn) TO pg_read_server_files; diff --git a/contrib/pg_walinspect/pg_walinspect.c b/contrib/pg_walinspect/pg_walinspect.c index ee88dc4992..4a8974ac1f 100644 --- a/contrib/pg_walinspect/pg_walinspect.c +++ b/contrib/pg_walinspect/pg_walinspect.c @@ -38,13 +38,13 @@ PG_FUNCTION_INFO_V1(pg_get_wal_records_info_till_end_of_wal); PG_FUNCTION_INFO_V1(pg_get_wal_stats); PG_FUNCTION_INFO_V1(pg_get_wal_stats_till_end_of_wal); -static bool IsFutureLSN(XLogRecPtr lsn, XLogRecPtr *curr_lsn); +static XLogRecPtr GetCurrentLSN(void); static XLogReaderState *InitXLogReaderState(XLogRecPtr lsn); static XLogRecord *ReadNextXLogRecord(XLogReaderState *xlogreader); static void GetWALRecordInfo(XLogReaderState *record, Datum *values, bool *nulls, uint32 ncols); -static XLogRecPtr ValidateInputLSNs(bool till_end_of_wal, - XLogRecPtr start_lsn, XLogRecPtr end_lsn); +static void GetInputLSNs(FunctionCallInfo fcinfo, XLogRecPtr *start_lsn, + XLogRecPtr *end_lsn); static void GetWALRecordsInfo(FunctionCallInfo fcinfo, XLogRecPtr start_lsn, XLogRecPtr end_lsn); static void GetXLogSummaryStats(XLogStats *stats, ReturnSetInfo *rsinfo, @@ -60,27 +60,25 @@ static void GetWalStats(FunctionCallInfo fcinfo, XLogRecPtr start_lsn, static void GetWALBlockInfo(FunctionCallInfo fcinfo, XLogReaderState *record); /* - * Check if the given LSN is in future. Also, return the LSN up to which the - * server has WAL. + * Return the LSN up to which the server has WAL. */ -static bool -IsFutureLSN(XLogRecPtr lsn, XLogRecPtr *curr_lsn) +static XLogRecPtr +GetCurrentLSN(void) { + XLogRecPtr curr_lsn; + /* * We determine the current LSN of the server similar to how page_read * callback read_local_xlog_page_no_wait does. */ if (!RecoveryInProgress()) - *curr_lsn = GetFlushRecPtr(NULL); + curr_lsn = GetFlushRecPtr(NULL); else - *curr_lsn = GetXLogReplayRecPtr(NULL); + curr_lsn = GetXLogReplayRecPtr(NULL); - Assert(!XLogRecPtrIsInvalid(*curr_lsn)); + Assert(!XLogRecPtrIsInvalid(curr_lsn)); - if (lsn >= *curr_lsn) - return true; - - return false; + return curr_lsn; } /* @@ -355,8 +353,14 @@ GetWALBlockInfo(FunctionCallInfo fcinfo, XLogReaderState *record) * to a record. Decompression is applied to the full page images, if * necessary. * - * This function emits an error if a future start or end WAL LSN i.e. WAL LSN - * the database system doesn't know about is specified. + * This function determines end LSN if it is not specified or specified as NULL + * or invalid. In such cases, the end LSN is assigned with the last flushed LSN + * when not in recovery or the last replayed LSN when in recovery. + * + * This function emits an error if start LSN is invalid or in future i.e. LSN + * the database system doesn't know about. + * + * This function returns NULL, when start LSN is past the end LSN. */ Datum pg_get_wal_block_info(PG_FUNCTION_ARGS) @@ -367,10 +371,14 @@ pg_get_wal_block_info(PG_FUNCTION_ARGS) MemoryContext old_cxt; MemoryContext tmp_cxt; - start_lsn = PG_GETARG_LSN(0); - end_lsn = PG_GETARG_LSN(1); + GetInputLSNs(fcinfo, &start_lsn, &end_lsn); - end_lsn = ValidateInputLSNs(false, start_lsn, end_lsn); + /* + * When start LSN is past the end LSN, let's return NULL instead of raising + * an error for better usability of the functions. + */ + if (start_lsn >= end_lsn) + PG_RETURN_NULL(); InitMaterializedSRF(fcinfo, 0); @@ -405,8 +413,8 @@ pg_get_wal_block_info(PG_FUNCTION_ARGS) /* * Get WAL record info. * - * This function emits an error if a future WAL LSN i.e. WAL LSN the database - * system doesn't know about is specified. + * This function emits an error if input LSN is invalid or in future i.e. LSN + * the database system doesn't know about. */ Datum pg_get_wal_record_info(PG_FUNCTION_ARGS) @@ -423,7 +431,14 @@ pg_get_wal_record_info(PG_FUNCTION_ARGS) lsn = PG_GETARG_LSN(0); - if (IsFutureLSN(lsn, &curr_lsn)) + if (XLogRecPtrIsInvalid(lsn)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid input LSN"))); + + curr_lsn = GetCurrentLSN(); + + if (lsn >= curr_lsn) { /* * GetFlushRecPtr or GetXLogReplayRecPtr gives "end+1" LSN of the last @@ -462,44 +477,36 @@ pg_get_wal_record_info(PG_FUNCTION_ARGS) } /* - * Validate the input LSNs and compute end LSN for till_end_of_wal versions. + * Get start LSN and get/deduce end LSN. */ -static XLogRecPtr -ValidateInputLSNs(bool till_end_of_wal, XLogRecPtr start_lsn, - XLogRecPtr end_lsn) +static void +GetInputLSNs(FunctionCallInfo fcinfo, XLogRecPtr *start_lsn, + XLogRecPtr *end_lsn) { XLogRecPtr curr_lsn; - if (IsFutureLSN(start_lsn, &curr_lsn)) - { - /* - * GetFlushRecPtr or GetXLogReplayRecPtr gives "end+1" LSN of the last - * record flushed or replayed respectively. But let's use the LSN up - * to "end" in user facing message. - */ + if (PG_ARGISNULL(0) || + ((*start_lsn = PG_GETARG_LSN(0)) == InvalidXLogRecPtr)) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("cannot accept future start LSN"), - errdetail("Last known WAL LSN on the database system is at %X/%X.", - LSN_FORMAT_ARGS(curr_lsn)))); - } - - if (till_end_of_wal) - end_lsn = curr_lsn; + errmsg("invalid start LSN"))); - if (end_lsn > curr_lsn) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("cannot accept future end LSN"), - errdetail("Last known WAL LSN on the database system is at %X/%X.", - LSN_FORMAT_ARGS(curr_lsn)))); + curr_lsn = GetCurrentLSN(); - if (start_lsn >= end_lsn) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("WAL start LSN must be less than end LSN"))); + if (PG_ARGISNULL(1)) + *end_lsn = curr_lsn; + else + { + *end_lsn = PG_GETARG_LSN(1); - return end_lsn; + /* + * Adjust end LSN to what the system knows at this point instead of + * raising errors for better usability of the functions. + */ + if (XLogRecPtrIsInvalid(*end_lsn) || + *end_lsn > curr_lsn) + *end_lsn = curr_lsn; + } } /* @@ -554,8 +561,14 @@ GetWALRecordsInfo(FunctionCallInfo fcinfo, XLogRecPtr start_lsn, /* * Get info and data of all WAL records between start LSN and end LSN. * - * This function emits an error if a future start or end WAL LSN i.e. WAL LSN - * the database system doesn't know about is specified. + * This function determines end LSN if it is not specified or specified as NULL + * or invalid. In such cases, the end LSN is assigned with the last flushed LSN + * when not in recovery or the last replayed LSN when in recovery. + * + * This function emits an error if start LSN is invalid or in future i.e. LSN + * the database system doesn't know about. + * + * This function returns NULL, when start LSN is past the end LSN. */ Datum pg_get_wal_records_info(PG_FUNCTION_ARGS) @@ -563,31 +576,14 @@ pg_get_wal_records_info(PG_FUNCTION_ARGS) XLogRecPtr start_lsn; XLogRecPtr end_lsn; - start_lsn = PG_GETARG_LSN(0); - end_lsn = PG_GETARG_LSN(1); - - end_lsn = ValidateInputLSNs(false, start_lsn, end_lsn); - - GetWALRecordsInfo(fcinfo, start_lsn, end_lsn); - - PG_RETURN_VOID(); -} - -/* - * Get info and data of all WAL records from start LSN till end of WAL. - * - * This function emits an error if a future start i.e. WAL LSN the database - * system doesn't know about is specified. - */ -Datum -pg_get_wal_records_info_till_end_of_wal(PG_FUNCTION_ARGS) -{ - XLogRecPtr start_lsn; - XLogRecPtr end_lsn = InvalidXLogRecPtr; - - start_lsn = PG_GETARG_LSN(0); + GetInputLSNs(fcinfo, &start_lsn, &end_lsn); - end_lsn = ValidateInputLSNs(true, start_lsn, end_lsn); + /* + * When start LSN is past the end LSN, let's return NULL instead of raising + * an error for better usability of the functions. + */ + if (start_lsn >= end_lsn) + PG_RETURN_NULL(); GetWALRecordsInfo(fcinfo, start_lsn, end_lsn); @@ -792,8 +788,14 @@ GetWalStats(FunctionCallInfo fcinfo, XLogRecPtr start_lsn, /* * Get stats of all WAL records between start LSN and end LSN. * - * This function emits an error if a future start or end WAL LSN i.e. WAL LSN - * the database system doesn't know about is specified. + * This function determines end LSN if it is not specified or specified as NULL + * or invalid. In such cases, the end LSN is assigned with the last flushed LSN + * when not in recovery or the last replayed LSN when in recovery. + * + * This function emits an error if start LSN is invalid or in future i.e. LSN + * the database system doesn't know about. + * + * This function returns NULL, when start LSN is past the end LSN. */ Datum pg_get_wal_stats(PG_FUNCTION_ARGS) @@ -802,11 +804,16 @@ pg_get_wal_stats(PG_FUNCTION_ARGS) XLogRecPtr end_lsn; bool stats_per_record; - start_lsn = PG_GETARG_LSN(0); - end_lsn = PG_GETARG_LSN(1); - stats_per_record = PG_GETARG_BOOL(2); + GetInputLSNs(fcinfo, &start_lsn, &end_lsn); + + /* + * When start LSN is past the end LSN, let's return NULL instead of raising + * an error for better usability of the functions. + */ + if (start_lsn >= end_lsn) + PG_RETURN_NULL(); - end_lsn = ValidateInputLSNs(false, start_lsn, end_lsn); + stats_per_record = PG_GETARG_BOOL(2); GetWalStats(fcinfo, start_lsn, end_lsn, stats_per_record); @@ -814,22 +821,70 @@ pg_get_wal_stats(PG_FUNCTION_ARGS) } /* - * Get stats of all WAL records from start LSN till end of WAL. + * NB: Following till_end_of_wal functions have been removed in newer versions + * of extension. However, we keep them around for backward compatibility. Which + * means, these functions work if someone explicitly installs the older + * extension version (using CREATE EXTENSION pg_walinspect WITH VERSION '1.0';) + * containing them. * - * This function emits an error if a future start i.e. WAL LSN the database - * system doesn't know about is specified. + * If definitions of these functions are removed completely, the extension + * fails to install. + * + * In newer versions, one can use pg_get_wal_records_info()/pg_get_wal_stats() + * for the same till_end_of_wal functionality. */ + +Datum +pg_get_wal_records_info_till_end_of_wal(PG_FUNCTION_ARGS) +{ + XLogRecPtr start_lsn; + XLogRecPtr end_lsn; + + if (PG_ARGISNULL(0) || + ((start_lsn = PG_GETARG_LSN(0)) == InvalidXLogRecPtr)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid start LSN"))); + + /* Let's compute end LSN ourselves. */ + end_lsn = GetCurrentLSN(); + + /* + * When start LSN is past the end LSN, let's return NULL instead of raising + * an error for better usability of the functions. + */ + if (start_lsn >= end_lsn) + PG_RETURN_NULL(); + + GetWALRecordsInfo(fcinfo, start_lsn, end_lsn); + + PG_RETURN_VOID(); +} + Datum pg_get_wal_stats_till_end_of_wal(PG_FUNCTION_ARGS) { XLogRecPtr start_lsn; - XLogRecPtr end_lsn = InvalidXLogRecPtr; + XLogRecPtr end_lsn; bool stats_per_record; - start_lsn = PG_GETARG_LSN(0); - stats_per_record = PG_GETARG_BOOL(1); + if (PG_ARGISNULL(0) || + ((start_lsn = PG_GETARG_LSN(0)) == InvalidXLogRecPtr)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid start LSN"))); + + /* Let's compute end LSN ourselves. */ + end_lsn = GetCurrentLSN(); + + /* + * When start LSN is past the end LSN, let's return NULL instead of raising + * an error for better usability of the functions. + */ + if (start_lsn >= end_lsn) + PG_RETURN_NULL(); - end_lsn = ValidateInputLSNs(true, start_lsn, end_lsn); + stats_per_record = PG_GETARG_BOOL(2); GetWalStats(fcinfo, start_lsn, end_lsn, stats_per_record); diff --git a/contrib/pg_walinspect/sql/oldextversions.sql b/contrib/pg_walinspect/sql/oldextversions.sql new file mode 100644 index 0000000000..3ccea0cdb2 --- /dev/null +++ b/contrib/pg_walinspect/sql/oldextversions.sql @@ -0,0 +1,27 @@ +-- test old extension version entry points + +DROP EXTENSION pg_walinspect; +CREATE EXTENSION pg_walinspect WITH VERSION '1.0'; + +-- Move to new version 1.1 +ALTER EXTENSION pg_walinspect UPDATE TO '1.1'; + +-- New function introduced in 1.1 +SELECT pg_get_functiondef('pg_get_wal_block_info'::regproc); + +-- Redefined function +SELECT pg_get_functiondef('pg_get_wal_records_info'::regproc); + +-- Removed function +SELECT pg_get_functiondef('pg_get_wal_records_info_till_end_of_wal'::regproc); + +-- Redefined function +SELECT pg_get_functiondef('pg_get_wal_stats'::regproc); + +-- Removed function +SELECT pg_get_functiondef('pg_get_wal_stats_till_end_of_wal'::regproc); + +-- Redefined function +SELECT pg_get_functiondef('pg_get_wal_block_info'::regproc); + +DROP EXTENSION pg_walinspect; diff --git a/contrib/pg_walinspect/sql/pg_walinspect.sql b/contrib/pg_walinspect/sql/pg_walinspect.sql index 01a120f398..602aa9c7ae 100644 --- a/contrib/pg_walinspect/sql/pg_walinspect.sql +++ b/contrib/pg_walinspect/sql/pg_walinspect.sql @@ -4,43 +4,50 @@ CREATE EXTENSION pg_walinspect; SELECT 'init' FROM pg_create_physical_replication_slot('regress_pg_walinspect_slot', true, false); CREATE TABLE sample_tbl(col1 int, col2 int); - SELECT pg_current_wal_lsn() AS wal_lsn1 \gset - INSERT INTO sample_tbl SELECT * FROM generate_series(1, 2); - SELECT pg_current_wal_lsn() AS wal_lsn2 \gset - INSERT INTO sample_tbl SELECT * FROM generate_series(3, 4); -- =================================================================== -- Tests for input validation -- =================================================================== - -SELECT COUNT(*) >= 0 AS ok FROM pg_get_wal_records_info(:'wal_lsn2', :'wal_lsn1'); -- ERROR - -SELECT COUNT(*) >= 0 AS ok FROM pg_get_wal_stats(:'wal_lsn2', :'wal_lsn1'); -- ERROR +-- Invalid input LSNs +SELECT * FROM pg_get_wal_record_info('0/0'); -- ERROR +SELECT * FROM pg_get_wal_records_info('0/0'); -- ERROR +SELECT * FROM pg_get_wal_stats('0/0'); -- ERROR +SELECT * FROM pg_get_wal_block_info('0/0'); -- ERROR + +-- Start LSN NULL +SELECT * FROM pg_get_wal_records_info(NULL); -- ERROR +SELECT * FROM pg_get_wal_stats(NULL); -- ERROR +SELECT * FROM pg_get_wal_block_info(NULL); -- ERROR + +-- Start LSN > End LSN +SELECT * FROM pg_get_wal_records_info(:'wal_lsn2', :'wal_lsn1'); -- NULL +SELECT * FROM pg_get_wal_stats(:'wal_lsn2', :'wal_lsn1'); -- NULL +SELECT * FROM pg_get_wal_block_info(:'wal_lsn2', :'wal_lsn1'); -- NULL -- =================================================================== -- Tests for all function executions -- =================================================================== +SELECT COUNT(*) >= 1 AS ok FROM pg_get_wal_record_info(:'wal_lsn1'); +SELECT COUNT(*) >= 1 AS ok FROM pg_get_wal_records_info(:'wal_lsn1', :'wal_lsn2'); -SELECT COUNT(*) >= 0 AS ok FROM pg_get_wal_record_info(:'wal_lsn1'); - -SELECT COUNT(*) >= 0 AS ok FROM pg_get_wal_records_info(:'wal_lsn1', :'wal_lsn2'); - -SELECT COUNT(*) >= 0 AS ok FROM pg_get_wal_records_info_till_end_of_wal(:'wal_lsn1'); +-- Get info till end of WAL +SELECT COUNT(*) >= 1 AS ok FROM pg_get_wal_records_info(:'wal_lsn1'); +SELECT COUNT(*) >= 1 AS ok FROM pg_get_wal_records_info(:'wal_lsn1', '0/0'); +SELECT COUNT(*) >= 1 AS ok FROM pg_get_wal_records_info(:'wal_lsn1', NULL); -SELECT COUNT(*) >= 0 AS ok FROM pg_get_wal_stats(:'wal_lsn1', :'wal_lsn2'); - -SELECT COUNT(*) >= 0 AS ok FROM pg_get_wal_stats_till_end_of_wal(:'wal_lsn1'); +-- Get stats till end of WAL +SELECT COUNT(*) >= 1 AS ok FROM pg_get_wal_stats(:'wal_lsn1'); +SELECT COUNT(*) >= 1 AS ok FROM pg_get_wal_stats(:'wal_lsn1', '0/0'); +SELECT COUNT(*) >= 1 AS ok FROM pg_get_wal_stats(:'wal_lsn1', NULL); -- =================================================================== -- Test for filtering out WAL records of a particular table -- =================================================================== - SELECT oid AS sample_tbl_oid FROM pg_class WHERE relname = 'sample_tbl' \gset - SELECT COUNT(*) >= 1 AS ok FROM pg_get_wal_records_info(:'wal_lsn1', :'wal_lsn2') WHERE block_ref LIKE concat('%', :'sample_tbl_oid', '%') AND resource_manager = 'Heap'; @@ -48,14 +55,12 @@ SELECT COUNT(*) >= 1 AS ok FROM pg_get_wal_records_info(:'wal_lsn1', :'wal_lsn2' -- Test for filtering out WAL records based on resource_manager and -- record_type -- =================================================================== - SELECT COUNT(*) >= 1 AS ok FROM pg_get_wal_records_info(:'wal_lsn1', :'wal_lsn2') WHERE resource_manager = 'Heap' AND record_type = 'INSERT'; -- =================================================================== -- Tests to get block information from WAL record -- =================================================================== - -- Update table to generate some block data SELECT pg_current_wal_lsn() AS wal_lsn3 \gset UPDATE sample_tbl SET col1 = col1 + 1 WHERE col1 = 1; @@ -63,6 +68,9 @@ SELECT pg_current_wal_lsn() AS wal_lsn4 \gset -- Check if we get block data from WAL record. SELECT COUNT(*) >= 1 AS ok FROM pg_get_wal_block_info(:'wal_lsn3', :'wal_lsn4') WHERE relfilenode = :'sample_tbl_oid' AND blockdata IS NOT NULL; +-- Check till end of WAL if we get block data from WAL record. +SELECT COUNT(*) >= 1 AS ok FROM pg_get_wal_block_info(:'wal_lsn3') + WHERE relfilenode = :'sample_tbl_oid'; -- Force full-page image on the next update. SELECT pg_current_wal_lsn() AS wal_lsn5 \gset @@ -72,6 +80,9 @@ SELECT pg_current_wal_lsn() AS wal_lsn6 \gset -- Check if we get FPI from WAL record. SELECT COUNT(*) >= 1 AS ok FROM pg_get_wal_block_info(:'wal_lsn5', :'wal_lsn6') WHERE relfilenode = :'sample_tbl_oid' AND fpi IS NOT NULL; +-- Check till end of WAL if we get FPI from WAL record. +SELECT COUNT(*) >= 1 AS ok FROM pg_get_wal_block_info(:'wal_lsn5') + WHERE relfilenode = :'sample_tbl_oid'; -- =================================================================== -- Tests for permissions @@ -80,29 +91,22 @@ CREATE ROLE regress_pg_walinspect; SELECT has_function_privilege('regress_pg_walinspect', 'pg_get_wal_record_info(pg_lsn)', 'EXECUTE'); -- no - SELECT has_function_privilege('regress_pg_walinspect', 'pg_get_wal_records_info(pg_lsn, pg_lsn) ', 'EXECUTE'); -- no - SELECT has_function_privilege('regress_pg_walinspect', 'pg_get_wal_stats(pg_lsn, pg_lsn, boolean) ', 'EXECUTE'); -- no - SELECT has_function_privilege('regress_pg_walinspect', 'pg_get_wal_block_info(pg_lsn, pg_lsn) ', 'EXECUTE'); -- no -- Functions accessible by users with role pg_read_server_files - GRANT pg_read_server_files TO regress_pg_walinspect; SELECT has_function_privilege('regress_pg_walinspect', 'pg_get_wal_record_info(pg_lsn)', 'EXECUTE'); -- yes - SELECT has_function_privilege('regress_pg_walinspect', 'pg_get_wal_records_info(pg_lsn, pg_lsn) ', 'EXECUTE'); -- yes - SELECT has_function_privilege('regress_pg_walinspect', 'pg_get_wal_stats(pg_lsn, pg_lsn, boolean) ', 'EXECUTE'); -- yes - SELECT has_function_privilege('regress_pg_walinspect', 'pg_get_wal_block_info(pg_lsn, pg_lsn) ', 'EXECUTE'); -- yes @@ -111,46 +115,34 @@ REVOKE pg_read_server_files FROM regress_pg_walinspect; -- Superuser can grant execute to other users GRANT EXECUTE ON FUNCTION pg_get_wal_record_info(pg_lsn) TO regress_pg_walinspect; - GRANT EXECUTE ON FUNCTION pg_get_wal_records_info(pg_lsn, pg_lsn) TO regress_pg_walinspect; - GRANT EXECUTE ON FUNCTION pg_get_wal_stats(pg_lsn, pg_lsn, boolean) TO regress_pg_walinspect; - GRANT EXECUTE ON FUNCTION pg_get_wal_block_info(pg_lsn, pg_lsn) TO regress_pg_walinspect; SELECT has_function_privilege('regress_pg_walinspect', 'pg_get_wal_record_info(pg_lsn)', 'EXECUTE'); -- yes - SELECT has_function_privilege('regress_pg_walinspect', 'pg_get_wal_records_info(pg_lsn, pg_lsn) ', 'EXECUTE'); -- yes - SELECT has_function_privilege('regress_pg_walinspect', 'pg_get_wal_stats(pg_lsn, pg_lsn, boolean) ', 'EXECUTE'); -- yes - SELECT has_function_privilege('regress_pg_walinspect', 'pg_get_wal_block_info(pg_lsn, pg_lsn) ', 'EXECUTE'); -- yes REVOKE EXECUTE ON FUNCTION pg_get_wal_record_info(pg_lsn) FROM regress_pg_walinspect; - REVOKE EXECUTE ON FUNCTION pg_get_wal_records_info(pg_lsn, pg_lsn) FROM regress_pg_walinspect; - REVOKE EXECUTE ON FUNCTION pg_get_wal_stats(pg_lsn, pg_lsn, boolean) FROM regress_pg_walinspect; - REVOKE EXECUTE ON FUNCTION pg_get_wal_block_info(pg_lsn, pg_lsn) FROM regress_pg_walinspect; -- =================================================================== -- Clean up -- =================================================================== - DROP ROLE regress_pg_walinspect; - SELECT pg_drop_replication_slot('regress_pg_walinspect_slot'); - DROP TABLE sample_tbl; diff --git a/doc/src/sgml/pgwalinspect.sgml b/doc/src/sgml/pgwalinspect.sgml index 3b19863dce..4ecbd32ba1 100644 --- a/doc/src/sgml/pgwalinspect.sgml +++ b/doc/src/sgml/pgwalinspect.sgml @@ -61,6 +61,7 @@ Gets WAL record information of a given LSN. If the given LSN isn't at the start of a WAL record, it gives the information of the next available valid WAL record; or an error if no such record is found. + If given LSN is not yet available, the function will raise an error. For example, usage of the function is as follows: @@ -85,7 +86,7 @@ block_ref | blkref #0: rel 1663/5/60221 fork main blk 2 - pg_get_wal_records_info(start_lsn pg_lsn, end_lsn pg_lsn) + pg_get_wal_records_info(start_lsn pg_lsn, end_lsn pg_lsn DEFAULT NULL) returns setof record @@ -95,8 +96,9 @@ block_ref | blkref #0: rel 1663/5/60221 fork main blk 2 Gets information of all the valid WAL records between start_lsn and end_lsn. Returns one row per WAL record. If start_lsn - or end_lsn are not yet available, the - function will raise an error. For example: + is not yet available, it will raise an error. If end_lsn + isn't specified, it returns information till the end of WAL. For example, + usage of the function is as follows: postgres=# SELECT * FROM pg_get_wal_records_info('0/1E913618', '0/1E913740') LIMIT 1; -[ RECORD 1 ]----+-------------------------------------------------------------- @@ -116,27 +118,10 @@ block_ref | - - - - pg_get_wal_records_info_till_end_of_wal(start_lsn pg_lsn) - returns setof record - - - - - - This function is the same as pg_get_wal_records_info(), - except that it gets information of all the valid WAL records from - start_lsn till the end of WAL. - - - - - pg_get_wal_stats(start_lsn pg_lsn, end_lsn pg_lsn, per_record boolean DEFAULT false) + pg_get_wal_stats(start_lsn pg_lsn, end_lsn pg_lsn DEFAULT NULL, per_record boolean DEFAULT false) returns setof record @@ -149,9 +134,10 @@ block_ref | resource_manager type. When per_record is set to true, it returns one row per record_type. - If start_lsn - or end_lsn are not yet available, the - function will raise an error. For example: + If start_lsn is not yet available, it will + raise an error. If end_lsn isn't specified, it + returns information till the end of WAL. For example, usage of the + function is as follows: postgres=# SELECT * FROM pg_get_wal_stats('0/1E847D00', '0/1E84F500') WHERE count > 0 LIMIT 1 AND @@ -171,23 +157,6 @@ combined_size_percentage | 2.8634072910530795 - - - - pg_get_wal_stats_till_end_of_wal(start_lsn pg_lsn, per_record boolean DEFAULT false) - returns setof record - - - - - - This function is the same as pg_get_wal_stats(), - except that it gets statistics of all the valid WAL records from - start_lsn till end of WAL. - - - - pg_get_wal_block_info(start_lsn pg_lsn, end_lsn pg_lsn) returns setof record @@ -202,9 +171,10 @@ combined_size_percentage | 2.8634072910530795 and their information associated with all the valid WAL records between start_lsn and end_lsn. Returns one row per block registered - in a WAL record. If start_lsn or - end_lsn are not yet available, the function - will raise an error. For example: + in a WAL record. If start_lsn is not yet + available, it will raise an error. If end_lsn + isn't specified, it returns information till the end of WAL. For example, + usage of the function is as follows: postgres=# SELECT lsn, blockid, reltablespace, reldatabase, relfilenode, relblocknumber, forkname, @@ -227,8 +197,21 @@ fpiinfo | {HAS_HOLE,APPLY} - + + + + Note that pg_get_wal_records_info_till_end_of_wal and + pg_get_wal_stats_till_end_of_wal functions have been + removed in the pg_walinspect version + 1.1. The same functionality can be achieved with + pg_get_wal_records_info and + pg_get_wal_stats functions. However, + till_end_of_wal functions will still work if the + extension is installed explicitly with version 1.0. + + + -- 2.34.1