diff --git a/doc/xml/release/2025/2.55.0.xml b/doc/xml/release/2025/2.55.0.xml
index 19a0be42c..6ef54d0a8 100644
--- a/doc/xml/release/2025/2.55.0.xml
+++ b/doc/xml/release/2025/2.55.0.xml
@@ -92,6 +92,17 @@
Add support for S3/GCS requester pays.
+
+
+
+
+
+
+
+
+ PostgreSQL 18 experimental support.
+
+
diff --git a/src/build/postgres/postgres.yaml b/src/build/postgres/postgres.yaml
index ecc981da8..38b0d6c2b 100644
--- a/src/build/postgres/postgres.yaml
+++ b/src/build/postgres/postgres.yaml
@@ -15,3 +15,5 @@ version:
- 15
- 16
- 17
+ - 18:
+ release: false
diff --git a/src/postgres/interface/version.vendor.h b/src/postgres/interface/version.vendor.h
index bb5b21952..81412cf65 100644
--- a/src/postgres/interface/version.vendor.h
+++ b/src/postgres/interface/version.vendor.h
@@ -154,6 +154,19 @@ Types from src/include/catalog/catversion.h
// ---------------------------------------------------------------------------------------------------------------------------------
#if PG_VERSION > PG_VERSION_MAX
+#elif PG_VERSION >= PG_VERSION_18
+
+/*
+ * We could use anything we wanted for version numbers, but I recommend
+ * following the "YYYYMMDDN" style often used for DNS zone serial numbers.
+ * YYYYMMDD are the date of the change, and N is the number of the change
+ * on that day. (Hopefully we'll never commit ten independent sets of
+ * catalog changes on the same day...)
+ */
+
+/* yyyymmddN */
+#define CATALOG_VERSION_NO 202504091
+
#elif PG_VERSION >= PG_VERSION_17
/*
@@ -316,6 +329,11 @@ Types from src/include/catalog/pg_control.h
// ---------------------------------------------------------------------------------------------------------------------------------
#if PG_VERSION > PG_VERSION_MAX
+#elif PG_VERSION >= PG_VERSION_18
+
+/* Version identifier for this pg_control format */
+#define PG_CONTROL_VERSION 1800
+
#elif PG_VERSION >= PG_VERSION_17
/* Version identifier for this pg_control format */
@@ -590,6 +608,148 @@ typedef enum DBState
// ---------------------------------------------------------------------------------------------------------------------------------
#if PG_VERSION > PG_VERSION_MAX
+#elif PG_VERSION >= PG_VERSION_18
+
+/*
+ * Contents of pg_control.
+ */
+typedef struct ControlFileData
+{
+ /*
+ * Unique system identifier --- to ensure we match up xlog files with the
+ * installation that produced them.
+ */
+ uint64 system_identifier;
+
+ /*
+ * Version identifier information. Keep these fields at the same offset,
+ * especially pg_control_version; they won't be real useful if they move
+ * around. (For historical reasons they must be 8 bytes into the file
+ * rather than immediately at the front.)
+ *
+ * pg_control_version identifies the format of pg_control itself.
+ * catalog_version_no identifies the format of the system catalogs.
+ *
+ * There are additional version identifiers in individual files; for
+ * example, WAL logs contain per-page magic numbers that can serve as
+ * version cues for the WAL log.
+ */
+ uint32 pg_control_version; /* PG_CONTROL_VERSION */
+ uint32 catalog_version_no; /* see catversion.h */
+
+ /*
+ * System status data
+ */
+ DBState state; /* see enum above */
+ pg_time_t time; /* time stamp of last pg_control update */
+ XLogRecPtr checkPoint; /* last check point record ptr */
+
+ CheckPoint checkPointCopy; /* copy of last check point record */
+
+ XLogRecPtr unloggedLSN; /* current fake LSN value, for unlogged rels */
+
+ /*
+ * These two values determine the minimum point we must recover up to
+ * before starting up:
+ *
+ * minRecoveryPoint is updated to the latest replayed LSN whenever we
+ * flush a data change during archive recovery. That guards against
+ * starting archive recovery, aborting it, and restarting with an earlier
+ * stop location. If we've already flushed data changes from WAL record X
+ * to disk, we mustn't start up until we reach X again. Zero when not
+ * doing archive recovery.
+ *
+ * backupStartPoint is the redo pointer of the backup start checkpoint, if
+ * we are recovering from an online backup and haven't reached the end of
+ * backup yet. It is reset to zero when the end of backup is reached, and
+ * we mustn't start up before that. A boolean would suffice otherwise, but
+ * we use the redo pointer as a cross-check when we see an end-of-backup
+ * record, to make sure the end-of-backup record corresponds the base
+ * backup we're recovering from.
+ *
+ * backupEndPoint is the backup end location, if we are recovering from an
+ * online backup which was taken from the standby and haven't reached the
+ * end of backup yet. It is initialized to the minimum recovery point in
+ * pg_control which was backed up last. It is reset to zero when the end
+ * of backup is reached, and we mustn't start up before that.
+ *
+ * If backupEndRequired is true, we know for sure that we're restoring
+ * from a backup, and must see a backup-end record before we can safely
+ * start up.
+ */
+ XLogRecPtr minRecoveryPoint;
+ TimeLineID minRecoveryPointTLI;
+ XLogRecPtr backupStartPoint;
+ XLogRecPtr backupEndPoint;
+ bool backupEndRequired;
+
+ /*
+ * Parameter settings that determine if the WAL can be used for archival
+ * or hot standby.
+ */
+ int wal_level;
+ bool wal_log_hints;
+ int MaxConnections;
+ int max_worker_processes;
+ int max_wal_senders;
+ int max_prepared_xacts;
+ int max_locks_per_xact;
+ bool track_commit_timestamp;
+
+ /*
+ * This data is used to check for hardware-architecture compatibility of
+ * the database and the backend executable. We need not check endianness
+ * explicitly, since the pg_control version will surely look wrong to a
+ * machine of different endianness, but we do need to worry about MAXALIGN
+ * and floating-point format. (Note: storage layout nominally also
+ * depends on SHORTALIGN and INTALIGN, but in practice these are the same
+ * on all architectures of interest.)
+ *
+ * Testing just one double value is not a very bulletproof test for
+ * floating-point compatibility, but it will catch most cases.
+ */
+ uint32 maxAlign; /* alignment requirement for tuples */
+ double floatFormat; /* constant 1234567.0 */
+#define FLOATFORMAT_VALUE 1234567.0
+
+ /*
+ * This data is used to make sure that configuration of this database is
+ * compatible with the backend executable.
+ */
+ uint32 blcksz; /* data block size for this DB */
+ uint32 relseg_size; /* blocks per segment of large relation */
+
+ uint32 xlog_blcksz; /* block size within WAL files */
+ uint32 xlog_seg_size; /* size of each WAL segment */
+
+ uint32 nameDataLen; /* catalog name field width */
+ uint32 indexMaxKeys; /* max number of columns in an index */
+
+ uint32 toast_max_chunk_size; /* chunk size in TOAST tables */
+ uint32 loblksize; /* chunk size in pg_largeobject */
+
+ bool float8ByVal; /* float8, int8, etc pass-by-value? */
+
+ /* Are data pages protected by checksums? Zero if no checksum version */
+ uint32 data_checksum_version;
+
+ /*
+ * True if the default signedness of char is "signed" on a platform where
+ * the cluster is initialized.
+ */
+ bool default_char_signedness;
+
+ /*
+ * Random nonce, used in authentication requests that need to proceed
+ * based on values that are cluster-unique, like a SASL exchange that
+ * failed at an early stage.
+ */
+ char mock_authentication_nonce[MOCK_AUTH_NONCE_LEN];
+
+ /* CRC of all above ... MUST BE LAST! */
+ pg_crc32c crc;
+} ControlFileData;
+
#elif PG_VERSION >= PG_VERSION_15
/*
@@ -1572,6 +1732,10 @@ Types from src/include/access/xlog_internal.h
// ---------------------------------------------------------------------------------------------------------------------------------
#if PG_VERSION > PG_VERSION_MAX
+#elif PG_VERSION >= PG_VERSION_18
+
+#define XLOG_PAGE_MAGIC 0xD118 /* can be used as WAL version indicator */
+
#elif PG_VERSION >= PG_VERSION_17
#define XLOG_PAGE_MAGIC 0xD116 /* can be used as WAL version indicator */
diff --git a/src/postgres/version.auto.h b/src/postgres/version.auto.h
index 986048010..60bc7397e 100644
--- a/src/postgres/version.auto.h
+++ b/src/postgres/version.auto.h
@@ -19,8 +19,9 @@ PostgreSQL version constants
#define PG_VERSION_15 150000
#define PG_VERSION_16 160000
#define PG_VERSION_17 170000
+#define PG_VERSION_18 180000
-#define PG_VERSION_MAX PG_VERSION_17
+#define PG_VERSION_MAX PG_VERSION_18
/***********************************************************************************************************************************
PostgreSQL version string constants for use in error messages
@@ -35,5 +36,6 @@ PostgreSQL version string constants for use in error messages
#define PG_VERSION_15_Z "15"
#define PG_VERSION_16_Z "16"
#define PG_VERSION_17_Z "17"
+#define PG_VERSION_18_Z "18"
#endif
diff --git a/test/container.yaml b/test/container.yaml
index ad6461d50..a94090f69 100644
--- a/test/container.yaml
+++ b/test/container.yaml
@@ -12,16 +12,19 @@
# - docker login -u pgbackrest
# - DATE=YYYYMMDDX;VM=X;ARCH=X;BASE=pgbackrest/test:${VM?}-base-${ARCH?};docker tag ${BASE?} ${BASE?}-${DATE?} && docker push ${BASE?}-${DATE?}
# **********************************************************************************************************************************
-20250228A:
+20250413A:
ppc64le:
- u22: 28fa02cb370bbdacadf984afb215d3973ed5ab3e
+ u22: a07ecf5a76b633c1c0c54ba4a836ed4c19f710b6
s390x:
- u22: f3e108dd7f808f6b0e82eadcfd72f585a7632530
+ u22: 535e05dc96aa24dd93b8801ed213c13d9d7a0ee4
+ x86_64:
+ u22: 3a2bd0b5842fd97cf4f67ecc990816efc78cb799
+
+20250228A:
x86_64:
d11: 01e6970744c2b2529a14832e92cb861c7da94308
f41: ce870455184e991e0efd90176da1412f0f3f72a2
rh8: 4d141c845abfbdbf402ba447cf2bd2e4357c8a63
u20: 862159b4d2169a4752b106639ca0f47c1ebb1f86
- u22: df6cb8401b0e8d55d856e0ec1a9387a5884a2b63
diff --git a/test/lib/pgBackRestTest/Common/ContainerTest.pm b/test/lib/pgBackRestTest/Common/ContainerTest.pm
index 88cca3d9e..8c6252aba 100644
--- a/test/lib/pgBackRestTest/Common/ContainerTest.pm
+++ b/test/lib/pgBackRestTest/Common/ContainerTest.pm
@@ -497,13 +497,17 @@ sub containerBuild
$strScript .=
" echo \"deb http://apt.postgresql.org/pub/repos/apt/ \$(lsb_release -s -c)-pgdg main" .
"\" >> /etc/apt/sources.list.d/pgdg.list && \\\n" .
+ ($strOS eq VM_U22 ?
+ " echo \"deb http://apt.postgresql.org/pub/repos/apt/ \$(lsb_release -s -c)-pgdg-snapshot main" .
+ " 18\" >> /etc/apt/sources.list.d/pgdg.list && \\\n" : '') .
" wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - && \\\n" .
" apt-get update && \\\n";
}
$strScript .=
- " apt-get install -y --no-install-recommends postgresql-common libpq-dev && \\\n" .
- " sed -i 's/^\\#create\\_main\\_cluster.*\$/create\\_main\\_cluster \\= false/' " .
+ " apt-get install -y --no-install-recommends" .
+ ($strOS eq VM_U22 ? " -t \$(lsb_release -s -c)-pgdg-snapshot" : '') . " postgresql-common libpq-dev && \\\n" .
+ " sed -i 's/^\\#create\\_main\\_cluster.*\$/create\\_main\\_cluster \\= false/' " .
"/etc/postgresql-common/createcluster.conf";
}
@@ -518,8 +522,10 @@ sub containerBuild
}
else
{
- $strScript .= " apt-get install -y --no-install-recommends";
- }
+ $strScript .=
+ " apt-get install -y --no-install-recommends" .
+ ($strOS eq VM_U22 ? " -t \$(lsb_release -s -c)-pgdg-snapshot" : '');
+ }
# Construct list of databases to install
foreach my $strDbVersion (@{$oOS->{&VM_DB}})
diff --git a/test/lib/pgBackRestTest/Common/DbVersion.pm b/test/lib/pgBackRestTest/Common/DbVersion.pm
index db70baced..c994c1ddc 100644
--- a/test/lib/pgBackRestTest/Common/DbVersion.pm
+++ b/test/lib/pgBackRestTest/Common/DbVersion.pm
@@ -35,6 +35,8 @@ use constant PG_VERSION_16 => '16';
push @EXPORT, qw(PG_VERSION_16);
use constant PG_VERSION_17 => '17';
push @EXPORT, qw(PG_VERSION_17);
+use constant PG_VERSION_18 => '18';
+ push @EXPORT, qw(PG_VERSION_18);
####################################################################################################################################
# versionSupport
@@ -47,7 +49,7 @@ sub versionSupport
my ($strOperation) = logDebugParam(__PACKAGE__ . '->versionSupport');
my @strySupportVersion = (PG_VERSION_95, PG_VERSION_96, PG_VERSION_10, PG_VERSION_11, PG_VERSION_12, PG_VERSION_13,
- PG_VERSION_14, PG_VERSION_15, PG_VERSION_16, PG_VERSION_17);
+ PG_VERSION_14, PG_VERSION_15, PG_VERSION_16, PG_VERSION_17, PG_VERSION_18);
# Return from function and log return values if any
return logDebugReturn
diff --git a/test/lib/pgBackRestTest/Common/VmTest.pm b/test/lib/pgBackRestTest/Common/VmTest.pm
index 89eeddbc1..22bd1e7d5 100644
--- a/test/lib/pgBackRestTest/Common/VmTest.pm
+++ b/test/lib/pgBackRestTest/Common/VmTest.pm
@@ -245,6 +245,7 @@ my $oyVm =
PG_VERSION_15,
PG_VERSION_16,
PG_VERSION_17,
+ PG_VERSION_18,
],
&VM_DB_TEST =>
@@ -253,6 +254,7 @@ my $oyVm =
PG_VERSION_11,
PG_VERSION_12,
PG_VERSION_17,
+ PG_VERSION_18,
],
},
};
diff --git a/test/src/common/harnessPostgres.c b/test/src/common/harnessPostgres.c
index 7955cac01..a6bff3f6d 100644
--- a/test/src/common/harnessPostgres.c
+++ b/test/src/common/harnessPostgres.c
@@ -55,6 +55,10 @@ uint32_t hrnPgInterfaceCatalogVersion170(void);
void hrnPgInterfaceControl170(unsigned int controlVersion, unsigned int crc, PgControl pgControl, unsigned char *buffer);
void hrnPgInterfaceWal170(unsigned int magic, PgWal pgWal, unsigned char *buffer);
+uint32_t hrnPgInterfaceCatalogVersion180(void);
+void hrnPgInterfaceControl180(unsigned int controlVersion, unsigned int crc, PgControl pgControl, unsigned char *buffer);
+void hrnPgInterfaceWal180(unsigned int magic, PgWal pgWal, unsigned char *buffer);
+
typedef struct HrnPgInterface
{
// Version of PostgreSQL supported by this interface
@@ -72,6 +76,13 @@ typedef struct HrnPgInterface
static const HrnPgInterface hrnPgInterface[] =
{
+ {
+ .version = PG_VERSION_18,
+
+ .catalogVersion = hrnPgInterfaceCatalogVersion180,
+ .control = hrnPgInterfaceControl180,
+ .wal = hrnPgInterfaceWal180,
+ },
{
.version = PG_VERSION_17,
diff --git a/test/src/common/harnessPostgres.h b/test/src/common/harnessPostgres.h
index 271665cb3..198a3401c 100644
--- a/test/src/common/harnessPostgres.h
+++ b/test/src/common/harnessPostgres.h
@@ -44,6 +44,8 @@ System id constants by version
#define HRN_PG_SYSTEMID_16_Z "10000000000000160000"
#define HRN_PG_SYSTEMID_17 (10000000000000000000ULL + (uint64_t)PG_VERSION_17)
#define HRN_PG_SYSTEMID_17_Z "10000000000000170000"
+#define HRN_PG_SYSTEMID_18 (10000000000000000000ULL + (uint64_t)PG_VERSION_18)
+#define HRN_PG_SYSTEMID_18_Z "10000000000000180000"
/***********************************************************************************************************************************
Put a control file to storage
diff --git a/test/src/common/harnessPostgres/harness180.c b/test/src/common/harnessPostgres/harness180.c
new file mode 100644
index 000000000..4f6ac895e
--- /dev/null
+++ b/test/src/common/harnessPostgres/harness180.c
@@ -0,0 +1,15 @@
+/***********************************************************************************************************************************
+Harness for PostgreSQL Interface (see PG_VERSION for version)
+***********************************************************************************************************************************/
+#include "build.auto.h"
+
+#define PG_VERSION PG_VERSION_18
+
+#include "common/harnessPostgres/harnessVersion.intern.h"
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wmissing-prototypes"
+
+HRN_PG_INTERFACE(180);
+
+#pragma GCC diagnostic pop
diff --git a/test/src/module/integration/allTest.c b/test/src/module/integration/allTest.c
index bca4ff54c..4a36a332b 100644
--- a/test/src/module/integration/allTest.c
+++ b/test/src/module/integration/allTest.c
@@ -29,6 +29,7 @@ static HrnHostTestDefine testMatrix[] =
{.pg = "15", .repo = "pg2", .tls = 0, .stg = "azure", .enc = 1, .cmp = "none", .rt = 2, .bnd = 1, .bi = 1},
{.pg = "16", .repo = "repo", .tls = 0, .stg = "sftp", .enc = 0, .cmp = "zst", .rt = 1, .bnd = 1, .bi = 1},
{.pg = "17", .repo = "repo", .tls = 0, .stg = "posix", .enc = 0, .cmp = "none", .rt = 1, .bnd = 0, .bi = 0},
+ {.pg = "18", .repo = "repo", .tls = 0, .stg = "posix", .enc = 0, .cmp = "none", .rt = 1, .bnd = 0, .bi = 0},
// {uncrustify_on}
};
diff --git a/test/src/module/postgres/interfaceTest.c b/test/src/module/postgres/interfaceTest.c
index 2a7bc7ecd..76f3714f6 100644
--- a/test/src/module/postgres/interfaceTest.c
+++ b/test/src/module/postgres/interfaceTest.c
@@ -140,7 +140,7 @@ testRun(void)
pgControlFromFile(storageTest, NULL), FormatError,
"wal segment size is 47 but must be a power of two between 1048576 and 1073741824 inclusive");
- HRN_PG_CONTROL_PUT(storageTest, PG_VERSION_17, .walSegmentSize = (unsigned int)2 * 1024 * 1024 * 1024);
+ HRN_PG_CONTROL_PUT(storageTest, PG_VERSION_18, .walSegmentSize = (unsigned int)2 * 1024 * 1024 * 1024);
TEST_ERROR(
pgControlFromFile(storageTest, NULL), FormatError,
@@ -225,13 +225,13 @@ testRun(void)
TEST_RESULT_UINT(info.pageSize, pgPageSize16, "check page size");
HRN_PG_CONTROL_PUT(
- storageTest, PG_VERSION_17, .systemId = 0xEFEFEFEFEF, .catalogVersion = hrnPgCatalogVersion(PG_VERSION_17),
+ storageTest, PG_VERSION_18, .systemId = 0xEFEFEFEFEF, .catalogVersion = hrnPgCatalogVersion(PG_VERSION_18),
.checkpoint = 0xAABBAABBEEFFEEFF, .timeline = 88, .pageSize = pgPageSize32);
TEST_ASSIGN(info, pgControlFromFile(storageTest, NULL), "get control info");
TEST_RESULT_UINT(info.systemId, 0xEFEFEFEFEF, "check system id");
- TEST_RESULT_UINT(info.version, PG_VERSION_17, "check version");
- TEST_RESULT_UINT(info.catalogVersion, 202406281, "check catalog version");
+ TEST_RESULT_UINT(info.version, PG_VERSION_18, "check version");
+ TEST_RESULT_UINT(info.catalogVersion, 202504091, "check catalog version");
TEST_RESULT_UINT(info.checkpoint, 0xAABBAABBEEFFEEFF, "check checkpoint");
TEST_RESULT_UINT(info.timeline, 88, "check timeline");
TEST_RESULT_UINT(info.pageSize, pgPageSize32, "check page size");