Subject: Add LZ4 compression in pg_dump

From: Georgios <[email-masked]>
Date: Fri, 25 Feb 2022 12:05:31 +0000

Hi,

please find attached a patchset which adds lz4 compression in pg_dump.

The first commit does the heavy lifting required for additional compression methods.
It expands testing coverage for the already supported gzip compression. Commit
bf9aa490db introduced cfp in compress_io.{c,h} with the intent of unifying
compression related code and allow for the introduction of additional archive
formats. However, pg_backup_archiver.c was not using that API. This commit
teaches pg_backup_archiver.c about cfp and is using it through out.

Furthermore, compression was chosen based on the value of the level passed
as an argument during the invocation of pg_dump or some hardcoded defaults. This
does not scale for more than one compression methods. Now the method used for
compression can be explicitly requested during command invocation, or set during
hardcoded defaults. Then it is stored in the relevant structs and passed in the
relevant functions, along side compression level which has lost it's special
meaning. The method for compression is not yet stored in the actual archive.
This is done in the next commit which does introduce a new method.

The previously named CompressionAlgorithm enum is changed for
CompressionMethod so that it matches better similar variables found through out
the code base.

In a fashion similar to the binary for pg_basebackup, the method for compression
is passed using the already existing -Z/--compress parameter of pg_dump. The
legacy format and behaviour is maintained. Additionally, the user can explicitly
pass a requested method and optionaly the level to be used after a semicolon,e.g. --compress=gzip:6

The second commit adds LZ4 compression in pg_dump and pg_restore.

Within compress_io.{c,h} there are two distinct APIs exposed, the streaming API
and a file API. The first one, is aimed at inlined use cases and thus simple
lz4.h calls can be used directly. The second one is generating output, or is
parsing input, which can be read/generated via the lz4 utility.

In the later case, the API is using an opaque wrapper around a file stream,
which aquired via fopen() or gzopen() respectively. It would then provide
wrappers around fread(), fwrite(), fgets(), fgetc(), feof(), and fclose(); or
their gz equivallents. However the LZ4F api does not provide this functionality.
So this has been implemented localy.

In order to maintain the API compatibility a new structure LZ4File is
introduced. It is responsible for keeping state and any yet unused generated
content. The later is required when the generated decompressed output, exceeds
the caller's buffer capacity.

Custom compressed archives need to now store the compression method in their
header. This requires a bump in the version number. The level of compression is
still stored in the dump, though admittedly is of no apparent use.

The series is authored by me. Rachel Heaton helped out with the expansion
of the testing coverage, testing in different platforms and providing debug information
on those, as well as native speaker wording.

Cheers,
//Georgios

From 8dfe12b08378571add6e1d178bed97a61e988593 Mon Sep 17 00:00:00 2001
From: Georgios Kokolatos <[email-masked]>
Date: Mon, 22 Nov 2021 09:58:49 +0000
Subject: [PATCH v1 1/2] Prepare pg_dump for additional compression methods

This commmit does the heavy lifting required for additional compression methods.
It expands testing coverage for the already supported gzip compression. Commit
bf9aa490db introduced cfp in compress_io.{c,h} with the intent of unifying
compression related code and allow for the introduction of additional archive
formats. However, pg_backup_archiver.c was not using that API. This commit
teaches pg_backup_archiver.c about cfp and is using it through out.

Furthermore, compression was chosen based on the value of the level passed
as an argument during the invocation of pg_dump or some hardcoded defaults. This
does not scale for more than one compression methods. Now the method used for
compression can be explicitly requested during command invocation, or set during
hardcoded defaults. Then it is stored in the relevant structs and passed in the
relevant functions, along side compression level which has lost it's special
meaning. The method for compression is not yet stored in the actual archive.
This is done in the next commit which does introduce a new method.

The previously named CompressionAlgorithm enum is changed for
CompressionMethod so that it matches better similar variables found through out
the code base.

In a fashion similar to the binary for pg_basebackup, the method for compression
is passed using the already existing -Z/--compress parameter of pg_dump. The
legacy format and behaviour is maintained. Additionally, the user can explicitly
pass a requested method and optionaly the level to be used after a semicolon,
e.g. --compress=gzip:6
---
 doc/src/sgml/ref/pg_dump.sgml         |  30 +-
 src/bin/pg_dump/Makefile              |   2 +
 src/bin/pg_dump/compress_io.c         | 416 ++++++++++++++++----------
 src/bin/pg_dump/compress_io.h         |  40 ++-
 src/bin/pg_dump/pg_backup.h           |  13 +-
 src/bin/pg_dump/pg_backup_archiver.c  | 171 +++++------
 src/bin/pg_dump/pg_backup_archiver.h  |  46 +--
 src/bin/pg_dump/pg_backup_custom.c    |  11 +-
 src/bin/pg_dump/pg_backup_directory.c |  12 +-
 src/bin/pg_dump/pg_backup_tar.c       |  16 +-
 src/bin/pg_dump/pg_dump.c             | 152 ++++++++--
 src/bin/pg_dump/t/001_basic.pl        |  10 +
 src/bin/pg_dump/t/002_pg_dump.pl      |  92 ++++++
 src/tools/pgindent/typedefs.list      |   2 +-
 14 files changed, 659 insertions(+), 354 deletions(-)

diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml
index 2f0042fd96..992b7312df 100644
--- a/doc/src/sgml/ref/pg_dump.sgml
+++ b/doc/src/sgml/ref/pg_dump.sgml
@@ -644,17 +644,31 @@ PostgreSQL documentation
      </varlistentry>
 
      <varlistentry>
-      <term><option>-Z <replaceable class="parameter">0..9</replaceable></option></term>
-      <term><option>--compress=<replaceable class="parameter">0..9</replaceable></option></term>
+      <term><option>-Z <replaceable class="parameter">level</replaceable></option></term>
+      <term><option>-Z <replaceable class="parameter">method</replaceable></option>[:<replaceable>level</replaceable>]</term>
+      <term><option>--compress=<replaceable class="parameter">level</replaceable></option></term>
+      <term><option>--compress=<replaceable class="parameter">method</replaceable></option>[:<replaceable>level</replaceable>]</term>
       <listitem>
        <para>
-        Specify the compression level to use.  Zero means no compression.
+        Specify the compression method and/or the compression level to use.
+        The compression method can be set to <literal>gzip</literal> or
+        <literal>none</literal> for no compression. A compression level can
+        be optionally specified, by appending the level number after a colon
+        (<literal>:</literal>). If no level is specified, the default compression
+        level will be used for the specified method. If only a level is
+        specified without mentioning a method, <literal>gzip</literal> compression
+        will be used.
+       </para>
+
+       <para>
         For the custom and directory archive formats, this specifies compression of
-        individual table-data segments, and the default is to compress
-        at a moderate level.
-        For plain text output, setting a nonzero compression level causes
-        the entire output file to be compressed, as though it had been
-        fed through <application>gzip</application>; but the default is not to compress.
+        individual table-data segments, and the default is to compress using
+        <literal>gzip</literal> at a moderate level. For plain text output,
+        setting a nonzero compression level causes the entire output file to be compressed,
+        as though it had been fed through <application>gzip</application>; but the default
+        is not to compress.
+       </para>
+       <para>
         The tar archive format currently does not support compression at all.
        </para>
       </listitem>
diff --git a/src/bin/pg_dump/Makefile b/src/bin/pg_dump/Makefile
index 84306d94ee..56e041c9b3 100644
--- a/src/bin/pg_dump/Makefile
+++ b/src/bin/pg_dump/Makefile
@@ -16,6 +16,8 @@ subdir = src/bin/pg_dump
 top_builddir = ../../..
 include $(top_builddir)/src/Makefile.global
 
+export GZIP_PROGRAM=$(GZIP)
+
 override CPPFLAGS := -I$(libpq_srcdir) $(CPPFLAGS)
 LDFLAGS_INTERNAL += -L$(top_builddir)/src/fe_utils -lpgfeutils $(libpq_pgport)
 
diff --git a/src/bin/pg_dump/compress_io.c b/src/bin/pg_dump/compress_io.c
index 9077fdb74d..630f9e4b18 100644
--- a/src/bin/pg_dump/compress_io.c
+++ b/src/bin/pg_dump/compress_io.c
@@ -64,7 +64,7 @@
 /* typedef appears in compress_io.h */
 struct CompressorState
 {
-	CompressionAlgorithm comprAlg;
+	CompressionMethod compressionMethod;
 	WriteFunc	writeF;
 
 #ifdef HAVE_LIBZ
@@ -74,9 +74,6 @@ struct CompressorState
 #endif
 };
 
-static void ParseCompressionOption(int compression, CompressionAlgorithm *alg,
-								   int *level);
-
 /* Routines that support zlib compressed data I/O */
 #ifdef HAVE_LIBZ
 static void InitCompressorZlib(CompressorState *cs, int level);
@@ -93,57 +90,30 @@ static void ReadDataFromArchiveNone(ArchiveHandle *AH, ReadFunc readF);
 static void WriteDataToArchiveNone(ArchiveHandle *AH, CompressorState *cs,
 								   const char *data, size_t dLen);
 
-/*
- * Interprets a numeric 'compression' value. The algorithm implied by the
- * value (zlib or none at the moment), is returned in *alg, and the
- * zlib compression level in *level.
- */
-static void
-ParseCompressionOption(int compression, CompressionAlgorithm *alg, int *level)
-{
-	if (compression == Z_DEFAULT_COMPRESSION ||
-		(compression > 0 && compression <= 9))
-		*alg = COMPR_ALG_LIBZ;
-	else if (compression == 0)
-		*alg = COMPR_ALG_NONE;
-	else
-	{
-		fatal("invalid compression code: %d", compression);
-		*alg = COMPR_ALG_NONE;	/* keep compiler quiet */
-	}
-
-	/* The level is just the passed-in value. */
-	if (level)
-		*level = compression;
-}
-
 /* Public interface routines */
 
 /* Allocate a new compressor */
 CompressorState *
-AllocateCompressor(int compression, WriteFunc writeF)
+AllocateCompressor(CompressionMethod compressionMethod,
+				   int compressionLevel, WriteFunc writeF)
 {
 	CompressorState *cs;
-	CompressionAlgorithm alg;
-	int			level;
-
-	ParseCompressionOption(compression, &alg, &level);
 
 #ifndef HAVE_LIBZ
-	if (alg == COMPR_ALG_LIBZ)
+	if (compressionMethod == COMPRESSION_GZIP)
 		fatal("not built with zlib support");
 #endif
 
 	cs = (CompressorState *) pg_malloc0(sizeof(CompressorState));
 	cs->writeF = writeF;
-	cs->comprAlg = alg;
+	cs->compressionMethod = compressionMethod;
 
 	/*
 	 * Perform compression algorithm specific initialization.
 	 */
 #ifdef HAVE_LIBZ
-	if (alg == COMPR_ALG_LIBZ)
-		InitCompressorZlib(cs, level);
+	if (compressionMethod == COMPRESSION_GZIP)
+		InitCompressorZlib(cs, compressionLevel);
 #endif
 
 	return cs;
@@ -154,21 +124,24 @@ AllocateCompressor(int compression, WriteFunc writeF)
  * out with ahwrite().
  */
 void
-ReadDataFromArchive(ArchiveHandle *AH, int compression, ReadFunc readF)
+ReadDataFromArchive(ArchiveHandle *AH, CompressionMethod compressionMethod,
+					int compressionLevel, ReadFunc readF)
 {
-	CompressionAlgorithm alg;
-
-	ParseCompressionOption(compression, &alg, NULL);
-
-	if (alg == COMPR_ALG_NONE)
-		ReadDataFromArchiveNone(AH, readF);
-	if (alg == COMPR_ALG_LIBZ)
+	switch (compressionMethod)
 	{
+		case COMPRESSION_NONE:
+			ReadDataFromArchiveNone(AH, readF);
+			break;
+		case COMPRESSION_GZIP:
 #ifdef HAVE_LIBZ
-		ReadDataFromArchiveZlib(AH, readF);
+			ReadDataFromArchiveZlib(AH, readF);
 #else
-		fatal("not built with zlib support");
+			fatal("not built with zlib support");
 #endif
+			break;
+		default:
+			fatal("invalid compression method");
+			break;
 	}
 }
 
@@ -179,18 +152,21 @@ void
 WriteDataToArchive(ArchiveHandle *AH, CompressorState *cs,
 				   const void *data, size_t dLen)
 {
-	switch (cs->comprAlg)
+	switch (cs->compressionMethod)
 	{
-		case COMPR_ALG_LIBZ:
+		case COMPRESSION_GZIP:
 #ifdef HAVE_LIBZ
 			WriteDataToArchiveZlib(AH, cs, data, dLen);
 #else
 			fatal("not built with zlib support");
 #endif
 			break;
-		case COMPR_ALG_NONE:
+		case COMPRESSION_NONE:
 			WriteDataToArchiveNone(AH, cs, data, dLen);
 			break;
+		default:
+			fatal("invalid compression method");
+			break;
 	}
 }
 
@@ -200,11 +176,23 @@ WriteDataToArchive(ArchiveHandle *AH, CompressorState *cs,
 void
 EndCompressor(ArchiveHandle *AH, CompressorState *cs)
 {
+	switch (cs->compressionMethod)
+	{
+		case COMPRESSION_GZIP:
 #ifdef HAVE_LIBZ
-	if (cs->comprAlg == COMPR_ALG_LIBZ)
-		EndCompressorZlib(AH, cs);
+			EndCompressorZlib(AH, cs);
+#else
+			fatal("not built with zlib support");
 #endif
-	free(cs);
+			break;
+		case COMPRESSION_NONE:
+			free(cs);
+			break;
+
+		default:
+			fatal("invalid compression method");
+			break;
+	}
 }
 
 /* Private routines, specific to each compression method. */
@@ -418,10 +406,8 @@ WriteDataToArchiveNone(ArchiveHandle *AH, CompressorState *cs,
  */
 struct cfp
 {
-	FILE	   *uncompressedfp;
-#ifdef HAVE_LIBZ
-	gzFile		compressedfp;
-#endif
+	CompressionMethod compressionMethod;
+	void	   *fp;
 };
 
 #ifdef HAVE_LIBZ
@@ -455,18 +441,18 @@ cfopen_read(const char *path, const char *mode)
 
 #ifdef HAVE_LIBZ
 	if (hasSuffix(path, ".gz"))
-		fp = cfopen(path, mode, 1);
+		fp = cfopen(path, mode, COMPRESSION_GZIP, 0);
 	else
 #endif
 	{
-		fp = cfopen(path, mode, 0);
+		fp = cfopen(path, mode, COMPRESSION_NONE, 0);
 #ifdef HAVE_LIBZ
 		if (fp == NULL)
 		{
 			char	   *fname;
 
 			fname = psprintf("%s.gz", path);
-			fp = cfopen(fname, mode, 1);
+			fp = cfopen(fname, mode, COMPRESSION_GZIP, 0);
 			free_keep_errno(fname);
 		}
 #endif
@@ -486,19 +472,21 @@ cfopen_read(const char *path, const char *mode)
  * On failure, return NULL with an error code in errno.
  */
 cfp *
-cfopen_write(const char *path, const char *mode, int compression)
+cfopen_write(const char *path, const char *mode,
+			 CompressionMethod compressionMethod,
+			 int compressionLevel)
 {
 	cfp		   *fp;
 
-	if (compression == 0)
-		fp = cfopen(path, mode, 0);
+	if (compressionMethod == COMPRESSION_NONE)
+		fp = cfopen(path, mode, compressionMethod, 0);
 	else
 	{
 #ifdef HAVE_LIBZ
 		char	   *fname;
 
 		fname = psprintf("%s.gz", path);
-		fp = cfopen(fname, mode, compression);
+		fp = cfopen(fname, mode, compressionMethod, compressionLevel);
 		free_keep_errno(fname);
 #else
 		fatal("not built with zlib support");
@@ -509,60 +497,94 @@ cfopen_write(const char *path, const char *mode, int compression)
 }
 
 /*
- * Opens file 'path' in 'mode'. If 'compression' is non-zero, the file
- * is opened with libz gzopen(), otherwise with plain fopen().
+ * This is the workhorse for cfopen() or cfdopen(). It opens file 'path' or
+ * associates a stream 'fd', if 'fd' is a valid descriptor, in 'mode'. The
+ * descriptor is not dup'ed and it is the caller's responsibility to do so.
+ * The caller must verify that the 'compressionMethod' is supported by the
+ * current build.
  *
  * On failure, return NULL with an error code in errno.
  */
-cfp *
-cfopen(const char *path, const char *mode, int compression)
+static cfp *
+cfopen_internal(const char *path, int fd, const char *mode,
+				CompressionMethod compressionMethod, int compressionLevel)
 {
 	cfp		   *fp = pg_malloc(sizeof(cfp));
 
-	if (compression != 0)
+	fp->compressionMethod = compressionMethod;
+
+	switch (compressionMethod)
 	{
-#ifdef HAVE_LIBZ
-		if (compression != Z_DEFAULT_COMPRESSION)
-		{
-			/* user has specified a compression level, so tell zlib to use it */
-			char		mode_compression[32];
+		case COMPRESSION_NONE:
+			if (fd >= 0)
+				fp->fp = fdopen(fd, mode);
+			else
+				fp->fp = fopen(path, mode);
+			if (fp->fp == NULL)
+			{
+				free_keep_errno(fp);
+				fp = NULL;
+			}
 
-			snprintf(mode_compression, sizeof(mode_compression), "%s%d",
-					 mode, compression);
-			fp->compressedfp = gzopen(path, mode_compression);
-		}
-		else
-		{
-			/* don't specify a level, just use the zlib default */
-			fp->compressedfp = gzopen(path, mode);
-		}
+			break;
+		case COMPRESSION_GZIP:
+#ifdef HAVE_LIBZ
+			if (compressionLevel != Z_DEFAULT_COMPRESSION)
+			{
+				/*
+				 * user has specified a compression level, so tell zlib to use
+				 * it
+				 */
+				char		mode_compression[32];
+
+				snprintf(mode_compression, sizeof(mode_compression), "%s%d",
+						 mode, compressionLevel);
+				if (fd >= 0)
+					fp->fp = gzdopen(fd, mode_compression);
+				else
+					fp->fp = gzopen(path, mode_compression);
+			}
+			else
+			{
+				/* don't specify a level, just use the zlib default */
+				if (fd >= 0)
+					fp->fp = gzdopen(fd, mode);
+				else
+					fp->fp = gzopen(path, mode);
+			}
 
-		fp->uncompressedfp = NULL;
-		if (fp->compressedfp == NULL)
-		{
-			free_keep_errno(fp);
-			fp = NULL;
-		}
+			if (fp->fp == NULL)
+			{
+				free_keep_errno(fp);
+				fp = NULL;
+			}
 #else
-		fatal("not built with zlib support");
-#endif
-	}
-	else
-	{
-#ifdef HAVE_LIBZ
-		fp->compressedfp = NULL;
+			fatal("not built with zlib support");
 #endif
-		fp->uncompressedfp = fopen(path, mode);
-		if (fp->uncompressedfp == NULL)
-		{
-			free_keep_errno(fp);
-			fp = NULL;
-		}
+			break;
+		default:
+			fatal("invalid compression method");
+			break;
 	}
 
 	return fp;
 }
 
+cfp *
+cfopen(const char *path, const char *mode,
+	   CompressionMethod compressionMethod,
+	   int compressionLevel)
+{
+	return cfopen_internal(path, -1, mode, compressionMethod, compressionLevel);
+}
+
+cfp *
+cfdopen(int fd, const char *mode,
+	   CompressionMethod compressionMethod,
+	   int compressionLevel)
+{
+	return cfopen_internal(NULL, fd, mode, compressionMethod, compressionLevel);
+}
 
 int
 cfread(void *ptr, int size, cfp *fp)
@@ -572,38 +594,61 @@ cfread(void *ptr, int size, cfp *fp)
 	if (size == 0)
 		return 0;
 
-#ifdef HAVE_LIBZ
-	if (fp->compressedfp)
+	switch (fp->compressionMethod)
 	{
-		ret = gzread(fp->compressedfp, ptr, size);
-		if (ret != size && !gzeof(fp->compressedfp))
-		{
-			int			errnum;
-			const char *errmsg = gzerror(fp->compressedfp, &errnum);
+		case COMPRESSION_NONE:
+			ret = fread(ptr, 1, size, fp->fp);
+			if (ret != size && !feof(fp->fp))
+				READ_ERROR_EXIT(fp->fp);
 
-			fatal("could not read from input file: %s",
-				  errnum == Z_ERRNO ? strerror(errno) : errmsg);
-		}
-	}
-	else
+			break;
+		case COMPRESSION_GZIP:
+#ifdef HAVE_LIBZ
+			ret = gzread(fp->fp, ptr, size);
+			if (ret != size && !gzeof(fp->fp))
+			{
+				int			errnum;
+				const char *errmsg = gzerror(fp->fp, &errnum);
+
+				fatal("could not read from input file: %s",
+					  errnum == Z_ERRNO ? strerror(errno) : errmsg);
+			}
+#else
+			fatal("not built with zlib support");
 #endif
-	{
-		ret = fread(ptr, 1, size, fp->uncompressedfp);
-		if (ret != size && !feof(fp->uncompressedfp))
-			READ_ERROR_EXIT(fp->uncompressedfp);
+			break;
+
+		default:
+			fatal("invalid compression method");
+			break;
 	}
+
 	return ret;
 }
 
 int
 cfwrite(const void *ptr, int size, cfp *fp)
 {
+	int			ret = 0;
+
+	switch (fp->compressionMethod)
+	{
+		case COMPRESSION_NONE:
+			ret = fwrite(ptr, 1, size, fp->fp);
+			break;
+		case COMPRESSION_GZIP:
 #ifdef HAVE_LIBZ
-	if (fp->compressedfp)
-		return gzwrite(fp->compressedfp, ptr, size);
-	else
+			ret = gzwrite(fp->fp, ptr, size);
+#else
+			fatal("not built with zlib support");
 #endif
-		return fwrite(ptr, 1, size, fp->uncompressedfp);
+			break;
+		default:
+			fatal("invalid compression method");
+			break;
+	}
+
+	return ret;
 }
 
 int
@@ -611,24 +656,31 @@ cfgetc(cfp *fp)
 {
 	int			ret;
 
-#ifdef HAVE_LIBZ
-	if (fp->compressedfp)
+	switch (fp->compressionMethod)
 	{
-		ret = gzgetc(fp->compressedfp);
-		if (ret == EOF)
-		{
-			if (!gzeof(fp->compressedfp))
-				fatal("could not read from input file: %s", strerror(errno));
-			else
-				fatal("could not read from input file: end of file");
-		}
-	}
-	else
+		case COMPRESSION_NONE:
+			ret = fgetc(fp->fp);
+			if (ret == EOF)
+				READ_ERROR_EXIT(fp->fp);
+
+			break;
+		case COMPRESSION_GZIP:
+#ifdef HAVE_LIBZ
+			ret = gzgetc((gzFile)fp->fp);
+			if (ret == EOF)
+			{
+				if (!gzeof(fp->fp))
+					fatal("could not read from input file: %s", strerror(errno));
+				else
+					fatal("could not read from input file: end of file");
+			}
+#else
+			fatal("not built with zlib support");
 #endif
-	{
-		ret = fgetc(fp->uncompressedfp);
-		if (ret == EOF)
-			READ_ERROR_EXIT(fp->uncompressedfp);
+			break;
+		default:
+			fatal("invalid compression method");
+			break;
 	}
 
 	return ret;
@@ -637,65 +689,107 @@ cfgetc(cfp *fp)
 char *
 cfgets(cfp *fp, char *buf, int len)
 {
+	char	   *ret;
+
+	switch (fp->compressionMethod)
+	{
+		case COMPRESSION_NONE:
+			ret = fgets(buf, len, fp->fp);
+
+			break;
+		case COMPRESSION_GZIP:
 #ifdef HAVE_LIBZ
-	if (fp->compressedfp)
-		return gzgets(fp->compressedfp, buf, len);
-	else
+			ret = gzgets(fp->fp, buf, len);
+#else
+			fatal("not built with zlib support");
 #endif
-		return fgets(buf, len, fp->uncompressedfp);
+			break;
+		default:
+			fatal("invalid compression method");
+			break;
+	}
+
+	return ret;
 }
 
 int
 cfclose(cfp *fp)
 {
-	int			result;
+	int			ret;
 
 	if (fp == NULL)
 	{
 		errno = EBADF;
 		return EOF;
 	}
-#ifdef HAVE_LIBZ
-	if (fp->compressedfp)
+
+	switch (fp->compressionMethod)
 	{
-		result = gzclose(fp->compressedfp);
-		fp->compressedfp = NULL;
-	}
-	else
+		case COMPRESSION_NONE:
+			ret = fclose(fp->fp);
+			fp->fp = NULL;
+
+			break;
+		case COMPRESSION_GZIP:
+#ifdef HAVE_LIBZ
+			ret = gzclose(fp->fp);
+			fp->fp = NULL;
+#else
+			fatal("not built with zlib support");
 #endif
-	{
-		result = fclose(fp->uncompressedfp);
-		fp->uncompressedfp = NULL;
+			break;
+		default:
+			fatal("invalid compression method");
+			break;
 	}
+
 	free_keep_errno(fp);
 
-	return result;
+	return ret;
 }
 
 int
 cfeof(cfp *fp)
 {
+	int			ret;
+
+	switch (fp->compressionMethod)
+	{
+		case COMPRESSION_NONE:
+			ret = feof(fp->fp);
+
+			break;
+		case COMPRESSION_GZIP:
 #ifdef HAVE_LIBZ
-	if (fp->compressedfp)
-		return gzeof(fp->compressedfp);
-	else
+			ret = gzeof(fp->fp);
+#else
+			fatal("not built with zlib support");
 #endif
-		return feof(fp->uncompressedfp);
+			break;
+		default:
+			fatal("invalid compression method");
+			break;
+	}
+
+	return ret;
 }
 
 const char *
 get_cfp_error(cfp *fp)
 {
-#ifdef HAVE_LIBZ
-	if (fp->compressedfp)
+	if (fp->compressionMethod == COMPRESSION_GZIP)
 	{
+#ifdef HAVE_LIBZ
 		int			errnum;
-		const char *errmsg = gzerror(fp->compressedfp, &errnum);
+		const char *errmsg = gzerror(fp->fp, &errnum);
 
 		if (errnum != Z_ERRNO)
 			return errmsg;
-	}
+#else
+		fatal("not built with zlib support");
 #endif
+	}
+
 	return strerror(errno);
 }
 
diff --git a/src/bin/pg_dump/compress_io.h b/src/bin/pg_dump/compress_io.h
index f635787692..de5ae4f61b 100644
--- a/src/bin/pg_dump/compress_io.h
+++ b/src/bin/pg_dump/compress_io.h
@@ -17,16 +17,25 @@
 
 #include "pg_backup_archiver.h"
 
+#ifdef HAVE_LIBZ
+#include <zlib.h>
+#define GZCLOSE(fh) gzclose(fh)
+#define GZWRITE(p, s, n, fh) gzwrite(fh, p, (n) * (s))
+#define GZREAD(p, s, n, fh) gzread(fh, p, (n) * (s))
+#define GZEOF(fh)	gzeof(fh)
+#else
+#define GZCLOSE(fh) fclose(fh)
+#define GZWRITE(p, s, n, fh) (fwrite(p, s, n, fh) * (s))
+#define GZREAD(p, s, n, fh) fread(p, s, n, fh)
+#define GZEOF(fh)	feof(fh)
+/* this is just the redefinition of a libz constant */
+#define Z_DEFAULT_COMPRESSION (-1)
+#endif
+
 /* Initial buffer sizes used in zlib compression. */
 #define ZLIB_OUT_SIZE	4096
 #define ZLIB_IN_SIZE	4096
 
-typedef enum
-{
-	COMPR_ALG_NONE,
-	COMPR_ALG_LIBZ
-} CompressionAlgorithm;
-
 /* Prototype for callback function to WriteDataToArchive() */
 typedef void (*WriteFunc) (ArchiveHandle *AH, const char *buf, size_t len);
 
@@ -46,8 +55,12 @@ typedef size_t (*ReadFunc) (ArchiveHandle *AH, char **buf, size_t *buflen);
 /* struct definition appears in compress_io.c */
 typedef struct CompressorState CompressorState;
 
-extern CompressorState *AllocateCompressor(int compression, WriteFunc writeF);
-extern void ReadDataFromArchive(ArchiveHandle *AH, int compression,
+extern CompressorState *AllocateCompressor(CompressionMethod compressionMethod,
+										   int compressionLevel,
+										   WriteFunc writeF);
+extern void ReadDataFromArchive(ArchiveHandle *AH,
+								CompressionMethod compressionMethod,
+								int compressionLevel,
 								ReadFunc readF);
 extern void WriteDataToArchive(ArchiveHandle *AH, CompressorState *cs,
 							   const void *data, size_t dLen);
@@ -56,9 +69,16 @@ extern void EndCompressor(ArchiveHandle *AH, CompressorState *cs);
 
 typedef struct cfp cfp;
 
-extern cfp *cfopen(const char *path, const char *mode, int compression);
+extern cfp *cfopen(const char *path, const char *mode,
+				   CompressionMethod compressionMethod,
+				   int compressionLevel);
+extern cfp *cfdopen(int fd, const char *mode,
+				   CompressionMethod compressionMethod,
+				   int compressionLevel);
 extern cfp *cfopen_read(const char *path, const char *mode);
-extern cfp *cfopen_write(const char *path, const char *mode, int compression);
+extern cfp *cfopen_write(const char *path, const char *mode,
+						 CompressionMethod compressionMethod,
+						 int compressionLevel);
 extern int	cfread(void *ptr, int size, cfp *fp);
 extern int	cfwrite(const void *ptr, int size, cfp *fp);
 extern int	cfgetc(cfp *fp);
diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h
index fcc5f6bd05..741810b066 100644
--- a/src/bin/pg_dump/pg_backup.h
+++ b/src/bin/pg_dump/pg_backup.h
@@ -75,6 +75,12 @@ enum _dumpPreparedQueries
 	NUM_PREP_QUERIES			/* must be last */
 };
 
+typedef enum _compressionMethod
+{
+	COMPRESSION_GZIP,
+	COMPRESSION_NONE
+} CompressionMethod;
+
 /* Parameters needed by ConnectDatabase; same for dump and restore */
 typedef struct _connParams
 {
@@ -143,7 +149,8 @@ typedef struct _restoreOptions
 
 	int			noDataForFailedTables;
 	int			exit_on_error;
-	int			compression;
+	CompressionMethod compressionMethod;
+	int			compressionLevel;
 	int			suppressDumpWarnings;	/* Suppress output of WARNING entries
 										 * to stderr */
 	bool		single_txn;
@@ -303,7 +310,9 @@ extern Archive *OpenArchive(const char *FileSpec, const ArchiveFormat fmt);
 
 /* Create a new archive */
 extern Archive *CreateArchive(const char *FileSpec, const ArchiveFormat fmt,
-							  const int compression, bool dosync, ArchiveMode mode,
+							  const CompressionMethod compressionMethod,
+							  const int compression,
+							  bool dosync, ArchiveMode mode,
 							  SetupWorkerPtrType setupDumpWorker);
 
 /* The --list option */
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index d41a99d6ea..7d96446f1a 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -31,6 +31,7 @@
 #endif
 
 #include "common/string.h"
+#include "compress_io.h"
 #include "dumputils.h"
 #include "fe_utils/string_utils.h"
 #include "lib/stringinfo.h"
@@ -43,13 +44,6 @@
 #define TEXT_DUMP_HEADER "--\n-- PostgreSQL database dump\n--\n\n"
 #define TEXT_DUMPALL_HEADER "--\n-- PostgreSQL database cluster dump\n--\n\n"
 
-/* state needed to save/restore an archive's output target */
-typedef struct _outputContext
-{
-	void	   *OF;
-	int			gzOut;
-} OutputContext;
-
 /*
  * State for tracking TocEntrys that are ready to process during a parallel
  * restore.  (This used to be a list, and we still call it that, though now
@@ -70,7 +64,9 @@ typedef struct _parallelReadyList

 static ArchiveHandle *_allocAH(const char *FileSpec, const ArchiveFormat fmt,
-							   const int compression, bool dosync, ArchiveMode mode,
+							   const CompressionMethod compressionMethod,
+							   const int compressionLevel,
+							   bool dosync, ArchiveMode mode,
 							   SetupWorkerPtrType setupWorkerPtr);
 static void _getObjectDescription(PQExpBuffer buf, TocEntry *te);
 static void _printTocEntry(ArchiveHandle *AH, TocEntry *te, bool isData);
@@ -98,9 +94,11 @@ static int	_discoverArchiveFormat(ArchiveHandle *AH);
 static int	RestoringToDB(ArchiveHandle *AH);
 static void dump_lo_buf(ArchiveHandle *AH);
 static void dumpTimestamp(ArchiveHandle *AH, const char *msg, time_t tim);
-static void SetOutput(ArchiveHandle *AH, const char *filename, int compression);
-static OutputContext SaveOutput(ArchiveHandle *AH);
-static void RestoreOutput(ArchiveHandle *AH, OutputContext savedContext);
+static void SetOutput(ArchiveHandle *AH, const char *filename,
+					  CompressionMethod compressionMethod,
+					  int compressionLevel);
+static cfp *SaveOutput(ArchiveHandle *AH);
+static void RestoreOutput(ArchiveHandle *AH, cfp *savedOutput);
 
 static int	restore_toc_entry(ArchiveHandle *AH, TocEntry *te, bool is_parallel);
 static void restore_toc_entries_prefork(ArchiveHandle *AH,
@@ -239,12 +237,15 @@ setupRestoreWorker(Archive *AHX)
 /* Public */
 Archive *
 CreateArchive(const char *FileSpec, const ArchiveFormat fmt,
-			  const int compression, bool dosync, ArchiveMode mode,
+			  const CompressionMethod compressionMethod,
+			  const int compressionLevel,
+			  bool dosync, ArchiveMode mode,
 			  SetupWorkerPtrType setupDumpWorker)
 
 {
-	ArchiveHandle *AH = _allocAH(FileSpec, fmt, compression, dosync,
-								 mode, setupDumpWorker);
+	ArchiveHandle *AH = _allocAH(FileSpec, fmt,
+								 compressionMethod, compressionLevel,
+								 dosync, mode, setupDumpWorker);
 
 	return (Archive *) AH;
 }
@@ -254,7 +255,8 @@ CreateArchive(const char *FileSpec, const ArchiveFormat fmt,
 Archive *
 OpenArchive(const char *FileSpec, const ArchiveFormat fmt)
 {
-	ArchiveHandle *AH = _allocAH(FileSpec, fmt, 0, true, archModeRead, setupRestoreWorker);
+	ArchiveHandle *AH = _allocAH(FileSpec, fmt, COMPRESSION_NONE, 0, true,
+								 archModeRead, setupRestoreWorker);
 
 	return (Archive *) AH;
 }
@@ -269,11 +271,8 @@ CloseArchive(Archive *AHX)
 	AH->ClosePtr(AH);
 
 	/* Close the output */
-	errno = 0;					/* in case gzclose() doesn't set it */
-	if (AH->gzOut)
-		res = GZCLOSE(AH->OF);
-	else if (AH->OF != stdout)
-		res = fclose(AH->OF);
+	errno = 0;
+	res = cfclose(AH->OF);
 
 	if (res != 0)
 		fatal("could not close output file: %m");
@@ -355,7 +354,7 @@ RestoreArchive(Archive *AHX)
 	RestoreOptions *ropt = AH->public.ropt;
 	bool		parallel_mode;
 	TocEntry   *te;
-	OutputContext sav;
+	cfp		   *sav;
 
 	AH->stage = STAGE_INITIALIZING;
 
@@ -384,7 +383,8 @@ RestoreArchive(Archive *AHX)
 	 * Make sure we won't need (de)compression we haven't got
 	 */
 #ifndef HAVE_LIBZ
-	if (AH->compression != 0 && AH->PrintTocDataPtr != NULL)
+	if (AH->compressionMethod == COMPRESSION_GZIP &&
+		AH->PrintTocDataPtr != NULL)
 	{
 		for (te = AH->toc->next; te != AH->toc; te = te->next)
 		{
@@ -459,8 +459,9 @@ RestoreArchive(Archive *AHX)
 	 * Setup the output file if necessary.
 	 */
 	sav = SaveOutput(AH);
-	if (ropt->filename || ropt->compression)
-		SetOutput(AH, ropt->filename, ropt->compression);
+	if (ropt->filename || ropt->compressionMethod != COMPRESSION_NONE)
+		SetOutput(AH, ropt->filename,
+				  ropt->compressionMethod, ropt->compressionLevel);
 
 	ahprintf(AH, "--\n-- PostgreSQL database dump\n--\n\n");
 
@@ -740,7 +741,7 @@ RestoreArchive(Archive *AHX)
 	 */
 	AH->stage = STAGE_FINALIZING;
 
-	if (ropt->filename || ropt->compression)
+	if (ropt->filename || ropt->compressionMethod != COMPRESSION_NONE)
 		RestoreOutput(AH, sav);
 
 	if (ropt->useDB)
@@ -970,6 +971,7 @@ NewRestoreOptions(void)
 	opts->format = archUnknown;
 	opts->cparams.promptPassword = TRI_DEFAULT;
 	opts->dumpSections = DUMP_UNSECTIONED;
+	opts->compressionMethod = COMPRESSION_NONE;
 
 	return opts;
 }
@@ -1117,13 +1119,13 @@ PrintTOCSummary(Archive *AHX)
 	RestoreOptions *ropt = AH->public.ropt;
 	TocEntry   *te;
 	teSection	curSection;
-	OutputContext sav;
+	cfp		   *sav;
 	const char *fmtName;
 	char		stamp_str[64];
 
 	sav = SaveOutput(AH);
 	if (ropt->filename)
-		SetOutput(AH, ropt->filename, 0 /* no compression */ );
+		SetOutput(AH, ropt->filename, COMPRESSION_NONE, 0);
 
 	if (strftime(stamp_str, sizeof(stamp_str), PGDUMP_STRFTIME_FMT,
 				 localtime(&AH->createDate)) == 0)
@@ -1132,7 +1134,7 @@ PrintTOCSummary(Archive *AHX)
 	ahprintf(AH, ";\n; Archive created at %s\n", stamp_str);
 	ahprintf(AH, ";     dbname: %s\n;     TOC Entries: %d\n;     Compression: %d\n",
 			 sanitize_line(AH->archdbname, false),
-			 AH->tocCount, AH->compression);
+			 AH->tocCount, AH->compressionLevel);
 
 	switch (AH->format)
 	{
@@ -1486,60 +1488,35 @@ archprintf(Archive *AH, const char *fmt,...)
  *******************************/
 
 static void
-SetOutput(ArchiveHandle *AH, const char *filename, int compression)
+SetOutput(ArchiveHandle *AH, const char *filename,
+		  CompressionMethod compressionMethod, int compressionLevel)
 {
-	int			fn;
+	const char *mode;
+	int			fn = -1;
 
 	if (filename)
 	{
 		if (strcmp(filename, "-") == 0)
 			fn = fileno(stdout);
-		else
-			fn = -1;
 	}
 	else if (AH->FH)
 		fn = fileno(AH->FH);
 	else if (AH->fSpec)
 	{
-		fn = -1;
 		filename = AH->fSpec;
 	}
 	else
 		fn = fileno(stdout);
 
-	/* If compression explicitly requested, use gzopen */
-#ifdef HAVE_LIBZ
-	if (compression != 0)
-	{
-		char		fmode[14];
+	if (AH->mode == archModeAppend)
+		mode = PG_BINARY_A;
+	else
+		mode = PG_BINARY_W;
 
-		/* Don't use PG_BINARY_x since this is zlib */
-		sprintf(fmode, "wb%d", compression);
-		if (fn >= 0)
-			AH->OF = gzdopen(dup(fn), fmode);
-		else
-			AH->OF = gzopen(filename, fmode);
-		AH->gzOut = 1;
-	}
+	if (fn >= 0)
+		AH->OF = cfdopen(dup(fn), mode, compressionMethod, compressionLevel);
 	else
-#endif
-	{							/* Use fopen */
-		if (AH->mode == archModeAppend)
-		{
-			if (fn >= 0)
-				AH->OF = fdopen(dup(fn), PG_BINARY_A);
-			else
-				AH->OF = fopen(filename, PG_BINARY_A);
-		}
-		else
-		{
-			if (fn >= 0)
-				AH->OF = fdopen(dup(fn), PG_BINARY_W);
-			else
-				AH->OF = fopen(filename, PG_BINARY_W);
-		}
-		AH->gzOut = 0;
-	}
+		AH->OF = cfopen(filename, mode, compressionMethod, compressionLevel);
 
 	if (!AH->OF)
 	{
@@ -1550,33 +1527,24 @@ SetOutput(ArchiveHandle *AH, const char *filename, int compression)
 	}
 }
 
-static OutputContext
+static cfp *
 SaveOutput(ArchiveHandle *AH)
 {
-	OutputContext sav;
-
-	sav.OF = AH->OF;
-	sav.gzOut = AH->gzOut;
-
-	return sav;
+	return (cfp *)AH->OF;
 }
 
 static void
-RestoreOutput(ArchiveHandle *AH, OutputContext savedContext)
+RestoreOutput(ArchiveHandle *AH, cfp *savedOutput)
 {
 	int			res;
 
-	errno = 0;					/* in case gzclose() doesn't set it */
-	if (AH->gzOut)
-		res = GZCLOSE(AH->OF);
-	else
-		res = fclose(AH->OF);
+	errno = 0;
+	res = cfclose(AH->OF);
 
 	if (res != 0)
 		fatal("could not close output file: %m");
 
-	AH->gzOut = savedContext.gzOut;
-	AH->OF = savedContext.OF;
+	AH->OF = savedOutput;
 }

@@ -1700,22 +1668,16 @@ ahwrite(const void *ptr, size_t size, size_t nmemb, ArchiveHandle *AH)
 
 		bytes_written = size * nmemb;
 	}
-	else if (AH->gzOut)
-		bytes_written = GZWRITE(ptr, size, nmemb, AH->OF);
 	else if (AH->CustomOutPtr)
 		bytes_written = AH->CustomOutPtr(AH, ptr, size * nmemb);
-
-	else
-	{
-		/*
-		 * If we're doing a restore, and it's direct to DB, and we're
-		 * connected then send it to the DB.
-		 */
-		if (RestoringToDB(AH))
+	/*
+	 * If we're doing a restore, and it's direct to DB, and we're
+	 * connected then send it to the DB.
+	 */
+	else if (RestoringToDB(AH))
 			bytes_written = ExecuteSqlCommandBuf(&AH->public, (const char *) ptr, size * nmemb);
-		else
-			bytes_written = fwrite(ptr, size, nmemb, AH->OF) * size;
-	}
+	else
+		bytes_written = cfwrite(ptr, size * nmemb, AH->OF);
 
 	if (bytes_written != size * nmemb)
 		WRITE_ERROR_EXIT;
@@ -2200,7 +2162,9 @@ _discoverArchiveFormat(ArchiveHandle *AH)
  */
 static ArchiveHandle *
 _allocAH(const char *FileSpec, const ArchiveFormat fmt,
-		 const int compression, bool dosync, ArchiveMode mode,
+		 const CompressionMethod compressionMethod,
+		 const int compressionLevel,
+		 bool dosync, ArchiveMode mode,
 		 SetupWorkerPtrType setupWorkerPtr)
 {
 	ArchiveHandle *AH;
@@ -2251,14 +2215,14 @@ _allocAH(const char *FileSpec, const ArchiveFormat fmt,
 	AH->toc->prev = AH->toc;
 
 	AH->mode = mode;
-	AH->compression = compression;
+	AH->compressionMethod = compressionMethod;
+	AH->compressionLevel = compressionLevel;
 	AH->dosync = dosync;
 
 	memset(&(AH->sqlparse), 0, sizeof(AH->sqlparse));
 
 	/* Open stdout with no compression for AH output handle */
-	AH->gzOut = 0;
-	AH->OF = stdout;
+	AH->OF = cfdopen(dup(fileno(stdout)), PG_BINARY_A, COMPRESSION_NONE, 0);
 
 	/*
 	 * On Windows, we need to use binary mode to read/write non-text files,
@@ -2266,7 +2230,7 @@ _allocAH(const char *FileSpec, const ArchiveFormat fmt,
 	 * Force stdin/stdout into binary mode if that is what we are using.
 	 */
 #ifdef WIN32
-	if ((fmt != archNull || compression != 0) &&
+	if ((fmt != archNull || compressionMethod != COMPRESSION_NONE) &&
 		(AH->fSpec == NULL || strcmp(AH->fSpec, "") == 0))
 	{
 		if (mode == archModeWrite)
@@ -3716,7 +3680,7 @@ WriteHead(ArchiveHandle *AH)
 	AH->WriteBytePtr(AH, AH->intSize);
 	AH->WriteBytePtr(AH, AH->offSize);
 	AH->WriteBytePtr(AH, AH->format);
-	WriteInt(AH, AH->compression);
+	WriteInt(AH, AH->compressionLevel);
 	crtm = *localtime(&AH->createDate);
 	WriteInt(AH, crtm.tm_sec);
 	WriteInt(AH, crtm.tm_min);
@@ -3790,18 +3754,21 @@ ReadHead(ArchiveHandle *AH)
 	if (AH->version >= K_VERS_1_2)
 	{
 		if (AH->version < K_VERS_1_4)
-			AH->compression = AH->ReadBytePtr(AH);
+			AH->compressionLevel = AH->ReadBytePtr(AH);
 		else
-			AH->compression = ReadInt(AH);
+			AH->compressionLevel = ReadInt(AH);
 	}
 	else
-		AH->compression = Z_DEFAULT_COMPRESSION;
+		AH->compressionLevel = Z_DEFAULT_COMPRESSION;
 
+	if (AH->compressionLevel != INT_MIN)
 #ifndef HAVE_LIBZ
-	if (AH->compression != 0)
 		pg_log_warning("archive is compressed, but this installation does not support compression -- no data will be available");
+#else
+		AH->compressionMethod = COMPRESSION_GZIP;
 #endif
 
+
 	if (AH->version >= K_VERS_1_4)
 	{
 		struct tm	crtm;
diff --git a/src/bin/pg_dump/pg_backup_archiver.h b/src/bin/pg_dump/pg_backup_archiver.h
index 540d4f6a83..837e9d73f5 100644
--- a/src/bin/pg_dump/pg_backup_archiver.h
+++ b/src/bin/pg_dump/pg_backup_archiver.h
@@ -32,30 +32,6 @@
 
 #define LOBBUFSIZE 16384
 
-#ifdef HAVE_LIBZ
-#include <zlib.h>
-#define GZCLOSE(fh) gzclose(fh)
-#define GZWRITE(p, s, n, fh) gzwrite(fh, p, (n) * (s))
-#define GZREAD(p, s, n, fh) gzread(fh, p, (n) * (s))
-#define GZEOF(fh)	gzeof(fh)
-#else
-#define GZCLOSE(fh) fclose(fh)
-#define GZWRITE(p, s, n, fh) (fwrite(p, s, n, fh) * (s))
-#define GZREAD(p, s, n, fh) fread(p, s, n, fh)
-#define GZEOF(fh)	feof(fh)
-/* this is just the redefinition of a libz constant */
-#define Z_DEFAULT_COMPRESSION (-1)
-
-typedef struct _z_stream
-{
-	void	   *next_in;
-	void	   *next_out;
-	size_t		avail_in;
-	size_t		avail_out;
-} z_stream;
-typedef z_stream *z_streamp;
-#endif
-
 /* Data block types */
 #define BLK_DATA 1
 #define BLK_BLOBS 3
@@ -319,8 +295,7 @@ struct _archiveHandle
 
 	char	   *fSpec;			/* Archive File Spec */
 	FILE	   *FH;				/* General purpose file handle */
-	void	   *OF;
-	int			gzOut;			/* Output file */
+	void	   *OF;				/* Output file */
 
 	struct _tocEntry *toc;		/* Header of circular list of TOC entries */
 	int			tocCount;		/* Number of TOC entries */
@@ -331,14 +306,17 @@ struct _archiveHandle
 	DumpId	   *tableDataId;	/* TABLE DATA ids, indexed by table dumpId */
 
 	struct _tocEntry *currToc;	/* Used when dumping data */
-	int			compression;	/*---------
-								 * Compression requested on open().
-								 * Possible values for compression:
-								 * -1	Z_DEFAULT_COMPRESSION
-								 *  0	COMPRESSION_NONE
-								 * 1-9 levels for gzip compression
-								 *---------
-								 */
+	CompressionMethod compressionMethod; /* Requested method for compression */
+	int			compressionLevel; /*---------
+								   * Requested level of compression for method.
+								   * Possible values for compression:
+								   * INT_MIN when no compression method is
+								   * requested.
+								   * -1	Z_DEFAULT_COMPRESSION for gzip
+								   * compression.
+								   * 1-9 levels for gzip compression.
+								   *---------
+								   */
 	bool		dosync;			/* data requested to be synced on sight */
 	ArchiveMode mode;			/* File mode - r or w */
 	void	   *formatData;		/* Header data specific to file format */
diff --git a/src/bin/pg_dump/pg_backup_custom.c b/src/bin/pg_dump/pg_backup_custom.c
index 77d402c323..7f38ea9cd5 100644
--- a/src/bin/pg_dump/pg_backup_custom.c
+++ b/src/bin/pg_dump/pg_backup_custom.c
@@ -298,7 +298,9 @@ _StartData(ArchiveHandle *AH, TocEntry *te)
 	_WriteByte(AH, BLK_DATA);	/* Block type */
 	WriteInt(AH, te->dumpId);	/* For sanity check */
 
-	ctx->cs = AllocateCompressor(AH->compression, _CustomWriteFunc);
+	ctx->cs = AllocateCompressor(AH->compressionMethod,
+								 AH->compressionLevel,
+								 _CustomWriteFunc);
 }
 
 /*
@@ -377,7 +379,9 @@ _StartBlob(ArchiveHandle *AH, TocEntry *te, Oid oid)
 
 	WriteInt(AH, oid);
 
-	ctx->cs = AllocateCompressor(AH->compression, _CustomWriteFunc);
+	ctx->cs = AllocateCompressor(AH->compressionMethod,
+								 AH->compressionLevel,
+								 _CustomWriteFunc);
 }
 
 /*
@@ -566,7 +570,8 @@ _PrintTocData(ArchiveHandle *AH, TocEntry *te)
 static void
 _PrintData(ArchiveHandle *AH)
 {
-	ReadDataFromArchive(AH, AH->compression, _CustomReadFunc);
+	ReadDataFromArchive(AH, AH->compressionMethod, AH->compressionLevel,
+						_CustomReadFunc);
 }
 
 static void
diff --git a/src/bin/pg_dump/pg_backup_directory.c b/src/bin/pg_dump/pg_backup_directory.c
index 7f4e340dea..0e60b447de 100644
--- a/src/bin/pg_dump/pg_backup_directory.c
+++ b/src/bin/pg_dump/pg_backup_directory.c
@@ -327,7 +327,9 @@ _StartData(ArchiveHandle *AH, TocEntry *te)
 
 	setFilePath(AH, fname, tctx->filename);
 
-	ctx->dataFH = cfopen_write(fname, PG_BINARY_W, AH->compression);
+	ctx->dataFH = cfopen_write(fname, PG_BINARY_W,
+							   AH->compressionMethod,
+							   AH->compressionLevel);
 	if (ctx->dataFH == NULL)
 		fatal("could not open output file \"%s\": %m", fname);
 }
@@ -581,7 +583,8 @@ _CloseArchive(ArchiveHandle *AH)
 		ctx->pstate = ParallelBackupStart(AH);
 
 		/* The TOC is always created uncompressed */
-		tocFH = cfopen_write(fname, PG_BINARY_W, 0);
+		tocFH = cfopen_write(fname, PG_BINARY_W,
+							 COMPRESSION_NONE, 0);
 		if (tocFH == NULL)
 			fatal("could not open output file \"%s\": %m", fname);
 		ctx->dataFH = tocFH;
@@ -644,7 +647,7 @@ _StartBlobs(ArchiveHandle *AH, TocEntry *te)
 	setFilePath(AH, fname, "blobs.toc");
 
 	/* The blob TOC file is never compressed */
-	ctx->blobsTocFH = cfopen_write(fname, "ab", 0);
+	ctx->blobsTocFH = cfopen_write(fname, "ab", COMPRESSION_NONE, 0);
 	if (ctx->blobsTocFH == NULL)
 		fatal("could not open output file \"%s\": %m", fname);
 }
@@ -662,7 +665,8 @@ _StartBlob(ArchiveHandle *AH, TocEntry *te, Oid oid)
 
 	snprintf(fname, MAXPGPATH, "%s/blob_%u.dat", ctx->directory, oid);
 
-	ctx->dataFH = cfopen_write(fname, PG_BINARY_W, AH->compression);
+	ctx->dataFH = cfopen_write(fname, PG_BINARY_W,
+							   AH->compressionMethod, AH->compressionLevel);
 
 	if (ctx->dataFH == NULL)
 		fatal("could not open output file \"%s\": %m", fname);
diff --git a/src/bin/pg_dump/pg_backup_tar.c b/src/bin/pg_dump/pg_backup_tar.c
index 5c351acda0..239b053013 100644
--- a/src/bin/pg_dump/pg_backup_tar.c
+++ b/src/bin/pg_dump/pg_backup_tar.c
@@ -35,6 +35,7 @@
 #include <unistd.h>
 
 #include "common/file_utils.h"
+#include "compress_io.h"
 #include "fe_utils/string_utils.h"
 #include "pg_backup_archiver.h"
 #include "pg_backup_tar.h"
@@ -199,7 +200,7 @@ InitArchiveFmt_Tar(ArchiveHandle *AH)
 		 * possible since gzdopen uses buffered IO which totally screws file
 		 * positioning.
 		 */
-		if (AH->compression != 0)
+		if (AH->compressionMethod != COMPRESSION_NONE)
 			fatal("compression is not supported by tar archive format");
 	}
 	else
@@ -249,7 +250,7 @@ _ArchiveEntry(ArchiveHandle *AH, TocEntry *te)
 	if (te->dataDumper != NULL)
 	{
 #ifdef HAVE_LIBZ
-		if (AH->compression == 0)
+		if (AH->compressionMethod == COMPRESSION_NONE)
 			sprintf(fn, "%d.dat", te->dumpId);
 		else
 			sprintf(fn, "%d.dat.gz", te->dumpId);
@@ -346,7 +347,7 @@ tarOpen(ArchiveHandle *AH, const char *filename, char mode)
 
 #ifdef HAVE_LIBZ
 
-		if (AH->compression == 0)
+		if (AH->compressionMethod == COMPRESSION_NONE)
 			tm->nFH = ctx->tarFH;
 		else
 			fatal("compression is not supported by tar archive format");
@@ -407,9 +408,9 @@ tarOpen(ArchiveHandle *AH, const char *filename, char mode)
 
 #ifdef HAVE_LIBZ
 
-		if (AH->compression != 0)
+		if (AH->compressionMethod != COMPRESSION_NONE)
 		{
-			sprintf(fmode, "wb%d", AH->compression);
+			sprintf(fmode, "wb%d", AH->compressionLevel);
 			tm->zFH = gzdopen(dup(fileno(tm->tmpFH)), fmode);
 			if (tm->zFH == NULL)
 				fatal("could not open temporary file");
@@ -437,7 +438,7 @@ tarClose(ArchiveHandle *AH, TAR_MEMBER *th)
 	/*
 	 * Close the GZ file since we dup'd. This will flush the buffers.
 	 */
-	if (AH->compression != 0)
+	if (AH->compressionMethod != COMPRESSION_NONE)
 	{
 		errno = 0;				/* in case gzclose() doesn't set it */
 		if (GZCLOSE(th->zFH) != 0)
@@ -865,7 +866,6 @@ _CloseArchive(ArchiveHandle *AH)
 		memcpy(ropt, AH->public.ropt, sizeof(RestoreOptions));
 		ropt->filename = NULL;
 		ropt->dropSchema = 1;
-		ropt->compression = 0;
 		ropt->superuser = NULL;
 		ropt->suppressDumpWarnings = true;
 
@@ -954,7 +954,7 @@ _StartBlob(ArchiveHandle *AH, TocEntry *te, Oid oid)
 	if (oid == 0)
 		fatal("invalid OID for large object (%u)", oid);
 
-	if (AH->compression != 0)
+	if (AH->compressionMethod != COMPRESSION_NONE)
 		sfx = ".gz";
 	else
 		sfx = "";
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index e69dcf8a48..7477d5112e 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -55,6 +55,7 @@
 #include "catalog/pg_trigger_d.h"
 #include "catalog/pg_type_d.h"
 #include "common/connect.h"
+#include "compress_io.h"
 #include "dumputils.h"
 #include "fe_utils/option_utils.h"
 #include "fe_utils/string_utils.h"
@@ -163,6 +164,9 @@ static void setup_connection(Archive *AH,
 							 const char *dumpencoding, const char *dumpsnapshot,
 							 char *use_role);
 static ArchiveFormat parseArchiveFormat(const char *format, ArchiveMode *mode);
+static bool parse_compression_option(const char *opt,
+									 CompressionMethod *compressionMethod,
+									 int *compressLevel);
 static void expand_schema_name_patterns(Archive *fout,
 										SimpleStringList *patterns,
 										SimpleOidList *oids,
@@ -336,8 +340,9 @@ main(int argc, char **argv)
 	const char *dumpsnapshot = NULL;
 	char	   *use_role = NULL;
 	int			numWorkers = 1;
-	int			compressLevel = -1;
 	int			plainText = 0;
+	int			compressLevel = INT_MIN;
+	CompressionMethod compressionMethod = COMPRESSION_NONE;
 	ArchiveFormat archiveFormat = archUnknown;
 	ArchiveMode archiveMode;
 
@@ -557,9 +562,9 @@ main(int argc, char **argv)
 				dopt.aclsSkip = true;
 				break;
 
-			case 'Z':			/* Compression Level */
-				if (!option_parse_int(optarg, "-Z/--compress", 0, 9,
-									  &compressLevel))
+			case 'Z':			/* Compression */
+				if (!parse_compression_option(optarg, &compressionMethod,
+											  &compressLevel))
 					exit_nicely(1);
 				break;
 
@@ -689,23 +694,18 @@ main(int argc, char **argv)
 	if (archiveFormat == archNull)
 		plainText = 1;
 
-	/* Custom and directory formats are compressed by default, others not */
-	if (compressLevel == -1)
+	/* Custom and directory formats are compressed by default (zlib), others not */
+	if (compressionMethod == COMPRESSION_NONE)
 	{
 #ifdef HAVE_LIBZ
 		if (archiveFormat == archCustom || archiveFormat == archDirectory)
+		{
+			compressionMethod = COMPRESSION_GZIP;
 			compressLevel = Z_DEFAULT_COMPRESSION;
-		else
+		}
 #endif
-			compressLevel = 0;
 	}
 
-#ifndef HAVE_LIBZ
-	if (compressLevel != 0)
-		pg_log_warning("requested compression not available in this installation -- archive will be uncompressed");
-	compressLevel = 0;
-#endif
-
 	/*
 	 * If emitting an archive format, we always want to emit a DATABASE item,
 	 * in case --create is specified at pg_restore time.
@@ -718,8 +718,9 @@ main(int argc, char **argv)
 		fatal("parallel backup only supported by the directory format");
 
 	/* Open the output file */
-	fout = CreateArchive(filename, archiveFormat, compressLevel, dosync,
-						 archiveMode, setupDumpWorker);
+	fout = CreateArchive(filename, archiveFormat,
+						 compressionMethod, compressLevel,
+						 dosync, archiveMode, setupDumpWorker);
 
 	/* Make dump options accessible right away */
 	SetArchiveOptions(fout, &dopt, NULL);
@@ -950,10 +951,8 @@ main(int argc, char **argv)
 	ropt->sequence_data = dopt.sequence_data;
 	ropt->binary_upgrade = dopt.binary_upgrade;
 
-	if (compressLevel == -1)
-		ropt->compression = 0;
-	else
-		ropt->compression = compressLevel;
+	ropt->compressionLevel = compressLevel;
+	ropt->compressionMethod = compressionMethod;
 
 	ropt->suppressDumpWarnings = true;	/* We've already shown them */
 
@@ -1000,7 +999,8 @@ help(const char *progname)
 	printf(_("  -j, --jobs=NUM               use this many parallel jobs to dump\n"));
 	printf(_("  -v, --verbose                verbose mode\n"));
 	printf(_("  -V, --version                output version information, then exit\n"));
-	printf(_("  -Z, --compress=0-9           compression level for compressed formats\n"));
+	printf(_("  -Z, --compress=[gzip,none][:LEVEL] or [LEVEL]\n"
+			 "                               compress output with given method or level\n"));
 	printf(_("  --lock-wait-timeout=TIMEOUT  fail after waiting TIMEOUT for a table lock\n"));
 	printf(_("  --no-sync                    do not wait for changes to be written safely to disk\n"));
 	printf(_("  -?, --help                   show this help, then exit\n"));
@@ -1260,6 +1260,116 @@ get_synchronized_snapshot(Archive *fout)
 	return result;
 }
 
+static bool
+parse_compression_method(const char *method,
+						 CompressionMethod *compressionMethod)
+{
+	bool res = true;
+
+	if (pg_strcasecmp(method, "gzip") == 0)
+		*compressionMethod = COMPRESSION_GZIP;
+	else if (pg_strcasecmp(method, "none") == 0)
+		*compressionMethod = COMPRESSION_NONE;
+	else
+	{
+		pg_log_error("invalid compression method \"%s\" (gzip, none)", method);
+		res = false;
+	}
+
+	return res;
+}
+
+/*
+ * Interprets a compression option of the format 'method[:LEVEL]' of legacy just
+ * '[LEVEL]'. In the later format, gzip is implied. The parsed method and level
+ * are returned in *compressionMethod and *compressionLevel. In case of error,
+ * the function returns false and then the values of *compression{Method,Level}
+ * are not to be trusted.
+ */
+static bool
+parse_compression_option(const char *opt, CompressionMethod *compressionMethod,
+						 int *compressLevel)
+{
+	char	   *method;
+	const char *sep;
+	int			methodlen;
+	bool		supports_compression = true;
+	bool		res = true;
+
+	/* find the separator if exists */
+	sep = strchr(opt, ':');
+
+	/*
+	 * If there is no separator, then it is either a legacy format, or only the
+	 * method has been passed.
+	 */
+	if (!sep)
+	{
+		if (strspn(opt, "-0123456789") == strlen(opt))
+		{
+			res = option_parse_int(opt, "-Z/--compress", 0, 9, compressLevel);
+			*compressionMethod = (*compressLevel > 0) ? COMPRESSION_GZIP :
+														COMPRESSION_NONE;
+		}
+		else
+			res = parse_compression_method(opt, compressionMethod);
+	}
+	else
+	{
+		/* otherwise, it should be method:LEVEL */
+		methodlen = sep - opt + 1;
+		method = pg_malloc0(methodlen);
+		snprintf(method, methodlen, "%.*s", methodlen - 1, opt);
+
+		res = parse_compression_method(method, compressionMethod);
+		if (res)
+		{
+			sep++;
+			if (*sep == '\0')
+			{
+				pg_log_error("no level defined for compression \"%s\"", method);
+				pg_free(method);
+				res = false;
+			}
+			else
+			{
+				res = option_parse_int(sep, "-Z/--compress [LEVEL]", 1, 9,
+									   compressLevel);
+			}
+		}
+	}
+
+	/* if there is an error, there is no need to check further */
+	if (!res)
+		return res;
+
+	/* one can set level when method is gzip */
+	if (*compressionMethod != COMPRESSION_GZIP && *compressLevel != INT_MIN)
+	{
+		pg_log_error("can only specify -Z/--compress [LEVEL] when method is gzip");
+		return false;
+	}
+
+	/* verify that the requested compression is supported */
+#ifndef HAVE_LIBZ
+	if (*compressionMethod == COMPRESSION_GZIP)
+		supports_compression = false;
+#endif
+#ifndef HAVE_LIBLZ4
+	if (*compressionMethod == COMPRESSION_LZ4)
+		supports_compression = false;
+#endif
+
+	if (!supports_compression)
+	{
+		pg_log_warning("requested compression not available in this installation -- archive will be uncompressed");
+		*compressionMethod = COMPRESSION_NONE;
+		*compressLevel = INT_MIN;
+	}
+
+	return true;
+}
+
 static ArchiveFormat
 parseArchiveFormat(const char *format, ArchiveMode *mode)
 {
diff --git a/src/bin/pg_dump/t/001_basic.pl b/src/bin/pg_dump/t/001_basic.pl
index 48faeb4371..840c3ac899 100644
--- a/src/bin/pg_dump/t/001_basic.pl
+++ b/src/bin/pg_dump/t/001_basic.pl
@@ -126,6 +126,16 @@ command_fails_like(
 	qr/\Qpg_dump: error: -Z\/--compress must be in range 0..9\E/,
 	'pg_dump: -Z/--compress must be in range');
 
+command_fails_like(
+	[ 'pg_dump', '--compress', 'garbage' ],
+	qr/\Qpg_dump: error: invalid compression method "garbage" (gzip, none)\E/,
+	'pg_dump: invalid --compress');
+
+command_fails_like(
+	[ 'pg_dump', '--compress', 'none:1' ],
+	qr/\Qpg_dump: error: can only specify -Z\/--compress [LEVEL] when method is gzip\E/,
+	'pg_dump: can only specify -Z/--compress [LEVEL] when method is gzip');
+
 command_fails_like(
 	[ 'pg_dump', '--extra-float-digits', '-16' ],
 	qr/\Qpg_dump: error: --extra-float-digits must be in range\E/,
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index dd065c758f..d30e96d705 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -55,6 +55,81 @@ my %pgdump_runs = (
 			"$tempdir/binary_upgrade.dump",
 		],
 	},
+
+	# Do not use --no-sync to give test coverage for data sync.
+	compression_gzip_custom_format => {
+		test_key => 'compression',
+		dump_cmd => [
+			'pg_dump',
+			'--format=custom', '--compress=gzip:1',
+			"--file=$tempdir/compression_gzip_custom_format.dump",
+			'postgres',
+		],
+		restore_cmd => [
+			'pg_restore',
+			"--file=$tempdir/compression_gzip_custom_format.sql",
+			"$tempdir/compression_gzip_custom_format.dump",
+		],
+	},
+
+	# Do not use --no-sync to give test coverage for data sync.
+	compression_gzip_directory_format => {
+		test_key => 'compression',
+		dump_cmd => [
+			'pg_dump',
+			'--format=directory', '--compress=1',
+			"--file=$tempdir/compression_gzip_directory_format",
+			'postgres',
+		],
+		# Give coverage for manually compressed blob.toc files during restore.
+		compress_cmd => [
+			$ENV{'GZIP_PROGRAM'},
+			'-f',
+			"$tempdir/compression_gzip_directory_format/blobs.toc",
+		],
+		restore_cmd => [
+			'pg_restore',
+			"--file=$tempdir/compression_gzip_directory_format.sql",
+			"$tempdir/compression_gzip_directory_format",
+		],
+	},
+
+	compression_gzip_directory_format_parallel => {
+		test_key => 'compression',
+		dump_cmd => [
+			'pg_dump', '--jobs=2',
+			'--format=directory', '--compress=gzip:6',
+			"--file=$tempdir/compression_gzip_directory_format_parallel",
+			'postgres',
+		],
+		# Give coverage for manually compressed blob.toc files during restore.
+		compress_cmd => [
+			$ENV{'GZIP_PROGRAM'},
+			'-f',
+			"$tempdir/compression_gzip_directory_format_parallel/blobs.toc",
+		],
+		restore_cmd => [
+			'pg_restore',
+			'--jobs=3',
+			"--file=$tempdir/compression_gzip_directory_format_parallel.sql",
+			"$tempdir/compression_gzip_directory_format_parallel",
+		],
+	},
+
+	# Check that the output is valid gzip
+	compression_gzip_plain_format => {
+		test_key => 'compression',
+		dump_cmd => [
+			'pg_dump', '--format=plain', '-Z1',
+			"--file=$tempdir/compression_gzip_plain_format.sql.gz",
+			'postgres',
+		],
+		compress_cmd => [
+			$ENV{'GZIP_PROGRAM'},
+			'-k', '-d',
+			"$tempdir/compression_gzip_plain_format.sql.gz",
+		],
+	},
 	clean => {
 		dump_cmd => [
 			'pg_dump',
@@ -424,6 +499,7 @@ my %full_runs = (
 	binary_upgrade           => 1,
 	clean                    => 1,
 	clean_if_exists          => 1,
+	compression              => 1,
 	createdb                 => 1,
 	defaults                 => 1,
 	exclude_dump_test_schema => 1,
@@ -2972,6 +3048,7 @@ my %tests = (
 			binary_upgrade          => 1,
 			clean                   => 1,
 			clean_if_exists         => 1,
+			compression             => 1,
 			createdb                => 1,
 			defaults                => 1,
 			exclude_test_table      => 1,
@@ -3045,6 +3122,7 @@ my %tests = (
 			binary_upgrade           => 1,
 			clean                    => 1,
 			clean_if_exists          => 1,
+			compression              => 1,
 			createdb                 => 1,
 			defaults                 => 1,
 			exclude_dump_test_schema => 1,
@@ -3821,9 +3899,23 @@ foreach my $run (sort keys %pgdump_runs)
 	my $test_key = $run;
 	my $run_db   = 'postgres';
 
+	my $supports_compression = check_pg_config("#define HAVE_LIBZ 1");
+	my $compress_program = $ENV{GZIP_PROGRAM};
+
 	$node->command_ok(\@{ $pgdump_runs{$run}->{dump_cmd} },
 		"$run: pg_dump runs");
 
+	if (defined($pgdump_runs{$run}->{compress_cmd}))
+	{
+		# Skip compression_cmd tests when compression is not supported,
+		# as the result is uncompressed or the utility program does not
+		# exist
+		next if !$supports_compression || !defined($compress_program)
+				|| $compress_program eq '';
+		command_ok( \@{ $pgdump_runs{$run}->{compress_cmd} },
+			"$run: compression commands");
+	}
+
 	if ($pgdump_runs{$run}->{restore_cmd})
 	{
 		$node->command_ok(\@{ $pgdump_runs{$run}->{restore_cmd} },
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index c6b302c7b2..ac8f20f798 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -411,7 +411,7 @@ CompiledExprState
 CompositeIOData
 CompositeTypeStmt
 CompoundAffixFlag
-CompressionAlgorithm
+CompressionMethod
 CompressorState
 ComputeXidHorizonsResult
 ConditionVariable
-- 
2.32.0

From f9576c60b6f14defd05c72d753435563553ac908 Mon Sep 17 00:00:00 2001
From: Georgios Kokolatos <[email-masked]>
Date: Wed, 23 Feb 2022 14:40:33 +0000
Subject: [PATCH v1 2/2] Add LZ4 compression in pg_{dump|restore}

Within compress_io.{c,h} there are two distinct APIs exposed, the streaming API
and a file API. The first one, is aimed at inlined use cases and thus simple
lz4.h calls can be used directly. The second one is generating output, or is
parsing input, which can be read/generated via the lz4 utility.

In the later case, the API is using an opaque wrapper around a file stream,
which aquired via fopen() or gzopen() respectively. It would then provide
wrappers around fread(), fwrite(), fgets(), fgetc(), feof(), and fclose(); or
their gz equivallents. However the LZ4F api does not provide this functionality.
So this has been implemented localy.

In order to maintain the API compatibility a new structure LZ4File is
introduced. It is responsible for keeping state and any yet unused generated
content. The later is required when the generated decompressed output, exceeds
the caller's buffer capacity.

Custom compressed archives need to now store the compression method in their
header. This requires a bump in the version number. The level of compression is
still stored in the dump, though admittedly is of no apparent use.
---
 doc/src/sgml/ref/pg_dump.sgml        |  23 +-
 src/bin/pg_dump/Makefile             |   1 +
 src/bin/pg_dump/compress_io.c        | 738 ++++++++++++++++++++++++---
 src/bin/pg_dump/pg_backup.h          |   1 +
 src/bin/pg_dump/pg_backup_archiver.c |  71 ++-
 src/bin/pg_dump/pg_backup_archiver.h |   3 +-
 src/bin/pg_dump/pg_dump.c            |  12 +-
 src/bin/pg_dump/t/001_basic.pl       |   4 +-
 src/bin/pg_dump/t/002_pg_dump.pl     | 141 ++++-
 9 files changed, 868 insertions(+), 126 deletions(-)

diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml
index 992b7312df..68a7c6a3bf 100644
--- a/doc/src/sgml/ref/pg_dump.sgml
+++ b/doc/src/sgml/ref/pg_dump.sgml
@@ -328,9 +328,10 @@ PostgreSQL documentation
            machine-readable format that <application>pg_restore</application>
            can read. A directory format archive can be manipulated with
            standard Unix tools; for example, files in an uncompressed archive
-           can be compressed with the <application>gzip</application> tool.
-           This format is compressed by default and also supports parallel
-           dumps.
+           can be compressed with the <application>gzip</application> or
+           <application>lz4</application>tool.
+           This format is compressed by default using <literal>gzip</literal>
+           and also supports parallel dumps.
           </para>
          </listitem>
         </varlistentry>
@@ -652,12 +653,12 @@ PostgreSQL documentation
        <para>
         Specify the compression method and/or the compression level to use.
         The compression method can be set to <literal>gzip</literal> or
-        <literal>none</literal> for no compression. A compression level can
-        be optionally specified, by appending the level number after a colon
-        (<literal>:</literal>). If no level is specified, the default compression
-        level will be used for the specified method. If only a level is
-        specified without mentioning a method, <literal>gzip</literal> compression
-        will be used.
+        <literal>lz4</literal> or <literal>none</literal> for no compression. A
+        compression level can be optionally specified, by appending the level
+        number after a colon (<literal>:</literal>). If no level is specified,
+        the default compression level will be used for the specified method. If
+        only a level is specified without mentioning a method,
+        <literal>gzip</literal> compression willbe used.
        </para>
 
        <para>
@@ -665,8 +666,8 @@ PostgreSQL documentation
         individual table-data segments, and the default is to compress using
         <literal>gzip</literal> at a moderate level. For plain text output,
         setting a nonzero compression level causes the entire output file to be compressed,
-        as though it had been fed through <application>gzip</application>; but the default
-        is not to compress.
+        as though it had been fed through <application>gzip</application> or
+        <application>lz4</application>; but the default is not to compress.
        </para>
        <para>
         The tar archive format currently does not support compression at all.
diff --git a/src/bin/pg_dump/Makefile b/src/bin/pg_dump/Makefile
index 56e041c9b3..625c00b123 100644
--- a/src/bin/pg_dump/Makefile
+++ b/src/bin/pg_dump/Makefile
@@ -17,6 +17,7 @@ top_builddir = ../../..
 include $(top_builddir)/src/Makefile.global
 
 export GZIP_PROGRAM=$(GZIP)
+export LZ4
 
 override CPPFLAGS := -I$(libpq_srcdir) $(CPPFLAGS)
 LDFLAGS_INTERNAL += -L$(top_builddir)/src/fe_utils -lpgfeutils $(libpq_pgport)
diff --git a/src/bin/pg_dump/compress_io.c b/src/bin/pg_dump/compress_io.c
index 630f9e4b18..790f225ec4 100644
--- a/src/bin/pg_dump/compress_io.c
+++ b/src/bin/pg_dump/compress_io.c
@@ -38,13 +38,15 @@
  * ----------------------
  *
  *	The compressed stream API is a wrapper around the C standard fopen() and
- *	libz's gzopen() APIs. It allows you to use the same functions for
- *	compressed and uncompressed streams. cfopen_read() first tries to open
- *	the file with given name, and if it fails, it tries to open the same
- *	file with the .gz suffix. cfopen_write() opens a file for writing, an
- *	extra argument specifies if the file should be compressed, and adds the
- *	.gz suffix to the filename if so. This allows you to easily handle both
- *	compressed and uncompressed files.
+ *	libz's gzopen() APIs and custom LZ4 calls which provide similar
+ *	functionality. It allows you to use the same functions for compressed and
+ *	uncompressed streams. cfopen_read() first tries to open the file with given
+ *	name, and if it fails, it tries to open the same file with the .gz suffix,
+ *	failing that it tries to open the same file with the .lz4 suffix.
+ *	cfopen_write() opens a file for writing, an extra argument specifies the
+ *	method to use should the file be compressed, and adds the appropriate
+ *	suffix, .gz or .lz4, to the filename if so. This allows you to easily handle
+ *	both compressed and uncompressed files.
  *
  * IDENTIFICATION
  *	   src/bin/pg_dump/compress_io.c
@@ -56,6 +58,14 @@
 #include "compress_io.h"
 #include "pg_backup_utils.h"
 
+#ifdef HAVE_LIBLZ4
+#include "lz4.h"
+#include "lz4frame.h"
+
+#define LZ4_OUT_SIZE	(4 * 1024)
+#define LZ4_IN_SIZE		(16 * 1024)
+#endif
+
 /*----------------------
  * Compressor API
  *----------------------
@@ -69,9 +79,9 @@ struct CompressorState
 
 #ifdef HAVE_LIBZ
 	z_streamp	zp;
-	char	   *zlibOut;
-	size_t		zlibOutSize;
 #endif
+	void	   *outbuf;
+	size_t		outsize;
 };
 
 /* Routines that support zlib compressed data I/O */
@@ -85,6 +95,15 @@ static void WriteDataToArchiveZlib(ArchiveHandle *AH, CompressorState *cs,
 static void EndCompressorZlib(ArchiveHandle *AH, CompressorState *cs);
 #endif
 
+/* Routines that support LZ4 compressed data I/O */
+#ifdef HAVE_LIBLZ4
+static void InitCompressorLZ4(CompressorState *cs);
+static void ReadDataFromArchiveLZ4(ArchiveHandle *AH, ReadFunc readF);
+static void WriteDataToArchiveLZ4(ArchiveHandle *AH, CompressorState *cs,
+								  const char *data, size_t dLen);
+static void EndCompressorLZ4(ArchiveHandle *AH, CompressorState *cs);
+#endif
+
 /* Routines that support uncompressed data I/O */
 static void ReadDataFromArchiveNone(ArchiveHandle *AH, ReadFunc readF);
 static void WriteDataToArchiveNone(ArchiveHandle *AH, CompressorState *cs,
@@ -103,6 +122,11 @@ AllocateCompressor(CompressionMethod compressionMethod,
 	if (compressionMethod == COMPRESSION_GZIP)
 		fatal("not built with zlib support");
 #endif
+#ifndef HAVE_LIBLZ4
+	if (compressionMethod == COMPRESSION_LZ4)
+		fatal("not built with LZ4 support");
+#endif
+
 
 	cs = (CompressorState *) pg_malloc0(sizeof(CompressorState));
 	cs->writeF = writeF;
@@ -115,6 +139,10 @@ AllocateCompressor(CompressionMethod compressionMethod,
 	if (compressionMethod == COMPRESSION_GZIP)
 		InitCompressorZlib(cs, compressionLevel);
 #endif
+#ifdef HAVE_LIBLZ4
+	if (compressionMethod == COMPRESSION_LZ4)
+		InitCompressorLZ4(cs);
+#endif
 
 	return cs;
 }
@@ -137,6 +165,13 @@ ReadDataFromArchive(ArchiveHandle *AH, CompressionMethod compressionMethod,
 			ReadDataFromArchiveZlib(AH, readF);
 #else
 			fatal("not built with zlib support");
+#endif
+			break;
+		case COMPRESSION_LZ4:
+#ifdef HAVE_LIBLZ4
+			ReadDataFromArchiveLZ4(AH, readF);
+#else
+			fatal("not built with lz4 support");
 #endif
 			break;
 		default:
@@ -159,6 +194,13 @@ WriteDataToArchive(ArchiveHandle *AH, CompressorState *cs,
 			WriteDataToArchiveZlib(AH, cs, data, dLen);
 #else
 			fatal("not built with zlib support");
+#endif
+			break;
+		case COMPRESSION_LZ4:
+#ifdef HAVE_LIBLZ4
+			WriteDataToArchiveLZ4(AH, cs, data, dLen);
+#else
+			fatal("not built with lz4 support");
 #endif
 			break;
 		case COMPRESSION_NONE:
@@ -183,6 +225,13 @@ EndCompressor(ArchiveHandle *AH, CompressorState *cs)
 			EndCompressorZlib(AH, cs);
 #else
 			fatal("not built with zlib support");
+#endif
+			break;
+		case COMPRESSION_LZ4:
+#ifdef HAVE_LIBLZ4
+			EndCompressorLZ4(AH, cs);
+#else
+			fatal("not built with lz4 support");
 #endif
 			break;
 		case COMPRESSION_NONE:
@@ -213,20 +262,20 @@ InitCompressorZlib(CompressorState *cs, int level)
 	zp->opaque = Z_NULL;
 
 	/*
-	 * zlibOutSize is the buffer size we tell zlib it can output to.  We
+	 * outsize is the buffer size we tell zlib it can output to.  We
 	 * actually allocate one extra byte because some routines want to append a
 	 * trailing zero byte to the zlib output.
 	 */
-	cs->zlibOut = (char *) pg_malloc(ZLIB_OUT_SIZE + 1);
-	cs->zlibOutSize = ZLIB_OUT_SIZE;
+	cs->outbuf = pg_malloc(ZLIB_OUT_SIZE + 1);
+	cs->outsize = ZLIB_OUT_SIZE;
 
 	if (deflateInit(zp, level) != Z_OK)
 		fatal("could not initialize compression library: %s",
 			  zp->msg);
 
 	/* Just be paranoid - maybe End is called after Start, with no Write */
-	zp->next_out = (void *) cs->zlibOut;
-	zp->avail_out = cs->zlibOutSize;
+	zp->next_out = cs->outbuf;
+	zp->avail_out = cs->outsize;
 }
 
 static void
@@ -243,7 +292,7 @@ EndCompressorZlib(ArchiveHandle *AH, CompressorState *cs)
 	if (deflateEnd(zp) != Z_OK)
 		fatal("could not close compression stream: %s", zp->msg);
 
-	free(cs->zlibOut);
+	free(cs->outbuf);
 	free(cs->zp);
 }
 
@@ -251,7 +300,7 @@ static void
 DeflateCompressorZlib(ArchiveHandle *AH, CompressorState *cs, bool flush)
 {
 	z_streamp	zp = cs->zp;
-	char	   *out = cs->zlibOut;
+	void	   *out = cs->outbuf;
 	int			res = Z_OK;
 
 	while (cs->zp->avail_in != 0 || flush)
@@ -259,7 +308,7 @@ DeflateCompressorZlib(ArchiveHandle *AH, CompressorState *cs, bool flush)
 		res = deflate(zp, flush ? Z_FINISH : Z_NO_FLUSH);
 		if (res == Z_STREAM_ERROR)
 			fatal("could not compress data: %s", zp->msg);
-		if ((flush && (zp->avail_out < cs->zlibOutSize))
+		if ((flush && (zp->avail_out < cs->outsize))
 			|| (zp->avail_out == 0)
 			|| (zp->avail_in != 0)
 			)
@@ -269,18 +318,18 @@ DeflateCompressorZlib(ArchiveHandle *AH, CompressorState *cs, bool flush)
 			 * chunk is the EOF marker in the custom format. This should never
 			 * happen but...
 			 */
-			if (zp->avail_out < cs->zlibOutSize)
+			if (zp->avail_out < cs->outsize)
 			{
 				/*
 				 * Any write function should do its own error checking but to
 				 * make sure we do a check here as well...
 				 */
-				size_t		len = cs->zlibOutSize - zp->avail_out;
+				size_t		len = cs->outsize - zp->avail_out;
 
-				cs->writeF(AH, out, len);
+				cs->writeF(AH, (char *)out, len);
 			}
-			zp->next_out = (void *) out;
-			zp->avail_out = cs->zlibOutSize;
+			zp->next_out = out;
+			zp->avail_out = cs->outsize;
 		}
 
 		if (res == Z_STREAM_END)
@@ -364,6 +413,71 @@ ReadDataFromArchiveZlib(ArchiveHandle *AH, ReadFunc readF)
 }
 #endif							/* HAVE_LIBZ */
 
+#ifdef HAVE_LIBLZ4
+static void
+InitCompressorLZ4(CompressorState *cs)
+{
+	/* Will be lazy init'd */
+	cs->outbuf = NULL;
+	cs->outsize = 0;
+}
+
+static void
+EndCompressorLZ4(ArchiveHandle *AH, CompressorState *cs)
+{
+	pg_free(cs->outbuf);
+
+	cs->outbuf = NULL;
+	cs->outsize = 0;
+}
+
+static void
+ReadDataFromArchiveLZ4(ArchiveHandle *AH, ReadFunc readF)
+{
+	LZ4_streamDecode_t lz4StreamDecode;
+	char	   *buf;
+	char	   *decbuf;
+	size_t		buflen;
+	size_t		cnt;
+
+	buflen = (4 * 1024) + 1;
+	buf = pg_malloc(buflen);
+	decbuf = pg_malloc(buflen);
+
+	LZ4_setStreamDecode(&lz4StreamDecode, NULL, 0);
+
+	while ((cnt = readF(AH, &buf, &buflen)))
+	{
+		int		decBytes = LZ4_decompress_safe_continue(&lz4StreamDecode,
+														buf, decbuf,
+														cnt, buflen);
+
+		ahwrite(decbuf, 1, decBytes, AH);
+	}
+}
+
+static void
+WriteDataToArchiveLZ4(ArchiveHandle *AH, CompressorState *cs,
+					  const char *data, size_t dLen)
+{
+	size_t		compressed;
+	size_t		requiredsize = LZ4_compressBound(dLen);
+
+	if (requiredsize > cs->outsize)
+	{
+		cs->outbuf = pg_realloc(cs->outbuf, requiredsize);
+		cs->outsize = requiredsize;
+	}
+
+	compressed = LZ4_compress_default(data, cs->outbuf,
+									  dLen, cs->outsize);
+
+	if (compressed <= 0)
+		fatal("failed to LZ4 compress data");
+
+	cs->writeF(AH, cs->outbuf, compressed);
+}
+#endif							/* HAVE_LIBLZ4 */
 
 /*
  * Functions for uncompressed output.
@@ -400,9 +514,36 @@ WriteDataToArchiveNone(ArchiveHandle *AH, CompressorState *cs,
  *----------------------
  */
 
+#ifdef HAVE_LIBLZ4
 /*
- * cfp represents an open stream, wrapping the underlying FILE or gzFile
- * pointer. This is opaque to the callers.
+ * State needed for LZ4 (de)compression using the cfp API.
+ */
+typedef struct LZ4File
+{
+	FILE	*fp;
+
+	LZ4F_preferences_t			prefs;
+
+	LZ4F_compressionContext_t	ctx;
+	LZ4F_decompressionContext_t	dtx;
+
+	bool	inited;
+	bool	compressing;
+
+	size_t	buflen;
+	char   *buffer;
+
+	size_t  overflowalloclen;
+	size_t	overflowlen;
+	char   *overflowbuf;
+
+	size_t	errcode;
+} LZ4File;
+#endif
+
+/*
+ * cfp represents an open stream, wrapping the underlying FILE, gzFile
+ * pointer, or LZ4File pointer. This is opaque to the callers.
  */
 struct cfp
 {
@@ -410,9 +551,7 @@ struct cfp
 	void	   *fp;
 };
 
-#ifdef HAVE_LIBZ
 static int	hasSuffix(const char *filename, const char *suffix);
-#endif
 
 /* free() without changing errno; useful in several places below */
 static void
@@ -424,26 +563,380 @@ free_keep_errno(void *p)
 	errno = save_errno;
 }
 
+#ifdef HAVE_LIBLZ4
+/*
+ * LZ4 equivalent to feof() or gzeof(). The end of file
+ * is reached if there is no decompressed output in the
+ * overflow buffer and the end of the file is reached.
+ */
+static int
+LZ4File_eof(LZ4File *fs)
+{
+	return fs->overflowlen == 0 && feof(fs->fp);
+}
+
+static const char *
+LZ4File_error(LZ4File *fs)
+{
+	const char *errmsg;
+
+	if (LZ4F_isError(fs->errcode))
+		errmsg = LZ4F_getErrorName(fs->errcode);
+	else
+		errmsg = strerror(errno);
+
+	return errmsg;
+}
+
+/*
+ * Prepare an already alloc'ed LZ4File struct for subsequent calls.
+ *
+ * It creates the nessary contexts for the operations. When compressing,
+ * it additionally writes the LZ4 header in the output stream.
+ */
+static int
+LZ4File_init(LZ4File *fs, int size, bool compressing)
+{
+	size_t	status;
+
+	if (fs->inited)
+		return 0;
+
+	fs->compressing = compressing;
+	fs->inited = true;
+
+	if (fs->compressing)
+	{
+		fs->buflen = LZ4F_compressBound(LZ4_IN_SIZE, &fs->prefs);
+		if (fs->buflen < LZ4F_HEADER_SIZE_MAX)
+			fs->buflen = LZ4F_HEADER_SIZE_MAX;
+
+		status = LZ4F_createCompressionContext(&fs->ctx, LZ4F_VERSION);
+		if (LZ4F_isError(status))
+		{
+			fs->errcode = status;
+			return 1;
+		}
+
+		fs->buffer = pg_malloc(fs->buflen);
+		status = LZ4F_compressBegin(fs->ctx, fs->buffer, fs->buflen, &fs->prefs);
+
+		if (LZ4F_isError(status))
+		{
+			fs->errcode = status;
+			return 1;
+		}
+
+		if (fwrite(fs->buffer, 1, status, fs->fp) != status)
+		{
+			errno = errno ? : ENOSPC;
+			return 1;
+		}
+	}
+	else
+	{
+		status = LZ4F_createDecompressionContext(&fs->dtx, LZ4F_VERSION);
+		if (LZ4F_isError(status))
+		{
+			fs->errcode = status;
+			return 1;
+		}
+
+		fs->buflen = size > LZ4_OUT_SIZE ? size : LZ4_OUT_SIZE;
+		fs->buffer = pg_malloc(fs->buflen);
+
+		fs->overflowalloclen = fs->buflen;
+		fs->overflowbuf = pg_malloc(fs->overflowalloclen);
+		fs->overflowlen = 0;
+	}
+
+	return 0;
+}
+
+/*
+ * Read already decompressed content from the overflow buffer into 'ptr' up to
+ * 'size' bytes, if available. If the eol_flag is set, then stop at the first
+ * occurance of the new line char prior to 'size' bytes.
+ *
+ * Any unread content in the overflow buffer, is moved to the beginning.
+ */
+static int
+LZ4File_read_overflow(LZ4File *fs, void *ptr, int size, bool eol_flag)
+{
+	char   *p;
+	int		readlen = 0;
+
+	if (fs->overflowlen == 0)
+		return 0;
+
+	if (fs->overflowlen >= size)
+		readlen = size;
+	else
+		readlen = fs->overflowlen;
+
+	if (eol_flag && (p = memchr(fs->overflowbuf, '\n', readlen)))
+		readlen = p - fs->overflowbuf + 1; /* Include the line terminating char */
+
+	memcpy(ptr, fs->overflowbuf, readlen);
+	fs->overflowlen -= readlen;
+
+	if (fs->overflowlen > 0)
+		memmove(fs->overflowbuf, fs->overflowbuf + readlen, fs->overflowlen);
+
+	return readlen;
+}
+
+/*
+ * The workhorse for reading decompressed content out of an LZ4 compressed
+ * stream.
+ *
+ * It will read up to 'ptrsize' decompressed content, or up to the new line char
+ * if found first when the eol_flag is set. It is possible that the decompressed
+ * output generated by reading any compressed input via the LZ4F API, exceeds
+ * 'ptrsize'. Any exceeding decompressed content is stored at an overflow
+ * buffer within LZ4File. Of course, when the function is called, it will first
+ * try to consume any decompressed content already present in the overflow
+ * buffer, before decompressing new content.
+ */
+static int
+LZ4File_read(LZ4File *fs, void *ptr, int ptrsize, bool eol_flag)
+{
+	size_t	dsize = 0;
+	size_t  rsize;
+	size_t	size = ptrsize;
+	bool	eol_found = false;
+
+	void *readbuf;
+
+	/* Lazy init */
+	if (!fs->inited && LZ4File_init(fs, size, false /* decompressing */))
+		return -1;
+
+	/* Verfiy that there is enough space in the outbuf */
+	if (size > fs->buflen)
+	{
+		fs->buflen = size;
+		fs->buffer = pg_realloc(fs->buffer, size);
+	}
+
+	/* use already decompressed content if available */
+	dsize = LZ4File_read_overflow(fs, ptr, size, eol_flag);
+	if (dsize == size || (eol_flag && memchr(ptr, '\n', dsize)))
+		return dsize;
+
+	readbuf = pg_malloc(size);
+
+	do
+	{
+		char   *rp;
+		char   *rend;
+
+		rsize = fread(readbuf, 1, size, fs->fp);
+		if (rsize < size && !feof(fs->fp))
+			return -1;
+
+		rp = (char *)readbuf;
+		rend = (char *)readbuf + rsize;
+
+		while (rp < rend)
+		{
+			size_t	status;
+			size_t	outlen = fs->buflen;
+			size_t	read_remain = rend - rp;
+
+			memset(fs->buffer, 0, outlen);
+			status = LZ4F_decompress(fs->dtx, fs->buffer, &outlen,
+									 rp, &read_remain, NULL);
+			if (LZ4F_isError(status))
+			{
+				fs->errcode = status;
+				return -1;
+			}
+
+			rp += read_remain;
+
+			/*
+			 * fill in what space is available in ptr
+			 * if the eol flag is set, either skip if one already found or fill up to EOL
+			 * if present in the outbuf
+			 */
+			if (outlen > 0 && dsize < size && eol_found == false)
+			{
+				char   *p;
+				size_t	lib = (eol_flag == 0) ? size - dsize : size -1 -dsize;
+				size_t	len = outlen < lib ? outlen : lib;
+
+				if (eol_flag == true && (p = memchr(fs->buffer, '\n', outlen)) &&
+					(size_t)(p - fs->buffer + 1) <= len)
+				{
+					len = p - fs->buffer + 1;
+					eol_found = true;
+				}
+
+				memcpy((char *)ptr + dsize, fs->buffer, len);
+				dsize += len;
+
+				/* move what did not fit, if any, at the begining of the buf */
+				if (len < outlen)
+					memmove(fs->buffer, fs->buffer + len, outlen - len);
+				outlen -= len;
+			}
+
+			/* if there is available output, save it */
+			if (outlen > 0)
+			{
+				while (fs->overflowlen + outlen > fs->overflowalloclen)
+				{
+					fs->overflowalloclen *= 2;
+					fs->overflowbuf = pg_realloc(fs->overflowbuf, fs->overflowalloclen);
+				}
+
+				memcpy(fs->overflowbuf + fs->overflowlen, fs->buffer, outlen);
+				fs->overflowlen += outlen;
+			}
+		}
+	} while (rsize == size && dsize < size && eol_found == 0);
+
+	pg_free(readbuf);
+
+	return (int)dsize;
+}
+
+/*
+ * Compress size bytes from ptr and write them to the stream.
+ */
+static int
+LZ4File_write(LZ4File *fs, const void *ptr, int size)
+{
+	size_t	status;
+	int		remaining = size;
+
+	if (!fs->inited && LZ4File_init(fs, size, true))
+		return -1;
+
+	while (remaining > 0)
+	{
+		int		chunk = remaining < LZ4_IN_SIZE ? remaining : LZ4_IN_SIZE;
+		remaining -= chunk;
+
+		status = LZ4F_compressUpdate(fs->ctx, fs->buffer, fs->buflen,
+									 ptr, chunk, NULL);
+		if (LZ4F_isError(status))
+		{
+			fs->errcode = status;
+			return -1;
+		}
+
+		if (fwrite(fs->buffer, 1, status, fs->fp) != status)
+		{
+			errno = errno ? : ENOSPC;
+			return 1;
+		}
+	}
+
+	return size;
+}
+
+/*
+ * fgetc() and gzgetc() equivalent implementation for LZ4 compressed files.
+ */
+static int
+LZ4File_getc(LZ4File *fs)
+{
+	unsigned char c;
+
+	if (LZ4File_read(fs, &c, 1, false) != 1)
+		return EOF;
+
+	return c;
+}
+
+/*
+ * fgets() and gzgets() equivalent implementation for LZ4 compressed files.
+ */
+static char *
+LZ4File_gets(LZ4File *fs, char *buf, int len)
+{
+	size_t	dsize;
+
+	dsize = LZ4File_read(fs, buf, len, true);
+	if (dsize < 0)
+		fatal("failed to read from archive %s", LZ4File_error(fs));
+
+	/* Done reading */
+	if (dsize == 0)
+		return NULL;
+
+	return buf;
+}
+
+/*
+ * Finalize (de)compression of a stream. When compressing it will write any
+ * remaining content and/or generated footer from the LZ4 API.
+ */
+static int
+LZ4File_close(LZ4File *fs)
+{
+	FILE	*fp;
+	size_t	status;
+	int		ret;
+
+	fp = fs->fp;
+	if (fs->inited)
+	{
+		if (fs->compressing)
+		{
+			status = LZ4F_compressEnd(fs->ctx, fs->buffer, fs->buflen, NULL);
+			if (LZ4F_isError(status))
+				fatal("failed to end compression: %s", LZ4F_getErrorName(status));
+			else if ((ret = fwrite(fs->buffer, 1, status, fs->fp)) != status)
+			{
+				errno = errno ? : ENOSPC;
+				WRITE_ERROR_EXIT;
+			}
+
+			status = LZ4F_freeCompressionContext(fs->ctx);
+			if (LZ4F_isError(status))
+				fatal("failed to end compression: %s", LZ4F_getErrorName(status));
+		}
+		else
+		{
+			status = LZ4F_freeDecompressionContext(fs->dtx);
+			if (LZ4F_isError(status))
+				fatal("failed to end decompression: %s", LZ4F_getErrorName(status));
+			pg_free(fs->overflowbuf);
+		}
+
+		pg_free(fs->buffer);
+	}
+
+	pg_free(fs);
+
+	return fclose(fp);
+}
+#endif
+
 /*
  * Open a file for reading. 'path' is the file to open, and 'mode' should
  * be either "r" or "rb".
  *
- * If the file at 'path' does not exist, we append the ".gz" suffix (if 'path'
- * doesn't already have it) and try again. So if you pass "foo" as 'path',
- * this will open either "foo" or "foo.gz".
+ * If the file at 'path' does not exist, we append the "{.gz,.lz4}" suffix (if
+ * 'path' doesn't already have it) and try again. So if you pass "foo" as 'path',
+ * this will open either "foo" or "foo.gz" or "foo.lz4", trying in that order.
  *
  * On failure, return NULL with an error code in errno.
+ *
  */
 cfp *
 cfopen_read(const char *path, const char *mode)
 {
-	cfp		   *fp;
+	cfp		   *fp = NULL;
 
-#ifdef HAVE_LIBZ
 	if (hasSuffix(path, ".gz"))
 		fp = cfopen(path, mode, COMPRESSION_GZIP, 0);
+	else if (hasSuffix(path, ".lz4"))
+		fp = cfopen(path, mode, COMPRESSION_LZ4, 0);
 	else
-#endif
 	{
 		fp = cfopen(path, mode, COMPRESSION_NONE, 0);
 #ifdef HAVE_LIBZ
@@ -455,8 +948,19 @@ cfopen_read(const char *path, const char *mode)
 			fp = cfopen(fname, mode, COMPRESSION_GZIP, 0);
 			free_keep_errno(fname);
 		}
+#endif
+#ifdef HAVE_LIBLZ4
+		if (fp == NULL)
+		{
+			char	   *fname;
+
+			fname = psprintf("%s.lz4", path);
+			fp = cfopen(fname, mode, COMPRESSION_LZ4, 0);
+			free_keep_errno(fname);
+		}
 #endif
 	}
+
 	return fp;
 }
 
@@ -465,9 +969,13 @@ cfopen_read(const char *path, const char *mode)
  * be a filemode as accepted by fopen() and gzopen() that indicates writing
  * ("w", "wb", "a", or "ab").
  *
- * If 'compression' is non-zero, a gzip compressed stream is opened, and
- * 'compression' indicates the compression level used. The ".gz" suffix
- * is automatically added to 'path' in that case.
+ * When 'compressionMethod' indicates gzip, a gzip compressed stream is opened,
+ * and 'compressionLevel' is used. The ".gz" suffix is automatically added to
+ * 'path' in that case. The same applies when 'compressionMethod' indicates lz4,
+ * but then the ".lz4" suffix is added instead.
+ *
+ * It is the caller's responsibility to verify that the requested
+ * 'compressionMethod' is supported by the build.
  *
  * On failure, return NULL with an error code in errno.
  */
@@ -476,23 +984,44 @@ cfopen_write(const char *path, const char *mode,
 			 CompressionMethod compressionMethod,
 			 int compressionLevel)
 {
-	cfp		   *fp;
+	cfp		   *fp = NULL;
 
-	if (compressionMethod == COMPRESSION_NONE)
-		fp = cfopen(path, mode, compressionMethod, 0);
-	else
+	switch (compressionMethod)
 	{
+		case COMPRESSION_NONE:
+			fp = cfopen(path, mode, compressionMethod, 0);
+			break;
+		case COMPRESSION_GZIP:
 #ifdef HAVE_LIBZ
-		char	   *fname;
+			{
+				char	   *fname;
 
-		fname = psprintf("%s.gz", path);
-		fp = cfopen(fname, mode, compressionMethod, compressionLevel);
-		free_keep_errno(fname);
+				fname = psprintf("%s.gz", path);
+				fp = cfopen(fname, mode, compressionMethod, compressionLevel);
+				free_keep_errno(fname);
+			}
 #else
-		fatal("not built with zlib support");
-		fp = NULL;				/* keep compiler quiet */
+			fatal("not built with zlib support");
 #endif
+			break;
+		case COMPRESSION_LZ4:
+#ifdef HAVE_LIBLZ4
+			{
+				char	   *fname;
+
+				fname = psprintf("%s.lz4", path);
+				fp = cfopen(fname, mode, compressionMethod, compressionLevel);
+				free_keep_errno(fname);
+			}
+#else
+			fatal("not built with LZ4 support");
+#endif
+			break;
+		default:
+			fatal("invalid compression method");
+			break;
 	}
+
 	return fp;
 }
 
@@ -509,7 +1038,7 @@ static cfp *
 cfopen_internal(const char *path, int fd, const char *mode,
 				CompressionMethod compressionMethod, int compressionLevel)
 {
-	cfp		   *fp = pg_malloc(sizeof(cfp));
+	cfp		   *fp = pg_malloc0(sizeof(cfp));
 
 	fp->compressionMethod = compressionMethod;
 
@@ -560,6 +1089,27 @@ cfopen_internal(const char *path, int fd, const char *mode,
 			}
 #else
 			fatal("not built with zlib support");
+#endif
+			break;
+		case COMPRESSION_LZ4:
+#ifdef HAVE_LIBLZ4
+			{
+				LZ4File *lz4fp = pg_malloc0(sizeof(*lz4fp));
+				if (fd >= 0)
+					lz4fp->fp = fdopen(fd, mode);
+				else
+					lz4fp->fp = fopen(path, mode);
+				if (lz4fp->fp == NULL)
+				{
+					free_keep_errno(lz4fp);
+					fp = NULL;
+				}
+				if (compressionLevel >= 0)
+					lz4fp->prefs.compressionLevel = compressionLevel;
+				fp->fp = lz4fp;
+			}
+#else
+			fatal("not built with LZ4 support");
 #endif
 			break;
 		default:
@@ -580,8 +1130,8 @@ cfopen(const char *path, const char *mode,
 
 cfp *
 cfdopen(int fd, const char *mode,
-	   CompressionMethod compressionMethod,
-	   int compressionLevel)
+		CompressionMethod compressionMethod,
+		int compressionLevel)
 {
 	return cfopen_internal(NULL, fd, mode, compressionMethod, compressionLevel);
 }
@@ -617,7 +1167,16 @@ cfread(void *ptr, int size, cfp *fp)
 			fatal("not built with zlib support");
 #endif
 			break;
-
+		case COMPRESSION_LZ4:
+#ifdef HAVE_LIBLZ4
+			ret = LZ4File_read(fp->fp, ptr, size, false);
+			if (ret != size && !LZ4File_eof(fp->fp))
+				fatal("could not read from input file: %s",
+					  LZ4File_error(fp->fp));
+#else
+			fatal("not built with LZ4 support");
+#endif
+			break;
 		default:
 			fatal("invalid compression method");
 			break;
@@ -641,6 +1200,13 @@ cfwrite(const void *ptr, int size, cfp *fp)
 			ret = gzwrite(fp->fp, ptr, size);
 #else
 			fatal("not built with zlib support");
+#endif
+			break;
+		case COMPRESSION_LZ4:
+#ifdef HAVE_LIBLZ4
+			ret = LZ4File_write(fp->fp, ptr, size);
+#else
+			fatal("not built with LZ4 support");
 #endif
 			break;
 		default:
@@ -676,6 +1242,20 @@ cfgetc(cfp *fp)
 			}
 #else
 			fatal("not built with zlib support");
+#endif
+			break;
+		case COMPRESSION_LZ4:
+#ifdef HAVE_LIBLZ4
+			ret = LZ4File_getc(fp->fp);
+			if (ret == EOF)
+			{
+				if (!LZ4File_eof(fp->fp))
+					fatal("could not read from input file: %s", strerror(errno));
+				else
+					fatal("could not read from input file: end of file");
+			}
+#else
+			fatal("not built with LZ4 support");
 #endif
 			break;
 		default:
@@ -695,13 +1275,19 @@ cfgets(cfp *fp, char *buf, int len)
 	{
 		case COMPRESSION_NONE:
 			ret = fgets(buf, len, fp->fp);
-
 			break;
 		case COMPRESSION_GZIP:
 #ifdef HAVE_LIBZ
 			ret = gzgets(fp->fp, buf, len);
 #else
 			fatal("not built with zlib support");
+#endif
+			break;
+		case COMPRESSION_LZ4:
+#ifdef HAVE_LIBLZ4
+			ret = LZ4File_gets(fp->fp, buf, len);
+#else
+			fatal("not built with LZ4 support");
 #endif
 			break;
 		default:
@@ -736,6 +1322,14 @@ cfclose(cfp *fp)
 			fp->fp = NULL;
 #else
 			fatal("not built with zlib support");
+#endif
+			break;
+		case COMPRESSION_LZ4:
+#ifdef HAVE_LIBLZ4
+			ret = LZ4File_close(fp->fp);
+			fp->fp = NULL;
+#else
+			fatal("not built with LZ4 support");
 #endif
 			break;
 		default:
@@ -764,6 +1358,13 @@ cfeof(cfp *fp)
 			ret = gzeof(fp->fp);
 #else
 			fatal("not built with zlib support");
+#endif
+			break;
+		case COMPRESSION_LZ4:
+#ifdef HAVE_LIBLZ4
+			ret = LZ4File_eof(fp->fp);
+#else
+			fatal("not built with LZ4 support");
 #endif
 			break;
 		default:
@@ -777,23 +1378,42 @@ cfeof(cfp *fp)
 const char *
 get_cfp_error(cfp *fp)
 {
-	if (fp->compressionMethod == COMPRESSION_GZIP)
+	const char *errmsg = NULL;
+
+	switch(fp->compressionMethod)
 	{
+		case COMPRESSION_NONE:
+			errmsg = strerror(errno);
+
+			break;
+		case COMPRESSION_GZIP:
 #ifdef HAVE_LIBZ
-		int			errnum;
-		const char *errmsg = gzerror(fp->fp, &errnum);
+			{
+				int			errnum;
+				errmsg = gzerror(fp->fp, &errnum);
 
-		if (errnum != Z_ERRNO)
-			return errmsg;
+				if (errnum == Z_ERRNO)
+					errmsg = strerror(errno);
+			}
 #else
-		fatal("not built with zlib support");
+			fatal("not built with zlib support");
+#endif
+			break;
+		case COMPRESSION_LZ4:
+#ifdef HAVE_LIBLZ4
+			errmsg = LZ4File_error(fp->fp);
+#else
+			fatal("not built with LZ4 support");
 #endif
+			break;
+		default:
+			fatal("invalid compression method");
+			break;
 	}
 
-	return strerror(errno);
+	return errmsg;
 }
 
-#ifdef HAVE_LIBZ
 static int
 hasSuffix(const char *filename, const char *suffix)
 {
@@ -807,5 +1427,3 @@ hasSuffix(const char *filename, const char *suffix)
 				  suffix,
 				  suffixlen) == 0;
 }
-
-#endif
diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h
index 741810b066..95b02dac3b 100644
--- a/src/bin/pg_dump/pg_backup.h
+++ b/src/bin/pg_dump/pg_backup.h
@@ -78,6 +78,7 @@ enum _dumpPreparedQueries
 typedef enum _compressionMethod
 {
 	COMPRESSION_GZIP,
+	COMPRESSION_LZ4,
 	COMPRESSION_NONE
 } CompressionMethod;
 
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index 7d96446f1a..e6f727534b 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -353,6 +353,7 @@ RestoreArchive(Archive *AHX)
 	ArchiveHandle *AH = (ArchiveHandle *) AHX;
 	RestoreOptions *ropt = AH->public.ropt;
 	bool		parallel_mode;
+	bool		supports_compression;
 	TocEntry   *te;
 	cfp		   *sav;
 
@@ -382,17 +383,27 @@ RestoreArchive(Archive *AHX)
 	/*
 	 * Make sure we won't need (de)compression we haven't got
 	 */
-#ifndef HAVE_LIBZ
-	if (AH->compressionMethod == COMPRESSION_GZIP &&
+	supports_compression = true;
+	if (AH->compressionMethod != COMPRESSION_NONE &&
 		AH->PrintTocDataPtr != NULL)
 	{
 		for (te = AH->toc->next; te != AH->toc; te = te->next)
 		{
 			if (te->hadDumper && (te->reqs & REQ_DATA) != 0)
-				fatal("cannot restore from compressed archive (compression not supported in this installation)");
+			{
+#ifndef HAVE_LIBZ
+				if (AH->compressionMethod == COMPRESSION_GZIP)
+					supports_compression = false;
+#endif
+#ifndef HAVE_LIBLZ4
+				if (AH->compressionMethod == COMPRESSION_LZ4)
+					supports_compression = false;
+#endif
+				if (supports_compression == false)
+					fatal("cannot restore from compressed archive (compression not supported in this installation)");
+			}
 		}
 	}
-#endif
 
 	/*
 	 * Prepare index arrays, so we can assume we have them throughout restore.
@@ -2019,6 +2030,18 @@ ReadStr(ArchiveHandle *AH)
 	return buf;
 }
 
+static bool
+_fileExistsInDirectory(const char *dir, const char *filename)
+{
+	struct stat st;
+	char		buf[MAXPGPATH];
+
+	if (snprintf(buf, MAXPGPATH, "%s/%s", dir, filename) >= MAXPGPATH)
+		fatal("directory name too long: \"%s\"", dir);
+
+	return (stat(buf, &st) == 0 && S_ISREG(st.st_mode));
+}
+
 static int
 _discoverArchiveFormat(ArchiveHandle *AH)
 {
@@ -2046,30 +2069,21 @@ _discoverArchiveFormat(ArchiveHandle *AH)
 
 		/*
 		 * Check if the specified archive is a directory. If so, check if
-		 * there's a "toc.dat" (or "toc.dat.gz") file in it.
+		 * there's a "toc.dat" (or "toc.dat.{gz,lz4}") file in it.
 		 */
 		if (stat(AH->fSpec, &st) == 0 && S_ISDIR(st.st_mode))
 		{
-			char		buf[MAXPGPATH];
 
-			if (snprintf(buf, MAXPGPATH, "%s/toc.dat", AH->fSpec) >= MAXPGPATH)
-				fatal("directory name too long: \"%s\"",
-					  AH->fSpec);
-			if (stat(buf, &st) == 0 && S_ISREG(st.st_mode))
-			{
-				AH->format = archDirectory;
+			AH->format = archDirectory;
+			if (_fileExistsInDirectory(AH->fSpec, "toc.dat"))
 				return AH->format;
-			}
-
 #ifdef HAVE_LIBZ
-			if (snprintf(buf, MAXPGPATH, "%s/toc.dat.gz", AH->fSpec) >= MAXPGPATH)
-				fatal("directory name too long: \"%s\"",
-					  AH->fSpec);
-			if (stat(buf, &st) == 0 && S_ISREG(st.st_mode))
-			{
-				AH->format = archDirectory;
+			if (_fileExistsInDirectory(AH->fSpec, "toc.dat.gz"))
+				return AH->format;
+#endif
+#ifdef HAVE_LIBLZ4
+			if (_fileExistsInDirectory(AH->fSpec, "toc.dat.lz4"))
 				return AH->format;
-			}
 #endif
 			fatal("directory \"%s\" does not appear to be a valid archive (\"toc.dat\" does not exist)",
 				  AH->fSpec);
@@ -3681,6 +3695,7 @@ WriteHead(ArchiveHandle *AH)
 	AH->WriteBytePtr(AH, AH->offSize);
 	AH->WriteBytePtr(AH, AH->format);
 	WriteInt(AH, AH->compressionLevel);
+	AH->WriteBytePtr(AH, AH->compressionMethod);
 	crtm = *localtime(&AH->createDate);
 	WriteInt(AH, crtm.tm_sec);
 	WriteInt(AH, crtm.tm_min);
@@ -3761,14 +3776,20 @@ ReadHead(ArchiveHandle *AH)
 	else
 		AH->compressionLevel = Z_DEFAULT_COMPRESSION;
 
-	if (AH->compressionLevel != INT_MIN)
+	if (AH->version >= K_VERS_1_15)
+		AH->compressionMethod = AH->ReadBytePtr(AH);
+	else if (AH->compressionLevel != 0)
+		AH->compressionMethod = COMPRESSION_GZIP;
+
 #ifndef HAVE_LIBZ
+	if (AH->compressionMethod == COMPRESSION_GZIP)
+	{
 		pg_log_warning("archive is compressed, but this installation does not support compression -- no data will be available");
-#else
-		AH->compressionMethod = COMPRESSION_GZIP;
+		AH->compressionMethod = COMPRESSION_NONE;
+		AH->compressionLevel = 0;
+	}
 #endif
 
-
 	if (AH->version >= K_VERS_1_4)
 	{
 		struct tm	crtm;
diff --git a/src/bin/pg_dump/pg_backup_archiver.h b/src/bin/pg_dump/pg_backup_archiver.h
index 837e9d73f5..037bfcf913 100644
--- a/src/bin/pg_dump/pg_backup_archiver.h
+++ b/src/bin/pg_dump/pg_backup_archiver.h
@@ -65,10 +65,11 @@
 #define K_VERS_1_13 MAKE_ARCHIVE_VERSION(1, 13, 0)	/* change search_path
 													 * behavior */
 #define K_VERS_1_14 MAKE_ARCHIVE_VERSION(1, 14, 0)	/* add tableam */
+#define K_VERS_1_15 MAKE_ARCHIVE_VERSION(1, 15, 0)	/* add compressionMethod in header */
 
 /* Current archive version number (the format we can output) */
 #define K_VERS_MAJOR 1
-#define K_VERS_MINOR 14
+#define K_VERS_MINOR 15
 #define K_VERS_REV 0
 #define K_VERS_SELF MAKE_ARCHIVE_VERSION(K_VERS_MAJOR, K_VERS_MINOR, K_VERS_REV)
 
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 7477d5112e..437a25b8d9 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -999,7 +999,7 @@ help(const char *progname)
 	printf(_("  -j, --jobs=NUM               use this many parallel jobs to dump\n"));
 	printf(_("  -v, --verbose                verbose mode\n"));
 	printf(_("  -V, --version                output version information, then exit\n"));
-	printf(_("  -Z, --compress=[gzip,none][:LEVEL] or [LEVEL]\n"
+	printf(_("  -Z, --compress=[gzip,lz4,none][:LEVEL] or [LEVEL]\n"
 			 "                               compress output with given method or level\n"));
 	printf(_("  --lock-wait-timeout=TIMEOUT  fail after waiting TIMEOUT for a table lock\n"));
 	printf(_("  --no-sync                    do not wait for changes to be written safely to disk\n"));
@@ -1268,11 +1268,13 @@ parse_compression_method(const char *method,
 
 	if (pg_strcasecmp(method, "gzip") == 0)
 		*compressionMethod = COMPRESSION_GZIP;
+	else if (pg_strcasecmp(method, "lz4") == 0)
+		*compressionMethod = COMPRESSION_LZ4;
 	else if (pg_strcasecmp(method, "none") == 0)
 		*compressionMethod = COMPRESSION_NONE;
 	else
 	{
-		pg_log_error("invalid compression method \"%s\" (gzip, none)", method);
+		pg_log_error("invalid compression method \"%s\" (gzip, lz4, none)", method);
 		res = false;
 	}
 
@@ -1343,10 +1345,10 @@ parse_compression_option(const char *opt, CompressionMethod *compressionMethod,
 	if (!res)
 		return res;
 
-	/* one can set level when method is gzip */
-	if (*compressionMethod != COMPRESSION_GZIP && *compressLevel != INT_MIN)
+	/* one can set level when a compression method is set */
+	if (*compressionMethod == COMPRESSION_NONE && *compressLevel != INT_MIN)
 	{
-		pg_log_error("can only specify -Z/--compress [LEVEL] when method is gzip");
+		pg_log_error("can only specify -Z/--compress [LEVEL] when method is set");
 		return false;
 	}
 
diff --git a/src/bin/pg_dump/t/001_basic.pl b/src/bin/pg_dump/t/001_basic.pl
index 840c3ac899..4185c19e8b 100644
--- a/src/bin/pg_dump/t/001_basic.pl
+++ b/src/bin/pg_dump/t/001_basic.pl
@@ -128,12 +128,12 @@ command_fails_like(
 
 command_fails_like(
 	[ 'pg_dump', '--compress', 'garbage' ],
-	qr/\Qpg_dump: error: invalid compression method "garbage" (gzip, none)\E/,
+	qr/\Qpg_dump: error: invalid compression method "garbage" (gzip, lz4, none)\E/,
 	'pg_dump: invalid --compress');
 
 command_fails_like(
 	[ 'pg_dump', '--compress', 'none:1' ],
-	qr/\Qpg_dump: error: can only specify -Z\/--compress [LEVEL] when method is gzip\E/,
+	qr/\Qpg_dump: error: can only specify -Z\/--compress [LEVEL] when method is set\E/,
 	'pg_dump: can only specify -Z/--compress [LEVEL] when method is gzip');
 
 command_fails_like(
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index d30e96d705..81e18f5a87 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -82,11 +82,14 @@ my %pgdump_runs = (
 			'postgres',
 		],
 		# Give coverage for manually compressed blob.toc files during restore.
-		compress_cmd => [
-			$ENV{'GZIP_PROGRAM'},
-			'-f',
-			"$tempdir/compression_gzip_directory_format/blobs.toc",
-		],
+		compression => {
+			method => 'gzip',
+			cmd => [
+				$ENV{'GZIP_PROGRAM'},
+				'-f',
+				"$tempdir/compression_gzip_directory_format/blobs.toc",
+			],
+		},
 		restore_cmd => [
 			'pg_restore',
 			"--file=$tempdir/compression_gzip_directory_format.sql",
@@ -102,12 +105,15 @@ my %pgdump_runs = (
 			"--file=$tempdir/compression_gzip_directory_format_parallel",
 			'postgres',
 		],
-		# Give coverage for manually compressed blob.toc files during restore.
-		compress_cmd => [
-			$ENV{'GZIP_PROGRAM'},
-			'-f',
-			"$tempdir/compression_gzip_directory_format_parallel/blobs.toc",
-		],
+		# Give coverage for manually compressed toc.dat files during restore.
+		compression => {
+			method => 'gzip',
+			cmd => [
+				$ENV{'GZIP_PROGRAM'},
+				'-f',
+				"$tempdir/compression_gzip_directory_format_parallel/toc.dat",
+			],
+		},
 		restore_cmd => [
 			'pg_restore',
 			'--jobs=3',
@@ -124,11 +130,95 @@ my %pgdump_runs = (
 			"--file=$tempdir/compression_gzip_plain_format.sql.gz",
 			'postgres',
 		],
-		compress_cmd => [
-			$ENV{'GZIP_PROGRAM'},
-			'-k', '-d',
-			"$tempdir/compression_gzip_plain_format.sql.gz",
+		compression => {
+			method => 'gzip',
+			cmd => [
+				$ENV{'GZIP_PROGRAM'},
+				'-k', '-d',
+				"$tempdir/compression_gzip_plain_format.sql.gz",
+			],
+		},
+	},
+	compression_lz4_custom_format => {
+		test_key => 'compression',
+		dump_cmd => [
+			'pg_dump', '--no-sync',
+			'--format=custom', '--compress=lz4:9',
+			"--file=$tempdir/compression_lz4_custom_format.dump",
+			'postgres',
+		],
+		restore_cmd => [
+			'pg_restore',
+			"--file=$tempdir/compression_lz4_custom_format.sql",
+			"$tempdir/compression_lz4_custom_format.dump",
+		],
+	},
+	compression_lz4_directory_format => {
+		test_key => 'compression',
+		dump_cmd => [
+			'pg_dump', '--no-sync',
+			'--format=directory', '--compress=lz4',
+			"--file=$tempdir/compression_lz4_directory_format",
+			'postgres',
+		],
+		# Give coverage for manually compressed toc.dat files during restore.
+		compression => {
+			method => 'lz4',
+			cmd => [
+				$ENV{'LZ4'},
+				'-z', '-f', '--rm',
+				"$tempdir/compression_lz4_directory_format/toc.dat",
+				"$tempdir/compression_lz4_directory_format/toc.dat.lz4",
+			],
+		},
+		restore_cmd => [
+			'pg_restore',
+			"--file=$tempdir/compression_lz4_directory_format.sql",
+			"$tempdir/compression_lz4_directory_format",
+		],
+	},
+	compression_lz4_directory_format_parallel => {
+		test_key => 'compression',
+		dump_cmd => [
+			'pg_dump', '--no-sync', '--jobs=2',
+			'--format=directory', '--compress=lz4:9',
+			"--file=$tempdir/compression_lz4_directory_format_parallel",
+			'postgres',
+		],
+		# Give coverage for manually compressed blob.toc files during restore.
+		compression => {
+			method => 'lz4',
+			cmd => [
+				$ENV{'LZ4'},
+				'-z', '-f', '--rm',
+				"$tempdir/compression_lz4_directory_format_parallel/blobs.toc",
+				"$tempdir/compression_lz4_directory_format_parallel/blobs.toc.lz4",
+			],
+		},
+		restore_cmd => [
+			'pg_restore', '--jobs=2',
+			"--file=$tempdir/compression_lz4_directory_format_parallel.sql",
+			"$tempdir/compression_lz4_directory_format_parallel",
+		],
+	},
+	# Check that the output is valid lz4
+	compression_lz4_plain_format => {
+		test_key => 'compression',
+		dump_cmd => [
+			'pg_dump',
+			'--no-sync',
+			'--format=plain', '--compress=lz4:1',
+			"--file=$tempdir/compression_lz4_plain_format.sql.lz4",
+			'postgres',
 		],
+		compression => {
+			method => 'lz4',
+			cmd => [
+				$ENV{LZ4}, '-d', '-f',
+				"$tempdir/compression_lz4_plain_format.sql.lz4",
+				"$tempdir/compression_lz4_plain_format.sql",
+			],
+		}
 	},
 	clean => {
 		dump_cmd => [
@@ -3899,21 +3989,28 @@ foreach my $run (sort keys %pgdump_runs)
 	my $test_key = $run;
 	my $run_db   = 'postgres';
 
-	my $supports_compression = check_pg_config("#define HAVE_LIBZ 1");
-	my $compress_program = $ENV{GZIP_PROGRAM};
+	my $gzip_program = defined($ENV{GZIP_PROGRAM}) && $ENV{GZIP_PROGRAM} ne '';
+	my $lz4_program = defined($ENV{LZ4}) && $ENV{LZ4} ne '';
+	my $supports_gzip = check_pg_config("#define HAVE_LIBZ 1");
+	my $supports_compression = check_pg_config("#define HAVE_LIBZ 1") ||
+							   $supports_lz4;
 
 	$node->command_ok(\@{ $pgdump_runs{$run}->{dump_cmd} },
 		"$run: pg_dump runs");
 
-	if (defined($pgdump_runs{$run}->{compress_cmd}))
+	if (defined($pgdump_runs{$run}->{'compression'}))
 	{
 		# Skip compression_cmd tests when compression is not supported,
 		# as the result is uncompressed or the utility program does not
 		# exist
-		next if !$supports_compression || !defined($compress_program)
-				|| $compress_program eq '';
-		command_ok( \@{ $pgdump_runs{$run}->{compress_cmd} },
-			"$run: compression commands");
+		next if !$supports_compression;
+
+		my ($compression) = $pgdump_runs{$run}->{'compression'};
+
+		next if ${compression}->{method} eq 'gzip' && (!$gzip_program || !$supports_gzip);
+		next if ${compression}->{method} eq 'lz4'  && (!$lz4_program || !$supports_lz4);
+
+		command_ok( \@{ $compression->{cmd} }, "$run: compression commands");
 	}
 
 	if ($pgdump_runs{$run}->{restore_cmd})
-- 
2.32.0

---

From: Michael Paquier <[email-masked]>
Date: Tue, 1 Mar 2022 17:04:35 +0900

> teaches pg_backup_archiver.c about cfp and is using it through out.

Thanks for the patch.  I have a few high-level comments.

The tests for GZIP had better be split into their own commit, as
that's a coverage improvement for the existing code.

I was assuming that this was going to be much larger :)

+static void ReadDataFromArchiveLZ4(ArchiveHandle *AH, ReadFunc
readF);

Hmm.  This is the same set of APIs as ZLIB and NONE to init, read,
write and end, but for the LZ4 compressor (NONE has no init/end).
Wouldn't it be better to refactor the existing pg_dump code to have a
central structure holding all the function definitions in a common
structure so as all those function signatures are set in stone in the
shape of a catalog of callbacks, making the addition of more
compression formats easier?  I would imagine that we'd split the code
of each compression method into their own file with their own context
data.  This would lead to a removal of compress_io.c, with its entry
points ReadDataFromArchive(), WriteDataToArchive() & co replaced by
pointers to each per-compression callback.

> This is done in the next commit which does introduce a new method.

That's one thing Robert was arguing about with pg_basebackup, so that
would be consistent, and the option set is backward-compatible as far
as I get it by reading the code.
--
Michael

---

From: Justin Pryzby <[email-masked]>
Date: Sat, 26 Mar 2022 11:21:56 -0500

LZ4F_HEADER_SIZE_MAX isn't defined in old LZ4.

I ran into that on an ubuntu LTS, so I don't think it's so old that it
shouldn't be handled more gracefully.  LZ4 should either have an explicit
version check, or else shouldn't depend on that feature (or should define a
safe fallback version if the library header doesn't define it).

https://packages.ubuntu.com/liblz4-1

0003: typo: of legacy => or legacy

There are a large number of ifdefs being added here - it'd be nice to minimize
that.  basebackup was organized to use separate files, which is one way.

$ git grep -c 'ifdef .*LZ4' src/bin/pg_dump/compress_io.c
src/bin/pg_dump/compress_io.c:19

In last year's CF entry, I had made a union within CompressorState.  LZ4
doesn't need z_streamp (and ztsd will need ZSTD_outBuffer, ZSTD_inBuffer,
ZSTD_CStream).

0002: I wonder if you're able to re-use any of the basebackup parsing stuff
from commit ffd53659c.  You're passing both the compression method *and* level.
I think there should be a structure which includes both.  In the future, that
can also handle additional options.  I hope to re-use these same things for
wal_compression=method:level.

You renamed this:

|-       COMPR_ALG_LIBZ
|-} CompressionAlgorithm;
|+       COMPRESSION_GZIP,
|+} CompressionMethod;

..But I don't think that's an improvement.  If you were to change it, it should
say something like PGDUMP_COMPRESS_ZLIB, since there are other compression
structs and typedefs.  zlib is not idential to gzip, which uses a different
header, so in WriteDataToArchive(), LIBZ is correct, and GZIP is incorrect.

The cf* changes in pg_backup_archiver could be split out into a separate
commit.  It's strictly a code simplification - not just preparation for more
compression algorithms.  The commit message should "See also:
bf9aa490db24b2334b3595ee33653bf2fe39208c".

The changes in 0002 for cfopen_write seem insufficient:
|+       if (compressionMethod == COMPRESSION_NONE)
|+               fp = cfopen(path, mode, compressionMethod, 0);
|        else
|        {
| #ifdef HAVE_LIBZ
|                char       *fname;
| 
|                fname = psprintf("%s.gz", path);
|-               fp = cfopen(fname, mode, compression);
|+               fp = cfopen(fname, mode, compressionMethod, compressionLevel);
|                free_keep_errno(fname);
| #else

The only difference between the LIBZ and uncompressed case is the file
extension, and it'll be the only difference with LZ4 too.  So I suggest to
first handle the file extension, and the rest of the code path is not
conditional on the compression method.  I don't think cfopen_write even needs
HAVE_LIBZ - can't you handle that in cfopen_internal() ?

This patch rejects -Z0, which ought to be accepted:
./src/bin/pg_dump/pg_dump -h /tmp regression -Fc -Z0 |wc
pg_dump: error: can only specify -Z/--compress [LEVEL] when method is set

Your 0003 patch shouldn't reference LZ4:
+#ifndef HAVE_LIBLZ4
+       if (*compressionMethod == COMPRESSION_LZ4)
+               supports_compression = false;
+#endif

The 0004 patch renames zlibOutSize to outsize - I think the patch series should
be constructed such as to minimize the size of the method-specific patches.  I
say this anticipating also adding support for zstd.  The preliminary patches
should have all the boring stuff.  It would help for reviewing to keep the 
patches split up, or to enumerate all the boring things that are being renamed
(like change OutputContext to cfp, rename zlibOutSize, ...).

0004: The include should use <lz4.h> and not "lz4.h"

freebsd/cfbot is failing.

I suggested off-list to add an 0099 patch to change LZ4 to the default, to
exercise it more on CI.

-- 
Justin

---

From: Justin Pryzby <[email-masked]>
Date: Sat, 26 Mar 2022 13:33:36 -0500

On Sat, Mar 26, 2022 at 11:21:56AM -0500, Justin Pryzby wrote:
> You're passing both the compression method *and* level.  I think there should
> be a structure which includes both.  In the future, that can also handle
> additional options.

I'm not sure if there's anything worth saving, but I did that last year with
0003-Support-multiple-compression-algs-levels-opts.patch
I sent a rebased copy off-list.
https://www.postgresql.org/message-id/flat/[email-masked]#ca1b9f9d3552c87fa874731cad9d8391

|	fatal("not built with LZ4 support");
|	fatal("not built with lz4 support");

Please use consistent capitalization of "lz4" - then the compiler can optimize
away duplicate strings.

> 0004: The include should use <lz4.h> and not "lz4.h"

Also, use USE_LZ4 rather than HAVE_LIBLZ4, per 75eae0908.

---

From: Daniel Gustafsson <[email-masked]>
Date: Sun, 27 Mar 2022 00:37:27 +0100

> On 26 Mar 2022, at 17:21, Justin Pryzby <[email-masked]> wrote:

> I suggested off-list to add an 0099 patch to change LZ4 to the default, to

No need to change the defaults in autoconf for that.  The CFBot uses the cirrus
file in the tree so changing what the job includes can be easily done (assuming
the CFBot hasn't changed this recently which I think it hasn't).  I used that
trick in the NSS patchset to add a completely new job for --with-ssl=nss beside
the --with-ssl=openssl job.

--
Daniel Gustafsson		https://vmware.com/

---

From: Justin Pryzby <[email-masked]>
Date: Sat, 26 Mar 2022 18:51:34 -0500

> the --with-ssl=openssl job.

I think you misunderstood - I'm suggesting not only to use with-lz4 (which was
always true since 93d973494), but to change pg_dump -Fc and -Fd to use LZ4 by
default (the same as I suggested for toast_compression, wal_compression, and
again in last year's patch to add zstd compression to pg_dump, for which
postgres was not ready).

@@ -781,6 +807,11 @@ main(int argc, char **argv)
                        compress.alg = COMPR_ALG_LIBZ;
                        compress.level = Z_DEFAULT_COMPRESSION;
 #endif
+
+#ifdef USE_ZSTD
+                       compress.alg = COMPR_ALG_ZSTD; // Set default for testing purposes
+                       compress.level = ZSTD_CLEVEL_DEFAULT;
+#endif

---

From: Robert Haas <[email-masked]>
Date: Sun, 27 Mar 2022 10:13:00 -0400

> wal_compression=method:level.

Yeah, we should really try to use that infrastructure instead of
inventing a bunch of different ways to do it. It might require some
renaming here and there, and I'm not sure whether we really want to
try to rush all this into the current release, but I think we should
find a way to get it done.

-- 
Robert Haas
EDB: http://www.enterprisedb.com

---

From: Justin Pryzby <[email-masked]>
Date: Sun, 27 Mar 2022 11:06:35 -0500

> find a way to get it done.

It seems like something a whole lot like parse_compress_options() should be in
common/.  Nobody wants to write it again, and I couldn't convince myself to
copy it when I looked at using it for wal_compression.

Maybe it should take an argument which specifies the default algorithm to use
for input of a numeric "level".  And reject such input if not specified, since
wal_compression has never taken a "level", so it's not useful or desirable to
have that default to some new algorithm.

I could write this down if you want, although I'm not sure how/if you intend
other people to use bc_algorithm and bc_algorithm.  I don't think it's
important to do for v15, but it seems like it could be done after featue
freeze.  pg_dump+lz4 is targetting v16, although there's a cleanup patch that
could also go in before branching.

-- 
Justin

---

From: Daniel Gustafsson <[email-masked]>
Date: Sun, 27 Mar 2022 21:55:52 +0200

> On 27 Mar 2022, at 00:51, Justin Pryzby <[email-masked]> wrote:
> 

Right, I clearly misunderstood, thanks for the clarification.

---

From: Robert Haas <[email-masked]>
Date: Mon, 28 Mar 2022 08:36:15 -0400

> have that default to some new algorithm.

That sounds odd to me. Wouldn't it be rather confusing if a bare
integer meant gzip for one case and lz4 for another?

> could also go in before branching.

Well, I think the first thing we should do is get rid of enum
WalCompressionMethod and use enum WalCompression instead. They've got
the same elements and very similar names, but the WalCompressionMethod
ones just have names like COMPRESSION_NONE, which is too generic,
whereas WalCompressionMethod uses WAL_COMPRESSION_NONE, which is
better. Then I think we should also rename the COMPR_ALG_* constants
in pg_dump.h to names like DUMP_COMPRESSION_*. Once we do that we've
got rid of all the unprefixed things that purport to be a list of
compression algorithms.

Then, if people are willing to adopt the syntax that the
backup_compression.c/h stuff supports as a project standard (+1 from
me) we can go the other way and rename that stuff to be more generic,
taking backup out of the name.

---

From: Michael Paquier <[email-masked]>
Date: Tue, 29 Mar 2022 14:03:12 +0900

> got rid of all the unprefixed things that purport to be a list of

Yes, having a centralized enum for the compression method would make
sense, along with the routines to parse and get the compression method
names.  At least that would be one step towards more unity in
src/common/.

> taking backup out of the name.

I am not sure about the specification part which is only used by base
backups that has no client-server requirements, so option values would
still require their own grammar.
--
Michael

---

From: Robert Haas <[email-masked]>
Date: Tue, 29 Mar 2022 09:14:03 -0400

> still require their own grammar.

I don't know what you mean by this. I think the specification stuff
could be reused in a lot of places. If you can ask for a base backup
with zstd:level=3,long=1,fancystuff=yes or whatever we end up with,
why not enable exactly the same for every other place that uses
compression? I don't know what "client-server requirements" is or what
that has to do with this.

---

From: Michael Paquier <[email-masked]>
Date: Wed, 30 Mar 2022 15:36:02 +0900

> that has to do with this.

Oh. I think that I got confused here.  I saw the backup component in
the file name and this has been associated with the client/server
choice that can be done in the options of pg_basebackup.  But
parse_bc_specification() does not include any knowledge about that:
pg_basebackup does this job in parse_compress_options().  I agree that
it looks possible to reuse that stuff in more places than just base
backups.
--
Michael

---

From: [email-masked]
Date: Fri, 01 Apr 2022 15:06:40 +0000

On Thursday, March 31st, 2022 at 4:34 AM, Michael Paquier <[email-masked]> wrote:
> On Wed, Mar 30, 2022 at 03:32:55PM +0000, [email-masked] wrote:
> > On Wednesday, March 30th, 2022 at 7:54 AM, Michael Paquier [email-masked] wrote:
>
> Okay. 0002 looks fine as-is, and I don't mind the extra fatal()
> calls. These could be asserts but that's not a big deal one way or
> the other. And the cleanup is now applied.

Thank you very much.

> > > It seems to me that it is enough to rely on {compress_cmd}, hence
> > > there should be no need for $compress_program, no?
> >
> > Maybe not. We don't want to the tests to fail if the utility is not
> > installed. That becomes even more evident as more methods are added.
> > However I realized that the presence of the environmental variable does
> > not guarrantee that the utility is actually installed. In the attached,
> > the existance of the utility is based on the return value of system_log().
>
> Hmm. [.. thinks ..] The thing that's itching me here is that you
> align the concept of compression with gzip, but that's not going to be
> true once more compression options are added to pg_dump, and that
> would make $supports_compression and $compress_program_exists
> incorrect. Perhaps the right answer would be to rename all that with
> a suffix like "_gzip" to make a difference? Or would there be enough
> control with a value of "compression_gzip" instead of "compression" in
> test_key?

I understand the itch. Indeed when LZ4 is added as compression method, this
block changes slightly. I went with the minimum amount changed. Please find
in 0001 of the attached this variable renamed as $gzip_program_exist. I thought
that as prefix it will match better the already used $ENV{GZIP_PROGRAM}.

> +my $compress_program_exists = (system_log("$ENV{GZIP_PROGRAM}", '-h',
> + '>', '/dev/null') == 0);
>
> Do we need this command execution at all? In all the other tests, we
> rely on a simple "if (!defined $gzip || $gzip eq '');", so we could do
> the same here.

You are very correct that we are using the simple version, and that is what
it was included in the previous versions of the current patch. However, I
did notice that the variable is hard-coded in Makefile.global.in and it does
not go through configure. By now, gzip is considered an essential package
in most installations, and this hard-code makes sense. Though I did remove
the utility from my system, (apt remove gzip) and tried the test with the
simple "if (!defined $gzip || $gzip eq '');", which predictably failed. For
this, I went with the system call, it is not too expensive and is rather
reliable.

It is true that the rest of the TAP tests that use this, e.g. in pg_basebackup,
also failed. There is an argument to go simple and I will be happy to revert
to the previous version.

> A last thing is that we should perhaps make a clear difference between
> the check that looks at if the code has been built with zlib and the
> check for the presence of GZIP_PROGRAM, as it can be useful in some
> environments to be able to run pg_dump built with zlib, even if the
> GZIP_PROGRAM command does not exist (I don't think this can be the
> case, but other tests are flexible).

You are very correct. We do that already in the current patch. Note that we skip
the test only when we specifically have to execute a compression command. Not
all compression tests define such command, exactly so that we can test those
cases as well. The point of using an external utility program is in order to
extend the coverage in previously untested yet supported scenarios, e.g. manual
compression of the *.toc files.

Also in the case where it will actually skip the compression command because the
gzip program is not present, it will execute the pg_dump command first.

> As of now, the patch relies on
> pg_dump enforcing uncompression if building under --without-zlib even
> if --compress/-Z is used, but that also means that those compression
> tests overlap with the existing tests in this case. Wouldn't it be
> more consistent to check after $supports_compression when executing
> the dump command for test_key = "compression[_gzip]"? This would mean
> keeping GZIP_PROGRAM as sole check when executing the compression
> command.

I can see the overlap case. Yet, I understand the test_key as serving different
purpose, as it is a key of %tests and %full_runs. I do not expect the database
content of the generated dump to change based on which compression method is used.

In the next round, I can see one explitcly requesting --compress=none to override
defaults. There is a benefit to group the tests for this scenario under the same
test_key, i.e. compression.

Also there will be cases where if the program exists, yet the codebase is compiled
without support for the method. Then compress_cmd or the restore_cmd that follows
will fail. For example, in the plain output, if we try to uncompress the generated
the test will fail with 'gzip: <filename> not in gzip format'. In the directory
format the compress_cmd will compress the *.toc files, but the restore_cmd will
fail because it does not build with support for them.

In the attached version, I propose that the compression_cmd is converted into
a hash. It contains two keys, the program and the arguments. Maybe it is easier
to read than before or than simply grabbing the first element of the array.

Cheers,
//Georgios

From 093f9104e1e50b777e05f24665c7c1125d97ace3 Mon Sep 17 00:00:00 2001
From: Georgios Kokolatos <[email-masked]>
Date: Fri, 1 Apr 2022 13:15:14 +0000
Subject: [PATCH v5 1/3] Extend compression coverage for pg_dump, pg_restore

---
 src/bin/pg_dump/Makefile         |   2 +
 src/bin/pg_dump/t/002_pg_dump.pl | 110 +++++++++++++++++++++++++++++++
 2 files changed, 112 insertions(+)

index 302f7e02d6..2f524b09bf 100644

diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index af5d6fa5a3..134cc0618b 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -26,6 +26,13 @@ my $tempdir       = PostgreSQL::Test::Utils::tempdir;
 # specified and is pulled from $PGPORT, which is set by the
 # PostgreSQL::Test::Cluster system.
 #
+# compress_cmd is the utility command for (de)compression, if any.
+# Note that this should generally be used on pg_dump's output
+# either to generate a text file to run the through the tests, or
+# to test pg_restore's ability to parse manually compressed files
+# that otherwise pg_dump does not compress on it's own
+# (e.g. *.toc).
+#
 # restore_cmd is the pg_restore command to run, if any.  Note
 # that this should generally be used when the pg_dump goes to
 # a non-text file and that the restore can then be used to
@@ -54,6 +61,88 @@ my %pgdump_runs = (
 			"$tempdir/binary_upgrade.dump",
 		],
 	},
+
+	# Do not use --no-sync to give test coverage for data sync.
+	compression_gzip_custom_format => {
+		test_key => 'compression',
+		dump_cmd => [
+			'pg_dump',
+			'--format=custom', '--compress=1',
+			"--file=$tempdir/compression_gzip_custom_format.dump",
+			'postgres',
+		],
+		restore_cmd => [
+			'pg_restore',
+			"--file=$tempdir/compression_gzip_custom_format.sql",
+			"$tempdir/compression_gzip_custom_format.dump",
+		],
+	},
+
+	# Do not use --no-sync to give test coverage for data sync.
+	compression_gzip_directory_format => {
+		test_key => 'compression',
+		dump_cmd => [
+			'pg_dump',
+			'--format=directory', '--compress=1',
+			"--file=$tempdir/compression_gzip_directory_format",
+			'postgres',
+		],
+		# Give coverage for manually compressed blob.toc files during restore.
+		compress_cmd => {
+			program => $ENV{'GZIP_PROGRAM'},
+			args => [
+				'-f',
+				"$tempdir/compression_gzip_directory_format/blobs.toc",
+			],
+		},
+		restore_cmd => [
+			'pg_restore',
+			"--file=$tempdir/compression_gzip_directory_format.sql",
+			"$tempdir/compression_gzip_directory_format",
+		],
+	},
+
+	compression_gzip_directory_format_parallel => {
+		test_key => 'compression',
+		dump_cmd => [
+			'pg_dump', '--jobs=2',
+			'--format=directory', '--compress=6',
+			"--file=$tempdir/compression_gzip_directory_format_parallel",
+			'postgres',
+		],
+		# Give coverage for manually compressed blob.toc files during restore.
+		compress_cmd => {
+			program => $ENV{'GZIP_PROGRAM'},
+			args => [
+				'-f',
+				"$tempdir/compression_gzip_directory_format_parallel/blobs.toc",
+			]
+		},
+		restore_cmd => [
+			'pg_restore',
+			'--jobs=3',
+			"--file=$tempdir/compression_gzip_directory_format_parallel.sql",
+			"$tempdir/compression_gzip_directory_format_parallel",
+		],
+	},
+
+	# Check that the output is valid gzip
+	compression_gzip_plain_format => {
+		test_key => 'compression',
+		dump_cmd => [
+			'pg_dump', '--format=plain', '-Z1',
+			"--file=$tempdir/compression_gzip_plain_format.sql.gz",
+			'postgres',
+		],
+		# Decompress the generated file to run through the tests
+		compress_cmd => {
+			program => $ENV{'GZIP_PROGRAM'},
+			args => [
+				'-d',
+				"$tempdir/compression_gzip_plain_format.sql.gz",
+			],
+		},
+	},
 	clean => {
 		dump_cmd => [
 			'pg_dump',
@@ -424,6 +513,7 @@ my %full_runs = (
 	binary_upgrade           => 1,
 	clean                    => 1,
 	clean_if_exists          => 1,
+	compression              => 1,
 	createdb                 => 1,
 	defaults                 => 1,
 	exclude_dump_test_schema => 1,
@@ -3098,6 +3188,7 @@ my %tests = (
 			binary_upgrade          => 1,
 			clean                   => 1,
 			clean_if_exists         => 1,
+			compression             => 1,
 			createdb                => 1,
 			defaults                => 1,
 			exclude_test_table      => 1,
@@ -3171,6 +3262,7 @@ my %tests = (
 			binary_upgrade           => 1,
 			clean                    => 1,
 			clean_if_exists          => 1,
+			compression              => 1,
 			createdb                 => 1,
 			defaults                 => 1,
 			exclude_dump_test_schema => 1,
@@ -3941,6 +4033,9 @@ command_fails_like(
 
 #########################################
 # Run all runs
+my $supports_gzip_compression = check_pg_config("#define HAVE_LIBZ 1");
+my $gzip_program_exists = (system_log("$ENV{GZIP_PROGRAM}", '-h',
+									  '>', '/dev/null') == 0);
 
 foreach my $run (sort keys %pgdump_runs)
 {
@@ -3950,6 +4045,21 @@ foreach my $run (sort keys %pgdump_runs)
 	$node->command_ok(\@{ $pgdump_runs{$run}->{dump_cmd} },
 		"$run: pg_dump runs");
 
+	if ($pgdump_runs{$run}->{compress_cmd})
+	{
+		my ($compress_cmd) = $pgdump_runs{$run}->{compress_cmd};
+
+		# Skip compression_cmd tests when compression is not supported,
+		# as the result is uncompressed or the utility program does not
+		# exist
+		next if !$supports_gzip_compression;
+		next if $compress_cmd->{program} eq "$ENV{GZIP_PROGRAM}" &&
+				!$gzip_program_exists;
+
+		my @full_cmd = ($compress_cmd->{program}, @{ $compress_cmd->{args} });
+		command_ok(\@full_cmd, "$run: compression commands");
+	}
+
 	if ($pgdump_runs{$run}->{restore_cmd})
 	{
 		$node->command_ok(\@{ $pgdump_runs{$run}->{restore_cmd} },
-- 
2.32.0

From 39dfbf28e781a4870a20110942bf6a1586b837b6 Mon Sep 17 00:00:00 2001
From: Georgios Kokolatos <[email-masked]>
Date: Fri, 1 Apr 2022 13:15:27 +0000
Subject: [PATCH v5 3/3] Add LZ4 compression in pg_{dump|restore}

Custom compressed archives need to now store the compression method in their
header. This requires a bump in the version number. The level of compression is
still stored in the dump, though admittedly is of no apparent use.
---
 doc/src/sgml/ref/pg_dump.sgml        |  23 +-
 src/bin/pg_dump/Makefile             |   1 +
 src/bin/pg_dump/compress_io.c        | 738 ++++++++++++++++++++++++---
 src/bin/pg_dump/pg_backup.h          |   3 +-
 src/bin/pg_dump/pg_backup_archiver.c |  71 ++-
 src/bin/pg_dump/pg_backup_archiver.h |   3 +-
 src/bin/pg_dump/pg_dump.c            |  12 +-
 src/bin/pg_dump/t/001_basic.pl       |   4 +-
 src/bin/pg_dump/t/002_pg_dump.pl     | 104 +++-
 9 files changed, 852 insertions(+), 107 deletions(-)

        <para>
index 2f524b09bf..2864ccabb9 100644

 
+#ifdef HAVE_LIBLZ4
+#include "lz4.h"
+#include "lz4frame.h"
+
 
 #ifdef HAVE_LIBZ
 	z_streamp	zp;
-	char	   *zlibOut;
-	size_t		zlibOutSize;
 #endif
+	void	   *outbuf;
+	size_t		outsize;
 };

 
 
 	return cs;
 }
 
 	/*

 
 static void
 
-	free(cs->zlibOut);
+	free(cs->outbuf);
 	free(cs->zp);
 }

 
 
 		if (res == Z_STREAM_END)
 
+#ifdef HAVE_LIBLZ4
+static void
 
 /*
 
+#ifdef HAVE_LIBLZ4
 /*
 
-#ifdef HAVE_LIBZ

+#ifdef HAVE_LIBLZ4
+/*
 
-#ifdef HAVE_LIBZ

 

 
 
 cfp *
 
-		if (errnum != Z_ERRNO)
-			return errmsg;
+				if (errnum == Z_ERRNO)

-#ifdef HAVE_LIBZ
 static int
 hasSuffix(const char *filename, const char *suffix)
 {
@@ -807,5 +1427,3 @@ hasSuffix(const char *filename, const char *suffix)
 				  suffix,
 				  suffixlen) == 0;
 }
-
-#endif
diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h
index 7645d3285a..10393f3fc4 100644
--- a/src/bin/pg_dump/pg_backup.h
+++ b/src/bin/pg_dump/pg_backup.h
@@ -78,8 +78,9 @@ enum _dumpPreparedQueries
 typedef enum _compressionMethod
 {
 	COMPRESSION_INVALID,
+	COMPRESSION_GZIP,
 	COMPRESSION_NONE,
-	COMPRESSION_GZIP
+	COMPRESSION_LZ4
 } CompressionMethod;

 
 	/*
 
+static bool
 
 		/*

 
-

diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 97ac17ebff..5351c71d2b 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1002,7 +1002,7 @@ help(const char *progname)
 	printf(_("  -j, --jobs=NUM               use this many parallel jobs to dump\n"));
 	printf(_("  -v, --verbose                verbose mode\n"));
 	printf(_("  -V, --version                output version information, then exit\n"));
-	printf(_("  -Z, --compress=[gzip,none][:LEVEL] or [LEVEL]\n"
+	printf(_("  -Z, --compress=[gzip,lz4,none][:LEVEL] or [LEVEL]\n"
 			 "                               compress output with given method or level\n"));
 	printf(_("  --lock-wait-timeout=TIMEOUT  fail after waiting TIMEOUT for a table lock\n"));
 	printf(_("  --no-sync                    do not wait for changes to be written safely to disk\n"));
@@ -1271,11 +1271,13 @@ parse_compression_method(const char *method,

@@ -1346,10 +1348,10 @@ parse_compression_option(const char *opt, CompressionMethod *compressionMethod,
 	if (!res)
 		return res;

diff --git a/src/bin/pg_dump/t/001_basic.pl b/src/bin/pg_dump/t/001_basic.pl
index d7a52ac1a8..0077288388 100644
--- a/src/bin/pg_dump/t/001_basic.pl
+++ b/src/bin/pg_dump/t/001_basic.pl
@@ -122,12 +122,12 @@ command_fails_like(
 
 command_fails_like(
 
 command_fails_like(
 
 command_fails_like(
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 05a4b0756b..acf4ffa2a9 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -143,6 +143,85 @@ my %pgdump_runs = (
 			],
 		},
 	},
+	compression_lz4_custom_format => {
+		test_key => 'compression',
+		dump_cmd => [
+			'pg_dump', '--no-sync',
+			'--format=custom', '--compress=lz4:9',
+			"--file=$tempdir/compression_lz4_custom_format.dump",
+			'postgres',
+		],
+		restore_cmd => [
+			'pg_restore',
+			"--file=$tempdir/compression_lz4_custom_format.sql",
+			"$tempdir/compression_lz4_custom_format.dump",
+		],
+	},
+	compression_lz4_directory_format => {
+		test_key => 'compression',
+		dump_cmd => [
+			'pg_dump', '--no-sync',
+			'--format=directory', '--compress=lz4',
+			"--file=$tempdir/compression_lz4_directory_format",
+			'postgres',
+		],
+		# Give coverage for manually compressed toc.dat files during restore.
+		compress_cmd => {
+			program => $ENV{'LZ4'},
+			args => [
+				'-z', '-f', '--rm',
+				"$tempdir/compression_lz4_directory_format/toc.dat",
+				"$tempdir/compression_lz4_directory_format/toc.dat.lz4",
+			],
+		},
+		restore_cmd => [
+			'pg_restore',
+			"--file=$tempdir/compression_lz4_directory_format.sql",
+			"$tempdir/compression_lz4_directory_format",
+		],
+	},
+	compression_lz4_directory_format_parallel => {
+		test_key => 'compression',
+		dump_cmd => [
+			'pg_dump', '--no-sync', '--jobs=2',
+			'--format=directory', '--compress=lz4:9',
+			"--file=$tempdir/compression_lz4_directory_format_parallel",
+			'postgres',
+		],
+		# Give coverage for manually compressed blob.toc files during restore.
+		compress_cmd => {
+			program => $ENV{'LZ4'},
+			args => [
+				'-z', '-f', '--rm',
+				"$tempdir/compression_lz4_directory_format_parallel/blobs.toc",
+				"$tempdir/compression_lz4_directory_format_parallel/blobs.toc.lz4",
+			],
+		},
+		restore_cmd => [
+			'pg_restore', '--jobs=2',
+			"--file=$tempdir/compression_lz4_directory_format_parallel.sql",
+			"$tempdir/compression_lz4_directory_format_parallel",
+		],
+	},
+	# Check that the output is valid lz4
+	compression_lz4_plain_format => {
+		test_key => 'compression',
+		dump_cmd => [
+			'pg_dump',
+			'--no-sync',
+			'--format=plain', '--compress=lz4:1',
+			"--file=$tempdir/compression_lz4_plain_format.sql.lz4",
+			'postgres',
+		],
+		compress_cmd => {
+			program => $ENV{'LZ4'},
+			args => [
+				'-d', '-f',
+				"$tempdir/compression_lz4_plain_format.sql.lz4",
+				"$tempdir/compression_lz4_plain_format.sql",
+			],
+		}
+	},
 	compression_none_dir_format => {
 		test_key => 'compression',
 		dump_cmd => [
@@ -162,6 +241,23 @@ my %pgdump_runs = (
 			"$tempdir/compression_none_dir_format",
 		],
 	},
+	compression_default_dir_format => {
+		test_key => 'compression',
+		dump_cmd => [
+			'pg_dump', '-Fd',
+			"--file=$tempdir/compression_default_dir_format",
+			'postgres',
+		],
+		glob_match => {
+			match => "$tempdir/compression_default_dir_format/*.dat.gz",
+			match_count => 1, # data
+		},
+		restore_cmd => [
+			'pg_restore', '-Fd',
+			"--file=$tempdir/compression_default_dir_format.sql",
+			"$tempdir/compression_default_dir_format",
+		],
+	},
 	clean => {
 		dump_cmd => [
 			'pg_dump',
@@ -4055,6 +4151,8 @@ command_fails_like(
 my $supports_gzip_compression = check_pg_config("#define HAVE_LIBZ 1");
 my $gzip_program_exists = (system_log("$ENV{GZIP_PROGRAM}", '-h',
 									  '>', '/dev/null') == 0);
+my $lz4_program_exists = (system_log("$ENV{LZ4}", '-h',
+									 '>', '/dev/null') == 0);
 
 foreach my $run (sort keys %pgdump_runs)
 {
@@ -4071,9 +4169,11 @@ foreach my $run (sort keys %pgdump_runs)
 		# Skip compression_cmd tests when compression is not supported,
 		# as the result is uncompressed or the utility program does not
 		# exist
-		next if !$supports_gzip_compression;
+		next if !$supports_gzip_compression && !$supports_lz4;
 		next if $compress_cmd->{program} eq "$ENV{GZIP_PROGRAM}" &&
 				!$gzip_program_exists;
+		next if $compress_cmd->{program} eq "$ENV{LZ4}" &&
+				!$lz4_program_exists;
 
 		my @full_cmd = ($compress_cmd->{program}, @{ $compress_cmd->{args} });
 		command_ok(\@full_cmd, "$run: compression commands");
@@ -4083,7 +4183,7 @@ foreach my $run (sort keys %pgdump_runs)
 	if ($pgdump_runs{$run}->{glob_match})
 	{
 		# Skip compression_cmd tests when compression is not supported
-		next if !$supports_gzip_compression;
+		next if !$supports_gzip_compression && !$supports_lz4;
 
 		my $match = $pgdump_runs{$run}->{glob_match}->{match};
 		my $match_count = defined($pgdump_runs{$run}->{glob_match}->{match_count}) ?
-- 
2.32.0

From b6c231f35b5163b755d2a79cc6d9b12920406731 Mon Sep 17 00:00:00 2001
From: Georgios Kokolatos <[email-masked]>
Date: Fri, 1 Apr 2022 13:15:22 +0000
Subject: [PATCH v5 2/3] Prepare pg_dump for additional compression methods

This commmit does the heavy lifting required for additional compression methods.
Commit  bf9aa490db introduced cfp in compress_io.{c,h} with the intent of
unifying compression related code and allow for the introduction of additional
archive formats. However, pg_backup_archiver.c was not using that API. This
commit teaches pg_backup_archiver.c about cfp and is using it through out.

In a fashion similar to the binary for pg_basebackup, the method for compression
is passed using the already existing -Z/--compress parameter of pg_dump. The
legacy format and behaviour is maintained. Additionally, the user can explicitly
pass a requested method and optionaly the level to be used after a semicolon,
e.g. --compress=gzip:6
---
 doc/src/sgml/ref/pg_dump.sgml         |  30 +-
 src/bin/pg_dump/compress_io.c         | 416 ++++++++++++++++----------
 src/bin/pg_dump/compress_io.h         |  32 +-
 src/bin/pg_dump/pg_backup.h           |  14 +-
 src/bin/pg_dump/pg_backup_archiver.c  | 171 +++++------
 src/bin/pg_dump/pg_backup_archiver.h  |  46 +--
 src/bin/pg_dump/pg_backup_custom.c    |  11 +-
 src/bin/pg_dump/pg_backup_directory.c |  12 +-
 src/bin/pg_dump/pg_backup_tar.c       |  12 +-
 src/bin/pg_dump/pg_dump.c             | 155 ++++++++--
 src/bin/pg_dump/t/001_basic.pl        |  14 +-
 src/bin/pg_dump/t/002_pg_dump.pl      |  49 ++-
 src/tools/pgindent/typedefs.list      |   2 +-
 13 files changed, 607 insertions(+), 357 deletions(-)

      <varlistentry>
 
 #ifdef HAVE_LIBZ

-/*

 #ifndef HAVE_LIBZ

 	/*
 
 	return cs;

 
 
 #ifdef HAVE_LIBZ
 
 #ifdef HAVE_LIBZ

-	if (compression == 0)

 /*
 
-	if (compression != 0)

 
 	return fp;
 }
 
+cfp *
 
 int
 
-#ifdef HAVE_LIBZ
-	if (fp->compressedfp)

 int
 
 int
 
-#ifdef HAVE_LIBZ
-	if (fp->compressedfp)
 
 	return ret;
 
 int
 cfclose(cfp *fp)
 {
-	int			result;
+	int			ret;
 
 	if (fp == NULL)
 	{
 		errno = EBADF;
 		return EOF;
 	}
-#ifdef HAVE_LIBZ
-	if (fp->compressedfp)
+
 
-	return result;
+	return ret;
 }
 
 int
 cfeof(cfp *fp)
 {
+	int			ret;
+
 
 const char *
 get_cfp_error(cfp *fp)
 {
-#ifdef HAVE_LIBZ
-	if (fp->compressedfp)
 
 		if (errnum != Z_ERRNO)
 			return errmsg;
-	}
+#else
 
diff --git a/src/bin/pg_dump/compress_io.h b/src/bin/pg_dump/compress_io.h
index f635787692..b8b366616c 100644
--- a/src/bin/pg_dump/compress_io.h
+++ b/src/bin/pg_dump/compress_io.h
@@ -17,16 +17,17 @@

+#ifdef HAVE_LIBZ
+#include <zlib.h>
+#else
 
-typedef enum
-{
-	COMPR_ALG_NONE,
-	COMPR_ALG_LIBZ
-} CompressionAlgorithm;
-
 
@@ -46,8 +47,12 @@ typedef size_t (*ReadFunc) (ArchiveHandle *AH, char **buf, size_t *buflen);
 /* struct definition appears in compress_io.c */
 typedef struct CompressorState CompressorState;
 
@@ -56,9 +61,16 @@ extern void EndCompressor(ArchiveHandle *AH, CompressorState *cs);
 
 typedef struct cfp cfp;
 
index fcc5f6bd05..7645d3285a 100644
@@ -75,6 +75,13 @@ enum _dumpPreparedQueries
 
+typedef enum _compressionMethod
+{
+	COMPRESSION_INVALID,
+	COMPRESSION_NONE,
+	COMPRESSION_GZIP
+} CompressionMethod;
+
 /* Parameters needed by ConnectDatabase; same for dump and restore */
 typedef struct _connParams
 {
@@ -143,7 +150,8 @@ typedef struct _restoreOptions
 
 	int			noDataForFailedTables;
 	int			exit_on_error;
-	int			compression;
+	CompressionMethod compressionMethod;
+	int			compressionLevel;
 	int			suppressDumpWarnings;	/* Suppress output of WARNING entries
 										 * to stderr */
 	bool		single_txn;
@@ -303,7 +311,9 @@ extern Archive *OpenArchive(const char *FileSpec, const ArchiveFormat fmt);

 /* The --list option */

 
 
 {
 
 	return (Archive *) AH;
 }
 
 	return (Archive *) AH;
 }
 
 	/* Close the output */
 
 	if (res != 0)

 

 
 	if (ropt->useDB)
 
 	return opts;
 }
 
 	sav = SaveOutput(AH);
 	if (ropt->filename)

 	switch (AH->format)
 	{
 
 static void
 
 	if (filename)
 	{

 
 	if (!AH->OF)
 	{
 
-static OutputContext
+static cfp *
 
 static void

 	if (res != 0)

 

 	AH->mode = mode;

 
 	/*

+
 
 #define LOBBUFSIZE 16384
 
-#ifdef HAVE_LIBZ
-#include <zlib.h>

 

 /*
 
 	WriteInt(AH, oid);

 /*
 
 static void

 

 
 
 	if (ctx->dataFH == NULL)
index 2491a091b9..b25b641caa 100644
 
@@ -194,7 +195,7 @@ InitArchiveFmt_Tar(ArchiveHandle *AH)
@@ -328,7 +329,7 @@ tarOpen(ArchiveHandle *AH, const char *filename, char mode)
 			}
 		}
 
-		if (AH->compression == 0)
+		if (AH->compressionMethod == COMPRESSION_NONE)
 			tm->nFH = ctx->tarFH;
 		else
 			fatal("compression is not supported by tar archive format");
@@ -383,7 +384,7 @@ tarOpen(ArchiveHandle *AH, const char *filename, char mode)
 
 		umask(old_umask);
 
-		if (AH->compression == 0)
+		if (AH->compressionMethod == COMPRESSION_NONE)
 			tm->nFH = tm->tmpFH;
 		else
 			fatal("compression is not supported by tar archive format");
@@ -401,7 +402,7 @@ tarOpen(ArchiveHandle *AH, const char *filename, char mode)
 static void
 tarClose(ArchiveHandle *AH, TAR_MEMBER *th)
 {
-	if (AH->compression != 0)
+	if (AH->compressionMethod != COMPRESSION_NONE)
 		fatal("compression is not supported by tar archive format");
 
 	if (th->mode == 'w')
@@ -801,7 +802,6 @@ _CloseArchive(ArchiveHandle *AH)
 		memcpy(ropt, AH->public.ropt, sizeof(RestoreOptions));
 		ropt->filename = NULL;
 		ropt->dropSchema = 1;
-		ropt->compression = 0;
 		ropt->superuser = NULL;
 		ropt->suppressDumpWarnings = true;
 
@@ -889,7 +889,7 @@ _StartBlob(ArchiveHandle *AH, TocEntry *te, Oid oid)
 	if (oid == 0)
 		fatal("invalid OID for large object (%u)", oid);

 	sprintf(fname, "blob_%u.dat", oid);
index 535b160165..97ac17ebff 100644
+	CompressionMethod compressionMethod = COMPRESSION_INVALID;

 
@@ -689,23 +694,21 @@ main(int argc, char **argv)
 	if (archiveFormat == archNull)
 		plainText = 1;
 
-	/* Custom and directory formats are compressed by default, others not */
-	if (compressLevel == -1)
+	/* Set default compressionMethod unless one already set by the user */
+	if (compressionMethod == COMPRESSION_INVALID)
 	{
+		compressionMethod = COMPRESSION_NONE;
+
 #ifdef HAVE_LIBZ
+		/* Custom and directory formats are compressed by default (zlib) */
 		if (archiveFormat == archCustom || archiveFormat == archDirectory)
+		{
+			compressionMethod = COMPRESSION_GZIP;
 			compressLevel = Z_DEFAULT_COMPRESSION;
-		else
+		}
 #endif
-			compressLevel = 0;
 	}
 
-#ifndef HAVE_LIBZ
@@ -718,8 +721,9 @@ main(int argc, char **argv)

@@ -950,10 +954,8 @@ main(int argc, char **argv)

 
@@ -1000,7 +1002,8 @@ help(const char *progname)
@@ -1260,6 +1263,116 @@ get_synchronized_snapshot(Archive *fout)
 	return result;
 }
 
+static bool
index 65e6c01fed..d7a52ac1a8 100644
@@ -120,6 +120,16 @@ command_fails_like(
 	qr/\Qpg_restore: error: cannot specify both --single-transaction and multiple jobs\E/,
 	'pg_restore: cannot specify both --single-transaction and multiple jobs');
 
+command_fails_like(
+	[ 'pg_dump', '--compress', 'garbage' ],
+	qr/\Qpg_dump: error: invalid compression method "garbage" (gzip, none)\E/,
+	'pg_dump: invalid --compress');
+
+command_fails_like(
+	[ 'pg_dump', '--compress', 'none:1' ],
+	qr/\Qpg_dump: error: can only specify -Z\/--compress [LEVEL] when method is gzip\E/,
+	'pg_dump: can only specify -Z/--compress [LEVEL] when method is gzip');
+
 command_fails_like(
 	[ 'pg_dump', '-Z', '-1' ],
 	qr/\Qpg_dump: error: -Z\/--compress must be in range 0..9\E/,
@@ -128,7 +138,7 @@ command_fails_like(
 if (check_pg_config("#define HAVE_LIBZ 1"))
 {
 	command_fails_like(
-		[ 'pg_dump', '--compress', '1', '--format', 'tar' ],
+		[ 'pg_dump', '--compress', 'gzip:1', '--format', 'tar' ],
 		qr/\Qpg_dump: error: compression is not supported by tar archive format\E/,
 		'pg_dump: compression is not supported by tar archive format');
 }
@@ -136,7 +146,7 @@ else
 {
 	# --jobs > 1 forces an error with tar format.
 	command_fails_like(
-		[ 'pg_dump', '--compress', '1', '--format', 'tar', '-j3' ],
+		[ 'pg_dump', '--compress', 'gzip:1', '--format', 'tar', '-j3' ],
 		qr/\Qpg_dump: warning: requested compression not available in this installation -- archive will be uncompressed\E/,
 		'pg_dump: warning: compression not available in this installation');
 }
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 134cc0618b..05a4b0756b 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -83,7 +83,7 @@ my %pgdump_runs = (
 		test_key => 'compression',
 		dump_cmd => [
 			'pg_dump',
-			'--format=directory', '--compress=1',
+			'--format=directory', '--compress=gzip:1',
 			"--file=$tempdir/compression_gzip_directory_format",
 			'postgres',
 		],
@@ -106,7 +106,7 @@ my %pgdump_runs = (
 		test_key => 'compression',
 		dump_cmd => [
 			'pg_dump', '--jobs=2',
-			'--format=directory', '--compress=6',
+			'--format=directory', '--compress=gzip:6',
 			"--file=$tempdir/compression_gzip_directory_format_parallel",
 			'postgres',
 		],
@@ -143,6 +143,25 @@ my %pgdump_runs = (
 			],
 		},
 	},
+	compression_none_dir_format => {
+		test_key => 'compression',
+		dump_cmd => [
+			'pg_dump', '-Fd',
+			'--compress=none',
+			"--file=$tempdir/compression_none_dir_format",
+			'postgres',
+		],
+		glob_match => {
+			no_match => "$tempdir/compression_none_dir_format/*.dat.gz",
+			match => "$tempdir/compression_none_dir_format/*.dat",
+			match_count => 2, # toc.dat and more
+		},
+		restore_cmd => [
+			'pg_restore', '-Fd',
+			"--file=$tempdir/compression_none_dir_format.sql",
+			"$tempdir/compression_none_dir_format",
+		],
+	},
 	clean => {
 		dump_cmd => [
 			'pg_dump',
@@ -242,7 +261,7 @@ my %pgdump_runs = (
 	defaults_dir_format => {
 		test_key => 'defaults',
 		dump_cmd => [
-			'pg_dump',                             '-Fd',
+			'pg_dump', '-Fd',
 			"--file=$tempdir/defaults_dir_format", 'postgres',
 		],
 		restore_cmd => [
@@ -4058,6 +4077,30 @@ foreach my $run (sort keys %pgdump_runs)
 
 		my @full_cmd = ($compress_cmd->{program}, @{ $compress_cmd->{args} });
 		command_ok(\@full_cmd, "$run: compression commands");
+
+	}
+
+	if ($pgdump_runs{$run}->{glob_match})
+	{
+		# Skip compression_cmd tests when compression is not supported
+		next if !$supports_gzip_compression;
+
+		my $match = $pgdump_runs{$run}->{glob_match}->{match};
+		my $match_count = defined($pgdump_runs{$run}->{glob_match}->{match_count}) ?
+							$pgdump_runs{$run}->{glob_match}->{match_count} : 1;
+		my @glob_matched = glob $match;
+
+		cmp_ok(scalar(@glob_matched), '>=', $match_count,
+			"Expected at least $match_count file(s) matching $match");
+
+		if ($pgdump_runs{$run}->{glob_match}->{no_match})
+		{
+			my $no_match = $pgdump_runs{$run}->{glob_match}->{no_match};
+			my @glob_matched = glob $no_match;
+
+			cmp_ok(scalar(@glob_matched), '==', 0,
+				"Expected no file(s) matching $no_match");
+		}
 	}
 
 	if ($pgdump_runs{$run}->{restore_cmd})
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72fafb795b..5ac8c156a9 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -412,7 +412,7 @@ CompiledExprState
 CompositeIOData
 CompositeTypeStmt
 CompoundAffixFlag
-CompressionAlgorithm
+CompressionMethod
 CompressorState
 ComputeXidHorizonsResult
 ConditionVariable
-- 
2.32.0

---

From: Michael Paquier <[email-masked]>
Date: Tue, 5 Apr 2022 10:34:49 +0900

> that as prefix it will match better the already used $ENV{GZIP_PROGRAM}.

Hmm.  I have spent some time on that, and upon review I really think
that we should skip the tests marked as dedicated to the gzip
compression entirely if the build is not compiled with this option,
rather than letting the code run a dump for nothing in some cases,
relying on the default to uncompress the contents in others.  In the
latter case, it happens that we have already some checks like
defaults_custom_format, but you already mentioned that.

We should also skip the later parts of the tests if the compression
program does not exist as we rely on it, but only if the command does
not exist.  This will count for LZ4.

> content of the generated dump to change based on which compression method is used.

Contrary to the current LZ4 tests in pg_dump, what we have here is a
check for a command-level run and not a data-level check.  So what's
introduced is a new concept, and we need a new way to control if the
tests should be entirely skipped or not, particularly if we finish by
not using test_key to make the difference.  Perhaps the best way to
address that is to have a new keyword in the $runs structure.  The
attached defines a new compile_option, that can be completed later for
new compression methods introduced in the tests.  So the idea is to
mark all the tests related to compression with the same test_key, and
the tests can be skipped depending on what compile_option requires.

> to read than before or than simply grabbing the first element of the array.

Splitting the program and its arguments makes sense.

At the end I am finishing with the attached.  I also saw an overlap
with the addition of --jobs for the directory format vs not using the
option, so I have removed the case where --jobs was not used in the
directory format.
--
Michael
From b705f77862c9e41a149ce078df638032903bce3d Mon Sep 17 00:00:00 2001
From: Michael Paquier <[email-masked]>
Date: Tue, 5 Apr 2022 10:30:06 +0900
Subject: [PATCH v6] Extend compression coverage for pg_dump, pg_restore

---
 src/bin/pg_dump/Makefile         |  2 +
 src/bin/pg_dump/t/002_pg_dump.pl | 93 +++++++++++++++++++++++++++++++-
 2 files changed, 93 insertions(+), 2 deletions(-)

 
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index af5d6fa5a3..dda3649373 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -20,12 +20,22 @@ my $tempdir       = PostgreSQL::Test::Utils::tempdir;
 # test_key indicates that a given run should simply use the same
 # set of like/unlike tests as another run, and which run that is.
 #
+# compile_option indicates if the commands run depend on a compilation
+# option, if any.  This controls if tests are skipped if a dependency
+# is not satisfied.
+#
 # dump_cmd is the pg_dump command to run, which is an array of
 # the full command and arguments to run.  Note that this is run
 # using $node->command_ok(), so the port does not need to be
 # specified and is pulled from $PGPORT, which is set by the
 # PostgreSQL::Test::Cluster system.
 #
+# compress_cmd is the utility command for (de)compression, if any.
+# Note that this should generally be used on pg_dump's output
+# either to generate a text file to run the through the tests, or
+# to test pg_restore's ability to parse manually compressed files
+# that otherwise pg_dump does not compress on its own (e.g. *.toc).
+#
 # restore_cmd is the pg_restore command to run, if any.  Note
 # that this should generally be used when the pg_dump goes to
 # a non-text file and that the restore can then be used to
@@ -54,6 +64,58 @@ my %pgdump_runs = (
 			"$tempdir/binary_upgrade.dump",
 		],
 	},
+
+	# Do not use --no-sync to give test coverage for data sync.
+	compression_gzip_custom => {
+		test_key => 'compression',
+		compile_option => 'gzip',
+		dump_cmd => [
+			'pg_dump',      '--format=custom',
+			'--compress=1', "--file=$tempdir/compression_gzip_custom.dump",
+			'postgres',
+		],
+		restore_cmd => [
+			'pg_restore',
+			"--file=$tempdir/compression_gzip_custom.sql",
+			"$tempdir/compression_gzip_custom.dump",
+		],
+	},
+
+	# Do not use --no-sync to give test coverage for data sync.
+	compression_gzip_dir => {
+		test_key => 'compression',
+		compile_option => 'gzip',
+		dump_cmd => [
+			'pg_dump',                              '--jobs=2',
+			'--format=directory',                   '--compress=1',
+			"--file=$tempdir/compression_gzip_dir", 'postgres',
+		],
+		# Give coverage for manually compressed blob.toc files during
+		# restore.
+		compress_cmd => {
+			program => $ENV{'GZIP_PROGRAM'},
+			args    => [ '-f', "$tempdir/compression_gzip_dir/blobs.toc", ],
+		},
+		restore_cmd => [
+			'pg_restore', '--jobs=2',
+			"--file=$tempdir/compression_gzip_dir.sql",
+			"$tempdir/compression_gzip_dir",
+		],
+	},
+
+	compression_gzip_plain => {
+		test_key => 'compression',
+		compile_option => 'gzip',
+		dump_cmd => [
+			'pg_dump', '--format=plain', '-Z1',
+			"--file=$tempdir/compression_gzip_plain.sql.gz", 'postgres',
+		],
+		# Decompress the generated file to run through the tests.
+		compress_cmd => {
+			program => $ENV{'GZIP_PROGRAM'},
+			args    => [ '-d', "$tempdir/compression_gzip_plain.sql.gz", ],
+		},
+	},
 	clean => {
 		dump_cmd => [
 			'pg_dump',
@@ -424,6 +486,7 @@ my %full_runs = (
 	binary_upgrade           => 1,
 	clean                    => 1,
 	clean_if_exists          => 1,
+	compression              => 1,
 	createdb                 => 1,
 	defaults                 => 1,
 	exclude_dump_test_schema => 1,
@@ -3098,6 +3161,7 @@ my %tests = (
 			binary_upgrade          => 1,
 			clean                   => 1,
 			clean_if_exists         => 1,
+			compression             => 1,
 			createdb                => 1,
 			defaults                => 1,
 			exclude_test_table      => 1,
@@ -3171,6 +3235,7 @@ my %tests = (
 			binary_upgrade           => 1,
 			clean                    => 1,
 			clean_if_exists          => 1,
+			compression              => 1,
 			createdb                 => 1,
 			defaults                 => 1,
 			exclude_dump_test_schema => 1,
@@ -3833,8 +3898,9 @@ if ($collation_check_stderr !~ /ERROR: /)
 	$collation_support = 1;
 }
 
-# Determine whether build supports LZ4.
-my $supports_lz4 = check_pg_config("#define USE_LZ4 1");
+# Determine whether build supports LZ4 and gzip.
+my $supports_lz4  = check_pg_config("#define USE_LZ4 1");
+my $supports_gzip = check_pg_config("#define HAVE_LIBZ 1");
 
 # Create additional databases for mutations of schema public
 $node->psql('postgres', 'create database regress_pg_dump_test;');
@@ -3947,9 +4013,32 @@ foreach my $run (sort keys %pgdump_runs)
 	my $test_key = $run;
 	my $run_db   = 'postgres';
 
+	# Skip command-level tests for gzip if there is no support for it.
+	if (   defined($pgdump_runs{$run}->{compile_option})
+	    && $pgdump_runs{$run}->{compile_option} eq 'gzip'
+	    && !$supports_gzip)
+	{
+		note "$run: skipped due to no gzip support";
+		next;
+	}
+
 	$node->command_ok(\@{ $pgdump_runs{$run}->{dump_cmd} },
 		"$run: pg_dump runs");
 
+	if ($pgdump_runs{$run}->{compress_cmd})
+	{
+		my ($compress_cmd) = $pgdump_runs{$run}->{compress_cmd};
+		my $compress_program = $compress_cmd->{program};
+
+		# Skip the rest of the test if the compression program is
+		# not defined.
+		next if (!defined($compress_program) || $compress_program eq '');
+
+		my @full_compress_cmd =
+		  ($compress_cmd->{program}, @{ $compress_cmd->{args} });
+		command_ok(\@full_compress_cmd, "$run: compression commands");
+	}
+
 	if ($pgdump_runs{$run}->{restore_cmd})
 	{
 		$node->command_ok(\@{ $pgdump_runs{$run}->{restore_cmd} },
-- 
2.35.1

---

From: [email-masked]
Date: Tue, 05 Apr 2022 07:13:35 +0000

------- Original Message -------

> Splitting the program and its arguments makes sense.

Great.

> option, so I have removed the case where --jobs was not used in the

Thank you. I agree with the attached and I will carry it forward to the
rest of the patchset.

Cheers,
//Georgios

---

From: Michael Paquier <[email-masked]>
Date: Tue, 5 Apr 2022 19:55:05 +0900

> Thank you. I agree with the attached and I will carry it forward to the

No need to carry it forward anymore, I think ;)
--
Michael

---

From: Tom Lane <[email-masked]>
Date: Sun, 12 Oct 2025 13:24:37 -0400

[ blast-from-the-past department ]

> option, so I have removed the case where --jobs was not used in the

(This patch became commit 98fe74218.)

I am wondering if you remember why this bit:

was set up to manually compress blobs.toc but not the main TOC in the
toc.dat file.  It turns out that Gzip_read is broken for the case
of a zero-length read request [1], but we never reach that case
unless toc.dat is compressed.  We don't cover the getc_func member
of the compression stream API, either.

I thought for a bit about proposing that we compress toc.dat but not
blobs.toc, but that loses coverage in another way: the gets_func API
turns out to be used only while reading blobs.toc.  So we need to
compress both files manually if we want full coverage.

I think this change won't lose coverage, because there are other tests
in 002_pg_dump.pl that exercise directory format without extra
compression of anything.

Thoughts?

			regards, tom lane

[1] https://www.postgresql.org/message-id/3686.1760232320%40sss.pgh.pa.us

---

From: Michael Paquier <[email-masked]>
Date: Tue, 14 Oct 2025 14:40:59 +0900

> of the compression stream API, either.

Using extra commands to emulate the manual compressions of the dump
elements is an idea that comes originally from Georgios, and we
included in the first version of the patch posted on the original
thread: 
https://www.postgresql.org/message-id/faUNEOpts9vunEaLnmxmG-DldLSg_ql137OC3JYDmgrOMHm1RvvWY2IdBkv_CRxm5spCCb_OmKNk2T03TMm0fBEWveFF9wA1WizPuAgB7Ss=@protonmail.com

If I recall correctly, it's something that the two of us have
discussed offline because Georgios had sent the patch to the lists.
He has argued about these additions in favor of coverage, because he
had noticed a bunch of edge cases we never reached with only the tests
in core while coding compression support.  Regarding toc.dat
specifically, I think that we have just failed to consider it as a
"valid" case, as far as I can see.  So I see no reason to not adding
coverage where toc.dat is manuall compressed, and you are giving a
good reason to add this part in the test.

> in 002_pg_dump.pl that exercise directory format without extra

It looks like it should be OK to just add a "manual" compression of
toc.dat in compression_gzip_dir.  That sounds OK to me here.  As far
as I can see on HEAD at 1c05fe11abb6, the problem has been fixed in
1f8062dd9668, without a test added for toc.dat.  Having coverage would
be nice.  I would also expand this concept to compression_zstd_dir and
compression_lz4_dir, but I guess that you are implying that already to
provide coverage for all the compression methods supported by pg_dump? 
--
Michael