Skip to content

Commit 7cdfeee

Browse files
Add contrib/pg_logicalinspect.
This module provides SQL functions that allow to inspect logical decoding components. It currently allows to inspect the contents of serialized logical snapshots of a running database cluster, which is useful for debugging or educational purposes. Author: Bertrand Drouvot Reviewed-by: Amit Kapila, Shveta Malik, Peter Smith, Peter Eisentraut Reviewed-by: David G. Johnston Discussion: https://postgr.es/m/ZscuZ92uGh3wm4tW%40ip-10-97-1-34.eu-west-3.compute.internal
1 parent e2fd615 commit 7cdfeee

File tree

18 files changed

+598
-39
lines changed

18 files changed

+598
-39
lines changed

contrib/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ SUBDIRS = \
3232
passwordcheck \
3333
pg_buffercache \
3434
pg_freespacemap \
35+
pg_logicalinspect \
3536
pg_prewarm \
3637
pg_stat_statements \
3738
pg_surgery \

contrib/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ subdir('passwordcheck')
4646
subdir('pg_buffercache')
4747
subdir('pgcrypto')
4848
subdir('pg_freespacemap')
49+
subdir('pg_logicalinspect')
4950
subdir('pg_prewarm')
5051
subdir('pgrowlocks')
5152
subdir('pg_stat_statements')

contrib/pg_logicalinspect/.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Generated subdirectories
2+
/log/
3+
/results/
4+
/output_iso/
5+
/tmp_check/
6+
/tmp_check_iso/

contrib/pg_logicalinspect/Makefile

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# contrib/pg_logicalinspect/Makefile
2+
3+
MODULE_big = pg_logicalinspect
4+
OBJS = \
5+
$(WIN32RES) \
6+
pg_logicalinspect.o
7+
PGFILEDESC = "pg_logicalinspect - functions to inspect logical decoding components"
8+
9+
EXTENSION = pg_logicalinspect
10+
DATA = pg_logicalinspect--1.0.sql
11+
12+
EXTRA_INSTALL = contrib/test_decoding
13+
14+
ISOLATION = logical_inspect
15+
16+
ISOLATION_OPTS = --temp-config $(top_srcdir)/contrib/pg_logicalinspect/logicalinspect.conf
17+
18+
# Disabled because these tests require "wal_level=logical", which
19+
# some installcheck users do not have (e.g. buildfarm clients).
20+
NO_INSTALLCHECK = 1
21+
22+
ifdef USE_PGXS
23+
PG_CONFIG = pg_config
24+
PGXS := $(shell $(PG_CONFIG) --pgxs)
25+
include $(PGXS)
26+
else
27+
subdir = contrib/pg_logicalinspect
28+
top_builddir = ../..
29+
include $(top_builddir)/src/Makefile.global
30+
include $(top_srcdir)/contrib/contrib-global.mk
31+
endif
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
Parsed test spec with 2 sessions
2+
3+
starting permutation: s0_init s0_begin s0_savepoint s0_truncate s1_checkpoint s1_get_changes s0_commit s0_begin s0_insert s1_checkpoint s1_get_changes s0_commit s1_get_changes s1_get_logical_snapshot_info s1_get_logical_snapshot_meta
4+
step s0_init: SELECT 'init' FROM pg_create_logical_replication_slot('isolation_slot', 'test_decoding');
5+
?column?
6+
--------
7+
init
8+
(1 row)
9+
10+
step s0_begin: BEGIN;
11+
step s0_savepoint: SAVEPOINT sp1;
12+
step s0_truncate: TRUNCATE tbl1;
13+
step s1_checkpoint: CHECKPOINT;
14+
step s1_get_changes: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'skip-empty-xacts', '1', 'include-xids', '0');
15+
data
16+
----
17+
(0 rows)
18+
19+
step s0_commit: COMMIT;
20+
step s0_begin: BEGIN;
21+
step s0_insert: INSERT INTO tbl1 VALUES (1);
22+
step s1_checkpoint: CHECKPOINT;
23+
step s1_get_changes: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'skip-empty-xacts', '1', 'include-xids', '0');
24+
data
25+
---------------------------------------
26+
BEGIN
27+
table public.tbl1: TRUNCATE: (no-flags)
28+
COMMIT
29+
(3 rows)
30+
31+
step s0_commit: COMMIT;
32+
step s1_get_changes: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'skip-empty-xacts', '1', 'include-xids', '0');
33+
data
34+
-------------------------------------------------------------
35+
BEGIN
36+
table public.tbl1: INSERT: val1[integer]:1 val2[integer]:null
37+
COMMIT
38+
(3 rows)
39+
40+
step s1_get_logical_snapshot_info: SELECT info.state, info.catchange_count, array_length(info.catchange_xip,1) AS catchange_array_length, info.committed_count, array_length(info.committed_xip,1) AS committed_array_length FROM pg_ls_logicalsnapdir(), pg_get_logical_snapshot_info(name) AS info ORDER BY 2;
41+
state |catchange_count|catchange_array_length|committed_count|committed_array_length
42+
----------+---------------+----------------------+---------------+----------------------
43+
consistent| 0| | 2| 2
44+
consistent| 2| 2| 0|
45+
(2 rows)
46+
47+
step s1_get_logical_snapshot_meta: SELECT COUNT(meta.*) from pg_ls_logicalsnapdir(), pg_get_logical_snapshot_meta(name) as meta;
48+
count
49+
-----
50+
2
51+
(1 row)
52+
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
wal_level = logical

contrib/pg_logicalinspect/meson.build

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Copyright (c) 2024, PostgreSQL Global Development Group
2+
3+
pg_logicalinspect_sources = files('pg_logicalinspect.c')
4+
5+
if host_system == 'windows'
6+
pg_logicalinspect_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
7+
'--NAME', 'pg_logicalinspect',
8+
'--FILEDESC', 'pg_logicalinspect - functions to inspect logical decoding components',])
9+
endif
10+
11+
pg_logicalinspect = shared_module('pg_logicalinspect',
12+
pg_logicalinspect_sources,
13+
kwargs: contrib_mod_args + {
14+
'dependencies': contrib_mod_args['dependencies'],
15+
},
16+
)
17+
contrib_targets += pg_logicalinspect
18+
19+
install_data(
20+
'pg_logicalinspect.control',
21+
'pg_logicalinspect--1.0.sql',
22+
kwargs: contrib_data_args,
23+
)
24+
25+
tests += {
26+
'name': 'pg_logicalinspect',
27+
'sd': meson.current_source_dir(),
28+
'bd': meson.current_build_dir(),
29+
'isolation': {
30+
'specs': [
31+
'logical_inspect',
32+
],
33+
'regress_args': [
34+
'--temp-config', files('logicalinspect.conf'),
35+
],
36+
# see above
37+
'runningcheck': false,
38+
},
39+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/* contrib/pg_logicalinspect/pg_logicalinspect--1.0.sql */
2+
3+
-- complain if script is sourced in psql, rather than via CREATE EXTENSION
4+
\echo Use "CREATE EXTENSION pg_logicalinspect" to load this file. \quit
5+
6+
--
7+
-- pg_get_logical_snapshot_meta()
8+
--
9+
CREATE FUNCTION pg_get_logical_snapshot_meta(IN filename text,
10+
OUT magic int4,
11+
OUT checksum int8,
12+
OUT version int4
13+
)
14+
AS 'MODULE_PATHNAME', 'pg_get_logical_snapshot_meta'
15+
LANGUAGE C STRICT PARALLEL SAFE;
16+
17+
REVOKE EXECUTE ON FUNCTION pg_get_logical_snapshot_meta(text) FROM PUBLIC;
18+
GRANT EXECUTE ON FUNCTION pg_get_logical_snapshot_meta(text) TO pg_read_server_files;
19+
20+
--
21+
-- pg_get_logical_snapshot_info()
22+
--
23+
CREATE FUNCTION pg_get_logical_snapshot_info(IN filename text,
24+
OUT state text,
25+
OUT xmin xid,
26+
OUT xmax xid,
27+
OUT start_decoding_at pg_lsn,
28+
OUT two_phase_at pg_lsn,
29+
OUT initial_xmin_horizon xid,
30+
OUT building_full_snapshot boolean,
31+
OUT in_slot_creation boolean,
32+
OUT last_serialized_snapshot pg_lsn,
33+
OUT next_phase_at xid,
34+
OUT committed_count int4,
35+
OUT committed_xip xid[],
36+
OUT catchange_count int4,
37+
OUT catchange_xip xid[]
38+
)
39+
AS 'MODULE_PATHNAME', 'pg_get_logical_snapshot_info'
40+
LANGUAGE C STRICT PARALLEL SAFE;
41+
42+
REVOKE EXECUTE ON FUNCTION pg_get_logical_snapshot_info(text) FROM PUBLIC;
43+
GRANT EXECUTE ON FUNCTION pg_get_logical_snapshot_info(text) TO pg_read_server_files;
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
/*-------------------------------------------------------------------------
2+
*
3+
* pg_logicalinspect.c
4+
* Functions to inspect contents of PostgreSQL logical snapshots
5+
*
6+
* Copyright (c) 2024, PostgreSQL Global Development Group
7+
*
8+
* IDENTIFICATION
9+
* contrib/pg_logicalinspect/pg_logicalinspect.c
10+
*
11+
*-------------------------------------------------------------------------
12+
*/
13+
#include "postgres.h"
14+
15+
#include "funcapi.h"
16+
#include "replication/snapbuild_internal.h"
17+
#include "utils/array.h"
18+
#include "utils/builtins.h"
19+
#include "utils/pg_lsn.h"
20+
21+
PG_MODULE_MAGIC;
22+
23+
PG_FUNCTION_INFO_V1(pg_get_logical_snapshot_meta);
24+
PG_FUNCTION_INFO_V1(pg_get_logical_snapshot_info);
25+
26+
/* Return the description of SnapBuildState */
27+
static const char *
28+
get_snapbuild_state_desc(SnapBuildState state)
29+
{
30+
const char *stateDesc = "unknown state";
31+
32+
switch (state)
33+
{
34+
case SNAPBUILD_START:
35+
stateDesc = "start";
36+
break;
37+
case SNAPBUILD_BUILDING_SNAPSHOT:
38+
stateDesc = "building";
39+
break;
40+
case SNAPBUILD_FULL_SNAPSHOT:
41+
stateDesc = "full";
42+
break;
43+
case SNAPBUILD_CONSISTENT:
44+
stateDesc = "consistent";
45+
break;
46+
}
47+
48+
return stateDesc;
49+
}
50+
51+
/*
52+
* Retrieve the logical snapshot file metadata.
53+
*/
54+
Datum
55+
pg_get_logical_snapshot_meta(PG_FUNCTION_ARGS)
56+
{
57+
#define PG_GET_LOGICAL_SNAPSHOT_META_COLS 3
58+
SnapBuildOnDisk ondisk;
59+
HeapTuple tuple;
60+
Datum values[PG_GET_LOGICAL_SNAPSHOT_META_COLS] = {0};
61+
bool nulls[PG_GET_LOGICAL_SNAPSHOT_META_COLS] = {0};
62+
TupleDesc tupdesc;
63+
char path[MAXPGPATH];
64+
int i = 0;
65+
text *filename_t = PG_GETARG_TEXT_PP(0);
66+
67+
/* Build a tuple descriptor for our result type */
68+
if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
69+
elog(ERROR, "return type must be a row type");
70+
71+
sprintf(path, "%s/%s",
72+
PG_LOGICAL_SNAPSHOTS_DIR,
73+
text_to_cstring(filename_t));
74+
75+
/* Validate and restore the snapshot to 'ondisk' */
76+
SnapBuildRestoreSnapshot(&ondisk, path, CurrentMemoryContext, false);
77+
78+
values[i++] = UInt32GetDatum(ondisk.magic);
79+
values[i++] = Int64GetDatum((int64) ondisk.checksum);
80+
values[i++] = UInt32GetDatum(ondisk.version);
81+
82+
Assert(i == PG_GET_LOGICAL_SNAPSHOT_META_COLS);
83+
84+
tuple = heap_form_tuple(tupdesc, values, nulls);
85+
86+
PG_RETURN_DATUM(HeapTupleGetDatum(tuple));
87+
88+
#undef PG_GET_LOGICAL_SNAPSHOT_META_COLS
89+
}
90+
91+
Datum
92+
pg_get_logical_snapshot_info(PG_FUNCTION_ARGS)
93+
{
94+
#define PG_GET_LOGICAL_SNAPSHOT_INFO_COLS 14
95+
SnapBuildOnDisk ondisk;
96+
HeapTuple tuple;
97+
Datum values[PG_GET_LOGICAL_SNAPSHOT_INFO_COLS] = {0};
98+
bool nulls[PG_GET_LOGICAL_SNAPSHOT_INFO_COLS] = {0};
99+
TupleDesc tupdesc;
100+
char path[MAXPGPATH];
101+
int i = 0;
102+
text *filename_t = PG_GETARG_TEXT_PP(0);
103+
104+
/* Build a tuple descriptor for our result type */
105+
if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
106+
elog(ERROR, "return type must be a row type");
107+
108+
sprintf(path, "%s/%s",
109+
PG_LOGICAL_SNAPSHOTS_DIR,
110+
text_to_cstring(filename_t));
111+
112+
/* Validate and restore the snapshot to 'ondisk' */
113+
SnapBuildRestoreSnapshot(&ondisk, path, CurrentMemoryContext, false);
114+
115+
values[i++] = CStringGetTextDatum(get_snapbuild_state_desc(ondisk.builder.state));
116+
values[i++] = TransactionIdGetDatum(ondisk.builder.xmin);
117+
values[i++] = TransactionIdGetDatum(ondisk.builder.xmax);
118+
values[i++] = LSNGetDatum(ondisk.builder.start_decoding_at);
119+
values[i++] = LSNGetDatum(ondisk.builder.two_phase_at);
120+
values[i++] = TransactionIdGetDatum(ondisk.builder.initial_xmin_horizon);
121+
values[i++] = BoolGetDatum(ondisk.builder.building_full_snapshot);
122+
values[i++] = BoolGetDatum(ondisk.builder.in_slot_creation);
123+
values[i++] = LSNGetDatum(ondisk.builder.last_serialized_snapshot);
124+
values[i++] = TransactionIdGetDatum(ondisk.builder.next_phase_at);
125+
126+
values[i++] = UInt32GetDatum(ondisk.builder.committed.xcnt);
127+
if (ondisk.builder.committed.xcnt > 0)
128+
{
129+
Datum *arrayelems;
130+
131+
arrayelems = (Datum *) palloc(ondisk.builder.committed.xcnt * sizeof(Datum));
132+
133+
for (int j = 0; j < ondisk.builder.committed.xcnt; j++)
134+
arrayelems[j] = TransactionIdGetDatum(ondisk.builder.committed.xip[j]);
135+
136+
values[i++] = PointerGetDatum(construct_array_builtin(arrayelems,
137+
ondisk.builder.committed.xcnt,
138+
XIDOID));
139+
}
140+
else
141+
nulls[i++] = true;
142+
143+
values[i++] = UInt32GetDatum(ondisk.builder.catchange.xcnt);
144+
if (ondisk.builder.catchange.xcnt > 0)
145+
{
146+
Datum *arrayelems;
147+
148+
arrayelems = (Datum *) palloc(ondisk.builder.catchange.xcnt * sizeof(Datum));
149+
150+
for (int j = 0; j < ondisk.builder.catchange.xcnt; j++)
151+
arrayelems[j] = TransactionIdGetDatum(ondisk.builder.catchange.xip[j]);
152+
153+
values[i++] = PointerGetDatum(construct_array_builtin(arrayelems,
154+
ondisk.builder.catchange.xcnt,
155+
XIDOID));
156+
}
157+
else
158+
nulls[i++] = true;
159+
160+
Assert(i == PG_GET_LOGICAL_SNAPSHOT_INFO_COLS);
161+
162+
tuple = heap_form_tuple(tupdesc, values, nulls);
163+
164+
PG_RETURN_DATUM(HeapTupleGetDatum(tuple));
165+
166+
#undef PG_GET_LOGICAL_SNAPSHOT_INFO_COLS
167+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# pg_logicalinspect extension
2+
comment = 'functions to inspect logical decoding components'
3+
default_version = '1.0'
4+
module_pathname = '$libdir/pg_logicalinspect'
5+
relocatable = true

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy