Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
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

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)