Add LZ4 compression in pg_dump

Started by Georgiosalmost 4 years ago32 messages
#1Georgios
gkokolatos@protonmail.com
2 attachment(s)

Hi,

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

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

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

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

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

The second commit adds LZ4 compression in pg_dump and pg_restore.

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

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

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

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

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

Cheers,
//Georgios

Attachments:

v1-0001-Prepare-pg_dump-for-additional-compression-method.patchtext/x-patch; name=v1-0001-Prepare-pg_dump-for-additional-compression-method.patchDownload
From 8dfe12b08378571add6e1d178bed97a61e988593 Mon Sep 17 00:00:00 2001
From: Georgios Kokolatos <gkokolatos@pm.me>
Date: Mon, 22 Nov 2021 09:58:49 +0000
Subject: [PATCH v1 1/2] Prepare pg_dump for additional compression methods

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

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

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

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

diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml
index 2f0042fd96..992b7312df 100644
--- a/doc/src/sgml/ref/pg_dump.sgml
+++ b/doc/src/sgml/ref/pg_dump.sgml
@@ -644,17 +644,31 @@ PostgreSQL documentation
      </varlistentry>
 
      <varlistentry>
-      <term><option>-Z <replaceable class="parameter">0..9</replaceable></option></term>
-      <term><option>--compress=<replaceable class="parameter">0..9</replaceable></option></term>
+      <term><option>-Z <replaceable class="parameter">level</replaceable></option></term>
+      <term><option>-Z <replaceable class="parameter">method</replaceable></option>[:<replaceable>level</replaceable>]</term>
+      <term><option>--compress=<replaceable class="parameter">level</replaceable></option></term>
+      <term><option>--compress=<replaceable class="parameter">method</replaceable></option>[:<replaceable>level</replaceable>]</term>
       <listitem>
        <para>
-        Specify the compression level to use.  Zero means no compression.
+        Specify the compression method and/or the compression level to use.
+        The compression method can be set to <literal>gzip</literal> or
+        <literal>none</literal> for no compression. A compression level can
+        be optionally specified, by appending the level number after a colon
+        (<literal>:</literal>). If no level is specified, the default compression
+        level will be used for the specified method. If only a level is
+        specified without mentioning a method, <literal>gzip</literal> compression
+        will be used.
+       </para>
+
+       <para>
         For the custom and directory archive formats, this specifies compression of
-        individual table-data segments, and the default is to compress
-        at a moderate level.
-        For plain text output, setting a nonzero compression level causes
-        the entire output file to be compressed, as though it had been
-        fed through <application>gzip</application>; but the default is not to compress.
+        individual table-data segments, and the default is to compress using
+        <literal>gzip</literal> at a moderate level. For plain text output,
+        setting a nonzero compression level causes the entire output file to be compressed,
+        as though it had been fed through <application>gzip</application>; but the default
+        is not to compress.
+       </para>
+       <para>
         The tar archive format currently does not support compression at all.
        </para>
       </listitem>
diff --git a/src/bin/pg_dump/Makefile b/src/bin/pg_dump/Makefile
index 84306d94ee..56e041c9b3 100644
--- a/src/bin/pg_dump/Makefile
+++ b/src/bin/pg_dump/Makefile
@@ -16,6 +16,8 @@ subdir = src/bin/pg_dump
 top_builddir = ../../..
 include $(top_builddir)/src/Makefile.global
 
+export GZIP_PROGRAM=$(GZIP)
+
 override CPPFLAGS := -I$(libpq_srcdir) $(CPPFLAGS)
 LDFLAGS_INTERNAL += -L$(top_builddir)/src/fe_utils -lpgfeutils $(libpq_pgport)
 
diff --git a/src/bin/pg_dump/compress_io.c b/src/bin/pg_dump/compress_io.c
index 9077fdb74d..630f9e4b18 100644
--- a/src/bin/pg_dump/compress_io.c
+++ b/src/bin/pg_dump/compress_io.c
@@ -64,7 +64,7 @@
 /* typedef appears in compress_io.h */
 struct CompressorState
 {
-	CompressionAlgorithm comprAlg;
+	CompressionMethod compressionMethod;
 	WriteFunc	writeF;
 
 #ifdef HAVE_LIBZ
@@ -74,9 +74,6 @@ struct CompressorState
 #endif
 };
 
-static void ParseCompressionOption(int compression, CompressionAlgorithm *alg,
-								   int *level);
-
 /* Routines that support zlib compressed data I/O */
 #ifdef HAVE_LIBZ
 static void InitCompressorZlib(CompressorState *cs, int level);
@@ -93,57 +90,30 @@ static void ReadDataFromArchiveNone(ArchiveHandle *AH, ReadFunc readF);
 static void WriteDataToArchiveNone(ArchiveHandle *AH, CompressorState *cs,
 								   const char *data, size_t dLen);
 
-/*
- * Interprets a numeric 'compression' value. The algorithm implied by the
- * value (zlib or none at the moment), is returned in *alg, and the
- * zlib compression level in *level.
- */
-static void
-ParseCompressionOption(int compression, CompressionAlgorithm *alg, int *level)
-{
-	if (compression == Z_DEFAULT_COMPRESSION ||
-		(compression > 0 && compression <= 9))
-		*alg = COMPR_ALG_LIBZ;
-	else if (compression == 0)
-		*alg = COMPR_ALG_NONE;
-	else
-	{
-		fatal("invalid compression code: %d", compression);
-		*alg = COMPR_ALG_NONE;	/* keep compiler quiet */
-	}
-
-	/* The level is just the passed-in value. */
-	if (level)
-		*level = compression;
-}
-
 /* Public interface routines */
 
 /* Allocate a new compressor */
 CompressorState *
-AllocateCompressor(int compression, WriteFunc writeF)
+AllocateCompressor(CompressionMethod compressionMethod,
+				   int compressionLevel, WriteFunc writeF)
 {
 	CompressorState *cs;
-	CompressionAlgorithm alg;
-	int			level;
-
-	ParseCompressionOption(compression, &alg, &level);
 
 #ifndef HAVE_LIBZ
-	if (alg == COMPR_ALG_LIBZ)
+	if (compressionMethod == COMPRESSION_GZIP)
 		fatal("not built with zlib support");
 #endif
 
 	cs = (CompressorState *) pg_malloc0(sizeof(CompressorState));
 	cs->writeF = writeF;
-	cs->comprAlg = alg;
+	cs->compressionMethod = compressionMethod;
 
 	/*
 	 * Perform compression algorithm specific initialization.
 	 */
 #ifdef HAVE_LIBZ
-	if (alg == COMPR_ALG_LIBZ)
-		InitCompressorZlib(cs, level);
+	if (compressionMethod == COMPRESSION_GZIP)
+		InitCompressorZlib(cs, compressionLevel);
 #endif
 
 	return cs;
@@ -154,21 +124,24 @@ AllocateCompressor(int compression, WriteFunc writeF)
  * out with ahwrite().
  */
 void
-ReadDataFromArchive(ArchiveHandle *AH, int compression, ReadFunc readF)
+ReadDataFromArchive(ArchiveHandle *AH, CompressionMethod compressionMethod,
+					int compressionLevel, ReadFunc readF)
 {
-	CompressionAlgorithm alg;
-
-	ParseCompressionOption(compression, &alg, NULL);
-
-	if (alg == COMPR_ALG_NONE)
-		ReadDataFromArchiveNone(AH, readF);
-	if (alg == COMPR_ALG_LIBZ)
+	switch (compressionMethod)
 	{
+		case COMPRESSION_NONE:
+			ReadDataFromArchiveNone(AH, readF);
+			break;
+		case COMPRESSION_GZIP:
 #ifdef HAVE_LIBZ
-		ReadDataFromArchiveZlib(AH, readF);
+			ReadDataFromArchiveZlib(AH, readF);
 #else
-		fatal("not built with zlib support");
+			fatal("not built with zlib support");
 #endif
+			break;
+		default:
+			fatal("invalid compression method");
+			break;
 	}
 }
 
@@ -179,18 +152,21 @@ void
 WriteDataToArchive(ArchiveHandle *AH, CompressorState *cs,
 				   const void *data, size_t dLen)
 {
-	switch (cs->comprAlg)
+	switch (cs->compressionMethod)
 	{
-		case COMPR_ALG_LIBZ:
+		case COMPRESSION_GZIP:
 #ifdef HAVE_LIBZ
 			WriteDataToArchiveZlib(AH, cs, data, dLen);
 #else
 			fatal("not built with zlib support");
 #endif
 			break;
-		case COMPR_ALG_NONE:
+		case COMPRESSION_NONE:
 			WriteDataToArchiveNone(AH, cs, data, dLen);
 			break;
+		default:
+			fatal("invalid compression method");
+			break;
 	}
 }
 
@@ -200,11 +176,23 @@ WriteDataToArchive(ArchiveHandle *AH, CompressorState *cs,
 void
 EndCompressor(ArchiveHandle *AH, CompressorState *cs)
 {
+	switch (cs->compressionMethod)
+	{
+		case COMPRESSION_GZIP:
 #ifdef HAVE_LIBZ
-	if (cs->comprAlg == COMPR_ALG_LIBZ)
-		EndCompressorZlib(AH, cs);
+			EndCompressorZlib(AH, cs);
+#else
+			fatal("not built with zlib support");
 #endif
-	free(cs);
+			break;
+		case COMPRESSION_NONE:
+			free(cs);
+			break;
+
+		default:
+			fatal("invalid compression method");
+			break;
+	}
 }
 
 /* Private routines, specific to each compression method. */
@@ -418,10 +406,8 @@ WriteDataToArchiveNone(ArchiveHandle *AH, CompressorState *cs,
  */
 struct cfp
 {
-	FILE	   *uncompressedfp;
-#ifdef HAVE_LIBZ
-	gzFile		compressedfp;
-#endif
+	CompressionMethod compressionMethod;
+	void	   *fp;
 };
 
 #ifdef HAVE_LIBZ
@@ -455,18 +441,18 @@ cfopen_read(const char *path, const char *mode)
 
 #ifdef HAVE_LIBZ
 	if (hasSuffix(path, ".gz"))
-		fp = cfopen(path, mode, 1);
+		fp = cfopen(path, mode, COMPRESSION_GZIP, 0);
 	else
 #endif
 	{
-		fp = cfopen(path, mode, 0);
+		fp = cfopen(path, mode, COMPRESSION_NONE, 0);
 #ifdef HAVE_LIBZ
 		if (fp == NULL)
 		{
 			char	   *fname;
 
 			fname = psprintf("%s.gz", path);
-			fp = cfopen(fname, mode, 1);
+			fp = cfopen(fname, mode, COMPRESSION_GZIP, 0);
 			free_keep_errno(fname);
 		}
 #endif
@@ -486,19 +472,21 @@ cfopen_read(const char *path, const char *mode)
  * On failure, return NULL with an error code in errno.
  */
 cfp *
-cfopen_write(const char *path, const char *mode, int compression)
+cfopen_write(const char *path, const char *mode,
+			 CompressionMethod compressionMethod,
+			 int compressionLevel)
 {
 	cfp		   *fp;
 
-	if (compression == 0)
-		fp = cfopen(path, mode, 0);
+	if (compressionMethod == COMPRESSION_NONE)
+		fp = cfopen(path, mode, compressionMethod, 0);
 	else
 	{
 #ifdef HAVE_LIBZ
 		char	   *fname;
 
 		fname = psprintf("%s.gz", path);
-		fp = cfopen(fname, mode, compression);
+		fp = cfopen(fname, mode, compressionMethod, compressionLevel);
 		free_keep_errno(fname);
 #else
 		fatal("not built with zlib support");
@@ -509,60 +497,94 @@ cfopen_write(const char *path, const char *mode, int compression)
 }
 
 /*
- * Opens file 'path' in 'mode'. If 'compression' is non-zero, the file
- * is opened with libz gzopen(), otherwise with plain fopen().
+ * This is the workhorse for cfopen() or cfdopen(). It opens file 'path' or
+ * associates a stream 'fd', if 'fd' is a valid descriptor, in 'mode'. The
+ * descriptor is not dup'ed and it is the caller's responsibility to do so.
+ * The caller must verify that the 'compressionMethod' is supported by the
+ * current build.
  *
  * On failure, return NULL with an error code in errno.
  */
-cfp *
-cfopen(const char *path, const char *mode, int compression)
+static cfp *
+cfopen_internal(const char *path, int fd, const char *mode,
+				CompressionMethod compressionMethod, int compressionLevel)
 {
 	cfp		   *fp = pg_malloc(sizeof(cfp));
 
-	if (compression != 0)
+	fp->compressionMethod = compressionMethod;
+
+	switch (compressionMethod)
 	{
-#ifdef HAVE_LIBZ
-		if (compression != Z_DEFAULT_COMPRESSION)
-		{
-			/* user has specified a compression level, so tell zlib to use it */
-			char		mode_compression[32];
+		case COMPRESSION_NONE:
+			if (fd >= 0)
+				fp->fp = fdopen(fd, mode);
+			else
+				fp->fp = fopen(path, mode);
+			if (fp->fp == NULL)
+			{
+				free_keep_errno(fp);
+				fp = NULL;
+			}
 
-			snprintf(mode_compression, sizeof(mode_compression), "%s%d",
-					 mode, compression);
-			fp->compressedfp = gzopen(path, mode_compression);
-		}
-		else
-		{
-			/* don't specify a level, just use the zlib default */
-			fp->compressedfp = gzopen(path, mode);
-		}
+			break;
+		case COMPRESSION_GZIP:
+#ifdef HAVE_LIBZ
+			if (compressionLevel != Z_DEFAULT_COMPRESSION)
+			{
+				/*
+				 * user has specified a compression level, so tell zlib to use
+				 * it
+				 */
+				char		mode_compression[32];
+
+				snprintf(mode_compression, sizeof(mode_compression), "%s%d",
+						 mode, compressionLevel);
+				if (fd >= 0)
+					fp->fp = gzdopen(fd, mode_compression);
+				else
+					fp->fp = gzopen(path, mode_compression);
+			}
+			else
+			{
+				/* don't specify a level, just use the zlib default */
+				if (fd >= 0)
+					fp->fp = gzdopen(fd, mode);
+				else
+					fp->fp = gzopen(path, mode);
+			}
 
-		fp->uncompressedfp = NULL;
-		if (fp->compressedfp == NULL)
-		{
-			free_keep_errno(fp);
-			fp = NULL;
-		}
+			if (fp->fp == NULL)
+			{
+				free_keep_errno(fp);
+				fp = NULL;
+			}
 #else
-		fatal("not built with zlib support");
-#endif
-	}
-	else
-	{
-#ifdef HAVE_LIBZ
-		fp->compressedfp = NULL;
+			fatal("not built with zlib support");
 #endif
-		fp->uncompressedfp = fopen(path, mode);
-		if (fp->uncompressedfp == NULL)
-		{
-			free_keep_errno(fp);
-			fp = NULL;
-		}
+			break;
+		default:
+			fatal("invalid compression method");
+			break;
 	}
 
 	return fp;
 }
 
+cfp *
+cfopen(const char *path, const char *mode,
+	   CompressionMethod compressionMethod,
+	   int compressionLevel)
+{
+	return cfopen_internal(path, -1, mode, compressionMethod, compressionLevel);
+}
+
+cfp *
+cfdopen(int fd, const char *mode,
+	   CompressionMethod compressionMethod,
+	   int compressionLevel)
+{
+	return cfopen_internal(NULL, fd, mode, compressionMethod, compressionLevel);
+}
 
 int
 cfread(void *ptr, int size, cfp *fp)
@@ -572,38 +594,61 @@ cfread(void *ptr, int size, cfp *fp)
 	if (size == 0)
 		return 0;
 
-#ifdef HAVE_LIBZ
-	if (fp->compressedfp)
+	switch (fp->compressionMethod)
 	{
-		ret = gzread(fp->compressedfp, ptr, size);
-		if (ret != size && !gzeof(fp->compressedfp))
-		{
-			int			errnum;
-			const char *errmsg = gzerror(fp->compressedfp, &errnum);
+		case COMPRESSION_NONE:
+			ret = fread(ptr, 1, size, fp->fp);
+			if (ret != size && !feof(fp->fp))
+				READ_ERROR_EXIT(fp->fp);
 
-			fatal("could not read from input file: %s",
-				  errnum == Z_ERRNO ? strerror(errno) : errmsg);
-		}
-	}
-	else
+			break;
+		case COMPRESSION_GZIP:
+#ifdef HAVE_LIBZ
+			ret = gzread(fp->fp, ptr, size);
+			if (ret != size && !gzeof(fp->fp))
+			{
+				int			errnum;
+				const char *errmsg = gzerror(fp->fp, &errnum);
+
+				fatal("could not read from input file: %s",
+					  errnum == Z_ERRNO ? strerror(errno) : errmsg);
+			}
+#else
+			fatal("not built with zlib support");
 #endif
-	{
-		ret = fread(ptr, 1, size, fp->uncompressedfp);
-		if (ret != size && !feof(fp->uncompressedfp))
-			READ_ERROR_EXIT(fp->uncompressedfp);
+			break;
+
+		default:
+			fatal("invalid compression method");
+			break;
 	}
+
 	return ret;
 }
 
 int
 cfwrite(const void *ptr, int size, cfp *fp)
 {
+	int			ret = 0;
+
+	switch (fp->compressionMethod)
+	{
+		case COMPRESSION_NONE:
+			ret = fwrite(ptr, 1, size, fp->fp);
+			break;
+		case COMPRESSION_GZIP:
 #ifdef HAVE_LIBZ
-	if (fp->compressedfp)
-		return gzwrite(fp->compressedfp, ptr, size);
-	else
+			ret = gzwrite(fp->fp, ptr, size);
+#else
+			fatal("not built with zlib support");
 #endif
-		return fwrite(ptr, 1, size, fp->uncompressedfp);
+			break;
+		default:
+			fatal("invalid compression method");
+			break;
+	}
+
+	return ret;
 }
 
 int
@@ -611,24 +656,31 @@ cfgetc(cfp *fp)
 {
 	int			ret;
 
-#ifdef HAVE_LIBZ
-	if (fp->compressedfp)
+	switch (fp->compressionMethod)
 	{
-		ret = gzgetc(fp->compressedfp);
-		if (ret == EOF)
-		{
-			if (!gzeof(fp->compressedfp))
-				fatal("could not read from input file: %s", strerror(errno));
-			else
-				fatal("could not read from input file: end of file");
-		}
-	}
-	else
+		case COMPRESSION_NONE:
+			ret = fgetc(fp->fp);
+			if (ret == EOF)
+				READ_ERROR_EXIT(fp->fp);
+
+			break;
+		case COMPRESSION_GZIP:
+#ifdef HAVE_LIBZ
+			ret = gzgetc((gzFile)fp->fp);
+			if (ret == EOF)
+			{
+				if (!gzeof(fp->fp))
+					fatal("could not read from input file: %s", strerror(errno));
+				else
+					fatal("could not read from input file: end of file");
+			}
+#else
+			fatal("not built with zlib support");
 #endif
-	{
-		ret = fgetc(fp->uncompressedfp);
-		if (ret == EOF)
-			READ_ERROR_EXIT(fp->uncompressedfp);
+			break;
+		default:
+			fatal("invalid compression method");
+			break;
 	}
 
 	return ret;
@@ -637,65 +689,107 @@ cfgetc(cfp *fp)
 char *
 cfgets(cfp *fp, char *buf, int len)
 {
+	char	   *ret;
+
+	switch (fp->compressionMethod)
+	{
+		case COMPRESSION_NONE:
+			ret = fgets(buf, len, fp->fp);
+
+			break;
+		case COMPRESSION_GZIP:
 #ifdef HAVE_LIBZ
-	if (fp->compressedfp)
-		return gzgets(fp->compressedfp, buf, len);
-	else
+			ret = gzgets(fp->fp, buf, len);
+#else
+			fatal("not built with zlib support");
 #endif
-		return fgets(buf, len, fp->uncompressedfp);
+			break;
+		default:
+			fatal("invalid compression method");
+			break;
+	}
+
+	return ret;
 }
 
 int
 cfclose(cfp *fp)
 {
-	int			result;
+	int			ret;
 
 	if (fp == NULL)
 	{
 		errno = EBADF;
 		return EOF;
 	}
-#ifdef HAVE_LIBZ
-	if (fp->compressedfp)
+
+	switch (fp->compressionMethod)
 	{
-		result = gzclose(fp->compressedfp);
-		fp->compressedfp = NULL;
-	}
-	else
+		case COMPRESSION_NONE:
+			ret = fclose(fp->fp);
+			fp->fp = NULL;
+
+			break;
+		case COMPRESSION_GZIP:
+#ifdef HAVE_LIBZ
+			ret = gzclose(fp->fp);
+			fp->fp = NULL;
+#else
+			fatal("not built with zlib support");
 #endif
-	{
-		result = fclose(fp->uncompressedfp);
-		fp->uncompressedfp = NULL;
+			break;
+		default:
+			fatal("invalid compression method");
+			break;
 	}
+
 	free_keep_errno(fp);
 
-	return result;
+	return ret;
 }
 
 int
 cfeof(cfp *fp)
 {
+	int			ret;
+
+	switch (fp->compressionMethod)
+	{
+		case COMPRESSION_NONE:
+			ret = feof(fp->fp);
+
+			break;
+		case COMPRESSION_GZIP:
 #ifdef HAVE_LIBZ
-	if (fp->compressedfp)
-		return gzeof(fp->compressedfp);
-	else
+			ret = gzeof(fp->fp);
+#else
+			fatal("not built with zlib support");
 #endif
-		return feof(fp->uncompressedfp);
+			break;
+		default:
+			fatal("invalid compression method");
+			break;
+	}
+
+	return ret;
 }
 
 const char *
 get_cfp_error(cfp *fp)
 {
-#ifdef HAVE_LIBZ
-	if (fp->compressedfp)
+	if (fp->compressionMethod == COMPRESSION_GZIP)
 	{
+#ifdef HAVE_LIBZ
 		int			errnum;
-		const char *errmsg = gzerror(fp->compressedfp, &errnum);
+		const char *errmsg = gzerror(fp->fp, &errnum);
 
 		if (errnum != Z_ERRNO)
 			return errmsg;
-	}
+#else
+		fatal("not built with zlib support");
 #endif
+	}
+
 	return strerror(errno);
 }
 
diff --git a/src/bin/pg_dump/compress_io.h b/src/bin/pg_dump/compress_io.h
index f635787692..de5ae4f61b 100644
--- a/src/bin/pg_dump/compress_io.h
+++ b/src/bin/pg_dump/compress_io.h
@@ -17,16 +17,25 @@
 
 #include "pg_backup_archiver.h"
 
+#ifdef HAVE_LIBZ
+#include <zlib.h>
+#define GZCLOSE(fh) gzclose(fh)
+#define GZWRITE(p, s, n, fh) gzwrite(fh, p, (n) * (s))
+#define GZREAD(p, s, n, fh) gzread(fh, p, (n) * (s))
+#define GZEOF(fh)	gzeof(fh)
+#else
+#define GZCLOSE(fh) fclose(fh)
+#define GZWRITE(p, s, n, fh) (fwrite(p, s, n, fh) * (s))
+#define GZREAD(p, s, n, fh) fread(p, s, n, fh)
+#define GZEOF(fh)	feof(fh)
+/* this is just the redefinition of a libz constant */
+#define Z_DEFAULT_COMPRESSION (-1)
+#endif
+
 /* Initial buffer sizes used in zlib compression. */
 #define ZLIB_OUT_SIZE	4096
 #define ZLIB_IN_SIZE	4096
 
-typedef enum
-{
-	COMPR_ALG_NONE,
-	COMPR_ALG_LIBZ
-} CompressionAlgorithm;
-
 /* Prototype for callback function to WriteDataToArchive() */
 typedef void (*WriteFunc) (ArchiveHandle *AH, const char *buf, size_t len);
 
@@ -46,8 +55,12 @@ typedef size_t (*ReadFunc) (ArchiveHandle *AH, char **buf, size_t *buflen);
 /* struct definition appears in compress_io.c */
 typedef struct CompressorState CompressorState;
 
-extern CompressorState *AllocateCompressor(int compression, WriteFunc writeF);
-extern void ReadDataFromArchive(ArchiveHandle *AH, int compression,
+extern CompressorState *AllocateCompressor(CompressionMethod compressionMethod,
+										   int compressionLevel,
+										   WriteFunc writeF);
+extern void ReadDataFromArchive(ArchiveHandle *AH,
+								CompressionMethod compressionMethod,
+								int compressionLevel,
 								ReadFunc readF);
 extern void WriteDataToArchive(ArchiveHandle *AH, CompressorState *cs,
 							   const void *data, size_t dLen);
@@ -56,9 +69,16 @@ extern void EndCompressor(ArchiveHandle *AH, CompressorState *cs);
 
 typedef struct cfp cfp;
 
-extern cfp *cfopen(const char *path, const char *mode, int compression);
+extern cfp *cfopen(const char *path, const char *mode,
+				   CompressionMethod compressionMethod,
+				   int compressionLevel);
+extern cfp *cfdopen(int fd, const char *mode,
+				   CompressionMethod compressionMethod,
+				   int compressionLevel);
 extern cfp *cfopen_read(const char *path, const char *mode);
-extern cfp *cfopen_write(const char *path, const char *mode, int compression);
+extern cfp *cfopen_write(const char *path, const char *mode,
+						 CompressionMethod compressionMethod,
+						 int compressionLevel);
 extern int	cfread(void *ptr, int size, cfp *fp);
 extern int	cfwrite(const void *ptr, int size, cfp *fp);
 extern int	cfgetc(cfp *fp);
diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h
index fcc5f6bd05..741810b066 100644
--- a/src/bin/pg_dump/pg_backup.h
+++ b/src/bin/pg_dump/pg_backup.h
@@ -75,6 +75,12 @@ enum _dumpPreparedQueries
 	NUM_PREP_QUERIES			/* must be last */
 };
 
+typedef enum _compressionMethod
+{
+	COMPRESSION_GZIP,
+	COMPRESSION_NONE
+} CompressionMethod;
+
 /* Parameters needed by ConnectDatabase; same for dump and restore */
 typedef struct _connParams
 {
@@ -143,7 +149,8 @@ typedef struct _restoreOptions
 
 	int			noDataForFailedTables;
 	int			exit_on_error;
-	int			compression;
+	CompressionMethod compressionMethod;
+	int			compressionLevel;
 	int			suppressDumpWarnings;	/* Suppress output of WARNING entries
 										 * to stderr */
 	bool		single_txn;
@@ -303,7 +310,9 @@ extern Archive *OpenArchive(const char *FileSpec, const ArchiveFormat fmt);
 
 /* Create a new archive */
 extern Archive *CreateArchive(const char *FileSpec, const ArchiveFormat fmt,
-							  const int compression, bool dosync, ArchiveMode mode,
+							  const CompressionMethod compressionMethod,
+							  const int compression,
+							  bool dosync, ArchiveMode mode,
 							  SetupWorkerPtrType setupDumpWorker);
 
 /* The --list option */
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index d41a99d6ea..7d96446f1a 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -31,6 +31,7 @@
 #endif
 
 #include "common/string.h"
+#include "compress_io.h"
 #include "dumputils.h"
 #include "fe_utils/string_utils.h"
 #include "lib/stringinfo.h"
@@ -43,13 +44,6 @@
 #define TEXT_DUMP_HEADER "--\n-- PostgreSQL database dump\n--\n\n"
 #define TEXT_DUMPALL_HEADER "--\n-- PostgreSQL database cluster dump\n--\n\n"
 
-/* state needed to save/restore an archive's output target */
-typedef struct _outputContext
-{
-	void	   *OF;
-	int			gzOut;
-} OutputContext;
-
 /*
  * State for tracking TocEntrys that are ready to process during a parallel
  * restore.  (This used to be a list, and we still call it that, though now
@@ -70,7 +64,9 @@ typedef struct _parallelReadyList
 
 
 static ArchiveHandle *_allocAH(const char *FileSpec, const ArchiveFormat fmt,
-							   const int compression, bool dosync, ArchiveMode mode,
+							   const CompressionMethod compressionMethod,
+							   const int compressionLevel,
+							   bool dosync, ArchiveMode mode,
 							   SetupWorkerPtrType setupWorkerPtr);
 static void _getObjectDescription(PQExpBuffer buf, TocEntry *te);
 static void _printTocEntry(ArchiveHandle *AH, TocEntry *te, bool isData);
@@ -98,9 +94,11 @@ static int	_discoverArchiveFormat(ArchiveHandle *AH);
 static int	RestoringToDB(ArchiveHandle *AH);
 static void dump_lo_buf(ArchiveHandle *AH);
 static void dumpTimestamp(ArchiveHandle *AH, const char *msg, time_t tim);
-static void SetOutput(ArchiveHandle *AH, const char *filename, int compression);
-static OutputContext SaveOutput(ArchiveHandle *AH);
-static void RestoreOutput(ArchiveHandle *AH, OutputContext savedContext);
+static void SetOutput(ArchiveHandle *AH, const char *filename,
+					  CompressionMethod compressionMethod,
+					  int compressionLevel);
+static cfp *SaveOutput(ArchiveHandle *AH);
+static void RestoreOutput(ArchiveHandle *AH, cfp *savedOutput);
 
 static int	restore_toc_entry(ArchiveHandle *AH, TocEntry *te, bool is_parallel);
 static void restore_toc_entries_prefork(ArchiveHandle *AH,
@@ -239,12 +237,15 @@ setupRestoreWorker(Archive *AHX)
 /* Public */
 Archive *
 CreateArchive(const char *FileSpec, const ArchiveFormat fmt,
-			  const int compression, bool dosync, ArchiveMode mode,
+			  const CompressionMethod compressionMethod,
+			  const int compressionLevel,
+			  bool dosync, ArchiveMode mode,
 			  SetupWorkerPtrType setupDumpWorker)
 
 {
-	ArchiveHandle *AH = _allocAH(FileSpec, fmt, compression, dosync,
-								 mode, setupDumpWorker);
+	ArchiveHandle *AH = _allocAH(FileSpec, fmt,
+								 compressionMethod, compressionLevel,
+								 dosync, mode, setupDumpWorker);
 
 	return (Archive *) AH;
 }
@@ -254,7 +255,8 @@ CreateArchive(const char *FileSpec, const ArchiveFormat fmt,
 Archive *
 OpenArchive(const char *FileSpec, const ArchiveFormat fmt)
 {
-	ArchiveHandle *AH = _allocAH(FileSpec, fmt, 0, true, archModeRead, setupRestoreWorker);
+	ArchiveHandle *AH = _allocAH(FileSpec, fmt, COMPRESSION_NONE, 0, true,
+								 archModeRead, setupRestoreWorker);
 
 	return (Archive *) AH;
 }
@@ -269,11 +271,8 @@ CloseArchive(Archive *AHX)
 	AH->ClosePtr(AH);
 
 	/* Close the output */
-	errno = 0;					/* in case gzclose() doesn't set it */
-	if (AH->gzOut)
-		res = GZCLOSE(AH->OF);
-	else if (AH->OF != stdout)
-		res = fclose(AH->OF);
+	errno = 0;
+	res = cfclose(AH->OF);
 
 	if (res != 0)
 		fatal("could not close output file: %m");
@@ -355,7 +354,7 @@ RestoreArchive(Archive *AHX)
 	RestoreOptions *ropt = AH->public.ropt;
 	bool		parallel_mode;
 	TocEntry   *te;
-	OutputContext sav;
+	cfp		   *sav;
 
 	AH->stage = STAGE_INITIALIZING;
 
@@ -384,7 +383,8 @@ RestoreArchive(Archive *AHX)
 	 * Make sure we won't need (de)compression we haven't got
 	 */
 #ifndef HAVE_LIBZ
-	if (AH->compression != 0 && AH->PrintTocDataPtr != NULL)
+	if (AH->compressionMethod == COMPRESSION_GZIP &&
+		AH->PrintTocDataPtr != NULL)
 	{
 		for (te = AH->toc->next; te != AH->toc; te = te->next)
 		{
@@ -459,8 +459,9 @@ RestoreArchive(Archive *AHX)
 	 * Setup the output file if necessary.
 	 */
 	sav = SaveOutput(AH);
-	if (ropt->filename || ropt->compression)
-		SetOutput(AH, ropt->filename, ropt->compression);
+	if (ropt->filename || ropt->compressionMethod != COMPRESSION_NONE)
+		SetOutput(AH, ropt->filename,
+				  ropt->compressionMethod, ropt->compressionLevel);
 
 	ahprintf(AH, "--\n-- PostgreSQL database dump\n--\n\n");
 
@@ -740,7 +741,7 @@ RestoreArchive(Archive *AHX)
 	 */
 	AH->stage = STAGE_FINALIZING;
 
-	if (ropt->filename || ropt->compression)
+	if (ropt->filename || ropt->compressionMethod != COMPRESSION_NONE)
 		RestoreOutput(AH, sav);
 
 	if (ropt->useDB)
@@ -970,6 +971,7 @@ NewRestoreOptions(void)
 	opts->format = archUnknown;
 	opts->cparams.promptPassword = TRI_DEFAULT;
 	opts->dumpSections = DUMP_UNSECTIONED;
+	opts->compressionMethod = COMPRESSION_NONE;
 
 	return opts;
 }
@@ -1117,13 +1119,13 @@ PrintTOCSummary(Archive *AHX)
 	RestoreOptions *ropt = AH->public.ropt;
 	TocEntry   *te;
 	teSection	curSection;
-	OutputContext sav;
+	cfp		   *sav;
 	const char *fmtName;
 	char		stamp_str[64];
 
 	sav = SaveOutput(AH);
 	if (ropt->filename)
-		SetOutput(AH, ropt->filename, 0 /* no compression */ );
+		SetOutput(AH, ropt->filename, COMPRESSION_NONE, 0);
 
 	if (strftime(stamp_str, sizeof(stamp_str), PGDUMP_STRFTIME_FMT,
 				 localtime(&AH->createDate)) == 0)
@@ -1132,7 +1134,7 @@ PrintTOCSummary(Archive *AHX)
 	ahprintf(AH, ";\n; Archive created at %s\n", stamp_str);
 	ahprintf(AH, ";     dbname: %s\n;     TOC Entries: %d\n;     Compression: %d\n",
 			 sanitize_line(AH->archdbname, false),
-			 AH->tocCount, AH->compression);
+			 AH->tocCount, AH->compressionLevel);
 
 	switch (AH->format)
 	{
@@ -1486,60 +1488,35 @@ archprintf(Archive *AH, const char *fmt,...)
  *******************************/
 
 static void
-SetOutput(ArchiveHandle *AH, const char *filename, int compression)
+SetOutput(ArchiveHandle *AH, const char *filename,
+		  CompressionMethod compressionMethod, int compressionLevel)
 {
-	int			fn;
+	const char *mode;
+	int			fn = -1;
 
 	if (filename)
 	{
 		if (strcmp(filename, "-") == 0)
 			fn = fileno(stdout);
-		else
-			fn = -1;
 	}
 	else if (AH->FH)
 		fn = fileno(AH->FH);
 	else if (AH->fSpec)
 	{
-		fn = -1;
 		filename = AH->fSpec;
 	}
 	else
 		fn = fileno(stdout);
 
-	/* If compression explicitly requested, use gzopen */
-#ifdef HAVE_LIBZ
-	if (compression != 0)
-	{
-		char		fmode[14];
+	if (AH->mode == archModeAppend)
+		mode = PG_BINARY_A;
+	else
+		mode = PG_BINARY_W;
 
-		/* Don't use PG_BINARY_x since this is zlib */
-		sprintf(fmode, "wb%d", compression);
-		if (fn >= 0)
-			AH->OF = gzdopen(dup(fn), fmode);
-		else
-			AH->OF = gzopen(filename, fmode);
-		AH->gzOut = 1;
-	}
+	if (fn >= 0)
+		AH->OF = cfdopen(dup(fn), mode, compressionMethod, compressionLevel);
 	else
-#endif
-	{							/* Use fopen */
-		if (AH->mode == archModeAppend)
-		{
-			if (fn >= 0)
-				AH->OF = fdopen(dup(fn), PG_BINARY_A);
-			else
-				AH->OF = fopen(filename, PG_BINARY_A);
-		}
-		else
-		{
-			if (fn >= 0)
-				AH->OF = fdopen(dup(fn), PG_BINARY_W);
-			else
-				AH->OF = fopen(filename, PG_BINARY_W);
-		}
-		AH->gzOut = 0;
-	}
+		AH->OF = cfopen(filename, mode, compressionMethod, compressionLevel);
 
 	if (!AH->OF)
 	{
@@ -1550,33 +1527,24 @@ SetOutput(ArchiveHandle *AH, const char *filename, int compression)
 	}
 }
 
-static OutputContext
+static cfp *
 SaveOutput(ArchiveHandle *AH)
 {
-	OutputContext sav;
-
-	sav.OF = AH->OF;
-	sav.gzOut = AH->gzOut;
-
-	return sav;
+	return (cfp *)AH->OF;
 }
 
 static void
-RestoreOutput(ArchiveHandle *AH, OutputContext savedContext)
+RestoreOutput(ArchiveHandle *AH, cfp *savedOutput)
 {
 	int			res;
 
-	errno = 0;					/* in case gzclose() doesn't set it */
-	if (AH->gzOut)
-		res = GZCLOSE(AH->OF);
-	else
-		res = fclose(AH->OF);
+	errno = 0;
+	res = cfclose(AH->OF);
 
 	if (res != 0)
 		fatal("could not close output file: %m");
 
-	AH->gzOut = savedContext.gzOut;
-	AH->OF = savedContext.OF;
+	AH->OF = savedOutput;
 }
 
 
@@ -1700,22 +1668,16 @@ ahwrite(const void *ptr, size_t size, size_t nmemb, ArchiveHandle *AH)
 
 		bytes_written = size * nmemb;
 	}
-	else if (AH->gzOut)
-		bytes_written = GZWRITE(ptr, size, nmemb, AH->OF);
 	else if (AH->CustomOutPtr)
 		bytes_written = AH->CustomOutPtr(AH, ptr, size * nmemb);
-
-	else
-	{
-		/*
-		 * If we're doing a restore, and it's direct to DB, and we're
-		 * connected then send it to the DB.
-		 */
-		if (RestoringToDB(AH))
+	/*
+	 * If we're doing a restore, and it's direct to DB, and we're
+	 * connected then send it to the DB.
+	 */
+	else if (RestoringToDB(AH))
 			bytes_written = ExecuteSqlCommandBuf(&AH->public, (const char *) ptr, size * nmemb);
-		else
-			bytes_written = fwrite(ptr, size, nmemb, AH->OF) * size;
-	}
+	else
+		bytes_written = cfwrite(ptr, size * nmemb, AH->OF);
 
 	if (bytes_written != size * nmemb)
 		WRITE_ERROR_EXIT;
@@ -2200,7 +2162,9 @@ _discoverArchiveFormat(ArchiveHandle *AH)
  */
 static ArchiveHandle *
 _allocAH(const char *FileSpec, const ArchiveFormat fmt,
-		 const int compression, bool dosync, ArchiveMode mode,
+		 const CompressionMethod compressionMethod,
+		 const int compressionLevel,
+		 bool dosync, ArchiveMode mode,
 		 SetupWorkerPtrType setupWorkerPtr)
 {
 	ArchiveHandle *AH;
@@ -2251,14 +2215,14 @@ _allocAH(const char *FileSpec, const ArchiveFormat fmt,
 	AH->toc->prev = AH->toc;
 
 	AH->mode = mode;
-	AH->compression = compression;
+	AH->compressionMethod = compressionMethod;
+	AH->compressionLevel = compressionLevel;
 	AH->dosync = dosync;
 
 	memset(&(AH->sqlparse), 0, sizeof(AH->sqlparse));
 
 	/* Open stdout with no compression for AH output handle */
-	AH->gzOut = 0;
-	AH->OF = stdout;
+	AH->OF = cfdopen(dup(fileno(stdout)), PG_BINARY_A, COMPRESSION_NONE, 0);
 
 	/*
 	 * On Windows, we need to use binary mode to read/write non-text files,
@@ -2266,7 +2230,7 @@ _allocAH(const char *FileSpec, const ArchiveFormat fmt,
 	 * Force stdin/stdout into binary mode if that is what we are using.
 	 */
 #ifdef WIN32
-	if ((fmt != archNull || compression != 0) &&
+	if ((fmt != archNull || compressionMethod != COMPRESSION_NONE) &&
 		(AH->fSpec == NULL || strcmp(AH->fSpec, "") == 0))
 	{
 		if (mode == archModeWrite)
@@ -3716,7 +3680,7 @@ WriteHead(ArchiveHandle *AH)
 	AH->WriteBytePtr(AH, AH->intSize);
 	AH->WriteBytePtr(AH, AH->offSize);
 	AH->WriteBytePtr(AH, AH->format);
-	WriteInt(AH, AH->compression);
+	WriteInt(AH, AH->compressionLevel);
 	crtm = *localtime(&AH->createDate);
 	WriteInt(AH, crtm.tm_sec);
 	WriteInt(AH, crtm.tm_min);
@@ -3790,18 +3754,21 @@ ReadHead(ArchiveHandle *AH)
 	if (AH->version >= K_VERS_1_2)
 	{
 		if (AH->version < K_VERS_1_4)
-			AH->compression = AH->ReadBytePtr(AH);
+			AH->compressionLevel = AH->ReadBytePtr(AH);
 		else
-			AH->compression = ReadInt(AH);
+			AH->compressionLevel = ReadInt(AH);
 	}
 	else
-		AH->compression = Z_DEFAULT_COMPRESSION;
+		AH->compressionLevel = Z_DEFAULT_COMPRESSION;
 
+	if (AH->compressionLevel != INT_MIN)
 #ifndef HAVE_LIBZ
-	if (AH->compression != 0)
 		pg_log_warning("archive is compressed, but this installation does not support compression -- no data will be available");
+#else
+		AH->compressionMethod = COMPRESSION_GZIP;
 #endif
 
+
 	if (AH->version >= K_VERS_1_4)
 	{
 		struct tm	crtm;
diff --git a/src/bin/pg_dump/pg_backup_archiver.h b/src/bin/pg_dump/pg_backup_archiver.h
index 540d4f6a83..837e9d73f5 100644
--- a/src/bin/pg_dump/pg_backup_archiver.h
+++ b/src/bin/pg_dump/pg_backup_archiver.h
@@ -32,30 +32,6 @@
 
 #define LOBBUFSIZE 16384
 
-#ifdef HAVE_LIBZ
-#include <zlib.h>
-#define GZCLOSE(fh) gzclose(fh)
-#define GZWRITE(p, s, n, fh) gzwrite(fh, p, (n) * (s))
-#define GZREAD(p, s, n, fh) gzread(fh, p, (n) * (s))
-#define GZEOF(fh)	gzeof(fh)
-#else
-#define GZCLOSE(fh) fclose(fh)
-#define GZWRITE(p, s, n, fh) (fwrite(p, s, n, fh) * (s))
-#define GZREAD(p, s, n, fh) fread(p, s, n, fh)
-#define GZEOF(fh)	feof(fh)
-/* this is just the redefinition of a libz constant */
-#define Z_DEFAULT_COMPRESSION (-1)
-
-typedef struct _z_stream
-{
-	void	   *next_in;
-	void	   *next_out;
-	size_t		avail_in;
-	size_t		avail_out;
-} z_stream;
-typedef z_stream *z_streamp;
-#endif
-
 /* Data block types */
 #define BLK_DATA 1
 #define BLK_BLOBS 3
@@ -319,8 +295,7 @@ struct _archiveHandle
 
 	char	   *fSpec;			/* Archive File Spec */
 	FILE	   *FH;				/* General purpose file handle */
-	void	   *OF;
-	int			gzOut;			/* Output file */
+	void	   *OF;				/* Output file */
 
 	struct _tocEntry *toc;		/* Header of circular list of TOC entries */
 	int			tocCount;		/* Number of TOC entries */
@@ -331,14 +306,17 @@ struct _archiveHandle
 	DumpId	   *tableDataId;	/* TABLE DATA ids, indexed by table dumpId */
 
 	struct _tocEntry *currToc;	/* Used when dumping data */
-	int			compression;	/*---------
-								 * Compression requested on open().
-								 * Possible values for compression:
-								 * -1	Z_DEFAULT_COMPRESSION
-								 *  0	COMPRESSION_NONE
-								 * 1-9 levels for gzip compression
-								 *---------
-								 */
+	CompressionMethod compressionMethod; /* Requested method for compression */
+	int			compressionLevel; /*---------
+								   * Requested level of compression for method.
+								   * Possible values for compression:
+								   * INT_MIN when no compression method is
+								   * requested.
+								   * -1	Z_DEFAULT_COMPRESSION for gzip
+								   * compression.
+								   * 1-9 levels for gzip compression.
+								   *---------
+								   */
 	bool		dosync;			/* data requested to be synced on sight */
 	ArchiveMode mode;			/* File mode - r or w */
 	void	   *formatData;		/* Header data specific to file format */
diff --git a/src/bin/pg_dump/pg_backup_custom.c b/src/bin/pg_dump/pg_backup_custom.c
index 77d402c323..7f38ea9cd5 100644
--- a/src/bin/pg_dump/pg_backup_custom.c
+++ b/src/bin/pg_dump/pg_backup_custom.c
@@ -298,7 +298,9 @@ _StartData(ArchiveHandle *AH, TocEntry *te)
 	_WriteByte(AH, BLK_DATA);	/* Block type */
 	WriteInt(AH, te->dumpId);	/* For sanity check */
 
-	ctx->cs = AllocateCompressor(AH->compression, _CustomWriteFunc);
+	ctx->cs = AllocateCompressor(AH->compressionMethod,
+								 AH->compressionLevel,
+								 _CustomWriteFunc);
 }
 
 /*
@@ -377,7 +379,9 @@ _StartBlob(ArchiveHandle *AH, TocEntry *te, Oid oid)
 
 	WriteInt(AH, oid);
 
-	ctx->cs = AllocateCompressor(AH->compression, _CustomWriteFunc);
+	ctx->cs = AllocateCompressor(AH->compressionMethod,
+								 AH->compressionLevel,
+								 _CustomWriteFunc);
 }
 
 /*
@@ -566,7 +570,8 @@ _PrintTocData(ArchiveHandle *AH, TocEntry *te)
 static void
 _PrintData(ArchiveHandle *AH)
 {
-	ReadDataFromArchive(AH, AH->compression, _CustomReadFunc);
+	ReadDataFromArchive(AH, AH->compressionMethod, AH->compressionLevel,
+						_CustomReadFunc);
 }
 
 static void
diff --git a/src/bin/pg_dump/pg_backup_directory.c b/src/bin/pg_dump/pg_backup_directory.c
index 7f4e340dea..0e60b447de 100644
--- a/src/bin/pg_dump/pg_backup_directory.c
+++ b/src/bin/pg_dump/pg_backup_directory.c
@@ -327,7 +327,9 @@ _StartData(ArchiveHandle *AH, TocEntry *te)
 
 	setFilePath(AH, fname, tctx->filename);
 
-	ctx->dataFH = cfopen_write(fname, PG_BINARY_W, AH->compression);
+	ctx->dataFH = cfopen_write(fname, PG_BINARY_W,
+							   AH->compressionMethod,
+							   AH->compressionLevel);
 	if (ctx->dataFH == NULL)
 		fatal("could not open output file \"%s\": %m", fname);
 }
@@ -581,7 +583,8 @@ _CloseArchive(ArchiveHandle *AH)
 		ctx->pstate = ParallelBackupStart(AH);
 
 		/* The TOC is always created uncompressed */
-		tocFH = cfopen_write(fname, PG_BINARY_W, 0);
+		tocFH = cfopen_write(fname, PG_BINARY_W,
+							 COMPRESSION_NONE, 0);
 		if (tocFH == NULL)
 			fatal("could not open output file \"%s\": %m", fname);
 		ctx->dataFH = tocFH;
@@ -644,7 +647,7 @@ _StartBlobs(ArchiveHandle *AH, TocEntry *te)
 	setFilePath(AH, fname, "blobs.toc");
 
 	/* The blob TOC file is never compressed */
-	ctx->blobsTocFH = cfopen_write(fname, "ab", 0);
+	ctx->blobsTocFH = cfopen_write(fname, "ab", COMPRESSION_NONE, 0);
 	if (ctx->blobsTocFH == NULL)
 		fatal("could not open output file \"%s\": %m", fname);
 }
@@ -662,7 +665,8 @@ _StartBlob(ArchiveHandle *AH, TocEntry *te, Oid oid)
 
 	snprintf(fname, MAXPGPATH, "%s/blob_%u.dat", ctx->directory, oid);
 
-	ctx->dataFH = cfopen_write(fname, PG_BINARY_W, AH->compression);
+	ctx->dataFH = cfopen_write(fname, PG_BINARY_W,
+							   AH->compressionMethod, AH->compressionLevel);
 
 	if (ctx->dataFH == NULL)
 		fatal("could not open output file \"%s\": %m", fname);
diff --git a/src/bin/pg_dump/pg_backup_tar.c b/src/bin/pg_dump/pg_backup_tar.c
index 5c351acda0..239b053013 100644
--- a/src/bin/pg_dump/pg_backup_tar.c
+++ b/src/bin/pg_dump/pg_backup_tar.c
@@ -35,6 +35,7 @@
 #include <unistd.h>
 
 #include "common/file_utils.h"
+#include "compress_io.h"
 #include "fe_utils/string_utils.h"
 #include "pg_backup_archiver.h"
 #include "pg_backup_tar.h"
@@ -199,7 +200,7 @@ InitArchiveFmt_Tar(ArchiveHandle *AH)
 		 * possible since gzdopen uses buffered IO which totally screws file
 		 * positioning.
 		 */
-		if (AH->compression != 0)
+		if (AH->compressionMethod != COMPRESSION_NONE)
 			fatal("compression is not supported by tar archive format");
 	}
 	else
@@ -249,7 +250,7 @@ _ArchiveEntry(ArchiveHandle *AH, TocEntry *te)
 	if (te->dataDumper != NULL)
 	{
 #ifdef HAVE_LIBZ
-		if (AH->compression == 0)
+		if (AH->compressionMethod == COMPRESSION_NONE)
 			sprintf(fn, "%d.dat", te->dumpId);
 		else
 			sprintf(fn, "%d.dat.gz", te->dumpId);
@@ -346,7 +347,7 @@ tarOpen(ArchiveHandle *AH, const char *filename, char mode)
 
 #ifdef HAVE_LIBZ
 
-		if (AH->compression == 0)
+		if (AH->compressionMethod == COMPRESSION_NONE)
 			tm->nFH = ctx->tarFH;
 		else
 			fatal("compression is not supported by tar archive format");
@@ -407,9 +408,9 @@ tarOpen(ArchiveHandle *AH, const char *filename, char mode)
 
 #ifdef HAVE_LIBZ
 
-		if (AH->compression != 0)
+		if (AH->compressionMethod != COMPRESSION_NONE)
 		{
-			sprintf(fmode, "wb%d", AH->compression);
+			sprintf(fmode, "wb%d", AH->compressionLevel);
 			tm->zFH = gzdopen(dup(fileno(tm->tmpFH)), fmode);
 			if (tm->zFH == NULL)
 				fatal("could not open temporary file");
@@ -437,7 +438,7 @@ tarClose(ArchiveHandle *AH, TAR_MEMBER *th)
 	/*
 	 * Close the GZ file since we dup'd. This will flush the buffers.
 	 */
-	if (AH->compression != 0)
+	if (AH->compressionMethod != COMPRESSION_NONE)
 	{
 		errno = 0;				/* in case gzclose() doesn't set it */
 		if (GZCLOSE(th->zFH) != 0)
@@ -865,7 +866,6 @@ _CloseArchive(ArchiveHandle *AH)
 		memcpy(ropt, AH->public.ropt, sizeof(RestoreOptions));
 		ropt->filename = NULL;
 		ropt->dropSchema = 1;
-		ropt->compression = 0;
 		ropt->superuser = NULL;
 		ropt->suppressDumpWarnings = true;
 
@@ -954,7 +954,7 @@ _StartBlob(ArchiveHandle *AH, TocEntry *te, Oid oid)
 	if (oid == 0)
 		fatal("invalid OID for large object (%u)", oid);
 
-	if (AH->compression != 0)
+	if (AH->compressionMethod != COMPRESSION_NONE)
 		sfx = ".gz";
 	else
 		sfx = "";
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index e69dcf8a48..7477d5112e 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -55,6 +55,7 @@
 #include "catalog/pg_trigger_d.h"
 #include "catalog/pg_type_d.h"
 #include "common/connect.h"
+#include "compress_io.h"
 #include "dumputils.h"
 #include "fe_utils/option_utils.h"
 #include "fe_utils/string_utils.h"
@@ -163,6 +164,9 @@ static void setup_connection(Archive *AH,
 							 const char *dumpencoding, const char *dumpsnapshot,
 							 char *use_role);
 static ArchiveFormat parseArchiveFormat(const char *format, ArchiveMode *mode);
+static bool parse_compression_option(const char *opt,
+									 CompressionMethod *compressionMethod,
+									 int *compressLevel);
 static void expand_schema_name_patterns(Archive *fout,
 										SimpleStringList *patterns,
 										SimpleOidList *oids,
@@ -336,8 +340,9 @@ main(int argc, char **argv)
 	const char *dumpsnapshot = NULL;
 	char	   *use_role = NULL;
 	int			numWorkers = 1;
-	int			compressLevel = -1;
 	int			plainText = 0;
+	int			compressLevel = INT_MIN;
+	CompressionMethod compressionMethod = COMPRESSION_NONE;
 	ArchiveFormat archiveFormat = archUnknown;
 	ArchiveMode archiveMode;
 
@@ -557,9 +562,9 @@ main(int argc, char **argv)
 				dopt.aclsSkip = true;
 				break;
 
-			case 'Z':			/* Compression Level */
-				if (!option_parse_int(optarg, "-Z/--compress", 0, 9,
-									  &compressLevel))
+			case 'Z':			/* Compression */
+				if (!parse_compression_option(optarg, &compressionMethod,
+											  &compressLevel))
 					exit_nicely(1);
 				break;
 
@@ -689,23 +694,18 @@ main(int argc, char **argv)
 	if (archiveFormat == archNull)
 		plainText = 1;
 
-	/* Custom and directory formats are compressed by default, others not */
-	if (compressLevel == -1)
+	/* Custom and directory formats are compressed by default (zlib), others not */
+	if (compressionMethod == COMPRESSION_NONE)
 	{
 #ifdef HAVE_LIBZ
 		if (archiveFormat == archCustom || archiveFormat == archDirectory)
+		{
+			compressionMethod = COMPRESSION_GZIP;
 			compressLevel = Z_DEFAULT_COMPRESSION;
-		else
+		}
 #endif
-			compressLevel = 0;
 	}
 
-#ifndef HAVE_LIBZ
-	if (compressLevel != 0)
-		pg_log_warning("requested compression not available in this installation -- archive will be uncompressed");
-	compressLevel = 0;
-#endif
-
 	/*
 	 * If emitting an archive format, we always want to emit a DATABASE item,
 	 * in case --create is specified at pg_restore time.
@@ -718,8 +718,9 @@ main(int argc, char **argv)
 		fatal("parallel backup only supported by the directory format");
 
 	/* Open the output file */
-	fout = CreateArchive(filename, archiveFormat, compressLevel, dosync,
-						 archiveMode, setupDumpWorker);
+	fout = CreateArchive(filename, archiveFormat,
+						 compressionMethod, compressLevel,
+						 dosync, archiveMode, setupDumpWorker);
 
 	/* Make dump options accessible right away */
 	SetArchiveOptions(fout, &dopt, NULL);
@@ -950,10 +951,8 @@ main(int argc, char **argv)
 	ropt->sequence_data = dopt.sequence_data;
 	ropt->binary_upgrade = dopt.binary_upgrade;
 
-	if (compressLevel == -1)
-		ropt->compression = 0;
-	else
-		ropt->compression = compressLevel;
+	ropt->compressionLevel = compressLevel;
+	ropt->compressionMethod = compressionMethod;
 
 	ropt->suppressDumpWarnings = true;	/* We've already shown them */
 
@@ -1000,7 +999,8 @@ help(const char *progname)
 	printf(_("  -j, --jobs=NUM               use this many parallel jobs to dump\n"));
 	printf(_("  -v, --verbose                verbose mode\n"));
 	printf(_("  -V, --version                output version information, then exit\n"));
-	printf(_("  -Z, --compress=0-9           compression level for compressed formats\n"));
+	printf(_("  -Z, --compress=[gzip,none][:LEVEL] or [LEVEL]\n"
+			 "                               compress output with given method or level\n"));
 	printf(_("  --lock-wait-timeout=TIMEOUT  fail after waiting TIMEOUT for a table lock\n"));
 	printf(_("  --no-sync                    do not wait for changes to be written safely to disk\n"));
 	printf(_("  -?, --help                   show this help, then exit\n"));
@@ -1260,6 +1260,116 @@ get_synchronized_snapshot(Archive *fout)
 	return result;
 }
 
+static bool
+parse_compression_method(const char *method,
+						 CompressionMethod *compressionMethod)
+{
+	bool res = true;
+
+	if (pg_strcasecmp(method, "gzip") == 0)
+		*compressionMethod = COMPRESSION_GZIP;
+	else if (pg_strcasecmp(method, "none") == 0)
+		*compressionMethod = COMPRESSION_NONE;
+	else
+	{
+		pg_log_error("invalid compression method \"%s\" (gzip, none)", method);
+		res = false;
+	}
+
+	return res;
+}
+
+/*
+ * Interprets a compression option of the format 'method[:LEVEL]' of legacy just
+ * '[LEVEL]'. In the later format, gzip is implied. The parsed method and level
+ * are returned in *compressionMethod and *compressionLevel. In case of error,
+ * the function returns false and then the values of *compression{Method,Level}
+ * are not to be trusted.
+ */
+static bool
+parse_compression_option(const char *opt, CompressionMethod *compressionMethod,
+						 int *compressLevel)
+{
+	char	   *method;
+	const char *sep;
+	int			methodlen;
+	bool		supports_compression = true;
+	bool		res = true;
+
+	/* find the separator if exists */
+	sep = strchr(opt, ':');
+
+	/*
+	 * If there is no separator, then it is either a legacy format, or only the
+	 * method has been passed.
+	 */
+	if (!sep)
+	{
+		if (strspn(opt, "-0123456789") == strlen(opt))
+		{
+			res = option_parse_int(opt, "-Z/--compress", 0, 9, compressLevel);
+			*compressionMethod = (*compressLevel > 0) ? COMPRESSION_GZIP :
+														COMPRESSION_NONE;
+		}
+		else
+			res = parse_compression_method(opt, compressionMethod);
+	}
+	else
+	{
+		/* otherwise, it should be method:LEVEL */
+		methodlen = sep - opt + 1;
+		method = pg_malloc0(methodlen);
+		snprintf(method, methodlen, "%.*s", methodlen - 1, opt);
+
+		res = parse_compression_method(method, compressionMethod);
+		if (res)
+		{
+			sep++;
+			if (*sep == '\0')
+			{
+				pg_log_error("no level defined for compression \"%s\"", method);
+				pg_free(method);
+				res = false;
+			}
+			else
+			{
+				res = option_parse_int(sep, "-Z/--compress [LEVEL]", 1, 9,
+									   compressLevel);
+			}
+		}
+	}
+
+	/* if there is an error, there is no need to check further */
+	if (!res)
+		return res;
+
+	/* one can set level when method is gzip */
+	if (*compressionMethod != COMPRESSION_GZIP && *compressLevel != INT_MIN)
+	{
+		pg_log_error("can only specify -Z/--compress [LEVEL] when method is gzip");
+		return false;
+	}
+
+	/* verify that the requested compression is supported */
+#ifndef HAVE_LIBZ
+	if (*compressionMethod == COMPRESSION_GZIP)
+		supports_compression = false;
+#endif
+#ifndef HAVE_LIBLZ4
+	if (*compressionMethod == COMPRESSION_LZ4)
+		supports_compression = false;
+#endif
+
+	if (!supports_compression)
+	{
+		pg_log_warning("requested compression not available in this installation -- archive will be uncompressed");
+		*compressionMethod = COMPRESSION_NONE;
+		*compressLevel = INT_MIN;
+	}
+
+	return true;
+}
+
 static ArchiveFormat
 parseArchiveFormat(const char *format, ArchiveMode *mode)
 {
diff --git a/src/bin/pg_dump/t/001_basic.pl b/src/bin/pg_dump/t/001_basic.pl
index 48faeb4371..840c3ac899 100644
--- a/src/bin/pg_dump/t/001_basic.pl
+++ b/src/bin/pg_dump/t/001_basic.pl
@@ -126,6 +126,16 @@ command_fails_like(
 	qr/\Qpg_dump: error: -Z\/--compress must be in range 0..9\E/,
 	'pg_dump: -Z/--compress must be in range');
 
+command_fails_like(
+	[ 'pg_dump', '--compress', 'garbage' ],
+	qr/\Qpg_dump: error: invalid compression method "garbage" (gzip, none)\E/,
+	'pg_dump: invalid --compress');
+
+command_fails_like(
+	[ 'pg_dump', '--compress', 'none:1' ],
+	qr/\Qpg_dump: error: can only specify -Z\/--compress [LEVEL] when method is gzip\E/,
+	'pg_dump: can only specify -Z/--compress [LEVEL] when method is gzip');
+
 command_fails_like(
 	[ 'pg_dump', '--extra-float-digits', '-16' ],
 	qr/\Qpg_dump: error: --extra-float-digits must be in range\E/,
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index dd065c758f..d30e96d705 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -55,6 +55,81 @@ my %pgdump_runs = (
 			"$tempdir/binary_upgrade.dump",
 		],
 	},
+
+	# Do not use --no-sync to give test coverage for data sync.
+	compression_gzip_custom_format => {
+		test_key => 'compression',
+		dump_cmd => [
+			'pg_dump',
+			'--format=custom', '--compress=gzip:1',
+			"--file=$tempdir/compression_gzip_custom_format.dump",
+			'postgres',
+		],
+		restore_cmd => [
+			'pg_restore',
+			"--file=$tempdir/compression_gzip_custom_format.sql",
+			"$tempdir/compression_gzip_custom_format.dump",
+		],
+	},
+
+	# Do not use --no-sync to give test coverage for data sync.
+	compression_gzip_directory_format => {
+		test_key => 'compression',
+		dump_cmd => [
+			'pg_dump',
+			'--format=directory', '--compress=1',
+			"--file=$tempdir/compression_gzip_directory_format",
+			'postgres',
+		],
+		# Give coverage for manually compressed blob.toc files during restore.
+		compress_cmd => [
+			$ENV{'GZIP_PROGRAM'},
+			'-f',
+			"$tempdir/compression_gzip_directory_format/blobs.toc",
+		],
+		restore_cmd => [
+			'pg_restore',
+			"--file=$tempdir/compression_gzip_directory_format.sql",
+			"$tempdir/compression_gzip_directory_format",
+		],
+	},
+
+	compression_gzip_directory_format_parallel => {
+		test_key => 'compression',
+		dump_cmd => [
+			'pg_dump', '--jobs=2',
+			'--format=directory', '--compress=gzip:6',
+			"--file=$tempdir/compression_gzip_directory_format_parallel",
+			'postgres',
+		],
+		# Give coverage for manually compressed blob.toc files during restore.
+		compress_cmd => [
+			$ENV{'GZIP_PROGRAM'},
+			'-f',
+			"$tempdir/compression_gzip_directory_format_parallel/blobs.toc",
+		],
+		restore_cmd => [
+			'pg_restore',
+			'--jobs=3',
+			"--file=$tempdir/compression_gzip_directory_format_parallel.sql",
+			"$tempdir/compression_gzip_directory_format_parallel",
+		],
+	},
+
+	# Check that the output is valid gzip
+	compression_gzip_plain_format => {
+		test_key => 'compression',
+		dump_cmd => [
+			'pg_dump', '--format=plain', '-Z1',
+			"--file=$tempdir/compression_gzip_plain_format.sql.gz",
+			'postgres',
+		],
+		compress_cmd => [
+			$ENV{'GZIP_PROGRAM'},
+			'-k', '-d',
+			"$tempdir/compression_gzip_plain_format.sql.gz",
+		],
+	},
 	clean => {
 		dump_cmd => [
 			'pg_dump',
@@ -424,6 +499,7 @@ my %full_runs = (
 	binary_upgrade           => 1,
 	clean                    => 1,
 	clean_if_exists          => 1,
+	compression              => 1,
 	createdb                 => 1,
 	defaults                 => 1,
 	exclude_dump_test_schema => 1,
@@ -2972,6 +3048,7 @@ my %tests = (
 			binary_upgrade          => 1,
 			clean                   => 1,
 			clean_if_exists         => 1,
+			compression             => 1,
 			createdb                => 1,
 			defaults                => 1,
 			exclude_test_table      => 1,
@@ -3045,6 +3122,7 @@ my %tests = (
 			binary_upgrade           => 1,
 			clean                    => 1,
 			clean_if_exists          => 1,
+			compression              => 1,
 			createdb                 => 1,
 			defaults                 => 1,
 			exclude_dump_test_schema => 1,
@@ -3821,9 +3899,23 @@ foreach my $run (sort keys %pgdump_runs)
 	my $test_key = $run;
 	my $run_db   = 'postgres';
 
+	my $supports_compression = check_pg_config("#define HAVE_LIBZ 1");
+	my $compress_program = $ENV{GZIP_PROGRAM};
+
 	$node->command_ok(\@{ $pgdump_runs{$run}->{dump_cmd} },
 		"$run: pg_dump runs");
 
+	if (defined($pgdump_runs{$run}->{compress_cmd}))
+	{
+		# Skip compression_cmd tests when compression is not supported,
+		# as the result is uncompressed or the utility program does not
+		# exist
+		next if !$supports_compression || !defined($compress_program)
+				|| $compress_program eq '';
+		command_ok( \@{ $pgdump_runs{$run}->{compress_cmd} },
+			"$run: compression commands");
+	}
+
 	if ($pgdump_runs{$run}->{restore_cmd})
 	{
 		$node->command_ok(\@{ $pgdump_runs{$run}->{restore_cmd} },
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index c6b302c7b2..ac8f20f798 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -411,7 +411,7 @@ CompiledExprState
 CompositeIOData
 CompositeTypeStmt
 CompoundAffixFlag
-CompressionAlgorithm
+CompressionMethod
 CompressorState
 ComputeXidHorizonsResult
 ConditionVariable
-- 
2.32.0

v1-0002-Add-LZ4-compression-in-pg_-dump-restore.patchtext/x-patch; name=v1-0002-Add-LZ4-compression-in-pg_-dump-restore.patchDownload
From f9576c60b6f14defd05c72d753435563553ac908 Mon Sep 17 00:00:00 2001
From: Georgios Kokolatos <gkokolatos@pm.me>
Date: Wed, 23 Feb 2022 14:40:33 +0000
Subject: [PATCH v1 2/2] Add LZ4 compression in pg_{dump|restore}

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

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

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

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

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

#2Michael Paquier
michael@paquier.xyz
In reply to: Georgios (#1)
Re: Add LZ4 compression in pg_dump

On Fri, Feb 25, 2022 at 12:05:31PM +0000, Georgios wrote:

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

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

+   # Do not use --no-sync to give test coverage for data sync.
+   compression_gzip_directory_format => {
+       test_key => 'compression',

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

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

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

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

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.

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

#3Justin Pryzby
pryzby@telsasoft.com
In reply to: Georgios (#1)
Re: Add LZ4 compression in pg_dump

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 ?

Show quoted text

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

#4Greg Stark
stark@mit.edu
In reply to: Justin Pryzby (#3)
Re: Add LZ4 compression in pg_dump

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.

#5Justin Pryzby
pryzby@telsasoft.com
In reply to: Greg Stark (#4)
Re: Add LZ4 compression in pg_dump

On Fri, Mar 25, 2022 at 01:20:47AM -0400, Greg Stark wrote:

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.

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

#6Rachel Heaton
rachelmheaton@gmail.com
In reply to: Justin Pryzby (#5)
4 attachment(s)
Re: Add LZ4 compression in pg_dump

On Fri, Mar 25, 2022 at 6:22 AM Justin Pryzby <pryzby@telsasoft.com> wrote:

On Fri, Mar 25, 2022 at 01:20:47AM -0400, Greg Stark wrote:

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.

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.

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

Attachments:

v2-0001-Extend-compression-coverage-for-pg_dump-pg_restor.patchapplication/octet-stream; name=v2-0001-Extend-compression-coverage-for-pg_dump-pg_restor.patchDownload
From 67480e216a201dfde9aa2dc48b39b37e39bf46fb Mon Sep 17 00:00:00 2001
From: Georgios Kokolatos <gkokolatos@pm.me>
Date: Fri, 25 Mar 2022 14:00:28 +0000
Subject: [PATCH v2 1/4] Extend compression coverage for pg_dump, pg_restore

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

diff --git a/src/bin/pg_dump/t/001_basic.pl b/src/bin/pg_dump/t/001_basic.pl
index 48faeb4371..8fdacea6f5 100644
--- a/src/bin/pg_dump/t/001_basic.pl
+++ b/src/bin/pg_dump/t/001_basic.pl
@@ -126,6 +126,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 3e55ff26f8..cbf4524503 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -55,6 +55,81 @@ my %pgdump_runs = (
 			"$tempdir/binary_upgrade.dump",
 		],
 	},
+
+	# Do not use --no-sync to give test coverage for data sync.
+	compression_gzip_custom_format => {
+		test_key => 'compression',
+		dump_cmd => [
+			'pg_dump',
+			'--format=custom', '--compress=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'},
+			'-k', '-d',
+			"$tempdir/compression_gzip_plain_format.sql.gz",
+		],
+	},
 	clean => {
 		dump_cmd => [
 			'pg_dump',
@@ -425,6 +500,7 @@ my %full_runs = (
 	binary_upgrade           => 1,
 	clean                    => 1,
 	clean_if_exists          => 1,
+	compression              => 1,
 	createdb                 => 1,
 	defaults                 => 1,
 	exclude_dump_test_schema => 1,
@@ -3007,6 +3083,7 @@ my %tests = (
 			binary_upgrade          => 1,
 			clean                   => 1,
 			clean_if_exists         => 1,
+			compression             => 1,
 			createdb                => 1,
 			defaults                => 1,
 			exclude_test_table      => 1,
@@ -3080,6 +3157,7 @@ my %tests = (
 			binary_upgrade           => 1,
 			clean                    => 1,
 			clean_if_exists          => 1,
+			compression              => 1,
 			createdb                 => 1,
 			defaults                 => 1,
 			exclude_dump_test_schema => 1,
@@ -3856,9 +3934,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.33.0

v2-0002-Remove-unsupported-bitrot-compression-from-pg_dum.patchapplication/octet-stream; name=v2-0002-Remove-unsupported-bitrot-compression-from-pg_dum.patchDownload
From fdd7d2087ade23bc5e729b478fb524f4d63776f9 Mon Sep 17 00:00:00 2001
From: Georgios Kokolatos <gkokolatos@pm.me>
Date: Fri, 25 Mar 2022 14:56:50 +0000
Subject: [PATCH v2 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 5c351acda0..a31376bfa8 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.33.0

v2-0003-Prepare-pg_dump-for-additional-compression-method.patchapplication/octet-stream; name=v2-0003-Prepare-pg_dump-for-additional-compression-method.patchDownload
From f7d10ce401a9b7bea2ecff6e4e739b733e74f3ae Mon Sep 17 00:00:00 2001
From: Georgios Kokolatos <gkokolatos@pm.me>
Date: Fri, 25 Mar 2022 15:12:41 +0000
Subject: [PATCH v2 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/Makefile              |   2 +
 src/bin/pg_dump/compress_io.c         | 425 ++++++++++++++++----------
 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 +-
 14 files changed, 616 insertions(+), 360 deletions(-)

diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml
index 2f0042fd96..992b7312df 100644
--- a/doc/src/sgml/ref/pg_dump.sgml
+++ b/doc/src/sgml/ref/pg_dump.sgml
@@ -644,17 +644,31 @@ PostgreSQL documentation
      </varlistentry>
 
      <varlistentry>
-      <term><option>-Z <replaceable class="parameter">0..9</replaceable></option></term>
-      <term><option>--compress=<replaceable class="parameter">0..9</replaceable></option></term>
+      <term><option>-Z <replaceable class="parameter">level</replaceable></option></term>
+      <term><option>-Z <replaceable class="parameter">method</replaceable></option>[:<replaceable>level</replaceable>]</term>
+      <term><option>--compress=<replaceable class="parameter">level</replaceable></option></term>
+      <term><option>--compress=<replaceable class="parameter">method</replaceable></option>[:<replaceable>level</replaceable>]</term>
       <listitem>
        <para>
-        Specify the compression level to use.  Zero means no compression.
+        Specify the compression method and/or the compression level to use.
+        The compression method can be set to <literal>gzip</literal> or
+        <literal>none</literal> for no compression. A compression level can
+        be optionally specified, by appending the level number after a colon
+        (<literal>:</literal>). If no level is specified, the default compression
+        level will be used for the specified method. If only a level is
+        specified without mentioning a method, <literal>gzip</literal> compression
+        will be used.
+       </para>
+
+       <para>
         For the custom and directory archive formats, this specifies compression of
-        individual table-data segments, and the default is to compress
-        at a moderate level.
-        For plain text output, setting a nonzero compression level causes
-        the entire output file to be compressed, as though it had been
-        fed through <application>gzip</application>; but the default is not to compress.
+        individual table-data segments, and the default is to compress using
+        <literal>gzip</literal> at a moderate level. For plain text output,
+        setting a nonzero compression level causes the entire output file to be compressed,
+        as though it had been fed through <application>gzip</application>; but the default
+        is not to compress.
+       </para>
+       <para>
         The tar archive format currently does not support compression at all.
        </para>
       </listitem>
diff --git a/src/bin/pg_dump/Makefile b/src/bin/pg_dump/Makefile
index 84306d94ee..56e041c9b3 100644
--- a/src/bin/pg_dump/Makefile
+++ b/src/bin/pg_dump/Makefile
@@ -16,6 +16,8 @@ subdir = src/bin/pg_dump
 top_builddir = ../../..
 include $(top_builddir)/src/Makefile.global
 
+export GZIP_PROGRAM=$(GZIP)
+
 override CPPFLAGS := -I$(libpq_srcdir) $(CPPFLAGS)
 LDFLAGS_INTERNAL += -L$(top_builddir)/src/fe_utils -lpgfeutils $(libpq_pgport)
 
diff --git a/src/bin/pg_dump/compress_io.c b/src/bin/pg_dump/compress_io.c
index 9077fdb74d..bab723c84f 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
@@ -479,26 +465,31 @@ 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.
+ *
+ * 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.
  */
 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 +500,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 +597,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 +659,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 +692,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 a31376bfa8..bd99b3826a 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 e69dcf8a48..cd91efdd7a 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 8fdacea6f5..f6bccfe55b 100644
--- a/src/bin/pg_dump/t/001_basic.pl
+++ b/src/bin/pg_dump/t/001_basic.pl
@@ -121,6 +121,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/,
@@ -129,14 +139,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 cbf4524503..49f04c9477 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -77,7 +77,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',
 		],
@@ -98,7 +98,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',
 		],
@@ -130,6 +130,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',
@@ -229,7 +248,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 => [
@@ -3950,6 +3969,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 d9b83f744f..7cdfe09e46 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -411,7 +411,7 @@ CompiledExprState
 CompositeIOData
 CompositeTypeStmt
 CompoundAffixFlag
-CompressionAlgorithm
+CompressionMethod
 CompressorState
 ComputeXidHorizonsResult
 ConditionVariable
-- 
2.33.0

v2-0004-Add-LZ4-compression-in-pg_-dump-restore.patchapplication/octet-stream; name=v2-0004-Add-LZ4-compression-in-pg_-dump-restore.patchDownload
From 5478e07e1c99db8403dccacab7e2f7768c498eac Mon Sep 17 00:00:00 2001
From: Georgios Kokolatos <gkokolatos@pm.me>
Date: Fri, 25 Mar 2022 16:34:50 +0000
Subject: [PATCH v2 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        | 731 ++++++++++++++++++++++++---
 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     | 206 ++++++--
 9 files changed, 905 insertions(+), 147 deletions(-)

diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml
index 992b7312df..310cb094da 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 will be used.
        </para>
 
        <para>
@@ -665,8 +666,8 @@ PostgreSQL documentation
         individual table-data segments, and the default is to compress using
         <literal>gzip</literal> at a moderate level. For plain text output,
         setting a nonzero compression level causes the entire output file to be compressed,
-        as though it had been fed through <application>gzip</application>; but the default
-        is not to compress.
+        as though it had been fed through <application>gzip</application> or
+        <application>lz4</application>; but the default is not to compress.
        </para>
        <para>
         The tar archive format currently does not support compression at all.
diff --git a/src/bin/pg_dump/Makefile b/src/bin/pg_dump/Makefile
index 56e041c9b3..625c00b123 100644
--- a/src/bin/pg_dump/Makefile
+++ b/src/bin/pg_dump/Makefile
@@ -17,6 +17,7 @@ top_builddir = ../../..
 include $(top_builddir)/src/Makefile.global
 
 export GZIP_PROGRAM=$(GZIP)
+export LZ4
 
 override CPPFLAGS := -I$(libpq_srcdir) $(CPPFLAGS)
 LDFLAGS_INTERNAL += -L$(top_builddir)/src/fe_utils -lpgfeutils $(libpq_pgport)
diff --git a/src/bin/pg_dump/compress_io.c b/src/bin/pg_dump/compress_io.c
index bab723c84f..8c372ff196 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 necessary 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;
 }
 
@@ -467,7 +971,8 @@ cfopen_read(const char *path, const char *mode)
  *
  * 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.
+ * '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.
@@ -479,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;
 }
 
@@ -512,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;
 
@@ -563,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:
@@ -583,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);
 }
@@ -620,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;
@@ -644,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:
@@ -679,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:
@@ -698,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:
@@ -739,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:
@@ -767,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:
@@ -780,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)
 {
@@ -810,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 cd91efdd7a..f15a565ac7 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 f6bccfe55b..e2c3634583 100644
--- a/src/bin/pg_dump/t/001_basic.pl
+++ b/src/bin/pg_dump/t/001_basic.pl
@@ -123,12 +123,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 49f04c9477..b18f45358e 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -82,11 +82,14 @@ my %pgdump_runs = (
 			'postgres',
 		],
 		# Give coverage for manually compressed blob.toc files during restore.
-		compress_cmd => [
-			$ENV{'GZIP_PROGRAM'},
-			'-f',
-			"$tempdir/compression_gzip_directory_format/blobs.toc",
-		],
+		compression => {
+			method => 'gzip',
+			cmd => [
+				$ENV{'GZIP_PROGRAM'},
+				'-f',
+				"$tempdir/compression_gzip_directory_format/blobs.toc",
+			],
+		},
 		restore_cmd => [
 			'pg_restore',
 			"--file=$tempdir/compression_gzip_directory_format.sql",
@@ -102,12 +105,15 @@ my %pgdump_runs = (
 			"--file=$tempdir/compression_gzip_directory_format_parallel",
 			'postgres',
 		],
-		# Give coverage for manually compressed blob.toc files during restore.
-		compress_cmd => [
-			$ENV{'GZIP_PROGRAM'},
-			'-f',
-			"$tempdir/compression_gzip_directory_format_parallel/blobs.toc",
-		],
+		# Give coverage for manually compressed toc.dat files during restore.
+		compression => {
+			method => 'gzip',
+			cmd => [
+				$ENV{'GZIP_PROGRAM'},
+				'-f',
+				"$tempdir/compression_gzip_directory_format_parallel/toc.dat",
+			],
+		},
 		restore_cmd => [
 			'pg_restore',
 			'--jobs=3',
@@ -124,12 +130,96 @@ my %pgdump_runs = (
 			"--file=$tempdir/compression_gzip_plain_format.sql.gz",
 			'postgres',
 		],
-		compress_cmd => [
-			$ENV{'GZIP_PROGRAM'},
-			'-k', '-d',
-			"$tempdir/compression_gzip_plain_format.sql.gz",
+		compression => {
+			method => 'gzip',
+			cmd => [
+				$ENV{'GZIP_PROGRAM'},
+				'-k', '-d',
+				"$tempdir/compression_gzip_plain_format.sql.gz",
+			],
+		},
+	},
+	compression_lz4_custom_format => {
+		test_key => 'compression',
+		dump_cmd => [
+			'pg_dump', '--no-sync',
+			'--format=custom', '--compress=lz4:9',
+			"--file=$tempdir/compression_lz4_custom_format.dump",
+			'postgres',
+		],
+		restore_cmd => [
+			'pg_restore',
+			"--file=$tempdir/compression_lz4_custom_format.sql",
+			"$tempdir/compression_lz4_custom_format.dump",
+		],
+	},
+	compression_lz4_directory_format => {
+		test_key => 'compression',
+		dump_cmd => [
+			'pg_dump', '--no-sync',
+			'--format=directory', '--compress=lz4',
+			"--file=$tempdir/compression_lz4_directory_format",
+			'postgres',
+		],
+		# Give coverage for manually compressed toc.dat files during restore.
+		compression => {
+			method => 'lz4',
+			cmd => [
+				$ENV{'LZ4'},
+				'-z', '-f', '--rm',
+				"$tempdir/compression_lz4_directory_format/toc.dat",
+				"$tempdir/compression_lz4_directory_format/toc.dat.lz4",
+			],
+		},
+		restore_cmd => [
+			'pg_restore',
+			"--file=$tempdir/compression_lz4_directory_format.sql",
+			"$tempdir/compression_lz4_directory_format",
 		],
 	},
+	compression_lz4_directory_format_parallel => {
+		test_key => 'compression',
+		dump_cmd => [
+			'pg_dump', '--no-sync', '--jobs=2',
+			'--format=directory', '--compress=lz4:9',
+			"--file=$tempdir/compression_lz4_directory_format_parallel",
+			'postgres',
+		],
+		# Give coverage for manually compressed blob.toc files during restore.
+		compression => {
+			method => 'lz4',
+			cmd => [
+				$ENV{'LZ4'},
+				'-z', '-f', '--rm',
+				"$tempdir/compression_lz4_directory_format_parallel/blobs.toc",
+				"$tempdir/compression_lz4_directory_format_parallel/blobs.toc.lz4",
+			],
+		},
+		restore_cmd => [
+			'pg_restore', '--jobs=2',
+			"--file=$tempdir/compression_lz4_directory_format_parallel.sql",
+			"$tempdir/compression_lz4_directory_format_parallel",
+		],
+	},
+	# Check that the output is valid lz4
+	compression_lz4_plain_format => {
+		test_key => 'compression',
+		dump_cmd => [
+			'pg_dump',
+			'--no-sync',
+			'--format=plain', '--compress=lz4:1',
+			"--file=$tempdir/compression_lz4_plain_format.sql.lz4",
+			'postgres',
+		],
+		compression => {
+			method => 'lz4',
+			cmd => [
+				$ENV{LZ4}, '-d', '-f',
+				"$tempdir/compression_lz4_plain_format.sql.lz4",
+				"$tempdir/compression_lz4_plain_format.sql",
+			],
+		}
+	},
 	compression_none_dir_format => {
 		test_key => 'compression',
 		dump_cmd => [
@@ -138,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',
@@ -149,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',
@@ -3953,45 +4066,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.33.0

#7Noname
gkokolatos@pm.me
In reply to: Rachel Heaton (#6)
Re: Add LZ4 compression in pg_dump

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

On Saturday, March 26th, 2022 at 12:13 AM, Rachel Heaton <rachelmheaton@gmail.com> wrote:

On Fri, Mar 25, 2022 at 6:22 AM Justin Pryzby pryzby@telsasoft.com wrote:

On Fri, Mar 25, 2022 at 01:20:47AM -0400, Greg Stark wrote:

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.

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.

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.

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.

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

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.

Show quoted text

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

#8Michael Paquier
michael@paquier.xyz
In reply to: Noname (#7)
Re: Add LZ4 compression in pg_dump

On Fri, Mar 25, 2022 at 11:43:17PM +0000, gkokolatos@pm.me wrote:

On Saturday, March 26th, 2022 at 12:13 AM, Rachel Heaton <rachelmheaton@gmail.com> wrote:

Here is an updated patchset from Georgios, with minor assistance from myself.
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.

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

#9Justin Pryzby
pryzby@telsasoft.com
In reply to: Michael Paquier (#8)
Re: Add LZ4 compression in pg_dump

On Sat, Mar 26, 2022 at 02:57:50PM +0900, Michael Paquier wrote:

I have a question about 0002, actually. What has led you to the
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.

#10Justin Pryzby
pryzby@telsasoft.com
In reply to: Georgios (#1)
Re: Add LZ4 compression in pg_dump

LZ4F_HEADER_SIZE_MAX isn't defined in old LZ4.

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

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

0003: typo: of legacy => or legacy

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

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

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

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

You renamed this:

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

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

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

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

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

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

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

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

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

freebsd/cfbot is failing.

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

--
Justin

#11Justin Pryzby
pryzby@telsasoft.com
In reply to: Justin Pryzby (#10)
Re: Add LZ4 compression in pg_dump

On Sat, Mar 26, 2022 at 11:21:56AM -0500, Justin Pryzby wrote:

You're passing both the compression method *and* level. I think there should
be a structure which includes both. In the future, that can also handle
additional options.

I'm not sure if there's anything worth saving, but I did that last year with
0003-Support-multiple-compression-algs-levels-opts.patch
I sent a rebased copy off-list.
/messages/by-id/20210104025321.GA9712@telsasoft.com

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

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

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

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

#12Daniel Gustafsson
daniel@yesql.se
In reply to: Justin Pryzby (#10)
Re: Add LZ4 compression in pg_dump

On 26 Mar 2022, at 17:21, Justin Pryzby <pryzby@telsasoft.com> wrote:

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

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

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

#13Justin Pryzby
pryzby@telsasoft.com
In reply to: Daniel Gustafsson (#12)
Re: Add LZ4 compression in pg_dump

On Sun, Mar 27, 2022 at 12:37:27AM +0100, Daniel Gustafsson wrote:

On 26 Mar 2022, at 17:21, Justin Pryzby <pryzby@telsasoft.com> wrote:

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

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

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

@@ -781,6 +807,11 @@ main(int argc, char **argv)
                        compress.alg = COMPR_ALG_LIBZ;
                        compress.level = Z_DEFAULT_COMPRESSION;
 #endif
+
+#ifdef USE_ZSTD
+                       compress.alg = COMPR_ALG_ZSTD; // Set default for testing purposes
+                       compress.level = ZSTD_CLEVEL_DEFAULT;
+#endif
#14Robert Haas
robertmhaas@gmail.com
In reply to: Justin Pryzby (#10)
Re: Add LZ4 compression in pg_dump

On Sat, Mar 26, 2022 at 12:22 PM Justin Pryzby <pryzby@telsasoft.com> wrote:

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

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

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

#15Justin Pryzby
pryzby@telsasoft.com
In reply to: Robert Haas (#14)
Re: Add LZ4 compression in pg_dump

On Sun, Mar 27, 2022 at 10:13:00AM -0400, Robert Haas wrote:

On Sat, Mar 26, 2022 at 12:22 PM Justin Pryzby <pryzby@telsasoft.com> wrote:

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

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

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

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

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

--
Justin

#16Daniel Gustafsson
daniel@yesql.se
In reply to: Justin Pryzby (#13)
Re: Add LZ4 compression in pg_dump

On 27 Mar 2022, at 00:51, Justin Pryzby <pryzby@telsasoft.com> wrote:

On Sun, Mar 27, 2022 at 12:37:27AM +0100, Daniel Gustafsson wrote:

On 26 Mar 2022, at 17:21, Justin Pryzby <pryzby@telsasoft.com> wrote:

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

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

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

Right, I clearly misunderstood, thanks for the clarification.

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

#17Robert Haas
robertmhaas@gmail.com
In reply to: Justin Pryzby (#15)
Re: Add LZ4 compression in pg_dump

On Sun, Mar 27, 2022 at 12:06 PM Justin Pryzby <pryzby@telsasoft.com> wrote:

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

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

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

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

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

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

#18Michael Paquier
michael@paquier.xyz
In reply to: Robert Haas (#17)
Re: Add LZ4 compression in pg_dump

On Mon, Mar 28, 2022 at 08:36:15AM -0400, Robert Haas wrote:

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

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

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

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

#19Michael Paquier
michael@paquier.xyz
In reply to: Justin Pryzby (#9)
Re: Add LZ4 compression in pg_dump

On Sat, Mar 26, 2022 at 01:14:41AM -0500, Justin Pryzby wrote:

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.

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
#20Noname
gkokolatos@pm.me
In reply to: Michael Paquier (#19)
4 attachment(s)
Re: Add LZ4 compression in pg_dump

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

On Tuesday, March 29th, 2022 at 9:27 AM, Michael Paquier <michael@paquier.xyz> wrote:

On Sat, Mar 26, 2022 at 01:14:41AM -0500, Justin Pryzby wrote:

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.

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.

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

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

Excellent, thank you.

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

Agreed. It was not properly moved forward. Fixed.

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

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

Attachments:

v3-0004-Add-LZ4-compression-in-pg_-dump-restore.patchtext/x-patch; name=v3-0004-Add-LZ4-compression-in-pg_-dump-restore.patchDownload
From ef373439ac3c5ec7663ff191b28f744cdc529e86 Mon Sep 17 00:00:00 2001
From: Georgios Kokolatos <gkokolatos@pm.me>
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

v3-0003-Prepare-pg_dump-for-additional-compression-method.patchtext/x-patch; name=v3-0003-Prepare-pg_dump-for-additional-compression-method.patchDownload
From d70f2bf62900f0e3feae97fb9ec4043c1aca35e9 Mon Sep 17 00:00:00 2001
From: Georgios Kokolatos <gkokolatos@pm.me>
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

v3-0001-Extend-compression-coverage-for-pg_dump-pg_restor.patchtext/x-patch; name=v3-0001-Extend-compression-coverage-for-pg_dump-pg_restor.patchDownload
From 65232a1ed8fdfb57775bef856a907965dfcda14d Mon Sep 17 00:00:00 2001
From: Georgios Kokolatos <gkokolatos@pm.me>
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

v3-0002-Remove-unsupported-bitrot-compression-from-pg_dum.patchtext/x-patch; name=v3-0002-Remove-unsupported-bitrot-compression-from-pg_dum.patchDownload
From 2842f3f09d209319abf02ad2a60efd71399e75b5 Mon Sep 17 00:00:00 2001
From: Georgios Kokolatos <gkokolatos@pm.me>
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

#21Robert Haas
robertmhaas@gmail.com
In reply to: Michael Paquier (#18)
Re: Add LZ4 compression in pg_dump

On Tue, Mar 29, 2022 at 1:03 AM Michael Paquier <michael@paquier.xyz> wrote:

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

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

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

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

#22Michael Paquier
michael@paquier.xyz
In reply to: Noname (#20)
Re: Add LZ4 compression in pg_dump

On Tue, Mar 29, 2022 at 09:46:27AM +0000, gkokolatos@pm.me wrote:

On Tuesday, March 29th, 2022 at 9:27 AM, Michael Paquier <michael@paquier.xyz> wrote:

On Sat, Mar 26, 2022 at 01:14:41AM -0500, Justin Pryzby wrote:
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.

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

Makes sense to me.

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

Excellent, thank you.

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?

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

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.

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

+ my $supports_compression = check_pg_config("#define HAVE_LIBZ 1");

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

+ my $compress_program = $ENV{GZIP_PROGRAM};

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

#23Michael Paquier
michael@paquier.xyz
In reply to: Robert Haas (#21)
Re: Add LZ4 compression in pg_dump

On Tue, Mar 29, 2022 at 09:14:03AM -0400, Robert Haas wrote:

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

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

#24Noname
gkokolatos@pm.me
In reply to: Michael Paquier (#22)
4 attachment(s)
Re: Add LZ4 compression in pg_dump

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

On Wednesday, March 30th, 2022 at 7:54 AM, Michael Paquier <michael@paquier.xyz> wrote:

On Tue, Mar 29, 2022 at 09:46:27AM +0000, gkokolatos@pm.me wrote:

On Tuesday, March 29th, 2022 at 9:27 AM, Michael Paquier michael@paquier.xyz wrote:

On Sat, Mar 26, 2022 at 01:14:41AM -0500, Justin Pryzby wrote:
+ 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.

Excellent, thank you.

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

Thank you.

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?

Agreed. Fixed.

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

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.

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

Agreed. Comment added.

+ my $supports_compression = check_pg_config("#define HAVE_LIBZ 1");

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.

+ my $compress_program = $ENV{GZIP_PROGRAM};

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

Maybe not. We don't want to the tests to fail if the utility is not
installed. That becomes even more evident as more methods are added.
However I realized that the presence of the environmental variable does
not guarrantee that the utility is actually installed. In the attached,
the existance of the utility is based on the return value of system_log().

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.

Agreed. Comment added.

Cheers,
//Georgios

Attachments:

v4-0002-Remove-unsupported-bitrot-compression-from-pg_dum.patchtext/x-patch; name=v4-0002-Remove-unsupported-bitrot-compression-from-pg_dum.patchDownload
From 0768f208a9f8462d4b1ad8210eb1a977aad15b41 Mon Sep 17 00:00:00 2001
From: Georgios Kokolatos <gkokolatos@pm.me>
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(-)

diff --git a/src/bin/pg_dump/pg_backup_tar.c b/src/bin/pg_dump/pg_backup_tar.c
index ccfbe346be..2491a091b9 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;
@@ -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)
 		fatal("invalid OID for large object (%u)", oid);
 
 	if (AH->compression != 0)
-		sfx = ".gz";
-	else
-		sfx = "";
+		fatal("compression is not supported by tar archive format");
 
-	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

v4-0001-Extend-compression-coverage-for-pg_dump-pg_restor.patchtext/x-patch; name=v4-0001-Extend-compression-coverage-for-pg_dump-pg_restor.patchDownload
From 4e33597455c8fa420bca5bd87013922478996017 Mon Sep 17 00:00:00 2001
From: Georgios Kokolatos <gkokolatos@pm.me>
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/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/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");
 
+	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 || !$compress_program_exists;
+
+		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

v4-0003-Prepare-pg_dump-for-additional-compression-method.patchtext/x-patch; name=v4-0003-Prepare-pg_dump-for-additional-compression-method.patchDownload
From 7d2edf176b1ba4c70253dbeec0e878b9ed515349 Mon Sep 17 00:00:00 2001
From: Georgios Kokolatos <gkokolatos@pm.me>
Date: Wed, 30 Mar 2022 15:03:53 +0000
Subject: [PATCH v4 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      |  45 ++-
 src/tools/pgindent/typedefs.list      |   2 +-
 13 files changed, 603 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..7645d3285a 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_INVALID,
+	COMPRESSION_NONE,
+	COMPRESSION_GZIP
+} CompressionMethod;
+
 /* Parameters needed by ConnectDatabase; same for dump and restore */
 typedef struct _connParams
 {
@@ -143,7 +150,8 @@ typedef struct _restoreOptions
 
 	int			noDataForFailedTables;
 	int			exit_on_error;
-	int			compression;
+	CompressionMethod compressionMethod;
+	int			compressionLevel;
 	int			suppressDumpWarnings;	/* Suppress output of WARNING entries
 										 * to stderr */
 	bool		single_txn;
@@ -303,7 +311,9 @@ extern Archive *OpenArchive(const char *FileSpec, const ArchiveFormat fmt);
 
 /* 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 2491a091b9..b25b641caa 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;
 
@@ -889,7 +889,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)
 		fatal("compression is not supported by tar archive format");
 
 	sprintf(fname, "blob_%u.dat", oid);
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 65e6c01fed..d7a52ac1a8 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,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)
 
 		command_ok( \@{ $pgdump_runs{$run}->{compress_cmd} },
 			"$run: compression commands");
+
+		if (defined($pgdump_runs{$run}->{glob_match}))
+		{
+			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 72fafb795b..5ac8c156a9 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -412,7 +412,7 @@ CompiledExprState
 CompositeIOData
 CompositeTypeStmt
 CompoundAffixFlag
-CompressionAlgorithm
+CompressionMethod
 CompressorState
 ComputeXidHorizonsResult
 ConditionVariable
-- 
2.32.0

v4-0004-Add-LZ4-compression-in-pg_-dump-restore.patchtext/x-patch; name=v4-0004-Add-LZ4-compression-in-pg_-dump-restore.patchDownload
From d3104c7cc1fc86fa36f7b67f645c5b72898de48c Mon Sep 17 00:00:00 2001
From: Georgios Kokolatos <gkokolatos@pm.me>
Date: Wed, 30 Mar 2022 15:11:01 +0000
Subject: [PATCH v4 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          |   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(-)

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 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;
 
 /* Parameters needed by ConnectDatabase; same for dump and restore */
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 d7a52ac1a8..0077288388 100644
--- a/src/bin/pg_dump/t/001_basic.pl
+++ b/src/bin/pg_dump/t/001_basic.pl
@@ -122,12 +122,12 @@ command_fails_like(
 
 command_fails_like(
 	[ '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 3a3dad2cc7..36bd700576 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -88,11 +88,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",
@@ -108,12 +111,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',
@@ -131,12 +137,96 @@ my %pgdump_runs = (
 			'postgres',
 		],
 		# Decompress the generated file to run through the tests
-		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",
+		],
+	},
+	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 => [
@@ -145,10 +235,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',
@@ -156,6 +249,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',
@@ -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");
+		}
 
-		if (defined($pgdump_runs{$run}->{glob_match}))
+		if (defined($compression->{glob_match}))
 		{
-			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 $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,
@@ -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

#25Michael Paquier
michael@paquier.xyz
In reply to: Noname (#24)
Re: Add LZ4 compression in pg_dump

On Wed, Mar 30, 2022 at 03:32:55PM +0000, gkokolatos@pm.me wrote:

On Wednesday, March 30th, 2022 at 7:54 AM, Michael Paquier <michael@paquier.xyz> wrote:

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?

Agreed. Fixed.

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.

+ my $compress_program = $ENV{GZIP_PROGRAM};

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

Maybe not. We don't want to the tests to fail if the utility is not
installed. That becomes even more evident as more methods are added.
However I realized that the presence of the environmental variable does
not guarrantee that the utility is actually installed. In the attached,
the existance of the utility is based on the return value of system_log().

Hmm. [.. thinks ..] The thing that's itching me here is that you
align the concept of compression with gzip, but that's not going to be
true once more compression options are added to pg_dump, and that
would make $supports_compression and $compress_program_exists
incorrect. Perhaps the right answer would be to rename all that with
a suffix like "_gzip" to make a difference? Or would there be enough
control with a value of "compression_gzip" instead of "compression" in
test_key?

+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

#26Noname
gkokolatos@pm.me
In reply to: Michael Paquier (#25)
3 attachment(s)
Re: Add LZ4 compression in pg_dump

On Thursday, March 31st, 2022 at 4:34 AM, Michael Paquier <michael@paquier.xyz> wrote:

On Wed, Mar 30, 2022 at 03:32:55PM +0000, gkokolatos@pm.me wrote:

On Wednesday, March 30th, 2022 at 7:54 AM, Michael Paquier michael@paquier.xyz wrote:

Okay. 0002 looks fine as-is, and I don't mind the extra fatal()
calls. These could be asserts but that's not a big deal one way or
the other. And the cleanup is now applied.

Thank you very much.

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

Maybe not. We don't want to the tests to fail if the utility is not
installed. That becomes even more evident as more methods are added.
However I realized that the presence of the environmental variable does
not guarrantee that the utility is actually installed. In the attached,
the existance of the utility is based on the return value of system_log().

Hmm. [.. thinks ..] The thing that's itching me here is that you
align the concept of compression with gzip, but that's not going to be
true once more compression options are added to pg_dump, and that
would make $supports_compression and $compress_program_exists
incorrect. Perhaps the right answer would be to rename all that with
a suffix like "_gzip" to make a difference? Or would there be enough
control with a value of "compression_gzip" instead of "compression" in
test_key?

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

+my $compress_program_exists = (system_log("$ENV{GZIP_PROGRAM}", '-h',
+ '>', '/dev/null') == 0);

Do we need this command execution at all? In all the other tests, we
rely on a simple "if (!defined $gzip || $gzip eq '');", so we could do
the same here.

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

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

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

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

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

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

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

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

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

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

Cheers,
//Georgios

Attachments:

v5-0001-Extend-compression-coverage-for-pg_dump-pg_restor.patchtext/x-patch; name=v5-0001-Extend-compression-coverage-for-pg_dump-pg_restor.patchDownload
From 093f9104e1e50b777e05f24665c7c1125d97ace3 Mon Sep 17 00:00:00 2001
From: Georgios Kokolatos <gkokolatos@pm.me>
Date: Fri, 1 Apr 2022 13:15:14 +0000
Subject: [PATCH v5 1/3] Extend compression coverage for pg_dump, pg_restore

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

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

v5-0003-Add-LZ4-compression-in-pg_-dump-restore.patchtext/x-patch; name=v5-0003-Add-LZ4-compression-in-pg_-dump-restore.patchDownload
From 39dfbf28e781a4870a20110942bf6a1586b837b6 Mon Sep 17 00:00:00 2001
From: Georgios Kokolatos <gkokolatos@pm.me>
Date: Fri, 1 Apr 2022 13:15:27 +0000
Subject: [PATCH v5 3/3] 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          |   3 +-
 src/bin/pg_dump/pg_backup_archiver.c |  71 ++-
 src/bin/pg_dump/pg_backup_archiver.h |   3 +-
 src/bin/pg_dump/pg_dump.c            |  12 +-
 src/bin/pg_dump/t/001_basic.pl       |   4 +-
 src/bin/pg_dump/t/002_pg_dump.pl     | 104 +++-
 9 files changed, 852 insertions(+), 107 deletions(-)

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

v5-0002-Prepare-pg_dump-for-additional-compression-method.patchtext/x-patch; name=v5-0002-Prepare-pg_dump-for-additional-compression-method.patchDownload
From b6c231f35b5163b755d2a79cc6d9b12920406731 Mon Sep 17 00:00:00 2001
From: Georgios Kokolatos <gkokolatos@pm.me>
Date: Fri, 1 Apr 2022 13:15:22 +0000
Subject: [PATCH v5 2/3] Prepare pg_dump for additional compression methods

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

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      |  49 ++-
 src/tools/pgindent/typedefs.list      |   2 +-
 13 files changed, 607 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..7645d3285a 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_INVALID,
+	COMPRESSION_NONE,
+	COMPRESSION_GZIP
+} CompressionMethod;
+
 /* Parameters needed by ConnectDatabase; same for dump and restore */
 typedef struct _connParams
 {
@@ -143,7 +150,8 @@ typedef struct _restoreOptions
 
 	int			noDataForFailedTables;
 	int			exit_on_error;
-	int			compression;
+	CompressionMethod compressionMethod;
+	int			compressionLevel;
 	int			suppressDumpWarnings;	/* Suppress output of WARNING entries
 										 * to stderr */
 	bool		single_txn;
@@ -303,7 +311,9 @@ extern Archive *OpenArchive(const char *FileSpec, const ArchiveFormat fmt);
 
 /* 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 2491a091b9..b25b641caa 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;
 
@@ -889,7 +889,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)
 		fatal("compression is not supported by tar archive format");
 
 	sprintf(fname, "blob_%u.dat", oid);
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 65e6c01fed..d7a52ac1a8 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,7 +138,7 @@ command_fails_like(
 if (check_pg_config("#define HAVE_LIBZ 1"))
 {
 	command_fails_like(
-		[ 'pg_dump', '--compress', '1', '--format', 'tar' ],
+		[ 'pg_dump', '--compress', 'gzip:1', '--format', 'tar' ],
 		qr/\Qpg_dump: error: compression is not supported by tar archive format\E/,
 		'pg_dump: compression is not supported by tar archive format');
 }
@@ -136,7 +146,7 @@ else
 {
 	# --jobs > 1 forces an error with tar format.
 	command_fails_like(
-		[ 'pg_dump', '--compress', '1', '--format', 'tar', '-j3' ],
+		[ 'pg_dump', '--compress', 'gzip:1', '--format', 'tar', '-j3' ],
 		qr/\Qpg_dump: warning: requested compression not available in this installation -- archive will be uncompressed\E/,
 		'pg_dump: warning: compression not available in this installation');
 }
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 134cc0618b..05a4b0756b 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -83,7 +83,7 @@ my %pgdump_runs = (
 		test_key => 'compression',
 		dump_cmd => [
 			'pg_dump',
-			'--format=directory', '--compress=1',
+			'--format=directory', '--compress=gzip:1',
 			"--file=$tempdir/compression_gzip_directory_format",
 			'postgres',
 		],
@@ -106,7 +106,7 @@ my %pgdump_runs = (
 		test_key => 'compression',
 		dump_cmd => [
 			'pg_dump', '--jobs=2',
-			'--format=directory', '--compress=6',
+			'--format=directory', '--compress=gzip:6',
 			"--file=$tempdir/compression_gzip_directory_format_parallel",
 			'postgres',
 		],
@@ -143,6 +143,25 @@ my %pgdump_runs = (
 			],
 		},
 	},
+	compression_none_dir_format => {
+		test_key => 'compression',
+		dump_cmd => [
+			'pg_dump', '-Fd',
+			'--compress=none',
+			"--file=$tempdir/compression_none_dir_format",
+			'postgres',
+		],
+		glob_match => {
+			no_match => "$tempdir/compression_none_dir_format/*.dat.gz",
+			match => "$tempdir/compression_none_dir_format/*.dat",
+			match_count => 2, # toc.dat and more
+		},
+		restore_cmd => [
+			'pg_restore', '-Fd',
+			"--file=$tempdir/compression_none_dir_format.sql",
+			"$tempdir/compression_none_dir_format",
+		],
+	},
 	clean => {
 		dump_cmd => [
 			'pg_dump',
@@ -242,7 +261,7 @@ my %pgdump_runs = (
 	defaults_dir_format => {
 		test_key => 'defaults',
 		dump_cmd => [
-			'pg_dump',                             '-Fd',
+			'pg_dump', '-Fd',
 			"--file=$tempdir/defaults_dir_format", 'postgres',
 		],
 		restore_cmd => [
@@ -4058,6 +4077,30 @@ foreach my $run (sort keys %pgdump_runs)
 
 		my @full_cmd = ($compress_cmd->{program}, @{ $compress_cmd->{args} });
 		command_ok(\@full_cmd, "$run: compression commands");
+
+	}
+
+	if ($pgdump_runs{$run}->{glob_match})
+	{
+		# Skip compression_cmd tests when compression is not supported
+		next if !$supports_gzip_compression;
+
+		my $match = $pgdump_runs{$run}->{glob_match}->{match};
+		my $match_count = defined($pgdump_runs{$run}->{glob_match}->{match_count}) ?
+							$pgdump_runs{$run}->{glob_match}->{match_count} : 1;
+		my @glob_matched = glob $match;
+
+		cmp_ok(scalar(@glob_matched), '>=', $match_count,
+			"Expected at least $match_count file(s) matching $match");
+
+		if ($pgdump_runs{$run}->{glob_match}->{no_match})
+		{
+			my $no_match = $pgdump_runs{$run}->{glob_match}->{no_match};
+			my @glob_matched = glob $no_match;
+
+			cmp_ok(scalar(@glob_matched), '==', 0,
+				"Expected no file(s) matching $no_match");
+		}
 	}
 
 	if ($pgdump_runs{$run}->{restore_cmd})
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 72fafb795b..5ac8c156a9 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -412,7 +412,7 @@ CompiledExprState
 CompositeIOData
 CompositeTypeStmt
 CompoundAffixFlag
-CompressionAlgorithm
+CompressionMethod
 CompressorState
 ComputeXidHorizonsResult
 ConditionVariable
-- 
2.32.0

#27Michael Paquier
michael@paquier.xyz
In reply to: Noname (#26)
1 attachment(s)
Re: Add LZ4 compression in pg_dump

On Fri, Apr 01, 2022 at 03:06:40PM +0000, gkokolatos@pm.me wrote:

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

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

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

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

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

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

Splitting the program and its arguments makes sense.

At the end I am finishing with the attached. I also saw an overlap
with the addition of --jobs for the directory format vs not using the
option, so I have removed the case where --jobs was not used in the
directory format.
--
Michael

Attachments:

v6-0001-Extend-compression-coverage-for-pg_dump-pg_restor.patchtext/x-diff; charset=us-asciiDownload
From b705f77862c9e41a149ce078df638032903bce3d Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Tue, 5 Apr 2022 10:30:06 +0900
Subject: [PATCH v6] Extend compression coverage for pg_dump, pg_restore

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

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

#28Noname
gkokolatos@pm.me
In reply to: Michael Paquier (#27)
Re: Add LZ4 compression in pg_dump

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

On Tuesday, April 5th, 2022 at 3:34 AM, Michael Paquier <michael@paquier.xyz> wrote:

On Fri, Apr 01, 2022 at 03:06:40PM +0000, gkokolatos@pm.me wrote:

Splitting the program and its arguments makes sense.

Great.

At the end I am finishing with the attached. I also saw an overlap
with the addition of --jobs for the directory format vs not using the
option, so I have removed the case where --jobs was not used in the
directory format.

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

Cheers,
//Georgios

Show quoted text

--
Michael

#29Michael Paquier
michael@paquier.xyz
In reply to: Noname (#28)
Re: Add LZ4 compression in pg_dump

On Tue, Apr 05, 2022 at 07:13:35AM +0000, gkokolatos@pm.me wrote:

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

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

#30Noname
gkokolatos@pm.me
In reply to: Michael Paquier (#29)
Re: Add LZ4 compression in pg_dump

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

On Tuesday, April 5th, 2022 at 12:55 PM, Michael Paquier <michael@paquier.xyz> wrote:

On Tue, Apr 05, 2022 at 07:13:35AM +0000, gkokolatos@pm.me wrote:
No need to carry it forward anymore, I think ;)

Thank you for committing!

Cheers,
//Georgios

Show quoted text

--
Michael

#31Tom Lane
tgl@sss.pgh.pa.us
In reply to: Michael Paquier (#27)
Re: Add LZ4 compression in pg_dump

[ blast-from-the-past department ]

Michael Paquier <michael@paquier.xyz> writes:

At the end I am finishing with the attached. I also saw an overlap
with the addition of --jobs for the directory format vs not using the
option, so I have removed the case where --jobs was not used in the
directory format.

(This patch became commit 98fe74218.)

I am wondering if you remember why this bit:

+        # Give coverage for manually compressed blob.toc files during
+        # restore.
+        compress_cmd => {
+            program => $ENV{'GZIP_PROGRAM'},
+            args    => [ '-f', "$tempdir/compression_gzip_dir/blobs.toc", ],
+        },

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

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

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

Thoughts?

regards, tom lane

[1]: /messages/by-id/3686.1760232320@sss.pgh.pa.us

#32Michael Paquier
michael@paquier.xyz
In reply to: Tom Lane (#31)
Re: Add LZ4 compression in pg_dump

On Sun, Oct 12, 2025 at 01:24:37PM -0400, Tom Lane wrote:

Michael Paquier <michael@paquier.xyz> writes:

At the end I am finishing with the attached. I also saw an overlap
with the addition of --jobs for the directory format vs not using the
option, so I have removed the case where --jobs was not used in the
directory format.

(This patch became commit 98fe74218.)

I am wondering if you remember why this bit:

+        # Give coverage for manually compressed blob.toc files during
+        # restore.
+        compress_cmd => {
+            program => $ENV{'GZIP_PROGRAM'},
+            args    => [ '-f', "$tempdir/compression_gzip_dir/blobs.toc", ],
+        },

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

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

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

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

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

Thoughts?

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