Subject: Re: Add LZ4 compression in pg_dump

From: Justin Pryzby <[email-masked]>
Date: Fri, 4 Mar 2022 10:10:39 -0600

The patch is failing on cfbot/freebsd.
http://cfbot.cputube.org/georgios-kokolatos.html

Also, I wondered if you'd looked at the "high compression" interfaces in
lz4hc.h ?  Should pg_dump use that ?

On Fri, Feb 25, 2022 at 08:03:40AM -0600, Justin Pryzby wrote:
> Thanks for working on this.  Your 0001 looks similar to what I did for zstd 1-2
> years ago.
> https://commitfest.postgresql.org/32/2888/
> 
> I rebased and attached the latest patches I had in case they're useful to you.
> I'd like to see zstd included in pg_dump eventually, but it was too much work
> to shepherd the patches.  Now that seems reasonable for pg16.
> 
> With the other compression patches I've worked on, we've used an extra patch
> with changes the default to the new compression algorithm, to force cfbot to
> exercize the new code.
> 
> Do you know the process with commitfests and cfbot ?
> There's also this, which allows running the tests on cirrus before mailing the
> patch to the hackers list.
> ./src/tools/ci/README

---

From: Greg Stark <[email-masked]>
Date: Fri, 25 Mar 2022 01:20:47 -0400

It seems development on this has stalled. If there's no further work
happening I guess I'll mark the patch returned with feedback. Feel
free to resubmit it to the next CF when there's progress.

---

From: Justin Pryzby <[email-masked]>
Date: Fri, 25 Mar 2022 08:22:53 -0500

> free to resubmit it to the next CF when there's progress.

Since it's a reasonably large patch (and one that I had myself started before)
and it's only been 20some days since (minor) review comments, and since the
focus right now is on committing features, and not reviewing new patches, and
this patch is new one month ago, and its 0002 not intended for pg15, therefor
I'm moving it to the next CF, where I hope to work with its authors to progress
it.

-- 
Justin

---

From: Rachel Heaton <[email-masked]>
Date: Fri, 25 Mar 2022 16:13:08 -0700

> I'm moving it to the next CF, where I hope to work with its authors to progress
Hi Folks,

Here is an updated patchset from Georgios, with minor assistance from myself.
The comments above should be addressed, but please let us know if
there are other things to go over. A functional change in this
patchset is when `--compress=none` is passed to pg_dump, it will not
compress for directory type (previously, it would use gzip if
present). The previous default behavior is retained.

- Rachel

---

From: [email-masked]
Date: Fri, 25 Mar 2022 23:43:17 +0000

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

On Saturday, March 26th, 2022 at 12:13 AM, Rachel Heaton <[email-masked]> wrote:

> On Fri, Mar 25, 2022 at 6:22 AM Justin Pryzby [email-masked] wrote:
>

We had some progress yet we didn't want to distract the list with too many
emails. Of course, it seemed stalled to the outside observer, yet I simply
wanted to set the record straight and say that we are actively working on it.

> > I'm moving it to the next CF, where I hope to work with its authors to progress

Thank you. It is much appreciated. We will sent updates when the next commitfest
starts in July as to not distract from the 15 work. Then, we can take it from there.

> The comments above should be addressed, but please let us know if

A small amendment to the above statement. This patchset does not include the
refactoring of compress_io suggested by Mr Paquier in the same thread, as it is
missing documentation. An updated version will be sent to include those changes
on the next commitfest.

---

From: Michael Paquier <[email-masked]>
Date: Sat, 26 Mar 2022 14:57:50 +0900

> missing documentation. An updated version will be sent to include those changes

The refactoring using callbacks would make the code much cleaner IMO
in the long term, with zstd waiting in the queue.  Now, I see some
pieces of the patch set that could be merged now without waiting for
the development cycle of 16 to begin, as of 0001 to add more tests and
0002.

I have a question about 0002, actually.  What has led you to the
conclusion that this code is dead and could be removed?
--
Michael

---

From: Justin Pryzby <[email-masked]>
Date: Sat, 26 Mar 2022 01:14:41 -0500

> conclusion that this code is dead and could be removed?

See 0001 and the manpage.

+               'pg_dump: compression is not supported by tar archive format');

When I submitted a patch to support zstd, I spent awhile trying to make
compression work with tar, but it's a significant effort and better done
separately.

---

From: Michael Paquier <[email-masked]>
Date: Tue, 29 Mar 2022 16:27:32 +0900

> compression work with tar, but it's a significant effort and better done

Wow.  This stuff is old enough to vote (c3e18804), dead since its
introduction.  There is indeed an argument for removing that, it is
not good to keep around that that has never been stressed and/or
used.  Upon review, the cleanup done looks correct, as we have never
been able to generate .dat.gz files in for a dump in the tar format.

+   command_fails_like(
+       [ 'pg_dump', '--compress', '1', '--format', 'tar' ],
This addition depending on HAVE_LIBZ is a good thing as a reminder of
any work that could be done in 0002.  Now that's waiting for 20 years
so I would not hold my breath on this support.  I think that this
could be just applied first, with 0002 on top of it, as a first
improvement.

+       compress_cmd => [
+           $ENV{'GZIP_PROGRAM'},
Patch 0001 is missing and update of pg_dump's Makefile to pass down
this environment variable to the test scripts, no?

+       compress_cmd => [
+           $ENV{'GZIP_PROGRAM'},
+           '-f',
[...]
+           $ENV{'GZIP_PROGRAM'},
+           '-k', '-d',
-f and -d are available everywhere I looked at, but is -k/--keep a
portable choice with a gzip command?  I don't see this option in
OpenBSD, for one.  So this test is going to cause problems on those
buildfarm machines, at least.  Couldn't this part be replaced by a
simple --test to check that what has been compressed is in correct
shape?  We know that this works, based on our recent experiences with
the other tests.
--
Michael

---

From: [email-masked]
Date: Tue, 29 Mar 2022 09:46:27 +0000

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

> been able to generate .dat.gz files in for a dump in the tar format.

Correct. My driving force behind it was to ease up the cleanup/refactoring
work that follows, by eliminating the callers of the GZ*() macros.

> could be just applied first, with 0002 on top of it, as a first

Excellent, thank you.

> this environment variable to the test scripts, no?

Agreed. It was not properly moved forward. Fixed.

> shape? We know that this works, based on our recent experiences with

I would argue that the simple '--test' will not do in this case, as the
TAP tests do need a file named <test>.sql to compare the contents with.
This file is generated either directly by pg_dump itself, or by running
pg_restore on pg_dump's output. In the case of compression pg_dump will
generate a <test>.sql.<compression program suffix> which can not be
used in the comparison tests. So the intention of this block, is not to
simply test for validity, but to also decompress pg_dump's output for it
to be able to be used.

I updated the patch to simply remove the '-k' flag.

Please find v3 attached. (only 0001 and 0002 are relevant, 0003 and
0004 are only for reference and are currently under active modification).

Cheers,
//Georgios
From ef373439ac3c5ec7663ff191b28f744cdc529e86 Mon Sep 17 00:00:00 2001
From: Georgios Kokolatos <[email-masked]>
Date: Tue, 29 Mar 2022 08:31:47 +0000
Subject: [PATCH v3 4/4] 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     | 207 ++++++--
 9 files changed, 911 insertions(+), 149 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 2f524b09bf..2864ccabb9 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 d9f6c9a087..c443e698f9 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,
 	COMPRESSION_INVALID
 } 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 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,
 
 	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;
 	}
 
@@ -1346,10 +1348,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 4713d0c8d4..e8d792e08a 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(
 	[ '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 152d5564eb..032aa94760 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -81,11 +81,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",
@@ -101,12 +104,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',
@@ -123,12 +129,97 @@ my %pgdump_runs = (
 			"--file=$tempdir/compression_gzip_plain_format.sql.gz",
 			'postgres',
 		],
-		compress_cmd => [
-			$ENV{'GZIP_PROGRAM'},
-			'-d',
-			"$tempdir/compression_gzip_plain_format.sql.gz",
+		compression => {
+			method => 'gzip',
+			cmd => [
+				$ENV{'GZIP_PROGRAM'},
+				'-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",
+>>>>>>> f6385a1dce (Add LZ4 compression in pg_{dump|restore})
 		],
 	},
+	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",
+			],
+		}
+	},
 	compression_none_dir_format => {
 		test_key => 'compression',
 		dump_cmd => [
@@ -137,10 +228,13 @@ my %pgdump_runs = (
 			"--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
+		compression => {
+			method => 'gzip',
+			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',
@@ -148,6 +242,26 @@ 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',
+		],
+		compression => {
+			method => 'gzip',
+			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',
@@ -4044,45 +4158,48 @@ 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 = $supports_gzip || $supports_lz4;
 
 	$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 (defined($pgdump_runs{$run}->{glob_match}))
+	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 '';
+		next if !$supports_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;
+		my ($compression) = $pgdump_runs{$run}->{'compression'};
 
-		cmp_ok(scalar(@glob_matched), '>=', $match_count,
-			"Expected at least $match_count file(s) matching $match");
+		next if ${compression}->{method} eq 'gzip' && (!$gzip_program || !$supports_gzip);
+		next if ${compression}->{method} eq 'lz4'  && (!$lz4_program || !$supports_lz4);
 
-		if (defined($pgdump_runs{$run}->{glob_match}->{no_match}))
+		if (defined($compression->{cmd}))
 		{
-			my $no_match = $pgdump_runs{$run}->{glob_match}->{no_match};
-			my @glob_matched = glob $no_match;
+			command_ok( \@{ $compression->{cmd} }, "$run: compression commands");
+		}
+
+		if (defined($compression->{glob_match}))
+		{
+			my $match = $compression->{glob_match}->{match};
+			my $match_count = $compression->{glob_match}->{match_count};
+			my @glob_matched = glob $match;
+
+			cmp_ok(scalar(@glob_matched), '>=', $match_count,
+				"Expected at least $match_count file(s) matching $match");
 
-			cmp_ok(scalar(@glob_matched), '==', 0,
-				"Expected no file(s) matching $no_match");
+			if (defined($pgdump_runs{$run}->{glob_match}->{no_match}))
+			{
+				my $no_match = $pgdump_runs{$run}->{glob_match}->{no_match};
+				my @glob_not_matched = glob $no_match;
+
+				cmp_ok(scalar(@glob_not_matched), '==', 0,
+					"Expected no file(s) matching $no_match");
+			}
 		}
 	}
 
-- 
2.32.0

From d70f2bf62900f0e3feae97fb9ec4043c1aca35e9 Mon Sep 17 00:00:00 2001
From: Georgios Kokolatos <[email-masked]>
Date: Tue, 29 Mar 2022 08:30:15 +0000
Subject: [PATCH v3 3/4] 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.

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/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      |  50 +++-
 src/tools/pgindent/typedefs.list      |   2 +-
 13 files changed, 608 insertions(+), 357 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/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..b8b366616c 100644
--- a/src/bin/pg_dump/compress_io.h
+++ b/src/bin/pg_dump/compress_io.h
@@ -17,16 +17,17 @@
 
 #include "pg_backup_archiver.h"
 
+#ifdef HAVE_LIBZ
+#include <zlib.h>
+#else
+/* 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 +47,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 +61,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..d9f6c9a087 100644
--- a/src/bin/pg_dump/pg_backup.h
+++ b/src/bin/pg_dump/pg_backup.h
@@ -75,6 +75,13 @@ enum _dumpPreparedQueries
 	NUM_PREP_QUERIES			/* must be last */
 };
 
+typedef enum _compressionMethod
+{
+	COMPRESSION_GZIP,
+	COMPRESSION_NONE,
+	COMPRESSION_INVALID
+} 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);
 
 /* 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 620e609055..3e60afd39d 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"
@@ -194,7 +195,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
@@ -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;
 
@@ -890,7 +890,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 535b160165..97ac17ebff 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_INVALID;
 	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,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
-	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 +721,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 +954,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 +1002,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 +1263,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 9c1238181a..4713d0c8d4 100644
--- a/src/bin/pg_dump/t/001_basic.pl
+++ b/src/bin/pg_dump/t/001_basic.pl
@@ -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,14 +138,14 @@ 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');
 }
 else
 {
 	command_fails_like(
-		[ 'pg_dump', '--compress', '1', '--format', 'tar' ],
+		[ 'pg_dump', '--compress', 'gzip:1', '--format', 'tar' ],
 		qr/\Qpg_dump: warning: requested compression not available in this installation -- archive will be uncompressed\E/,
 		'pg_dump: warning: requested compression not available in this installation -- archive will be uncompressed');
 }
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index b9f6dccec4..152d5564eb 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -76,7 +76,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',
 		],
@@ -97,7 +97,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',
 		],
@@ -129,6 +129,25 @@ my %pgdump_runs = (
 			"$tempdir/compression_gzip_plain_format.sql.gz",
 		],
 	},
+	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',
@@ -228,7 +247,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 => [
@@ -4041,6 +4060,31 @@ foreach my $run (sort keys %pgdump_runs)
 		command_ok( \@{ $pgdump_runs{$run}->{compress_cmd} },
 			"$run: compression commands");
 	}
+	if (defined($pgdump_runs{$run}->{glob_match}))
+	{
+		# 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 '';
+
+		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 (defined($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 182b233b4c..00c6ad9516 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 65232a1ed8fdfb57775bef856a907965dfcda14d Mon Sep 17 00:00:00 2001
From: Georgios Kokolatos <[email-masked]>
Date: Tue, 29 Mar 2022 08:29:15 +0000
Subject: [PATCH v3 1/4] Extend compression coverage for pg_dump, pg_restore

---
 src/bin/pg_dump/Makefile         |  2 +
 src/bin/pg_dump/t/001_basic.pl   | 15 ++++++
 src/bin/pg_dump/t/002_pg_dump.pl | 92 ++++++++++++++++++++++++++++++++
 3 files changed, 109 insertions(+)

diff --git a/src/bin/pg_dump/Makefile b/src/bin/pg_dump/Makefile
index 302f7e02d6..2f524b09bf 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/t/001_basic.pl b/src/bin/pg_dump/t/001_basic.pl
index e0bf3eb032..9c1238181a 100644
--- a/src/bin/pg_dump/t/001_basic.pl
+++ b/src/bin/pg_dump/t/001_basic.pl
@@ -125,6 +125,21 @@ command_fails_like(
 	qr/\Qpg_dump: error: -Z\/--compress must be in range 0..9\E/,
 	'pg_dump: -Z/--compress must be in range');
 
+if (check_pg_config("#define HAVE_LIBZ 1"))
+{
+	command_fails_like(
+		[ 'pg_dump', '--compress', '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');
+}
+else
+{
+	command_fails_like(
+		[ 'pg_dump', '--compress', '1', '--format', 'tar' ],
+		qr/\Qpg_dump: warning: requested compression not available in this installation -- archive will be uncompressed\E/,
+		'pg_dump: warning: requested compression not available in this installation -- archive will be uncompressed');
+}
+
 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 af5d6fa5a3..b9f6dccec4 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -54,6 +54,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=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=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'},
+			'-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,
@@ -3098,6 +3174,7 @@ my %tests = (
 			binary_upgrade          => 1,
 			clean                   => 1,
 			clean_if_exists         => 1,
+			compression             => 1,
 			createdb                => 1,
 			defaults                => 1,
 			exclude_test_table      => 1,
@@ -3171,6 +3248,7 @@ my %tests = (
 			binary_upgrade           => 1,
 			clean                    => 1,
 			clean_if_exists          => 1,
+			compression              => 1,
 			createdb                 => 1,
 			defaults                 => 1,
 			exclude_dump_test_schema => 1,
@@ -3947,9 +4025,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} },
-- 
2.32.0

From 2842f3f09d209319abf02ad2a60efd71399e75b5 Mon Sep 17 00:00:00 2001
From: Georgios Kokolatos <[email-masked]>
Date: Tue, 29 Mar 2022 08:29:25 +0000
Subject: [PATCH v3 2/4] Remove unsupported/bitrot compression from pg_dump tar

---
 src/bin/pg_dump/pg_backup_tar.c | 82 ++++-----------------------------
 1 file changed, 9 insertions(+), 73 deletions(-)

diff --git a/src/bin/pg_dump/pg_backup_tar.c b/src/bin/pg_dump/pg_backup_tar.c
index ccfbe346be..620e609055 100644
--- a/src/bin/pg_dump/pg_backup_tar.c
+++ b/src/bin/pg_dump/pg_backup_tar.c
@@ -65,11 +65,6 @@ static void _EndBlobs(ArchiveHandle *AH, TocEntry *te);
 
 typedef struct
 {
-#ifdef HAVE_LIBZ
-	gzFile		zFH;
-#else
-	FILE	   *zFH;
-#endif
 	FILE	   *nFH;
 	FILE	   *tarFH;
 	FILE	   *tmpFH;
@@ -248,14 +243,7 @@ _ArchiveEntry(ArchiveHandle *AH, TocEntry *te)
 	ctx = (lclTocEntry *) pg_malloc0(sizeof(lclTocEntry));
 	if (te->dataDumper != NULL)
 	{
-#ifdef HAVE_LIBZ
-		if (AH->compression == 0)
-			sprintf(fn, "%d.dat", te->dumpId);
-		else
-			sprintf(fn, "%d.dat.gz", te->dumpId);
-#else
-		sprintf(fn, "%d.dat", te->dumpId);
-#endif
+		snprintf(fn, sizeof(fn), "%d.dat", te->dumpId);
 		ctx->filename = pg_strdup(fn);
 	}
 	else
@@ -320,10 +308,6 @@ tarOpen(ArchiveHandle *AH, const char *filename, char mode)
 	lclContext *ctx = (lclContext *) AH->formatData;
 	TAR_MEMBER *tm;
 
-#ifdef HAVE_LIBZ
-	char		fmode[14];
-#endif
-
 	if (mode == 'r')
 	{
 		tm = _tarPositionTo(AH, filename);
@@ -344,16 +328,10 @@ tarOpen(ArchiveHandle *AH, const char *filename, char mode)
 			}
 		}
 
-#ifdef HAVE_LIBZ
-
 		if (AH->compression == 0)
 			tm->nFH = ctx->tarFH;
 		else
 			fatal("compression is not supported by tar archive format");
-		/* tm->zFH = gzdopen(dup(fileno(ctx->tarFH)), "rb"); */
-#else
-		tm->nFH = ctx->tarFH;
-#endif
 	}
 	else
 	{
@@ -405,21 +383,10 @@ tarOpen(ArchiveHandle *AH, const char *filename, char mode)
 
 		umask(old_umask);
 
-#ifdef HAVE_LIBZ
-
-		if (AH->compression != 0)
-		{
-			sprintf(fmode, "wb%d", AH->compression);
-			tm->zFH = gzdopen(dup(fileno(tm->tmpFH)), fmode);
-			if (tm->zFH == NULL)
-				fatal("could not open temporary file");
-		}
-		else
+		if (AH->compression == 0)
 			tm->nFH = tm->tmpFH;
-#else
-
-		tm->nFH = tm->tmpFH;
-#endif
+		else
+			fatal("compression is not supported by tar archive format");
 
 		tm->AH = AH;
 		tm->targetFile = pg_strdup(filename);
@@ -434,15 +401,8 @@ tarOpen(ArchiveHandle *AH, const char *filename, char mode)
 static void
 tarClose(ArchiveHandle *AH, TAR_MEMBER *th)
 {
-	/*
-	 * Close the GZ file since we dup'd. This will flush the buffers.
-	 */
 	if (AH->compression != 0)
-	{
-		errno = 0;				/* in case gzclose() doesn't set it */
-		if (GZCLOSE(th->zFH) != 0)
-			fatal("could not close tar member: %m");
-	}
+		fatal("compression is not supported by tar archive format");
 
 	if (th->mode == 'w')
 		_tarAddFile(AH, th);	/* This will close the temp file */
@@ -456,7 +416,6 @@ tarClose(ArchiveHandle *AH, TAR_MEMBER *th)
 		free(th->targetFile);
 
 	th->nFH = NULL;
-	th->zFH = NULL;
 }
 
 #ifdef __NOT_USED__
@@ -542,29 +501,9 @@ _tarReadRaw(ArchiveHandle *AH, void *buf, size_t len, TAR_MEMBER *th, FILE *fh)
 		}
 		else if (th)
 		{
-			if (th->zFH)
-			{
-				res = GZREAD(&((char *) buf)[used], 1, len, th->zFH);
-				if (res != len && !GZEOF(th->zFH))
-				{
-#ifdef HAVE_LIBZ
-					int			errnum;
-					const char *errmsg = gzerror(th->zFH, &errnum);
-
-					fatal("could not read from input file: %s",
-						  errnum == Z_ERRNO ? strerror(errno) : errmsg);
-#else
-					fatal("could not read from input file: %s",
-						  strerror(errno));
-#endif
-				}
-			}
-			else
-			{
-				res = fread(&((char *) buf)[used], 1, len, th->nFH);
-				if (res != len && !feof(th->nFH))
-					READ_ERROR_EXIT(th->nFH);
-			}
+			res = fread(&((char *) buf)[used], 1, len, th->nFH);
+			if (res != len && !feof(th->nFH))
+				READ_ERROR_EXIT(th->nFH);
 		}
 	}
 
@@ -596,10 +535,7 @@ tarWrite(const void *buf, size_t len, TAR_MEMBER *th)
 {
 	size_t		res;
 
-	if (th->zFH != NULL)
-		res = GZWRITE(buf, 1, len, th->zFH);
-	else
-		res = fwrite(buf, 1, len, th->nFH);
+	res = fwrite(buf, 1, len, th->nFH);
 
 	th->pos += res;
 	return res;
-- 
2.32.0

---

From: Michael Paquier <[email-masked]>
Date: Wed, 30 Mar 2022 14:54:34 +0900

> work that follows, by eliminating the callers of the GZ*() macros.

Makes sense to me.

>> could be just applied first, with 0002 on top of it, as a first

I have applied the test for --compress and --format=tar, separating it
from the rest.

While moving on with 0002, I have noticed the following in
_StartBlob():
    if (AH->compression != 0)
        sfx = ".gz";
    else
        sfx = "";
	
Shouldn't this bit also be simplified, adding a fatal() like the other
code paths, for safety?

> simply test for validity, but to also decompress pg_dump's output for it

Ahh, I see, thanks.  I would add a comment about that in the area of
compression_gzip_plain_format.

This part could be moved within the if block a couple of lines down.

It seems to me that it is enough to rely on {compress_cmd}, hence
there should be no need for $compress_program, no?

It seems to me that we should have a description for compress_cmd at
the top of 002_pg_dump.pl (close to "Definition of the pg_dump runs to
make").  There is an order dependency with restore_cmd.

> I updated the patch to simply remove the '-k' flag.

Okay.
--
Michael

---

From: [email-masked]
Date: Wed, 30 Mar 2022 15:32:55 +0000

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

On Wednesday, March 30th, 2022 at 7:54 AM, Michael Paquier <[email-masked]> wrote:

> > On Tuesday, March 29th, 2022 at 9:27 AM, Michael Paquier [email-masked] wrote:

Thank you.

> Shouldn't this bit also be simplified, adding a fatal() like the other

Agreed. Fixed.

> compression_gzip_plain_format.

Agreed. Comment added.

> This part could be moved within the if block a couple of lines down.

I moved it instead out of the for loop above to not have to call it on
each iteration.

> 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().

> make"). There is an order dependency with restore_cmd.

Agreed. Comment added.

Cheers,
//Georgios

From 0768f208a9f8462d4b1ad8210eb1a977aad15b41 Mon Sep 17 00:00:00 2001
From: Georgios Kokolatos <[email-masked]>
Date: Wed, 30 Mar 2022 15:00:02 +0000
Subject: [PATCH v4 2/4] Remove unsupported/bitrot compression from pg_dump tar

---
 src/bin/pg_dump/pg_backup_tar.c | 89 ++++-----------------------------
 1 file changed, 11 insertions(+), 78 deletions(-)

index ccfbe346be..2491a091b9 100644
 
 typedef struct
 {
-#ifdef HAVE_LIBZ
-	gzFile		zFH;
-#else
-	FILE	   *zFH;
-#endif
 	FILE	   *nFH;
 	FILE	   *tarFH;
 	FILE	   *tmpFH;
 
-#ifdef HAVE_LIBZ
-	char		fmode[14];
-#endif
-
 	if (mode == 'r')
 	{
 
-#ifdef HAVE_LIBZ
-
 
 		umask(old_umask);
 
-#ifdef HAVE_LIBZ
-
 
 		tm->AH = AH;
 
 	if (th->mode == 'w')
 
 	th->nFH = NULL;
-	th->zFH = NULL;
 }
 
 #ifdef __NOT_USED__

-	if (th->zFH != NULL)
 
 	th->pos += res;
 	return res;
@@ -949,17 +885,14 @@ _StartBlob(ArchiveHandle *AH, TocEntry *te, Oid oid)
 	lclContext *ctx = (lclContext *) AH->formatData;
 	lclTocEntry *tctx = (lclTocEntry *) te->formatData;
 	char		fname[255];
-	char	   *sfx;
 
 	if (oid == 0)
 
-		sfx = ".gz";
-		sfx = "";
 
-	sprintf(fname, "blob_%u.dat%s", oid, sfx);
+	sprintf(fname, "blob_%u.dat", oid);
 
 	tarPrintf(ctx->blobToc, "%u %s\n", oid, fname);
 
-- 
2.32.0

From 4e33597455c8fa420bca5bd87013922478996017 Mon Sep 17 00:00:00 2001
From: Georgios Kokolatos <[email-masked]>
Date: Wed, 30 Mar 2022 14:59:10 +0000
Subject: [PATCH v4 1/4] Extend compression coverage for pg_dump, pg_restore

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

 
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index af5d6fa5a3..91c3b978c4 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,82 @@ 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 => [
+			$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=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',
+		],
+		# Decompress the generated file to run through the tests
+		compress_cmd => [
+			$ENV{'GZIP_PROGRAM'},
+			'-d',
+			"$tempdir/compression_gzip_plain_format.sql.gz",
+		],
+	},
 	clean => {
 		dump_cmd => [
 			'pg_dump',
@@ -424,6 +507,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 +3182,7 @@ my %tests = (
 			binary_upgrade          => 1,
 			clean                   => 1,
 			clean_if_exists         => 1,
+			compression             => 1,
 			createdb                => 1,
 			defaults                => 1,
 			exclude_test_table      => 1,
@@ -3171,6 +3256,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 +4027,9 @@ command_fails_like(
 
 #########################################
 # Run all runs
+my $supports_compression = check_pg_config("#define HAVE_LIBZ 1");
+my $compress_program_exists = (system_log("$ENV{GZIP_PROGRAM}", '-h',
+										  '>', '/dev/null') == 0);
 
 foreach my $run (sort keys %pgdump_runs)
 {
@@ -3950,6 +4039,17 @@ foreach my $run (sort keys %pgdump_runs)
 	$node->command_ok(\@{ $pgdump_runs{$run}->{dump_cmd} },
 		"$run: pg_dump runs");
 
+		next if !$supports_compression || !$compress_program_exists;

From 7d2edf176b1ba4c70253dbeec0e878b9ed515349 Mon Sep 17 00:00:00 2001
From: Georgios Kokolatos <[email-masked]>
Date: Wed, 30 Mar 2022 15:03:53 +0000
Subject: [PATCH v4 3/4] Prepare pg_dump for additional compression methods

 src/bin/pg_dump/t/002_pg_dump.pl      |  45 ++-
 13 files changed, 603 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

 
+#ifdef HAVE_LIBZ
+#include <zlib.h>
+#else
 
-typedef enum
-{
-	COMPR_ALG_NONE,
-	COMPR_ALG_LIBZ
-} CompressionAlgorithm;
-

 
 typedef struct cfp cfp;
 
index fcc5f6bd05..7645d3285a 100644
 
+	COMPRESSION_INVALID,
+	COMPRESSION_GZIP

 
 /* 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

 
 		umask(old_umask);

 	if (th->mode == 'w')
 
@@ -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);

 

-#ifndef HAVE_LIBZ

 

 
+static bool
index 65e6c01fed..d7a52ac1a8 100644
 
+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 91c3b978c4..3a3dad2cc7 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',
 		],
@@ -104,7 +104,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',
 		],
@@ -137,6 +137,25 @@ my %pgdump_runs = (
 			"$tempdir/compression_gzip_plain_format.sql.gz",
 		],
 	},
+	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',
@@ -236,7 +255,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 => [
@@ -4048,6 +4067,26 @@ foreach my $run (sort keys %pgdump_runs)

index 72fafb795b..5ac8c156a9 100644

From d3104c7cc1fc86fa36f7b67f645c5b72898de48c Mon Sep 17 00:00:00 2001
From: Georgios Kokolatos <[email-masked]>
Date: Wed, 30 Mar 2022 15:11:01 +0000
Subject: [PATCH v4 4/4] 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     | 185 +++++--
 9 files changed, 903 insertions(+), 137 deletions(-)

        <para>

 
+#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
 
 		/*

 
-

 

 
index d7a52ac1a8..0077288388 100644
 
 command_fails_like(
 
 command_fails_like(
 
 command_fails_like(
index 3a3dad2cc7..36bd700576 100644
@@ -88,11 +88,14 @@ my %pgdump_runs = (
@@ -108,12 +111,15 @@ my %pgdump_runs = (
@@ -131,12 +137,96 @@ my %pgdump_runs = (
 		# Decompress the generated file to run through the tests
@@ -145,10 +235,13 @@ my %pgdump_runs = (
@@ -156,6 +249,26 @@ my %pgdump_runs = (
@@ -4046,9 +4159,11 @@ command_fails_like(
 
 #########################################
 # Run all runs
-my $supports_compression = check_pg_config("#define HAVE_LIBZ 1");
-my $compress_program_exists = (system_log("$ENV{GZIP_PROGRAM}", '-h',
-										  '>', '/dev/null') == 0);
+my $supports_gzip = 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)
 {
@@ -4058,21 +4173,27 @@ foreach my $run (sort keys %pgdump_runs)
 	$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 || !$compress_program_exists;
+		next if !$supports_gzip && !$supports_lz4;
+
+		my ($compression) = $pgdump_runs{$run}->{'compression'};
 
-		command_ok( \@{ $pgdump_runs{$run}->{compress_cmd} },
-			"$run: compression commands");
+		next if ${compression}->{method} eq 'gzip' && (!$gzip_program_exists || !$supports_gzip);
+		next if ${compression}->{method} eq 'lz4'  && (!$lz4_program_exists || !$supports_lz4);
+
+		if (defined($compression->{cmd}))
+		{
+			command_ok( \@{ $compression->{cmd} }, "$run: compression commands");
+		}
 
 			my @glob_matched = glob $match;
 
 			cmp_ok(scalar(@glob_matched), '>=', $match_count,
@@ -4081,9 +4202,9 @@ foreach my $run (sort keys %pgdump_runs)
 			if (defined($pgdump_runs{$run}->{glob_match}->{no_match}))
 			{
 				my $no_match = $pgdump_runs{$run}->{glob_match}->{no_match};
-				my @glob_matched = glob $no_match;
+				my @glob_not_matched = glob $no_match;
 
-				cmp_ok(scalar(@glob_matched), '==', 0,
+				cmp_ok(scalar(@glob_not_matched), '==', 0,
 					"Expected no file(s) matching $no_match");
 			}
 		}
-- 
2.32.0

---

From: Michael Paquier <[email-masked]>
Date: Thu, 31 Mar 2022 11:34:52 +0900

>> Shouldn't this bit also be simplified, adding a fatal() like the other

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.

> 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?

+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.

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).  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.
--
Michael