public inbox for [email protected]  
help / color / mirror / Atom feed
[PATCH 4/7] cmdline parser for compression alg/level/opts
14+ messages / 5 participants
[nested] [flat]

* [PATCH 4/7] cmdline parser for compression alg/level/opts
@ 2020-12-12 06:40  Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 14+ messages in thread

From: Justin Pryzby @ 2020-12-12 06:40 UTC (permalink / raw)

---
 src/bin/pg_dump/compress_io.c         | 235 ++++++++++++--------------
 src/bin/pg_dump/compress_io.h         |  20 +--
 src/bin/pg_dump/pg_backup.h           |  19 ++-
 src/bin/pg_dump/pg_backup_archiver.c  |  45 ++---
 src/bin/pg_dump/pg_backup_archiver.h  |  11 +-
 src/bin/pg_dump/pg_backup_custom.c    |   6 +-
 src/bin/pg_dump/pg_backup_directory.c |  19 ++-
 src/bin/pg_dump/pg_backup_tar.c       |  33 ++--
 src/bin/pg_dump/pg_dump.c             | 157 ++++++++++++++---
 9 files changed, 324 insertions(+), 221 deletions(-)

diff --git a/src/bin/pg_dump/compress_io.c b/src/bin/pg_dump/compress_io.c
index b51ba680a2..421b8e04cb 100644
--- a/src/bin/pg_dump/compress_io.c
+++ b/src/bin/pg_dump/compress_io.c
@@ -86,12 +86,9 @@ struct CompressorState
 
 };
 
-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);
+static void InitCompressorZlib(CompressorState *cs, Compress *compress);
 static void DeflateCompressorZlib(ArchiveHandle *AH, CompressorState *cs,
 								  bool flush);
 static void ReadDataFromArchiveZlib(ArchiveHandle *AH, ReadFunc readF);
@@ -101,9 +98,9 @@ static void EndCompressorZlib(ArchiveHandle *AH, CompressorState *cs);
 #endif
 
 #ifdef HAVE_LIBZSTD
-static void InitCompressorZstd(CompressorState *cs, int level);
+static ZSTD_CStream *ZstdCStreamParams(Compress *compress);
+static void InitCompressorZstd(CompressorState *cs, Compress *compress);
 static void EndCompressorZstd(ArchiveHandle *AH, CompressorState *cs);
-static void DeflateCompressorZstd(ArchiveHandle *AH, CompressorState *cs);
 static void WriteDataToArchiveZstd(ArchiveHandle *AH, CompressorState *cs,
 					   const char *data, size_t dLen);
 static void ReadDataFromArchiveZstd(ArchiveHandle *AH, ReadFunc readF);
@@ -114,80 +111,40 @@ 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)
-{
-	switch (compression)
-	{
-#ifdef HAVE_LIBZSTD
-		case ZSTD_COMPRESSION:
-			*alg = COMPR_ALG_ZSTD;
-			break;
-#endif
-#ifdef HAVE_ZLIB
-		case Z_DEFAULT_COMPRESSION:
-		case 1..9:
-			*alg = COMPR_ALG_LIBZ;
-			break;
-#endif
-		case 0:
-			*alg = COMPR_ALG_NONE;
-			break;
-		default:
-			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(Compress *compression, WriteFunc writeF)
 {
 	CompressorState *cs;
-	CompressionAlgorithm alg;
-	int			level;
-
-	ParseCompressionOption(compression, &alg, &level);
-
-#ifndef HAVE_LIBZ
-	if (alg == COMPR_ALG_LIBZ)
-		fatal("not built with zlib support");
-#endif
 
 	cs = (CompressorState *) pg_malloc0(sizeof(CompressorState));
 	cs->writeF = writeF;
-	cs->comprAlg = alg;
+	cs->comprAlg = compression->alg;
 
 	/*
 	 * Perform compression algorithm specific initialization.
 	 */
-	switch (alg)
+	Assert (compression->alg != COMPR_ALG_DEFAULT);
+	switch (compression->alg)
 	{
 #ifdef HAVE_LIBZ
 	case COMPR_ALG_LIBZ:
-		InitCompressorZlib(cs, level);
+		InitCompressorZlib(cs, compression);
 		break;
 #endif
 #ifdef HAVE_LIBZSTD
 	case COMPR_ALG_ZSTD:
-		InitCompressorZstd(cs, level);
+		InitCompressorZstd(cs, compression);
 		break;
 #endif
 	case COMPR_ALG_NONE:
 		/* Do nothing */
 		break;
-	// default:
+	default:
+		/* Should not happen */
+		fatal("requested compression not available in this installation");
 	}
 
 	return cs;
@@ -198,12 +155,9 @@ AllocateCompressor(int compression, WriteFunc writeF)
  * out with ahwrite().
  */
 void
-ReadDataFromArchive(ArchiveHandle *AH, int compression, ReadFunc readF)
+ReadDataFromArchive(ArchiveHandle *AH, ReadFunc readF)
 {
-	CompressionAlgorithm alg;
-
-	ParseCompressionOption(compression, &alg, NULL);
-
+	CompressionAlgorithm alg = AH->compression.alg;
 	if (alg == COMPR_ALG_NONE)
 		ReadDataFromArchiveNone(AH, readF);
 	else if (alg == COMPR_ALG_LIBZ)
@@ -233,25 +187,25 @@ WriteDataToArchive(ArchiveHandle *AH, CompressorState *cs,
 {
 	switch (cs->comprAlg)
 	{
-		case COMPR_ALG_LIBZ:
 #ifdef HAVE_LIBZ
+		case COMPR_ALG_LIBZ:
 			WriteDataToArchiveZlib(AH, cs, data, dLen);
-#else
-			fatal("not built with zlib support");
-#endif
 			break;
+#endif
 
-		case COMPR_ALG_ZSTD:
 #ifdef HAVE_LIBZSTD
+		case COMPR_ALG_ZSTD:
 			WriteDataToArchiveZstd(AH, cs, data, dLen);
-#else
-			fatal("not built with zstd support");
-#endif
 			break;
+#endif
 
 		case COMPR_ALG_NONE:
 			WriteDataToArchiveNone(AH, cs, data, dLen);
 			break;
+
+		default:
+			/* Should not happen */
+			fatal("requested compression not available in this installation");
 	}
 }
 
@@ -277,13 +231,42 @@ EndCompressor(ArchiveHandle *AH, CompressorState *cs)
 // XXX: put in separate files ?
 
 #ifdef HAVE_LIBZSTD
-static void
-InitCompressorZstd(CompressorState *cs, int level)
+static ZSTD_CStream*
+ZstdCStreamParams(Compress *compress)
 {
-	cs->u.zstd.cstream = ZSTD_createCStream();
-	if (cs->u.zstd.cstream == NULL)
+	size_t res;
+	ZSTD_CStream *cstream;
+
+	cstream = ZSTD_createCStream();
+	if (cstream == NULL)
 		fatal("could not initialize compression library");
 
+	if (compress->level != 0) // XXX: ZSTD_CLEVEL_DEFAULT
+	{
+		res = ZSTD_CCtx_setParameter(cstream,
+				ZSTD_c_compressionLevel, compress->level);
+		if (ZSTD_isError(res))
+			fatal("could not set compression level: %s",
+					ZSTD_getErrorName(res));
+	}
+
+	if (compress->zstdlong)
+	{
+		res = ZSTD_CCtx_setParameter(cstream,
+				ZSTD_c_enableLongDistanceMatching, 1); // XXX: allow to disable it
+		if (ZSTD_isError(res))
+			fatal("could not set compression parameter: %s",
+					ZSTD_getErrorName(res));
+	}
+
+	return cstream;
+}
+
+static void
+InitCompressorZstd(CompressorState *cs, Compress *compress)
+{
+	cs->u.zstd.cstream = ZstdCStreamParams(compress);
+
 	/* XXX: initialize safely like the corresponding zlib "paranoia" */
 	cs->u.zstd.output.size = ZSTD_CStreamOutSize();
 	cs->u.zstd.output.dst = pg_malloc(cs->u.zstd.output.size);
@@ -295,6 +278,8 @@ EndCompressorZstd(ArchiveHandle *AH, CompressorState *cs)
 {
 	ZSTD_outBuffer	*output = &cs->u.zstd.output;
 
+
+
 	for (;;)
 	{
 		size_t res;
@@ -318,11 +303,16 @@ EndCompressorZstd(ArchiveHandle *AH, CompressorState *cs)
 }
 
 static void
-DeflateCompressorZstd(ArchiveHandle *AH, CompressorState *cs)
+WriteDataToArchiveZstd(ArchiveHandle *AH, CompressorState *cs,
+					   const char *data, size_t dLen)
 {
 	ZSTD_inBuffer	*input = &cs->u.zstd.input;
 	ZSTD_outBuffer	*output = &cs->u.zstd.output;
 
+	input->src = (void *) unconstify(char *, data);
+	input->size = dLen;
+	input->pos = 0;
+
 	while (input->pos != input->size)
 	{
 		size_t		res;
@@ -349,16 +339,6 @@ DeflateCompressorZstd(ArchiveHandle *AH, CompressorState *cs)
 	}
 }
 
-static void
-WriteDataToArchiveZstd(ArchiveHandle *AH, CompressorState *cs,
-					   const char *data, size_t dLen)
-{
-	cs->u.zstd.input.src = (void *) unconstify(char *, data);
-	cs->u.zstd.input.size = dLen;
-	cs->u.zstd.input.pos = 0;
-	DeflateCompressorZstd(AH, cs);
-}
-
 /* Read data from a compressed zstd archive */
 static void
 ReadDataFromArchiveZstd(ArchiveHandle *AH, ReadFunc readF)
@@ -436,7 +416,7 @@ ReadDataFromArchiveZstd(ArchiveHandle *AH, ReadFunc readF)
  */
 
 static void
-InitCompressorZlib(CompressorState *cs, int level)
+InitCompressorZlib(CompressorState *cs, Compress *compress)
 {
 	z_streamp	zp;
 
@@ -453,7 +433,7 @@ InitCompressorZlib(CompressorState *cs, int level)
 	cs->zlibOut = (char *) pg_malloc(ZLIB_OUT_SIZE + 1);
 	cs->zlibOutSize = ZLIB_OUT_SIZE;
 
-	if (deflateInit(zp, level) != Z_OK)
+	if (deflateInit(zp, compress->level) != Z_OK)
 		fatal("could not initialize compression library: %s",
 			  zp->msg);
 
@@ -676,35 +656,31 @@ free_keep_errno(void *p)
  * 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',
+ * If the file at 'path' does not exist, we search with compressed suffix (if 'path'
+ * doesn't already have one) and try again. So if you pass "foo" as 'path',
  * this will open either "foo" or "foo.gz".
  *
  * On failure, return NULL with an error code in errno.
  */
 cfp *
-cfopen_read(const char *path, const char *mode, int compression)
+cfopen_read(const char *path, const char *mode, Compress *compression)
 {
 	cfp		   *fp;
 
-#ifdef HAVE_LIBZ
 	if (hasSuffix(path, ".gz") || hasSuffix(path, ".zst"))
 		fp = cfopen(path, mode, compression);
 	else
-#endif
 	{
 		fp = cfopen(path, mode, compression);
-#ifdef HAVE_LIBZ
 		if (fp == NULL)
 		{
 			char	   *fname;
-			char	   *suffix = compression == ZSTD_COMPRESSION ? "zst" : "gz";
+			const char *suffix = compress_suffix(compression);
 
-			fname = psprintf("%s.%s", path, suffix);
+			fname = psprintf("%s%s", path, suffix);
 			fp = cfopen(fname, mode, compression);
 			free_keep_errno(fname);
 		}
-#endif
 	}
 	return fp;
 }
@@ -714,32 +690,26 @@ cfopen_read(const char *path, const char *mode, int compression)
  * 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.
+ * Use compression if specified.
+ * The appropriate suffix is automatically added to 'path' in that case.
  *
  * 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, Compress *compression)
 {
 	cfp		   *fp;
 
-	if (compression == 0)
+	if (compression->alg == COMPR_ALG_NONE)
 		fp = cfopen(path, mode, compression);
 	else
 	{
-#ifdef HAVE_LIBZ // XXX
 		char	   *fname;
-		char	   *suffix = compression == ZSTD_COMPRESSION ? "zst" : "gz";
+		const char *suffix = compress_suffix(compression);
 
-		fname = psprintf("%s.%s", path, suffix);
+		fname = psprintf("%s%s", path, suffix);
 		fp = cfopen(fname, mode, compression);
 		free_keep_errno(fname);
-#else
-		fatal("not built with zlib support");
-		fp = NULL;				/* keep compiler quiet */
-#endif
 	}
 
 	return fp;
@@ -752,7 +722,7 @@ cfopen_write(const char *path, const char *mode, int compression)
  * On failure, return NULL with an error code in errno.
  */
 cfp *
-cfopen(const char *path, const char *mode, int compression)
+cfopen(const char *path, const char *mode, Compress *compression)
 {
 	cfp		   *fp = pg_malloc(sizeof(cfp));
 
@@ -764,18 +734,17 @@ cfopen(const char *path, const char *mode, int compression)
 	fp->zstd.fp = NULL;
 #endif
 
-	switch (compression)
+	switch (compression->alg)
 	{
 #ifdef HAVE_LIBZ
-	case 1 ... 9: // XXX: nonportable
-	case Z_DEFAULT_COMPRESSION:
-		if (compression != Z_DEFAULT_COMPRESSION)
+	case COMPR_ALG_LIBZ:
+		if (compression->level != 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, compression);
+					 mode, compression->level);
 			fp->compressedfp = gzopen(path, mode_compression);
 		}
 		else
@@ -795,9 +764,8 @@ cfopen(const char *path, const char *mode, int compression)
 #endif
 
 #ifdef HAVE_LIBZSTD
-	case ZSTD_COMPRESSION:
+	case COMPR_ALG_ZSTD:
 		fp->zstd.fp = fopen(path, mode);
-		// XXX: save the compression params
 		if (fp->zstd.fp == NULL)
 		{
 			free_keep_errno(fp);
@@ -806,16 +774,14 @@ cfopen(const char *path, const char *mode, int compression)
 		else if (strchr(mode, 'w'))
 		{
 			fp->zstd.dstream = NULL;
-			fp->zstd.output.size = ZSTD_CStreamOutSize(); // XXX
+			fp->zstd.output.size = ZSTD_CStreamOutSize();
 			fp->zstd.output.dst = pg_malloc0(fp->zstd.output.size);
-			fp->zstd.cstream = ZSTD_createCStream();
-			if (fp->zstd.cstream == NULL)
-				fatal("could not initialize compression library");
+			fp->zstd.cstream = ZstdCStreamParams(compression);
 		}
 		else if (strchr(mode, 'r'))
 		{
 			fp->zstd.cstream = NULL;
-			fp->zstd.input.size = ZSTD_DStreamOutSize(); // XXX
+			fp->zstd.input.size = ZSTD_DStreamInSize();
 			fp->zstd.input.src = pg_malloc0(fp->zstd.input.size);
 			fp->zstd.dstream = ZSTD_createDStream();
 			if (fp->zstd.dstream == NULL)
@@ -825,15 +791,19 @@ cfopen(const char *path, const char *mode, int compression)
 		break;
 #endif
 
-	default:
+	case COMPR_ALG_NONE:
 		fp->uncompressedfp = fopen(path, mode);
 		if (fp->uncompressedfp == NULL)
 		{
 			free_keep_errno(fp);
 			fp = NULL;
 		}
-
 		return fp;
+		break;
+
+	default:
+		/* should not happen */
+		fatal("requested compression not available in this installation");
 	}
 }
 
@@ -944,7 +914,7 @@ cfwrite(const void *ptr, int size, cfp *fp)
 	else
 #endif
 
-		return fwrite(ptr, 1, size, fp->uncompressedfp);
+	return fwrite(ptr, 1, size, fp->uncompressedfp);
 }
 
 int
@@ -1005,7 +975,7 @@ cfgets(cfp *fp, char *buf, int len)
 		buf[res] = 0;
 		if (strchr(buf, '\n'))
 			*strchr(buf, '\n') = '\0';
-		return buf;
+		return res > 0 ? buf : 0;
 	}
 	else
 #endif
@@ -1122,5 +1092,22 @@ hasSuffix(const char *filename, const char *suffix)
 				  suffix,
 				  suffixlen) == 0;
 }
-
 #endif
+
+/*
+ * Return a string for the given AH's compression.
+ * The string is statically allocated.
+ */
+const char *
+compress_suffix(Compress *compression)
+{
+	switch (compression->alg)
+	{
+		case COMPR_ALG_LIBZ:
+			return ".gz";
+		case COMPR_ALG_ZSTD:
+			return ".zst";
+		default:
+			return "";
+	}
+}
diff --git a/src/bin/pg_dump/compress_io.h b/src/bin/pg_dump/compress_io.h
index f0ce06f176..2c073676eb 100644
--- a/src/bin/pg_dump/compress_io.h
+++ b/src/bin/pg_dump/compress_io.h
@@ -21,13 +21,6 @@
 #define ZLIB_OUT_SIZE	4096
 #define ZLIB_IN_SIZE	4096
 
-typedef enum
-{
-	COMPR_ALG_NONE,
-	COMPR_ALG_LIBZ,
-	COMPR_ALG_ZSTD,
-} CompressionAlgorithm;
-
 /* Prototype for callback function to WriteDataToArchive() */
 typedef void (*WriteFunc) (ArchiveHandle *AH, const char *buf, size_t len);
 
@@ -47,8 +40,8 @@ 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(Compress *compression, WriteFunc writeF);
+extern void ReadDataFromArchive(ArchiveHandle *AH,
 								ReadFunc readF);
 extern void WriteDataToArchive(ArchiveHandle *AH, CompressorState *cs,
 							   const void *data, size_t dLen);
@@ -57,9 +50,9 @@ 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_read(const char *path, const char *mode, int compression);
-extern cfp *cfopen_write(const char *path, const char *mode, int compression);
+extern cfp *cfopen(const char *path, const char *mode, Compress *compression);
+extern cfp *cfopen_read(const char *path, const char *mode, Compress *compression);
+extern cfp *cfopen_write(const char *path, const char *mode, Compress *compression);
 extern int	cfread(void *ptr, int size, cfp *fp);
 extern int	cfwrite(const void *ptr, int size, cfp *fp);
 extern int	cfgetc(cfp *fp);
@@ -68,4 +61,7 @@ extern int	cfclose(cfp *fp);
 extern int	cfeof(cfp *fp);
 extern const char *get_cfp_error(cfp *fp);
 
+/* also used by tar */
+extern const char * compress_suffix(Compress *compression);
+
 #endif
diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h
index 9d0056a569..1c1954dd1a 100644
--- a/src/bin/pg_dump/pg_backup.h
+++ b/src/bin/pg_dump/pg_backup.h
@@ -72,6 +72,21 @@ typedef struct _connParams
 	char	   *override_dbname;
 } ConnParams;
 
+typedef enum
+{
+		COMPR_ALG_DEFAULT = -1,
+		COMPR_ALG_NONE,
+		COMPR_ALG_LIBZ,
+		COMPR_ALG_ZSTD,
+} CompressionAlgorithm;
+
+typedef struct Compress {
+	CompressionAlgorithm	alg;
+	int			level; // XXX: default
+	bool		zstdlong;
+} Compress;
+
+
 typedef struct _restoreOptions
 {
 	int			createDB;		/* Issue commands to create the database */
@@ -125,7 +140,7 @@ typedef struct _restoreOptions
 
 	int			noDataForFailedTables;
 	int			exit_on_error;
-	int			compression;
+	Compress	compression;
 	int			suppressDumpWarnings;	/* Suppress output of WARNING entries
 										 * to stderr */
 	bool		single_txn;
@@ -281,7 +296,7 @@ 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,
+							  Compress *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 1f82c6499b..2f1d59ce7a 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -70,7 +70,7 @@ typedef struct _parallelReadyList
 
 
 static ArchiveHandle *_allocAH(const char *FileSpec, const ArchiveFormat fmt,
-							   const int compression, bool dosync, ArchiveMode mode,
+							   Compress *compression, bool dosync, ArchiveMode mode,
 							   SetupWorkerPtrType setupWorkerPtr);
 static void _getObjectDescription(PQExpBuffer buf, TocEntry *te);
 static void _printTocEntry(ArchiveHandle *AH, TocEntry *te, bool isData);
@@ -98,7 +98,7 @@ 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 void SetOutput(ArchiveHandle *AH, const char *filename, Compress *compress);
 static OutputContext SaveOutput(ArchiveHandle *AH);
 static void RestoreOutput(ArchiveHandle *AH, OutputContext savedContext);
 
@@ -238,7 +238,7 @@ setupRestoreWorker(Archive *AHX)
 /* Public */
 Archive *
 CreateArchive(const char *FileSpec, const ArchiveFormat fmt,
-			  const int compression, bool dosync, ArchiveMode mode,
+			  Compress *compression, bool dosync, ArchiveMode mode,
 			  SetupWorkerPtrType setupDumpWorker)
 
 {
@@ -253,7 +253,9 @@ CreateArchive(const char *FileSpec, const ArchiveFormat fmt,
 Archive *
 OpenArchive(const char *FileSpec, const ArchiveFormat fmt)
 {
-	ArchiveHandle *AH = _allocAH(FileSpec, fmt, 0, true, archModeRead, setupRestoreWorker);
+	Compress compress = {0};
+	ArchiveHandle *AH = _allocAH(FileSpec, fmt, &compress, true, archModeRead,
+			setupRestoreWorker);
 
 	return (Archive *) AH;
 }
@@ -382,7 +384,7 @@ 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->compression.alg != COMPR_ALG_NONE && AH->PrintTocDataPtr != NULL)
 	{
 		for (te = AH->toc->next; te != AH->toc; te = te->next)
 		{
@@ -457,8 +459,8 @@ 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->compression.alg != COMPR_ALG_NONE)
+		SetOutput(AH, ropt->filename, &ropt->compression);
 
 	ahprintf(AH, "--\n-- PostgreSQL database dump\n--\n\n");
 
@@ -738,7 +740,7 @@ RestoreArchive(Archive *AHX)
 	 */
 	AH->stage = STAGE_FINALIZING;
 
-	if (ropt->filename || ropt->compression)
+	if (ropt->filename || ropt->compression.alg != COMPR_ALG_NONE)
 		RestoreOutput(AH, sav);
 
 	if (ropt->useDB)
@@ -1121,10 +1123,11 @@ PrintTOCSummary(Archive *AHX)
 	OutputContext sav;
 	const char *fmtName;
 	char		stamp_str[64];
+	Compress	nocompression = {0};
 
 	sav = SaveOutput(AH);
 	if (ropt->filename)
-		SetOutput(AH, ropt->filename, 0 /* no compression */ );
+		SetOutput(AH, ropt->filename, &nocompression);
 
 	if (strftime(stamp_str, sizeof(stamp_str), PGDUMP_STRFTIME_FMT,
 				 localtime(&AH->createDate)) == 0)
@@ -1133,7 +1136,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->compression.alg);
 
 	switch (AH->format)
 	{
@@ -1487,7 +1490,7 @@ archprintf(Archive *AH, const char *fmt,...)
  *******************************/
 
 static void
-SetOutput(ArchiveHandle *AH, const char *filename, int compression)
+SetOutput(ArchiveHandle *AH, const char *filename, Compress *compression)
 {
 	int			fn;
 
@@ -1510,12 +1513,12 @@ SetOutput(ArchiveHandle *AH, const char *filename, int compression)
 
 	/* If compression explicitly requested, use gzopen */
 #ifdef HAVE_LIBZ
-	if (compression != 0)
+	if (compression->alg != COMPR_ALG_NONE)
 	{
 		char		fmode[14];
 
 		/* Don't use PG_BINARY_x since this is zlib */
-		sprintf(fmode, "wb%d", compression);
+		sprintf(fmode, "wb%d", compression->level);
 		if (fn >= 0)
 			AH->OF = gzdopen(dup(fn), fmode);
 		else
@@ -2259,7 +2262,7 @@ _discoverArchiveFormat(ArchiveHandle *AH)
  */
 static ArchiveHandle *
 _allocAH(const char *FileSpec, const ArchiveFormat fmt,
-		 const int compression, bool dosync, ArchiveMode mode,
+		 Compress *compression, bool dosync, ArchiveMode mode,
 		 SetupWorkerPtrType setupWorkerPtr)
 {
 	ArchiveHandle *AH;
@@ -2310,7 +2313,7 @@ _allocAH(const char *FileSpec, const ArchiveFormat fmt,
 	AH->toc->prev = AH->toc;
 
 	AH->mode = mode;
-	AH->compression = compression;
+	AH->compression = *compression;
 	AH->dosync = dosync;
 
 	memset(&(AH->sqlparse), 0, sizeof(AH->sqlparse));
@@ -2325,7 +2328,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 || compression->alg != COMPR_ALG_NONE) &&
 		(AH->fSpec == NULL || strcmp(AH->fSpec, "") == 0))
 	{
 		if (mode == archModeWrite)
@@ -3741,7 +3744,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->compression.alg);
 	crtm = *localtime(&AH->createDate);
 	WriteInt(AH, crtm.tm_sec);
 	WriteInt(AH, crtm.tm_min);
@@ -3816,15 +3819,15 @@ ReadHead(ArchiveHandle *AH)
 	if (AH->version >= K_VERS_1_2)
 	{
 		if (AH->version < K_VERS_1_4)
-			AH->compression = AH->ReadBytePtr(AH);
+			AH->compression.alg = AH->ReadBytePtr(AH);
 		else
-			AH->compression = ReadInt(AH);
+			AH->compression.alg = ReadInt(AH);
 	}
 	else
-		AH->compression = Z_DEFAULT_COMPRESSION;
+		AH->compression.alg = Z_DEFAULT_COMPRESSION;
 
 #ifndef HAVE_LIBZ
-	if (AH->compression != 0)
+	if (AH->compression.alg != COMPR_ALG_NONE)
 		pg_log_warning("archive is compressed, but this installation does not support compression -- no data will be available");
 #endif
 
diff --git a/src/bin/pg_dump/pg_backup_archiver.h b/src/bin/pg_dump/pg_backup_archiver.h
index 1a229ebedb..641ba2d043 100644
--- a/src/bin/pg_dump/pg_backup_archiver.h
+++ b/src/bin/pg_dump/pg_backup_archiver.h
@@ -47,8 +47,6 @@
 #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
 {
@@ -62,7 +60,6 @@ typedef z_stream *z_streamp;
 
 #ifdef HAVE_LIBZSTD
 #include <zstd.h>
-#define ZSTD_COMPRESSION	-2
 #endif	/* HAVE_LIBZSTD */
 
 /* Data block types */
@@ -334,13 +331,7 @@ 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:
-								 * -2	ZSTD_COMPRESSION
-								 * -1	Z_DEFAULT_COMPRESSION
-								 *  0	COMPRESSION_NONE
-								 * 1-9 levels for gzip compression */
+	Compress	compression;	/* Compression requested on open */
 	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..55a887a236 100644
--- a/src/bin/pg_dump/pg_backup_custom.c
+++ b/src/bin/pg_dump/pg_backup_custom.c
@@ -298,7 +298,7 @@ _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->compression, _CustomWriteFunc);
 }
 
 /*
@@ -377,7 +377,7 @@ _StartBlob(ArchiveHandle *AH, TocEntry *te, Oid oid)
 
 	WriteInt(AH, oid);
 
-	ctx->cs = AllocateCompressor(AH->compression, _CustomWriteFunc);
+	ctx->cs = AllocateCompressor(&AH->compression, _CustomWriteFunc);
 }
 
 /*
@@ -566,7 +566,7 @@ _PrintTocData(ArchiveHandle *AH, TocEntry *te)
 static void
 _PrintData(ArchiveHandle *AH)
 {
-	ReadDataFromArchive(AH, AH->compression, _CustomReadFunc);
+	ReadDataFromArchive(AH, _CustomReadFunc);
 }
 
 static void
diff --git a/src/bin/pg_dump/pg_backup_directory.c b/src/bin/pg_dump/pg_backup_directory.c
index 6e55cb425e..dfa36d5dc1 100644
--- a/src/bin/pg_dump/pg_backup_directory.c
+++ b/src/bin/pg_dump/pg_backup_directory.c
@@ -202,7 +202,7 @@ InitArchiveFmt_Directory(ArchiveHandle *AH)
 
 		setFilePath(AH, fname, "toc.dat");
 
-		tocFH = cfopen_read(fname, PG_BINARY_R, AH->compression);
+		tocFH = cfopen_read(fname, PG_BINARY_R, &AH->compression);
 		if (tocFH == NULL)
 			fatal("could not open input file \"%s\": %m", fname);
 
@@ -327,7 +327,7 @@ _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->compression);
 	if (ctx->dataFH == NULL)
 		fatal("could not open output file \"%s\": %m", fname);
 }
@@ -388,12 +388,12 @@ _PrintFileData(ArchiveHandle *AH, char *filename)
 	if (!filename)
 		return;
 
-	cfp = cfopen_read(filename, PG_BINARY_R, AH->compression);
+	cfp = cfopen_read(filename, PG_BINARY_R, &AH->compression);
 
 	if (!cfp)
 		fatal("could not open input file \"%s\": %m", filename);
 
-	buf = pg_malloc(ZLIB_OUT_SIZE);
+	buf = pg_malloc(ZLIB_OUT_SIZE); // XXX
 	buflen = ZLIB_OUT_SIZE;
 
 	while ((cnt = cfread(buf, buflen, cfp)))
@@ -435,12 +435,13 @@ _LoadBlobs(ArchiveHandle *AH)
 	lclContext *ctx = (lclContext *) AH->formatData;
 	char		fname[MAXPGPATH];
 	char		line[MAXPGPATH];
+	Compress	nocompression = {0};
 
 	StartRestoreBlobs(AH);
 
 	setFilePath(AH, fname, "blobs.toc");
 
-	ctx->blobsTocFH = cfopen_read(fname, PG_BINARY_R, AH->compression);
+	ctx->blobsTocFH = cfopen_read(fname, PG_BINARY_R, &nocompression);
 
 	if (ctx->blobsTocFH == NULL)
 		fatal("could not open large object TOC file \"%s\" for input: %m",
@@ -573,6 +574,7 @@ _CloseArchive(ArchiveHandle *AH)
 	{
 		cfp		   *tocFH;
 		char		fname[MAXPGPATH];
+		Compress	nocompression = {0};
 
 		setFilePath(AH, fname, "toc.dat");
 
@@ -580,7 +582,7 @@ _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, &nocompression);
 		if (tocFH == NULL)
 			fatal("could not open output file \"%s\": %m", fname);
 		ctx->dataFH = tocFH;
@@ -639,11 +641,12 @@ _StartBlobs(ArchiveHandle *AH, TocEntry *te)
 {
 	lclContext *ctx = (lclContext *) AH->formatData;
 	char		fname[MAXPGPATH];
+	Compress	nocompression = {0};
 
 	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", &nocompression);
 	if (ctx->blobsTocFH == NULL)
 		fatal("could not open output file \"%s\": %m", fname);
 }
@@ -661,7 +664,7 @@ _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->compression);
 
 	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 54e708875c..5c9d17bae7 100644
--- a/src/bin/pg_dump/pg_backup_tar.c
+++ b/src/bin/pg_dump/pg_backup_tar.c
@@ -39,6 +39,7 @@
 #include "pg_backup_archiver.h"
 #include "pg_backup_tar.h"
 #include "pg_backup_utils.h"
+#include "compress_io.h"
 #include "pgtar.h"
 
 static void _ArchiveEntry(ArchiveHandle *AH, TocEntry *te);
@@ -196,10 +197,10 @@ InitArchiveFmt_Tar(ArchiveHandle *AH)
 
 		/*
 		 * We don't support compression because reading the files back is not
-		 * possible since gzdopen uses buffered IO which totally screws file
+		 * possible since gzdopen uses buffered IO which totally screws file XXX
 		 * positioning.
 		 */
-		if (AH->compression != 0)
+		if (AH->compression.alg != COMPR_ALG_NONE)
 			fatal("compression is not supported by tar archive format");
 	}
 	else
@@ -254,14 +255,8 @@ _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
+		const char *suffix = compress_suffix(&AH->compression);
+		sprintf(fn, "%d.dat%s", te->dumpId, suffix);
 		ctx->filename = pg_strdup(fn);
 	}
 	else
@@ -352,7 +347,7 @@ tarOpen(ArchiveHandle *AH, const char *filename, char mode)
 
 #ifdef HAVE_LIBZ
 
-		if (AH->compression == 0)
+		if (AH->compression.alg == COMPR_ALG_NONE)
 			tm->nFH = ctx->tarFH;
 		else
 			fatal("compression is not supported by tar archive format");
@@ -413,9 +408,9 @@ tarOpen(ArchiveHandle *AH, const char *filename, char mode)
 
 #ifdef HAVE_LIBZ
 
-		if (AH->compression != 0)
+		if (AH->compression.alg != COMPR_ALG_NONE)
 		{
-			sprintf(fmode, "wb%d", AH->compression);
+			sprintf(fmode, "wb%d", AH->compression.level);
 			tm->zFH = gzdopen(dup(fileno(tm->tmpFH)), fmode);
 			if (tm->zFH == NULL)
 				fatal("could not open temporary file");
@@ -443,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->compression.alg != COMPR_ALG_NONE)
 		if (GZCLOSE(th->zFH) != 0)
 			fatal("could not close tar member");
 
@@ -868,7 +863,7 @@ _CloseArchive(ArchiveHandle *AH)
 		memcpy(ropt, AH->public.ropt, sizeof(RestoreOptions));
 		ropt->filename = NULL;
 		ropt->dropSchema = 1;
-		ropt->compression = 0;
+		ropt->compression.alg = COMPR_ALG_NONE;
 		ropt->superuser = NULL;
 		ropt->suppressDumpWarnings = true;
 
@@ -952,16 +947,12 @@ _StartBlob(ArchiveHandle *AH, TocEntry *te, Oid oid)
 	lclContext *ctx = (lclContext *) AH->formatData;
 	lclTocEntry *tctx = (lclTocEntry *) te->formatData;
 	char		fname[255];
-	char	   *sfx;
+	const char *sfx;
 
 	if (oid == 0)
 		fatal("invalid OID for large object (%u)", oid);
 
-	if (AH->compression != 0)
-		sfx = ".gz";
-	else
-		sfx = "";
-
+	sfx = compress_suffix(&AH->compression);
 	sprintf(fname, "blob_%u.dat%s", oid, sfx);
 
 	tarPrintf(ctx->blobToc, "%u %s\n", oid, fname);
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index f21eb021c7..5f80d05716 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -59,6 +59,7 @@
 #include "getopt_long.h"
 #include "libpq/libpq-fs.h"
 #include "parallel.h"
+#include "compress_io.h"
 #include "pg_backup_db.h"
 #include "pg_backup_utils.h"
 #include "pg_dump.h"
@@ -297,6 +298,111 @@ static void setupDumpWorker(Archive *AHX);
 static TableInfo *getRootTableInfo(TableInfo *tbinfo);
 
 
+/* Parse the string into compression options */
+static void
+parse_compression(const char *optarg, Compress *compress)
+{
+	if (optarg[0] == '0' && optarg[1] == '\0')
+		compress->alg = COMPR_ALG_NONE;
+	else if ((optarg[0] > '0' && optarg[0] <= '9') ||
+		optarg[0] == '-')
+	{
+		compress->alg = COMPR_ALG_LIBZ;
+		compress->level = atoi(optarg);
+		if (optarg[1] != '\0')
+		{
+			pg_log_error("compression level must be in range 0..9");
+			exit_nicely(1);
+		}
+	}
+	else
+	{
+		/* Parse a more flexible string like level=3 alg=zlib opts=long */
+		for (;;)
+		{
+			char *eq = strchr(optarg, '=');
+			int len;
+
+			if (eq == NULL)
+			{
+				pg_log_error("compression options must be key=value: %s", optarg);
+				exit_nicely(1);
+			}
+
+			len = eq - optarg;
+			if (strncmp(optarg, "alg", len) == 0)
+			{
+				if (strchr(eq, ' '))
+					len = strchr(eq, ' ') - eq - 1;
+				else
+					len = strlen(eq) - len;
+				if (strncmp(1+eq, "zlib", len) == 0 ||
+						strncmp(1+eq, "libz", len) == 0)
+					compress->alg = COMPR_ALG_LIBZ;
+				else if (strncmp(1+eq, "zstd", len) == 0)
+					compress->alg = COMPR_ALG_ZSTD;
+				else
+				{
+					pg_log_error("unknown compression algorithm: %s", 1+eq);
+					exit_nicely(1);
+				}
+			}
+			else if (strncmp(optarg, "level", len) == 0)
+				compress->level = atoi(1+eq);
+			else if (strncmp(optarg, "opt", len) == 0)
+			{
+				if (strchr(eq, ' '))
+					len = strchr(eq, ' ') - eq - 1;
+				else
+					len = strlen(eq) - len;
+				if (strncmp(1+eq, "zstdlong", len) == 0)
+					compress->zstdlong = true;
+				else
+				{
+					pg_log_error("unknown compression option: %s", 1+eq);
+					exit_nicely(1);
+				}
+			}
+			else
+			{
+				pg_log_error("unknown compression setting: %s", optarg);
+				exit_nicely(1);
+			}
+
+			optarg = strchr(eq, ' ');
+			if (!optarg++)
+				break;
+		}
+
+		/* zstd will check its own compression level later */
+		if (compress->alg != COMPR_ALG_ZSTD)
+		{
+			if (compress->level < 0 || compress->level > 9)
+			{
+				pg_log_error("compression level must be in range 0..9");
+				exit_nicely(1);
+			}
+			if (compress->zstdlong)
+			{
+				pg_log_error("compression option not supported with this algorithm");
+				exit_nicely(1);
+			}
+		}
+
+		/* XXX: 0 means "unset" */
+		if (compress->level == 0)
+		{
+			const int default_compress_level[] = {
+				0,			/* COMPR_ALG_NONE */
+				Z_DEFAULT_COMPRESSION,	/* COMPR_ALG_ZLIB */
+				ZSTD_CLEVEL_DEFAULT,	/* COMPR_ALG_ZSTD */
+			};
+
+			compress->level = default_compress_level[compress->alg];
+		}
+	}
+}
+
 int
 main(int argc, char **argv)
 {
@@ -319,7 +425,7 @@ main(int argc, char **argv)
 	char	   *use_role = NULL;
 	long		rowsPerInsert;
 	int			numWorkers = 1;
-	int			compressLevel = -1;
+	Compress	compress = { .alg = COMPR_ALG_DEFAULT };
 	int			plainText = 0;
 	ArchiveFormat archiveFormat = archUnknown;
 	ArchiveMode archiveMode;
@@ -532,13 +638,7 @@ main(int argc, char **argv)
 				break;
 
 			case 'Z':			/* Compression Level */
-				compressLevel = atoi(optarg);
-				if ((compressLevel < 0 || compressLevel > 9) &&
-					compressLevel != -2)
-				{
-					pg_log_error("compression level must be in range 0..9");
-					exit_nicely(1);
-				}
+				parse_compression(optarg, &compress);
 				break;
 
 			case 0:
@@ -680,20 +780,40 @@ main(int argc, char **argv)
 		plainText = 1;
 
 	/* Custom and directory formats are compressed by default, others not */
-	if (compressLevel == -1)
+	if (compress.alg == COMPR_ALG_DEFAULT)
 	{
-#ifdef HAVE_LIBZ
 		if (archiveFormat == archCustom || archiveFormat == archDirectory)
-			compressLevel = Z_DEFAULT_COMPRESSION;
-		else
+		{
+#ifdef HAVE_LIBZ
+			compress.alg = COMPR_ALG_LIBZ;
+			compress.level = Z_DEFAULT_COMPRESSION;
+#endif
+#ifdef HAVE_LIBZSTD
+			compress.alg = COMPR_ALG_ZSTD; // Set default for testing purposes
+			compress.level = ZSTD_CLEVEL_DEFAULT;
 #endif
-			compressLevel = 0;
+		}
+		else
+		{
+			compress.alg = COMPR_ALG_NONE;
+			compress.level = 0;
+		}
 	}
 
 #ifndef HAVE_LIBZ
-	if (compressLevel != 0)
+	if (compress.alg == COMPR_ALG_LIBZ)
+	{
 		pg_log_warning("requested compression not available in this installation -- archive will be uncompressed");
-	compressLevel = 0;
+		compress.alg = 0;
+	}
+#endif
+
+#ifndef HAVE_LIBZ
+	if (compress.alg == COMPR_ALG_ZSTD)
+	{
+		pg_log_warning("requested compression not available in this installation -- archive will be uncompressed");
+		compress.alg = 0;
+	}
 #endif
 
 	/*
@@ -724,7 +844,7 @@ main(int argc, char **argv)
 		fatal("option --index-collation-versions-unknown only works in binary upgrade mode");
 
 	/* Open the output file */
-	fout = CreateArchive(filename, archiveFormat, compressLevel, dosync,
+	fout = CreateArchive(filename, archiveFormat, &compress, dosync,
 						 archiveMode, setupDumpWorker);
 
 	/* Make dump options accessible right away */
@@ -958,10 +1078,7 @@ 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->compression = compress;
 
 	ropt->suppressDumpWarnings = true;	/* We've already shown them */
 
-- 
2.17.0


--w1A23YewkF9s+fLd
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0005-union-with-a-CompressionAlgorithm-alg.patch"



^ permalink  raw  reply  [nested|flat] 14+ messages in thread

* [PATCH 4/7] cmdline parser for compression alg/level/opts
@ 2020-12-12 06:40  Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 14+ messages in thread

From: Justin Pryzby @ 2020-12-12 06:40 UTC (permalink / raw)

---
 src/bin/pg_dump/compress_io.c         | 153 +++++++++++---------------
 src/bin/pg_dump/compress_io.h         |  17 +--
 src/bin/pg_dump/core                  | Bin 0 -> 1257472 bytes
 src/bin/pg_dump/kwlookup.c            |   1 +
 src/bin/pg_dump/pg_backup.h           |  19 +++-
 src/bin/pg_dump/pg_backup_archiver.c  |  46 ++++----
 src/bin/pg_dump/pg_backup_archiver.h  |  11 +-
 src/bin/pg_dump/pg_backup_custom.c    |   6 +-
 src/bin/pg_dump/pg_backup_directory.c |  19 ++--
 src/bin/pg_dump/pg_backup_tar.c       |  48 ++++----
 src/bin/pg_dump/pg_dump.c             | 144 ++++++++++++++++++++----
 11 files changed, 278 insertions(+), 186 deletions(-)
 create mode 100644 src/bin/pg_dump/core
 create mode 120000 src/bin/pg_dump/kwlookup.c

diff --git a/src/bin/pg_dump/compress_io.c b/src/bin/pg_dump/compress_io.c
index b51ba680a2..10db22ff88 100644
--- a/src/bin/pg_dump/compress_io.c
+++ b/src/bin/pg_dump/compress_io.c
@@ -86,12 +86,9 @@ struct CompressorState
 
 };
 
-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);
+static void InitCompressorZlib(CompressorState *cs, Compress *compress);
 static void DeflateCompressorZlib(ArchiveHandle *AH, CompressorState *cs,
 								  bool flush);
 static void ReadDataFromArchiveZlib(ArchiveHandle *AH, ReadFunc readF);
@@ -101,7 +98,7 @@ static void EndCompressorZlib(ArchiveHandle *AH, CompressorState *cs);
 #endif
 
 #ifdef HAVE_LIBZSTD
-static void InitCompressorZstd(CompressorState *cs, int level);
+static void InitCompressorZstd(CompressorState *cs, Compress *compress);
 static void EndCompressorZstd(ArchiveHandle *AH, CompressorState *cs);
 static void DeflateCompressorZstd(ArchiveHandle *AH, CompressorState *cs);
 static void WriteDataToArchiveZstd(ArchiveHandle *AH, CompressorState *cs,
@@ -114,80 +111,40 @@ 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)
-{
-	switch (compression)
-	{
-#ifdef HAVE_LIBZSTD
-		case ZSTD_COMPRESSION:
-			*alg = COMPR_ALG_ZSTD;
-			break;
-#endif
-#ifdef HAVE_ZLIB
-		case Z_DEFAULT_COMPRESSION:
-		case 1..9:
-			*alg = COMPR_ALG_LIBZ;
-			break;
-#endif
-		case 0:
-			*alg = COMPR_ALG_NONE;
-			break;
-		default:
-			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(Compress *compression, WriteFunc writeF)
 {
 	CompressorState *cs;
-	CompressionAlgorithm alg;
-	int			level;
-
-	ParseCompressionOption(compression, &alg, &level);
-
-#ifndef HAVE_LIBZ
-	if (alg == COMPR_ALG_LIBZ)
-		fatal("not built with zlib support");
-#endif
 
 	cs = (CompressorState *) pg_malloc0(sizeof(CompressorState));
 	cs->writeF = writeF;
-	cs->comprAlg = alg;
+	cs->comprAlg = compression->alg;
 
 	/*
 	 * Perform compression algorithm specific initialization.
 	 */
-	switch (alg)
+	Assert (compression->alg != COMPR_ALG_DEFAULT);
+	switch (compression->alg)
 	{
 #ifdef HAVE_LIBZ
 	case COMPR_ALG_LIBZ:
-		InitCompressorZlib(cs, level);
+		InitCompressorZlib(cs, compression);
 		break;
 #endif
 #ifdef HAVE_LIBZSTD
 	case COMPR_ALG_ZSTD:
-		InitCompressorZstd(cs, level);
+		InitCompressorZstd(cs, compression);
 		break;
 #endif
 	case COMPR_ALG_NONE:
 		/* Do nothing */
 		break;
-	// default:
+	default:
+		/* Should not happen */
+		fatal("requested compression not available in this installation");
 	}
 
 	return cs;
@@ -198,12 +155,9 @@ AllocateCompressor(int compression, WriteFunc writeF)
  * out with ahwrite().
  */
 void
-ReadDataFromArchive(ArchiveHandle *AH, int compression, ReadFunc readF)
+ReadDataFromArchive(ArchiveHandle *AH, ReadFunc readF)
 {
-	CompressionAlgorithm alg;
-
-	ParseCompressionOption(compression, &alg, NULL);
-
+	CompressionAlgorithm alg = AH->compression.alg;
 	if (alg == COMPR_ALG_NONE)
 		ReadDataFromArchiveNone(AH, readF);
 	else if (alg == COMPR_ALG_LIBZ)
@@ -233,25 +187,25 @@ WriteDataToArchive(ArchiveHandle *AH, CompressorState *cs,
 {
 	switch (cs->comprAlg)
 	{
-		case COMPR_ALG_LIBZ:
 #ifdef HAVE_LIBZ
+		case COMPR_ALG_LIBZ:
 			WriteDataToArchiveZlib(AH, cs, data, dLen);
-#else
-			fatal("not built with zlib support");
-#endif
 			break;
+#endif
 
-		case COMPR_ALG_ZSTD:
 #ifdef HAVE_LIBZSTD
+		case COMPR_ALG_ZSTD:
 			WriteDataToArchiveZstd(AH, cs, data, dLen);
-#else
-			fatal("not built with zstd support");
-#endif
 			break;
+#endif
 
 		case COMPR_ALG_NONE:
 			WriteDataToArchiveNone(AH, cs, data, dLen);
 			break;
+
+		default:
+			/* Should not happen */
+			fatal("requested compression not available in this installation");
 	}
 }
 
@@ -278,9 +232,30 @@ EndCompressor(ArchiveHandle *AH, CompressorState *cs)
 
 #ifdef HAVE_LIBZSTD
 static void
-InitCompressorZstd(CompressorState *cs, int level)
+InitCompressorZstd(CompressorState *cs, Compress *compress)
 {
+	size_t res;
+
 	cs->u.zstd.cstream = ZSTD_createCStream();
+
+	if (compress->level != 0) // XXX
+	{
+		res = ZSTD_CCtx_setParameter(cs->u.zstd.cstream,
+				ZSTD_c_compressionLevel, compress->level);
+		if (ZSTD_isError(res))
+			fatal("could not set compression level: %s",
+					ZSTD_getErrorName(res));
+	}
+
+	if (compress->zstdlong)
+	{
+		res = ZSTD_CCtx_setParameter(cs->u.zstd.cstream,
+				ZSTD_c_enableLongDistanceMatching, 1); // XXX: allow to disable it
+		if (ZSTD_isError(res))
+			fatal("could not set compression parameter: %s",
+					ZSTD_getErrorName(res));
+	}
+
 	if (cs->u.zstd.cstream == NULL)
 		fatal("could not initialize compression library");
 
@@ -436,7 +411,7 @@ ReadDataFromArchiveZstd(ArchiveHandle *AH, ReadFunc readF)
  */
 
 static void
-InitCompressorZlib(CompressorState *cs, int level)
+InitCompressorZlib(CompressorState *cs, Compress *compress)
 {
 	z_streamp	zp;
 
@@ -453,7 +428,7 @@ InitCompressorZlib(CompressorState *cs, int level)
 	cs->zlibOut = (char *) pg_malloc(ZLIB_OUT_SIZE + 1);
 	cs->zlibOutSize = ZLIB_OUT_SIZE;
 
-	if (deflateInit(zp, level) != Z_OK)
+	if (deflateInit(zp, compress->level) != Z_OK)
 		fatal("could not initialize compression library: %s",
 			  zp->msg);
 
@@ -683,7 +658,7 @@ free_keep_errno(void *p)
  * On failure, return NULL with an error code in errno.
  */
 cfp *
-cfopen_read(const char *path, const char *mode, int compression)
+cfopen_read(const char *path, const char *mode, Compress *compression)
 {
 	cfp		   *fp;
 
@@ -698,7 +673,7 @@ cfopen_read(const char *path, const char *mode, int compression)
 		if (fp == NULL)
 		{
 			char	   *fname;
-			char	   *suffix = compression == ZSTD_COMPRESSION ? "zst" : "gz";
+			char	   *suffix = compression->alg == COMPR_ALG_ZSTD ? "zst" : "gz";
 
 			fname = psprintf("%s.%s", path, suffix);
 			fp = cfopen(fname, mode, compression);
@@ -714,24 +689,23 @@ cfopen_read(const char *path, const char *mode, int compression)
  * 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.
+ * Use compression if specified.
+ * The appropriate suffix is automatically added to 'path' in that case.
  *
  * 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, Compress *compression)
 {
 	cfp		   *fp;
 
-	if (compression == 0)
+	if (compression->alg == COMPR_ALG_NONE)
 		fp = cfopen(path, mode, compression);
 	else
 	{
 #ifdef HAVE_LIBZ // XXX
 		char	   *fname;
-		char	   *suffix = compression == ZSTD_COMPRESSION ? "zst" : "gz";
+		char	   *suffix = compression->alg == COMPR_ALG_ZSTD ? "zst" : "gz";
 
 		fname = psprintf("%s.%s", path, suffix);
 		fp = cfopen(fname, mode, compression);
@@ -752,7 +726,7 @@ cfopen_write(const char *path, const char *mode, int compression)
  * On failure, return NULL with an error code in errno.
  */
 cfp *
-cfopen(const char *path, const char *mode, int compression)
+cfopen(const char *path, const char *mode, Compress *compression)
 {
 	cfp		   *fp = pg_malloc(sizeof(cfp));
 
@@ -764,18 +738,17 @@ cfopen(const char *path, const char *mode, int compression)
 	fp->zstd.fp = NULL;
 #endif
 
-	switch (compression)
+	switch (compression->alg)
 	{
 #ifdef HAVE_LIBZ
-	case 1 ... 9: // XXX: nonportable
-	case Z_DEFAULT_COMPRESSION:
-		if (compression != Z_DEFAULT_COMPRESSION)
+	case COMPR_ALG_LIBZ:
+		if (compression->level != 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, compression);
+					 mode, compression->level);
 			fp->compressedfp = gzopen(path, mode_compression);
 		}
 		else
@@ -795,7 +768,7 @@ cfopen(const char *path, const char *mode, int compression)
 #endif
 
 #ifdef HAVE_LIBZSTD
-	case ZSTD_COMPRESSION:
+	case COMPR_ALG_ZSTD:
 		fp->zstd.fp = fopen(path, mode);
 		// XXX: save the compression params
 		if (fp->zstd.fp == NULL)
@@ -825,15 +798,19 @@ cfopen(const char *path, const char *mode, int compression)
 		break;
 #endif
 
-	default:
+	case COMPR_ALG_NONE:
 		fp->uncompressedfp = fopen(path, mode);
 		if (fp->uncompressedfp == NULL)
 		{
 			free_keep_errno(fp);
 			fp = NULL;
 		}
-
 		return fp;
+		break;
+
+	default:
+		/* should not happen */
+		fatal("requested compression not available in this installation");
 	}
 }
 
@@ -1005,7 +982,7 @@ cfgets(cfp *fp, char *buf, int len)
 		buf[res] = 0;
 		if (strchr(buf, '\n'))
 			*strchr(buf, '\n') = '\0';
-		return buf;
+		return res > 0 ? buf : 0;
 	}
 	else
 #endif
diff --git a/src/bin/pg_dump/compress_io.h b/src/bin/pg_dump/compress_io.h
index f0ce06f176..ae37be08d9 100644
--- a/src/bin/pg_dump/compress_io.h
+++ b/src/bin/pg_dump/compress_io.h
@@ -21,13 +21,6 @@
 #define ZLIB_OUT_SIZE	4096
 #define ZLIB_IN_SIZE	4096
 
-typedef enum
-{
-	COMPR_ALG_NONE,
-	COMPR_ALG_LIBZ,
-	COMPR_ALG_ZSTD,
-} CompressionAlgorithm;
-
 /* Prototype for callback function to WriteDataToArchive() */
 typedef void (*WriteFunc) (ArchiveHandle *AH, const char *buf, size_t len);
 
@@ -47,8 +40,8 @@ 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(Compress *compression, WriteFunc writeF);
+extern void ReadDataFromArchive(ArchiveHandle *AH,
 								ReadFunc readF);
 extern void WriteDataToArchive(ArchiveHandle *AH, CompressorState *cs,
 							   const void *data, size_t dLen);
@@ -57,9 +50,9 @@ 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_read(const char *path, const char *mode, int compression);
-extern cfp *cfopen_write(const char *path, const char *mode, int compression);
+extern cfp *cfopen(const char *path, const char *mode, Compress *compression);
+extern cfp *cfopen_read(const char *path, const char *mode, Compress *compression);
+extern cfp *cfopen_write(const char *path, const char *mode, Compress *compression);
 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/core b/src/bin/pg_dump/core
new file mode 100644
index 0000000000000000000000000000000000000000..bdb556ec717e50744ccc2a7256ba9f90cb41427c
GIT binary patch
literal 1257472
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

literal 0
HcmV?d00001

diff --git a/src/bin/pg_dump/kwlookup.c b/src/bin/pg_dump/kwlookup.c
new file mode 120000
index 0000000000..73c3abc1c7
--- /dev/null
+++ b/src/bin/pg_dump/kwlookup.c
@@ -0,0 +1 @@
+../../../src/backend/parser/kwlookup.c
\ No newline at end of file
diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h
index 9d0056a569..00d77ac16a 100644
--- a/src/bin/pg_dump/pg_backup.h
+++ b/src/bin/pg_dump/pg_backup.h
@@ -72,6 +72,21 @@ typedef struct _connParams
 	char	   *override_dbname;
 } ConnParams;
 
+typedef enum
+{
+		COMPR_ALG_DEFAULT = -1,
+		COMPR_ALG_NONE,
+		COMPR_ALG_LIBZ,
+		COMPR_ALG_ZSTD,
+} CompressionAlgorithm;
+
+typedef struct Compress {
+	CompressionAlgorithm	alg;
+	int			level;
+	bool		zstdlong;
+} Compress;
+
+
 typedef struct _restoreOptions
 {
 	int			createDB;		/* Issue commands to create the database */
@@ -125,7 +140,7 @@ typedef struct _restoreOptions
 
 	int			noDataForFailedTables;
 	int			exit_on_error;
-	int			compression;
+	Compress	compression;
 	int			suppressDumpWarnings;	/* Suppress output of WARNING entries
 										 * to stderr */
 	bool		single_txn;
@@ -281,7 +296,7 @@ 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,
+							  Compress *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 1f82c6499b..be2b0882d9 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -70,7 +70,7 @@ typedef struct _parallelReadyList
 
 
 static ArchiveHandle *_allocAH(const char *FileSpec, const ArchiveFormat fmt,
-							   const int compression, bool dosync, ArchiveMode mode,
+							   Compress *compression, bool dosync, ArchiveMode mode,
 							   SetupWorkerPtrType setupWorkerPtr);
 static void _getObjectDescription(PQExpBuffer buf, TocEntry *te);
 static void _printTocEntry(ArchiveHandle *AH, TocEntry *te, bool isData);
@@ -98,7 +98,7 @@ 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 void SetOutput(ArchiveHandle *AH, const char *filename, Compress *compress);
 static OutputContext SaveOutput(ArchiveHandle *AH);
 static void RestoreOutput(ArchiveHandle *AH, OutputContext savedContext);
 
@@ -238,7 +238,7 @@ setupRestoreWorker(Archive *AHX)
 /* Public */
 Archive *
 CreateArchive(const char *FileSpec, const ArchiveFormat fmt,
-			  const int compression, bool dosync, ArchiveMode mode,
+			  Compress *compression, bool dosync, ArchiveMode mode,
 			  SetupWorkerPtrType setupDumpWorker)
 
 {
@@ -253,7 +253,9 @@ CreateArchive(const char *FileSpec, const ArchiveFormat fmt,
 Archive *
 OpenArchive(const char *FileSpec, const ArchiveFormat fmt)
 {
-	ArchiveHandle *AH = _allocAH(FileSpec, fmt, 0, true, archModeRead, setupRestoreWorker);
+	Compress compress = {0};
+	ArchiveHandle *AH = _allocAH(FileSpec, fmt, &compress, true, archModeRead,
+			setupRestoreWorker);
 
 	return (Archive *) AH;
 }
@@ -382,7 +384,7 @@ 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->compression.alg != COMPR_ALG_NONE && AH->PrintTocDataPtr != NULL)
 	{
 		for (te = AH->toc->next; te != AH->toc; te = te->next)
 		{
@@ -457,8 +459,8 @@ 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->compression.alg != COMPR_ALG_NONE)
+		SetOutput(AH, ropt->filename, &ropt->compression);
 
 	ahprintf(AH, "--\n-- PostgreSQL database dump\n--\n\n");
 
@@ -738,7 +740,7 @@ RestoreArchive(Archive *AHX)
 	 */
 	AH->stage = STAGE_FINALIZING;
 
-	if (ropt->filename || ropt->compression)
+	if (ropt->filename || ropt->compression.alg != COMPR_ALG_NONE)
 		RestoreOutput(AH, sav);
 
 	if (ropt->useDB)
@@ -1121,10 +1123,11 @@ PrintTOCSummary(Archive *AHX)
 	OutputContext sav;
 	const char *fmtName;
 	char		stamp_str[64];
+	Compress	nocompression = {0};
 
 	sav = SaveOutput(AH);
 	if (ropt->filename)
-		SetOutput(AH, ropt->filename, 0 /* no compression */ );
+		SetOutput(AH, ropt->filename, &nocompression);
 
 	if (strftime(stamp_str, sizeof(stamp_str), PGDUMP_STRFTIME_FMT,
 				 localtime(&AH->createDate)) == 0)
@@ -1133,7 +1136,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->compression.alg);
 
 	switch (AH->format)
 	{
@@ -1487,7 +1490,7 @@ archprintf(Archive *AH, const char *fmt,...)
  *******************************/
 
 static void
-SetOutput(ArchiveHandle *AH, const char *filename, int compression)
+SetOutput(ArchiveHandle *AH, const char *filename, Compress *compression)
 {
 	int			fn;
 
@@ -1510,12 +1513,12 @@ SetOutput(ArchiveHandle *AH, const char *filename, int compression)
 
 	/* If compression explicitly requested, use gzopen */
 #ifdef HAVE_LIBZ
-	if (compression != 0)
+	if (compression->alg != COMPR_ALG_NONE)
 	{
 		char		fmode[14];
 
 		/* Don't use PG_BINARY_x since this is zlib */
-		sprintf(fmode, "wb%d", compression);
+		sprintf(fmode, "wb%d", compression->level);
 		if (fn >= 0)
 			AH->OF = gzdopen(dup(fn), fmode);
 		else
@@ -2259,7 +2262,7 @@ _discoverArchiveFormat(ArchiveHandle *AH)
  */
 static ArchiveHandle *
 _allocAH(const char *FileSpec, const ArchiveFormat fmt,
-		 const int compression, bool dosync, ArchiveMode mode,
+		 Compress *compression, bool dosync, ArchiveMode mode,
 		 SetupWorkerPtrType setupWorkerPtr)
 {
 	ArchiveHandle *AH;
@@ -2310,9 +2313,8 @@ _allocAH(const char *FileSpec, const ArchiveFormat fmt,
 	AH->toc->prev = AH->toc;
 
 	AH->mode = mode;
-	AH->compression = compression;
+	AH->compression = *compression;
 	AH->dosync = dosync;
-
 	memset(&(AH->sqlparse), 0, sizeof(AH->sqlparse));
 
 	/* Open stdout with no compression for AH output handle */
@@ -2325,7 +2327,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 || compression->alg != COMPR_ALG_NONE) &&
 		(AH->fSpec == NULL || strcmp(AH->fSpec, "") == 0))
 	{
 		if (mode == archModeWrite)
@@ -3741,7 +3743,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->compression.alg);
 	crtm = *localtime(&AH->createDate);
 	WriteInt(AH, crtm.tm_sec);
 	WriteInt(AH, crtm.tm_min);
@@ -3816,15 +3818,15 @@ ReadHead(ArchiveHandle *AH)
 	if (AH->version >= K_VERS_1_2)
 	{
 		if (AH->version < K_VERS_1_4)
-			AH->compression = AH->ReadBytePtr(AH);
+			AH->compression.alg = AH->ReadBytePtr(AH);
 		else
-			AH->compression = ReadInt(AH);
+			AH->compression.alg = ReadInt(AH);
 	}
 	else
-		AH->compression = Z_DEFAULT_COMPRESSION;
+		AH->compression.alg = Z_DEFAULT_COMPRESSION;
 
 #ifndef HAVE_LIBZ
-	if (AH->compression != 0)
+	if (AH->compression.alg != COMPR_ALG_NONE)
 		pg_log_warning("archive is compressed, but this installation does not support compression -- no data will be available");
 #endif
 
diff --git a/src/bin/pg_dump/pg_backup_archiver.h b/src/bin/pg_dump/pg_backup_archiver.h
index 1a229ebedb..641ba2d043 100644
--- a/src/bin/pg_dump/pg_backup_archiver.h
+++ b/src/bin/pg_dump/pg_backup_archiver.h
@@ -47,8 +47,6 @@
 #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
 {
@@ -62,7 +60,6 @@ typedef z_stream *z_streamp;
 
 #ifdef HAVE_LIBZSTD
 #include <zstd.h>
-#define ZSTD_COMPRESSION	-2
 #endif	/* HAVE_LIBZSTD */
 
 /* Data block types */
@@ -334,13 +331,7 @@ 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:
-								 * -2	ZSTD_COMPRESSION
-								 * -1	Z_DEFAULT_COMPRESSION
-								 *  0	COMPRESSION_NONE
-								 * 1-9 levels for gzip compression */
+	Compress	compression;	/* Compression requested on open */
 	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..55a887a236 100644
--- a/src/bin/pg_dump/pg_backup_custom.c
+++ b/src/bin/pg_dump/pg_backup_custom.c
@@ -298,7 +298,7 @@ _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->compression, _CustomWriteFunc);
 }
 
 /*
@@ -377,7 +377,7 @@ _StartBlob(ArchiveHandle *AH, TocEntry *te, Oid oid)
 
 	WriteInt(AH, oid);
 
-	ctx->cs = AllocateCompressor(AH->compression, _CustomWriteFunc);
+	ctx->cs = AllocateCompressor(&AH->compression, _CustomWriteFunc);
 }
 
 /*
@@ -566,7 +566,7 @@ _PrintTocData(ArchiveHandle *AH, TocEntry *te)
 static void
 _PrintData(ArchiveHandle *AH)
 {
-	ReadDataFromArchive(AH, AH->compression, _CustomReadFunc);
+	ReadDataFromArchive(AH, _CustomReadFunc);
 }
 
 static void
diff --git a/src/bin/pg_dump/pg_backup_directory.c b/src/bin/pg_dump/pg_backup_directory.c
index 6e55cb425e..dfa36d5dc1 100644
--- a/src/bin/pg_dump/pg_backup_directory.c
+++ b/src/bin/pg_dump/pg_backup_directory.c
@@ -202,7 +202,7 @@ InitArchiveFmt_Directory(ArchiveHandle *AH)
 
 		setFilePath(AH, fname, "toc.dat");
 
-		tocFH = cfopen_read(fname, PG_BINARY_R, AH->compression);
+		tocFH = cfopen_read(fname, PG_BINARY_R, &AH->compression);
 		if (tocFH == NULL)
 			fatal("could not open input file \"%s\": %m", fname);
 
@@ -327,7 +327,7 @@ _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->compression);
 	if (ctx->dataFH == NULL)
 		fatal("could not open output file \"%s\": %m", fname);
 }
@@ -388,12 +388,12 @@ _PrintFileData(ArchiveHandle *AH, char *filename)
 	if (!filename)
 		return;
 
-	cfp = cfopen_read(filename, PG_BINARY_R, AH->compression);
+	cfp = cfopen_read(filename, PG_BINARY_R, &AH->compression);
 
 	if (!cfp)
 		fatal("could not open input file \"%s\": %m", filename);
 
-	buf = pg_malloc(ZLIB_OUT_SIZE);
+	buf = pg_malloc(ZLIB_OUT_SIZE); // XXX
 	buflen = ZLIB_OUT_SIZE;
 
 	while ((cnt = cfread(buf, buflen, cfp)))
@@ -435,12 +435,13 @@ _LoadBlobs(ArchiveHandle *AH)
 	lclContext *ctx = (lclContext *) AH->formatData;
 	char		fname[MAXPGPATH];
 	char		line[MAXPGPATH];
+	Compress	nocompression = {0};
 
 	StartRestoreBlobs(AH);
 
 	setFilePath(AH, fname, "blobs.toc");
 
-	ctx->blobsTocFH = cfopen_read(fname, PG_BINARY_R, AH->compression);
+	ctx->blobsTocFH = cfopen_read(fname, PG_BINARY_R, &nocompression);
 
 	if (ctx->blobsTocFH == NULL)
 		fatal("could not open large object TOC file \"%s\" for input: %m",
@@ -573,6 +574,7 @@ _CloseArchive(ArchiveHandle *AH)
 	{
 		cfp		   *tocFH;
 		char		fname[MAXPGPATH];
+		Compress	nocompression = {0};
 
 		setFilePath(AH, fname, "toc.dat");
 
@@ -580,7 +582,7 @@ _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, &nocompression);
 		if (tocFH == NULL)
 			fatal("could not open output file \"%s\": %m", fname);
 		ctx->dataFH = tocFH;
@@ -639,11 +641,12 @@ _StartBlobs(ArchiveHandle *AH, TocEntry *te)
 {
 	lclContext *ctx = (lclContext *) AH->formatData;
 	char		fname[MAXPGPATH];
+	Compress	nocompression = {0};
 
 	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", &nocompression);
 	if (ctx->blobsTocFH == NULL)
 		fatal("could not open output file \"%s\": %m", fname);
 }
@@ -661,7 +664,7 @@ _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->compression);
 
 	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 54e708875c..79e528ea89 100644
--- a/src/bin/pg_dump/pg_backup_tar.c
+++ b/src/bin/pg_dump/pg_backup_tar.c
@@ -196,10 +196,10 @@ InitArchiveFmt_Tar(ArchiveHandle *AH)
 
 		/*
 		 * We don't support compression because reading the files back is not
-		 * possible since gzdopen uses buffered IO which totally screws file
+		 * possible since gzdopen uses buffered IO which totally screws file XXX
 		 * positioning.
 		 */
-		if (AH->compression != 0)
+		if (AH->compression.alg != COMPR_ALG_NONE)
 			fatal("compression is not supported by tar archive format");
 	}
 	else
@@ -241,6 +241,22 @@ InitArchiveFmt_Tar(ArchiveHandle *AH)
 	}
 }
 
+/*
+ * Return a string for the given AH's compression.
+ * The string is statically allocated.
+ */
+static const char *
+compress_suffix(ArchiveHandle *AH)
+{
+	CompressionAlgorithm alg;
+	alg = AH->compression.alg;
+	if (alg == COMPR_ALG_LIBZ)
+		return ".gz";
+	if (alg == COMPR_ALG_ZSTD)
+		return ".zstd";
+	return "";
+}
+
 /*
  * - Start a new TOC entry
  *	 Setup the output file name.
@@ -254,14 +270,8 @@ _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
+		const char *suffix = compress_suffix(AH);
+		sprintf(fn, "%d.dat%s", te->dumpId, suffix);
 		ctx->filename = pg_strdup(fn);
 	}
 	else
@@ -352,7 +362,7 @@ tarOpen(ArchiveHandle *AH, const char *filename, char mode)
 
 #ifdef HAVE_LIBZ
 
-		if (AH->compression == 0)
+		if (AH->compression.alg == COMPR_ALG_NONE)
 			tm->nFH = ctx->tarFH;
 		else
 			fatal("compression is not supported by tar archive format");
@@ -413,9 +423,9 @@ tarOpen(ArchiveHandle *AH, const char *filename, char mode)
 
 #ifdef HAVE_LIBZ
 
-		if (AH->compression != 0)
+		if (AH->compression.alg != COMPR_ALG_NONE)
 		{
-			sprintf(fmode, "wb%d", AH->compression);
+			sprintf(fmode, "wb%d", AH->compression.level);
 			tm->zFH = gzdopen(dup(fileno(tm->tmpFH)), fmode);
 			if (tm->zFH == NULL)
 				fatal("could not open temporary file");
@@ -443,7 +453,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->compression.alg != COMPR_ALG_NONE)
 		if (GZCLOSE(th->zFH) != 0)
 			fatal("could not close tar member");
 
@@ -868,7 +878,7 @@ _CloseArchive(ArchiveHandle *AH)
 		memcpy(ropt, AH->public.ropt, sizeof(RestoreOptions));
 		ropt->filename = NULL;
 		ropt->dropSchema = 1;
-		ropt->compression = 0;
+		ropt->compression.alg = COMPR_ALG_NONE;
 		ropt->superuser = NULL;
 		ropt->suppressDumpWarnings = true;
 
@@ -952,16 +962,12 @@ _StartBlob(ArchiveHandle *AH, TocEntry *te, Oid oid)
 	lclContext *ctx = (lclContext *) AH->formatData;
 	lclTocEntry *tctx = (lclTocEntry *) te->formatData;
 	char		fname[255];
-	char	   *sfx;
+	const char *sfx;
 
 	if (oid == 0)
 		fatal("invalid OID for large object (%u)", oid);
 
-	if (AH->compression != 0)
-		sfx = ".gz";
-	else
-		sfx = "";
-
+	sfx = compress_suffix(AH);
 	sprintf(fname, "blob_%u.dat%s", oid, sfx);
 
 	tarPrintf(ctx->blobToc, "%u %s\n", oid, fname);
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index f21eb021c7..c8662f731b 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -59,6 +59,7 @@
 #include "getopt_long.h"
 #include "libpq/libpq-fs.h"
 #include "parallel.h"
+#include "compress_io.h"
 #include "pg_backup_db.h"
 #include "pg_backup_utils.h"
 #include "pg_dump.h"
@@ -297,6 +298,99 @@ static void setupDumpWorker(Archive *AHX);
 static TableInfo *getRootTableInfo(TableInfo *tbinfo);
 
 
+/* Parse the string into compression options */
+static void
+parse_compression(const char *optarg, Compress *compress)
+{
+	if (optarg[0] == '0' && optarg[1] == '\0')
+		compress->alg = COMPR_ALG_NONE;
+	else if ((optarg[0] > '0' && optarg[0] <= '9') ||
+		optarg[0] == '-')
+	{
+		compress->alg = COMPR_ALG_LIBZ;
+		compress->level = atoi(optarg);
+		if (optarg[1] != '\0')
+		{
+			pg_log_error("compression level must be in range 0..9");
+			exit_nicely(1);
+		}
+	}
+	else
+	{
+		/* Parse a more flexible string like level=3 alg=zlib opts=long */
+		for (;;)
+		{
+			char *eq = strchr(optarg, '=');
+			int len;
+
+			if (eq == NULL)
+			{
+				pg_log_error("compression options must be key=value: %s", optarg);
+				exit_nicely(1);
+			}
+
+			len = eq - optarg;
+			if (strncmp(optarg, "alg", len) == 0)
+			{
+				if (strchr(eq, ' '))
+					len = strchr(eq, ' ') - eq - 1;
+				else
+					len = strlen(eq) - len;
+				if (strncmp(1+eq, "zlib", len) == 0 ||
+						strncmp(1+eq, "libz", len) == 0)
+					compress->alg = COMPR_ALG_LIBZ;
+				else if (strncmp(1+eq, "zstd", len) == 0)
+					compress->alg = COMPR_ALG_ZSTD;
+				else
+				{
+					pg_log_error("unknown compression algorithm: %s", 1+eq);
+					exit_nicely(1);
+				}
+			}
+			else if (strncmp(optarg, "level", len) == 0)
+				compress->level = atoi(1+eq);
+			else if (strncmp(optarg, "opt", len) == 0)
+			{
+				if (strchr(eq, ' '))
+					len = strchr(eq, ' ') - eq - 1;
+				else
+					len = strlen(eq) - len;
+				if (strncmp(1+eq, "zstdlong", len) == 0)
+					compress->zstdlong = true;
+				else
+				{
+					pg_log_error("unknown compression option: %s", 1+eq);
+					exit_nicely(1);
+				}
+			}
+			else
+			{
+				pg_log_error("unknown compression setting: %s", optarg);
+				exit_nicely(1);
+			}
+
+			optarg = strchr(eq, ' ');
+			if (!optarg++)
+				break;
+		}
+
+		/* zstd will check its own compression level later */
+		if (compress->alg != COMPR_ALG_ZSTD)
+		{
+			if (compress->level < 0 || compress->level > 9)
+			{
+				pg_log_error("compression level must be in range 0..9");
+				exit_nicely(1);
+			}
+			if (compress->zstdlong)
+			{
+				pg_log_error("compression option not supported with this algorithm");
+				exit_nicely(1);
+			}
+		}
+	}
+}
+
 int
 main(int argc, char **argv)
 {
@@ -319,7 +413,7 @@ main(int argc, char **argv)
 	char	   *use_role = NULL;
 	long		rowsPerInsert;
 	int			numWorkers = 1;
-	int			compressLevel = -1;
+	Compress	compress = { .alg = COMPR_ALG_DEFAULT };
 	int			plainText = 0;
 	ArchiveFormat archiveFormat = archUnknown;
 	ArchiveMode archiveMode;
@@ -532,13 +626,7 @@ main(int argc, char **argv)
 				break;
 
 			case 'Z':			/* Compression Level */
-				compressLevel = atoi(optarg);
-				if ((compressLevel < 0 || compressLevel > 9) &&
-					compressLevel != -2)
-				{
-					pg_log_error("compression level must be in range 0..9");
-					exit_nicely(1);
-				}
+				parse_compression(optarg, &compress);
 				break;
 
 			case 0:
@@ -680,20 +768,39 @@ main(int argc, char **argv)
 		plainText = 1;
 
 	/* Custom and directory formats are compressed by default, others not */
-	if (compressLevel == -1)
+	if (compress.alg == COMPR_ALG_DEFAULT)
 	{
-#ifdef HAVE_LIBZ
 		if (archiveFormat == archCustom || archiveFormat == archDirectory)
-			compressLevel = Z_DEFAULT_COMPRESSION;
-		else
+		{
+#ifdef HAVE_LIBZ
+			compress.alg = COMPR_ALG_LIBZ;
 #endif
-			compressLevel = 0;
+#ifdef HAVE_LIBZSTD
+			compress.alg = COMPR_ALG_ZSTD; // Set default for testing purposes
+#endif
+			compress.level = 3;
+		}
+		else
+		{
+			compress.alg = COMPR_ALG_NONE;
+			compress.level = 0;
+		}
 	}
 
 #ifndef HAVE_LIBZ
-	if (compressLevel != 0)
+	if (compress.alg == COMPR_ALG_LIBZ)
+	{
 		pg_log_warning("requested compression not available in this installation -- archive will be uncompressed");
-	compressLevel = 0;
+		compress.alg = 0;
+	}
+#endif
+
+#ifndef HAVE_LIBZ
+	if (compress.alg == COMPR_ALG_ZSTD)
+	{
+		pg_log_warning("requested compression not available in this installation -- archive will be uncompressed");
+		compress.alg = 0;
+	}
 #endif
 
 	/*
@@ -724,7 +831,7 @@ main(int argc, char **argv)
 		fatal("option --index-collation-versions-unknown only works in binary upgrade mode");
 
 	/* Open the output file */
-	fout = CreateArchive(filename, archiveFormat, compressLevel, dosync,
+	fout = CreateArchive(filename, archiveFormat, &compress, dosync,
 						 archiveMode, setupDumpWorker);
 
 	/* Make dump options accessible right away */
@@ -958,10 +1065,7 @@ 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->compression = compress;
 
 	ropt->suppressDumpWarnings = true;	/* We've already shown them */
 
-- 
2.17.0


--bgLLobvf7eP6VP5c
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="0005-union-with-a-CompressionAlgorithm-alg.patch"



^ permalink  raw  reply  [nested|flat] 14+ messages in thread

* ExecTypeSetColNames is fundamentally broken
@ 2021-12-05 18:45  Tom Lane <[email protected]>
  0 siblings, 1 reply; 14+ messages in thread

From: Tom Lane @ 2021-12-05 18:45 UTC (permalink / raw)
  To: [email protected]

Changing 
Fcc: +inbox
--------
I looked into the failure reported at [1].  Basically what's happening
there is that we're allowing a composite datum of type RECORD to get
stored into a table, whereupon other backends can't make sense of it
since they lack the appropriate typcache entry.  The reason the datum
is marked as type RECORD is that ExecTypeSetColNames set things up
that way, after observing that the tuple descriptor obtained from
the current table definition didn't have the column names it thought
it should have.

Now, in the test case as given,	ExecTypeSetColNames is in error to
think that, because it fails to account for the possibility that the
tupdesc contains dropped columns that weren't dropped when the relevant
RTE was made.  However, if the test case is modified so that we just
rename rather than drop some pre-existing column, then even with a
fix for that problem ExecTypeSetColNames would do the wrong thing.

I made several attempts to work around this problem, but I've now
concluded that changing the type OID in ExecTypeSetColNames is just
fundamentally broken.  It can't be okay to decide that a Var that
claims to emit type A actually emits type B, and especially not to
do so as late as executor startup.  I speculated in the other thread
that we could do so during planning instead, but that turns out to
just move the problems around.  I think this must be so, because the
whole idea is bogus.  For example, if we have a function that is
declared to take type "ct", it can't be okay in general to pass it
type "record" instead.  We've mistakenly thought that we could fuzz
this as long as the two types are physically compatible --- but how
can we think that the column names of a composite type aren't a
basic part of its definition?

So 0001 attached fixes this by revoking the decision to apply
ExecTypeSetColNames in cases where a Var or RowExpr is declared
to return a named composite type.  This causes a couple of regression
test results to change, but they are ones that were specifically
added to exercise this behavior that we now see to be invalid.
(In passing, this also adjusts ExecEvalWholeRowVar to fail if the
Var claims to be of a domain-over-composite.  I'm not sure what
I was thinking when I changed it to allow that; the case should
not arise, and if it did, it'd be questionable because we're not
checking domain constraints here.)

0002 is some inessential mop-up that avoids storing useless column
name lists in RowExprs where they won't be used.

I'm not really thrilled about back-patching a behavioral change
of this sort, but I don't see any good alternative.  Corrupting
people's tables is not okay.

Thoughts?

			regards, tom lane

[1] https://www.postgresql.org/message-id/flat/CAOFAq3BeawPiw9pc3bVGZ%3DRint2txWEBCeDC2wNAhtCZoo2ZqA%40m...



Attachments:

  [text/x-diff] 0001-dont-change-named-rowtype-to-RECORD.patch (10.2K, ../../[email protected]/2-0001-dont-change-named-rowtype-to-RECORD.patch)
  download | inline diff:
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 892b4e17e0..f0f3b4ffb0 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -1913,16 +1913,16 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				{
 					/* generic record, use types of given expressions */
 					tupdesc = ExecTypeFromExprList(rowexpr->args);
+					/* ... but adopt RowExpr's column aliases */
+					ExecTypeSetColNames(tupdesc, rowexpr->colnames);
+					/* Bless the tupdesc so it can be looked up later */
+					BlessTupleDesc(tupdesc);
 				}
 				else
 				{
 					/* it's been cast to a named type, use that */
 					tupdesc = lookup_rowtype_tupdesc_copy(rowexpr->row_typeid, -1);
 				}
-				/* In either case, adopt RowExpr's column aliases */
-				ExecTypeSetColNames(tupdesc, rowexpr->colnames);
-				/* Bless the tupdesc in case it's now of type RECORD */
-				BlessTupleDesc(tupdesc);
 
 				/*
 				 * In the named-type case, the tupdesc could have more columns
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index eb49817cee..b7b79e0b75 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -4021,12 +4021,8 @@ ExecEvalWholeRowVar(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
 			 * generates an INT4 NULL regardless of the dropped column type).
 			 * If we find a dropped column and cannot verify that case (1)
 			 * holds, we have to use the slow path to check (2) for each row.
-			 *
-			 * If vartype is a domain over composite, just look through that
-			 * to the base composite type.
 			 */
-			var_tupdesc = lookup_rowtype_tupdesc_domain(variable->vartype,
-														-1, false);
+			var_tupdesc = lookup_rowtype_tupdesc(variable->vartype, -1);
 
 			slot_tupdesc = slot->tts_tupleDescriptor;
 
@@ -4063,9 +4059,8 @@ ExecEvalWholeRowVar(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
 
 			/*
 			 * Use the variable's declared rowtype as the descriptor for the
-			 * output values, modulo possibly assigning new column names
-			 * below. In particular, we *must* absorb any attisdropped
-			 * markings.
+			 * output values.  In particular, we *must* absorb any
+			 * attisdropped markings.
 			 */
 			oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
 			output_tupdesc = CreateTupleDescCopy(var_tupdesc);
@@ -4083,39 +4078,38 @@ ExecEvalWholeRowVar(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
 			oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
 			output_tupdesc = CreateTupleDescCopy(slot->tts_tupleDescriptor);
 			MemoryContextSwitchTo(oldcontext);
-		}
 
-		/*
-		 * Construct a tuple descriptor for the composite values we'll
-		 * produce, and make sure its record type is "blessed".  The main
-		 * reason to do this is to be sure that operations such as
-		 * row_to_json() will see the desired column names when they look up
-		 * the descriptor from the type information embedded in the composite
-		 * values.
-		 *
-		 * We already got the correct physical datatype info above, but now we
-		 * should try to find the source RTE and adopt its column aliases, in
-		 * case they are different from the original rowtype's names.  For
-		 * example, in "SELECT foo(t) FROM tab t(x,y)", the first two columns
-		 * in the composite output should be named "x" and "y" regardless of
-		 * tab's column names.
-		 *
-		 * If we can't locate the RTE, assume the column names we've got are
-		 * OK.  (As of this writing, the only cases where we can't locate the
-		 * RTE are in execution of trigger WHEN clauses, and then the Var will
-		 * have the trigger's relation's rowtype, so its names are fine.)
-		 * Also, if the creator of the RTE didn't bother to fill in an eref
-		 * field, assume our column names are OK.  (This happens in COPY, and
-		 * perhaps other places.)
-		 */
-		if (econtext->ecxt_estate &&
-			variable->varno <= econtext->ecxt_estate->es_range_table_size)
-		{
-			RangeTblEntry *rte = exec_rt_fetch(variable->varno,
-											   econtext->ecxt_estate);
+			/*
+			 * It's possible that the input slot is a relation scan slot and
+			 * so is marked with that relation's rowtype.  But we're supposed
+			 * to be returning RECORD, so reset to that.
+			 */
+			output_tupdesc->tdtypeid = RECORDOID;
+			output_tupdesc->tdtypmod = -1;
 
-			if (rte->eref)
-				ExecTypeSetColNames(output_tupdesc, rte->eref->colnames);
+			/*
+			 * We already got the correct physical datatype info above, but
+			 * now we should try to find the source RTE and adopt its column
+			 * aliases, since it's unlikely that the input slot has the
+			 * desired names.
+			 *
+			 * If we can't locate the RTE, assume the column names we've got
+			 * are OK.  (As of this writing, the only cases where we can't
+			 * locate the RTE are in execution of trigger WHEN clauses, and
+			 * then the Var will have the trigger's relation's rowtype, so its
+			 * names are fine.)  Also, if the creator of the RTE didn't bother
+			 * to fill in an eref field, assume our column names are OK. (This
+			 * happens in COPY, and perhaps other places.)
+			 */
+			if (econtext->ecxt_estate &&
+				variable->varno <= econtext->ecxt_estate->es_range_table_size)
+			{
+				RangeTblEntry *rte = exec_rt_fetch(variable->varno,
+												   econtext->ecxt_estate);
+
+				if (rte->eref)
+					ExecTypeSetColNames(output_tupdesc, rte->eref->colnames);
+			}
 		}
 
 		/* Bless the tupdesc if needed, and save it in the execution state */
diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c
index f447802843..5004b3b165 100644
--- a/src/backend/executor/execTuples.c
+++ b/src/backend/executor/execTuples.c
@@ -2022,51 +2022,40 @@ ExecTypeFromExprList(List *exprList)
 }
 
 /*
- * ExecTypeSetColNames - set column names in a TupleDesc
+ * ExecTypeSetColNames - set column names in a RECORD TupleDesc
  *
  * Column names must be provided as an alias list (list of String nodes).
- *
- * For some callers, the supplied tupdesc has a named rowtype (not RECORD)
- * and it is moderately likely that the alias list matches the column names
- * already present in the tupdesc.  If we do change any column names then
- * we must reset the tupdesc's type to anonymous RECORD; but we avoid doing
- * so if no names change.
  */
 void
 ExecTypeSetColNames(TupleDesc typeInfo, List *namesList)
 {
-	bool		modified = false;
 	int			colno = 0;
 	ListCell   *lc;
 
+	/* It's only OK to change col names in a not-yet-blessed RECORD type */
+	Assert(typeInfo->tdtypeid == RECORDOID);
+	Assert(typeInfo->tdtypmod < 0);
+
 	foreach(lc, namesList)
 	{
 		char	   *cname = strVal(lfirst(lc));
 		Form_pg_attribute attr;
 
-		/* Guard against too-long names list */
+		/* Guard against too-long names list (probably can't happen) */
 		if (colno >= typeInfo->natts)
 			break;
 		attr = TupleDescAttr(typeInfo, colno);
 		colno++;
 
-		/* Ignore empty aliases (these must be for dropped columns) */
-		if (cname[0] == '\0')
+		/*
+		 * Do nothing for empty aliases or dropped columns (these cases
+		 * probably can't arise in RECORD types, either)
+		 */
+		if (cname[0] == '\0' || attr->attisdropped)
 			continue;
 
-		/* Change tupdesc only if alias is actually different */
-		if (strcmp(cname, NameStr(attr->attname)) != 0)
-		{
-			namestrcpy(&(attr->attname), cname);
-			modified = true;
-		}
-	}
-
-	/* If we modified the tupdesc, it's now a new record type */
-	if (modified)
-	{
-		typeInfo->tdtypeid = RECORDOID;
-		typeInfo->tdtypmod = -1;
+		/* OK, assign the column name */
+		namestrcpy(&(attr->attname), cname);
 	}
 }
 
diff --git a/src/test/regress/expected/rowtypes.out b/src/test/regress/expected/rowtypes.out
index 0e71baf8fb..3245fb6a35 100644
--- a/src/test/regress/expected/rowtypes.out
+++ b/src/test/regress/expected/rowtypes.out
@@ -1010,14 +1010,15 @@ select row_to_json(i) from int8_tbl i;
  {"q1":4567890123456789,"q2":-4567890123456789}
 (5 rows)
 
+-- since "i" is of type "int8_tbl", attaching aliases doesn't change anything:
 select row_to_json(i) from int8_tbl i(x,y);
-                 row_to_json                  
-----------------------------------------------
- {"x":123,"y":456}
- {"x":123,"y":4567890123456789}
- {"x":4567890123456789,"y":123}
- {"x":4567890123456789,"y":4567890123456789}
- {"x":4567890123456789,"y":-4567890123456789}
+                  row_to_json                   
+------------------------------------------------
+ {"q1":123,"q2":456}
+ {"q1":123,"q2":4567890123456789}
+ {"q1":4567890123456789,"q2":123}
+ {"q1":4567890123456789,"q2":4567890123456789}
+ {"q1":4567890123456789,"q2":-4567890123456789}
 (5 rows)
 
 create temp view vv1 as select * from int8_tbl;
@@ -1032,15 +1033,16 @@ select row_to_json(i) from vv1 i;
 (5 rows)
 
 select row_to_json(i) from vv1 i(x,y);
-                 row_to_json                  
-----------------------------------------------
- {"x":123,"y":456}
- {"x":123,"y":4567890123456789}
- {"x":4567890123456789,"y":123}
- {"x":4567890123456789,"y":4567890123456789}
- {"x":4567890123456789,"y":-4567890123456789}
+                  row_to_json                   
+------------------------------------------------
+ {"q1":123,"q2":456}
+ {"q1":123,"q2":4567890123456789}
+ {"q1":4567890123456789,"q2":123}
+ {"q1":4567890123456789,"q2":4567890123456789}
+ {"q1":4567890123456789,"q2":-4567890123456789}
 (5 rows)
 
+-- in these examples, we'll report the exposed column names of the subselect:
 select row_to_json(ss) from
   (select q1, q2 from int8_tbl) as ss;
                   row_to_json                   
diff --git a/src/test/regress/sql/rowtypes.sql b/src/test/regress/sql/rowtypes.sql
index 845e3305f3..b8786603d9 100644
--- a/src/test/regress/sql/rowtypes.sql
+++ b/src/test/regress/sql/rowtypes.sql
@@ -417,12 +417,14 @@ select longname(f) from fullname f;
 --
 
 select row_to_json(i) from int8_tbl i;
+-- since "i" is of type "int8_tbl", attaching aliases doesn't change anything:
 select row_to_json(i) from int8_tbl i(x,y);
 
 create temp view vv1 as select * from int8_tbl;
 select row_to_json(i) from vv1 i;
 select row_to_json(i) from vv1 i(x,y);
 
+-- in these examples, we'll report the exposed column names of the subselect:
 select row_to_json(ss) from
   (select q1, q2 from int8_tbl) as ss;
 select row_to_json(ss) from


  [text/x-diff] 0002-dont-attach-useless-colname-lists.patch (4.4K, ../../[email protected]/3-0002-dont-attach-useless-colname-lists.patch)
  download | inline diff:
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 387a35e112..43674aace4 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -2279,8 +2279,8 @@ pullup_replace_vars_callback(Var *var,
 		 * If generating an expansion for a var of a named rowtype (ie, this
 		 * is a plain relation RTE), then we must include dummy items for
 		 * dropped columns.  If the var is RECORD (ie, this is a JOIN), then
-		 * omit dropped columns. Either way, attach column names to the
-		 * RowExpr for use of ruleutils.c.
+		 * omit dropped columns.  In the latter case, attach column names to
+		 * the RowExpr for use of the executor and ruleutils.c.
 		 *
 		 * In order to be able to cache the results, we always generate the
 		 * expansion with varlevelsup = 0, and then adjust if needed.
@@ -2301,7 +2301,7 @@ pullup_replace_vars_callback(Var *var,
 		rowexpr->args = fields;
 		rowexpr->row_typeid = var->vartype;
 		rowexpr->row_format = COERCE_IMPLICIT_CAST;
-		rowexpr->colnames = colnames;
+		rowexpr->colnames = (var->vartype == RECORDOID) ? colnames : NIL;
 		rowexpr->location = var->location;
 		newnode = (Node *) rowexpr;
 
diff --git a/src/backend/optimizer/util/var.c b/src/backend/optimizer/util/var.c
index e57c3aa124..b30e8c9be6 100644
--- a/src/backend/optimizer/util/var.c
+++ b/src/backend/optimizer/util/var.c
@@ -802,6 +802,7 @@ flatten_join_alias_vars_mutator(Node *node,
 			rowexpr->args = fields;
 			rowexpr->row_typeid = var->vartype;
 			rowexpr->row_format = COERCE_IMPLICIT_CAST;
+			/* vartype will always be RECORDOID, so we always need colnames */
 			rowexpr->colnames = colnames;
 			rowexpr->location = var->location;
 
diff --git a/src/backend/rewrite/rewriteManip.c b/src/backend/rewrite/rewriteManip.c
index d4e0b8b4de..30922a006a 100644
--- a/src/backend/rewrite/rewriteManip.c
+++ b/src/backend/rewrite/rewriteManip.c
@@ -1424,8 +1424,8 @@ ReplaceVarsFromTargetList_callback(Var *var,
 		 * If generating an expansion for a var of a named rowtype (ie, this
 		 * is a plain relation RTE), then we must include dummy items for
 		 * dropped columns.  If the var is RECORD (ie, this is a JOIN), then
-		 * omit dropped columns.  Either way, attach column names to the
-		 * RowExpr for use of ruleutils.c.
+		 * omit dropped columns.  In the latter case, attach column names to
+		 * the RowExpr for use of the executor and ruleutils.c.
 		 */
 		expandRTE(rcon->target_rte,
 				  var->varno, var->varlevelsup, var->location,
@@ -1438,7 +1438,7 @@ ReplaceVarsFromTargetList_callback(Var *var,
 		rowexpr->args = fields;
 		rowexpr->row_typeid = var->vartype;
 		rowexpr->row_format = COERCE_IMPLICIT_CAST;
-		rowexpr->colnames = colnames;
+		rowexpr->colnames = (var->vartype == RECORDOID) ? colnames : NIL;
 		rowexpr->location = var->location;
 
 		return (Node *) rowexpr;
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 433437643e..b039ea305a 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1052,15 +1052,13 @@ typedef struct ArrayExpr
  * than vice versa.)  It is important not to assume that length(args) is
  * the same as the number of columns logically present in the rowtype.
  *
- * colnames provides field names in cases where the names can't easily be
- * obtained otherwise.  Names *must* be provided if row_typeid is RECORDOID.
- * If row_typeid identifies a known composite type, colnames can be NIL to
- * indicate the type's cataloged field names apply.  Note that colnames can
- * be non-NIL even for a composite type, and typically is when the RowExpr
- * was created by expanding a whole-row Var.  This is so that we can retain
- * the column alias names of the RTE that the Var referenced (which would
- * otherwise be very difficult to extract from the parsetree).  Like the
- * args list, colnames is one-for-one with physical fields of the rowtype.
+ * colnames provides field names if the ROW() result is of type RECORD.
+ * Names *must* be provided if row_typeid is RECORDOID; but if it is a
+ * named composite type, colnames will be ignored in favor of using the
+ * type's cataloged field names, so colnames should be NIL.  Like the
+ * args list, colnames is defined to be one-for-one with physical fields
+ * of the rowtype (although dropped columns shouldn't appear in the
+ * RECORD case, so this fine point is currently moot).
  */
 typedef struct RowExpr
 {


^ permalink  raw  reply  [nested|flat] 14+ messages in thread

* Re: ExecTypeSetColNames is fundamentally broken
@ 2021-12-06 20:30  Robert Haas <[email protected]>
  parent: Tom Lane <[email protected]>
  0 siblings, 1 reply; 14+ messages in thread

From: Robert Haas @ 2021-12-06 20:30 UTC (permalink / raw)
  To: Tom Lane <[email protected]>; +Cc: PostgreSQL Hackers <[email protected]>

On Sun, Dec 5, 2021 at 1:46 PM Tom Lane <[email protected]> wrote:
> So 0001 attached fixes this by revoking the decision to apply
> ExecTypeSetColNames in cases where a Var or RowExpr is declared
> to return a named composite type.  This causes a couple of regression
> test results to change, but they are ones that were specifically
> added to exercise this behavior that we now see to be invalid.

I don't understand the code so I can't comment on the code, but I find
the regression test changes pretty suspect. Attaching any alias list
to the RTE ought to rename the output columns for all purposes, not
just the ones we as implementers find convenient. I understand that we
have to do *something* here and that the present behavior is buggy and
unacceptable ... but I'm not sure I accept that the only possible fix
is the one you propose here.

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





^ permalink  raw  reply  [nested|flat] 14+ messages in thread

* Re: ExecTypeSetColNames is fundamentally broken
@ 2021-12-06 21:05  Tom Lane <[email protected]>
  parent: Robert Haas <[email protected]>
  0 siblings, 1 reply; 14+ messages in thread

From: Tom Lane @ 2021-12-06 21:05 UTC (permalink / raw)
  To: Robert Haas <[email protected]>; +Cc: PostgreSQL Hackers <[email protected]>

Robert Haas <[email protected]> writes:
> I don't understand the code so I can't comment on the code, but I find
> the regression test changes pretty suspect. Attaching any alias list
> to the RTE ought to rename the output columns for all purposes, not
> just the ones we as implementers find convenient.

Well, that was what I thought when I wrote bf7ca1587, but it leads
to logical contradictions.  Consider

create table t (a int, b int);

create function f(t) returns ... ;

select f(t) from t;

select f(t) from t(x,y);

If we adopt the "rename for all purposes" interpretation, then
the second SELECT must fail, because what f() is being passed is
no longer of type t.  If you ask me, that'll be a bigger problem
for users than the change I'm proposing (quite independently of
how hard it might be to implement).  It certainly will break
a behavior that goes back much further than bf7ca1587.

			regards, tom lane





^ permalink  raw  reply  [nested|flat] 14+ messages in thread

* Re: ExecTypeSetColNames is fundamentally broken
@ 2021-12-07 16:19  Robert Haas <[email protected]>
  parent: Tom Lane <[email protected]>
  0 siblings, 1 reply; 14+ messages in thread

From: Robert Haas @ 2021-12-07 16:19 UTC (permalink / raw)
  To: Tom Lane <[email protected]>; +Cc: PostgreSQL Hackers <[email protected]>

On Mon, Dec 6, 2021 at 4:05 PM Tom Lane <[email protected]> wrote:
> Well, that was what I thought when I wrote bf7ca1587, but it leads
> to logical contradictions.  Consider
>
> create table t (a int, b int);
>
> create function f(t) returns ... ;
>
> select f(t) from t;
>
> select f(t) from t(x,y);
>
> If we adopt the "rename for all purposes" interpretation, then
> the second SELECT must fail, because what f() is being passed is
> no longer of type t.  If you ask me, that'll be a bigger problem
> for users than the change I'm proposing (quite independently of
> how hard it might be to implement).  It certainly will break
> a behavior that goes back much further than bf7ca1587.

For me, the second SELECT does fail:

rhaas=# select f(t) from t(x,y);
ERROR:  column "x" does not exist
LINE 1: select f(t) from t(x,y);
                           ^

If it didn't fail, what would the behavior be? I suppose you could
make an argument for trying to match up the columns by position, but
if so this ought to work:

rhaas=# create table u(a int, b int);
CREATE TABLE
rhaas=# select f(u) from u;
ERROR:  function f(u) does not exist
rhaas=# select f(u::t) from u;
ERROR:  cannot cast type u to t

Matching columns by name can't work because the names don't match.
Matching columns by position does not work. So if that example
succeeds, the only real explanation is that we magically know that
we've still got a value of type t despite the user's best attempt to
decree otherwise. I know PostgreSQL sometimes ... does things like
that. I have no idea why anyone would consider it a desirable
behavior, though.

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





^ permalink  raw  reply  [nested|flat] 14+ messages in thread

* Re: ExecTypeSetColNames is fundamentally broken
@ 2021-12-07 17:30  Tom Lane <[email protected]>
  parent: Robert Haas <[email protected]>
  0 siblings, 1 reply; 14+ messages in thread

From: Tom Lane @ 2021-12-07 17:30 UTC (permalink / raw)
  To: Robert Haas <[email protected]>; +Cc: PostgreSQL Hackers <[email protected]>

Robert Haas <[email protected]> writes:
> On Mon, Dec 6, 2021 at 4:05 PM Tom Lane <[email protected]> wrote:
>> select f(t) from t(x,y);
>> 
>> If we adopt the "rename for all purposes" interpretation, then
>> the second SELECT must fail, because what f() is being passed is
>> no longer of type t.

> For me, the second SELECT does fail:

> rhaas=# select f(t) from t(x,y);
> ERROR:  column "x" does not exist

Ah, sorry, I fat-fingered the alias syntax.  Here's a tested example:

regression=# create table t (a int, b int);
CREATE TABLE
regression=# insert into t values(11,12);
INSERT 0 1
regression=# create function f(t) returns int as 'select $1.a' language sql;
CREATE FUNCTION
regression=# select f(t) from t as t(x,y);
 f  
----
 11
(1 row)

If we consider that the alias renames the columns "for all purposes",
how is it okay for f() to select the "a" column?

Another way to phrase the issue is that the column names seen
by f() are currently different from those seen by row_to_json():

regression=# select row_to_json(t) from t as t(x,y);
   row_to_json   
-----------------
 {"x":11,"y":12}
(1 row)

and that seems hard to justify.

			regards, tom lane





^ permalink  raw  reply  [nested|flat] 14+ messages in thread

* Re: ExecTypeSetColNames is fundamentally broken
@ 2021-12-07 17:58  Robert Haas <[email protected]>
  parent: Tom Lane <[email protected]>
  0 siblings, 1 reply; 14+ messages in thread

From: Robert Haas @ 2021-12-07 17:58 UTC (permalink / raw)
  To: Tom Lane <[email protected]>; +Cc: PostgreSQL Hackers <[email protected]>

On Tue, Dec 7, 2021 at 12:30 PM Tom Lane <[email protected]> wrote:
> If we consider that the alias renames the columns "for all purposes",
> how is it okay for f() to select the "a" column?

I'd say it isn't.

> Another way to phrase the issue is that the column names seen
> by f() are currently different from those seen by row_to_json():
>
> regression=# select row_to_json(t) from t as t(x,y);
>    row_to_json
> -----------------
>  {"x":11,"y":12}
> (1 row)
>
> and that seems hard to justify.

Yeah, I agree. The problem I have here is that, with your proposed
fix, it still won't be very consistent. True, row_to_json() and f()
will both see the unaliased column names ... but a straight select *
from t as t(x,y) will show the aliased names. That's unarguably better
than corrupting your data, but it seems "astonishing" in the POLA
sense.

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





^ permalink  raw  reply  [nested|flat] 14+ messages in thread

* Re: ExecTypeSetColNames is fundamentally broken
@ 2021-12-07 18:19  Tom Lane <[email protected]>
  parent: Robert Haas <[email protected]>
  0 siblings, 0 replies; 14+ messages in thread

From: Tom Lane @ 2021-12-07 18:19 UTC (permalink / raw)
  To: Robert Haas <[email protected]>; +Cc: PostgreSQL Hackers <[email protected]>

Robert Haas <[email protected]> writes:
> On Tue, Dec 7, 2021 at 12:30 PM Tom Lane <[email protected]> wrote:
>> If we consider that the alias renames the columns "for all purposes",
>> how is it okay for f() to select the "a" column?

> I'd say it isn't.

In a green field I'd probably agree with you, but IMO that will
break far too much existing SQL code.

It'd cause problems for us too, not only end-users.  As an example,
ruleutils.c would have to avoid attaching new column aliases to
tables that are referenced as whole-row Vars.  I'm not very sure
that that's even possible without creating insurmountable ambiguity
issues.  There are also fun issues around what happens to a stored
query after a table column rename.  Right now the query acts as
though it uses the old name as a column alias, and that introduces
no semantic problem; but that behavior would no longer be
acceptable.

So the alternatives I see are to revert what bf7ca1587 tried to do
here, or to try to make it work that way across-the-board, which
implies (a) a very much larger amount of work, and (b) breaking
important behaviors that are decades older than that commit.
It's not even entirely clear that we could get to complete
consistency if we went down that path.

			regards, tom lane





^ permalink  raw  reply  [nested|flat] 14+ messages in thread

* Re: Reducing memory consumed by RestrictInfo list translations in partitionwise join planning
@ 2025-04-01 08:01  Amit Langote <[email protected]>
  0 siblings, 1 reply; 14+ messages in thread

From: Amit Langote @ 2025-04-01 08:01 UTC (permalink / raw)
  To: Ashutosh Bapat <[email protected]>; +Cc: David Rowley <[email protected]>; Alvaro Herrera <[email protected]>; Dmitry Dolgov <[email protected]>; [email protected]; vignesh C <[email protected]>; pgsql-hackers; Richard Guo <[email protected]>

On Mon, Mar 31, 2025 at 7:00 PM Ashutosh Bapat
<[email protected]> wrote:
> On Mon, Mar 31, 2025 at 8:27 AM David Rowley <[email protected]> wrote:
> >
> > On Sat, 29 Mar 2025 at 05:46, Ashutosh Bapat
> > <[email protected]> wrote:
> > > PFA patches. 0001 and 0002 are the same as the previous set. 0003
> > > changes the initial hash table size to the length of ec_derives.
> >
> > I'm just not following the logic in making it the length of the
> > ec_derives List. If you have 32 buckets and try to insert 32 elements,
> > you're guaranteed to need a resize after inserting 28 elements. See
> > the grow_threshold logic SH_UPDATE_PARAMETERS(). The point of making
> > it 64 was to ensure the table is never unnecessarily sparse and to
> > also ensure we make it at least big enough for the minimum number of
> > ec_derives that we're about to insert.
>
> If I am reading SH_CREATE correctly, it will initially set the size to
> 32/.9 = 36 and in SH_UPDATE_PARAMETERS will set grow_threshold to 36 *
> .9 = 32. So it will leave some room even after inserting the initial
> elements. But looking at SH_INSERT_HASH_INTERNAL(), it will soon
> expand the table even if there's space. We certainly need a size much
> more than 32. 32 is an arbitrary/empirical threshold to create a hash
> table. Using that threshold as initial hash table size means the table
> size would be arbitrary too. Using twice the  length of
> ec_derives_list seems more reasonable since the length will decide the
> initial number of entries.

I think David’s suggestion to use 64 as the fixed initial size is
simpler and more predictable. Since list_length * 2 will always be >=
64 anyway, unless we expect clause counts to grow significantly right
after the threshold, the fixed size avoids the need to reason about
sizing heuristics and keeps the logic clearer.

It also lines up well with David’s point -- 64 strikes a good balance
for both memory usage and CPU efficiency. Unless there’s a strong
reason to favor dynamic sizing, I’d prefer to stick with the fixed 64.

> > I think you'd be better off coming up with some logic like putting the
> > lowest pointer value'd EM first in the key and ensure that all lookups
> > do that too by wrapping the lookups in some helper function. That'll
> > half the number of hash table entries at the cost of some very cheap
> > comparisons.
>
> That's a good suggestion. I searched for C standard documentation
> which specifies that the pointer comparison, especially inequality, is
> stable and safe to use. But I didn't find any. While according to the
> C standard, the result
> of comparison between pointers within the same array or a struct is
> specified, that between pointers from two different objects is
> unspecified. The existing code relies on the EM pointers being stable
> and also relies on equality between
> them to be stable. It has withstood the test of time and a variety of
> compilers. Hence I think it should be safe to rely on pointer
> comparisons being stable. But since I didn't find any documentation
> which confirms it, I have left those changes as a separate patch. Some
> internet sources discussing pointer comparison can be found at [1],
> [2] (which mentions the C standard but doesn't provide a link).
>
> PFA the next patchset
>
> 0001, 0002 are same as in the previous set
> 0003 changes the initial hash table size to
> length(ec->ec_derives_list) * 4 to accomodate for commuted entries.
> 0004 uses canonical keys per your suggestion and also reduces the
> initial hash table size to  length(ec->ec_derives_list) * 2.

Thanks for the new patches.

As David suggested off-list, it seems better to have a static inline
function for the key canonicalization logic than to duplicate it in
the insert and lookup paths, as done in your 0004.

Also, using macros for the ec_derives_hash threshold and initial size
seems cleaner than hardcoding the values.

I’ve combined your 0003 and 0004 into the attached 0003, which:

* Adds the canonicalization logic via fill_ec_derives_key().

* Replaces literal values for threshold and initial size with macros,
defined near the key and entry types.

* Updates code comments to reflect the new design.

I wasn’t sure why you removed this comment:

- * We do not attempt a second lookup with EMs swapped when using the hash
- * table; such clauses are inserted under both orderings at the time of
- * insertion.

We still avoid the second lookup, but the reason has changed -- we now
canonicalize keys during both insertion and lookup, which makes the
second probe unnecessary. So I rewrote it as:

+ * We do not attempt a second lookup with EMs swapped, because the key is
+ * canonicalized during both insertion and lookup.

0003’s commit message includes a blurb I plan to paste into the main
patch’s message (with minor tweaks) when we squash the patches.

-- 
Thanks, Amit Langote


Attachments:

  [application/octet-stream] v6-0003-Canonicalize-keys-for-derived-clause-hash-table.patch (7.9K, ../../CA+HiwqHfheOxVxHTZXzdkhdkKw2LM83+wuudJRp86bz4UkPMtA@mail.gmail.com/2-v6-0003-Canonicalize-keys-for-derived-clause-hash-table.patch)
  download | inline diff:
From 47593262e8f6dd37aa29ab6dfda82ec00c57fa09 Mon Sep 17 00:00:00 2001
From: Amit Langote <[email protected]>
Date: Tue, 1 Apr 2025 16:06:48 +0900
Subject: [PATCH v6 3/3] Canonicalize keys for derived clause hash table

Derived clauses are now stored in the ec_derives_hash using canonicalized
keys: the lower-addressed EquivalenceMember is always placed in em1, and
the higher-addressed one in em2. This avoids storing or searching for
both permutations of the same clause and eliminates the need for
redundant lookups.

For clauses involving a constant EM, only the non-constant EM is included
in the key; em1 is set to NULL and em2 to the non-constant EM.

The initial hash table size is set to twice the length of ec_derives_list
to avoid immediate resizing.

This design follows suggestions by David Rowley, who proposed both the
key canonicalization to reduce entry count and guidance on sizing the
hash table to balance memory usage and CPU efficiency.
---
 src/backend/optimizer/path/equivclass.c | 146 ++++++++++++++----------
 1 file changed, 84 insertions(+), 62 deletions(-)

diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c
index b988db4d7b3..a1bccbdd38f 100644
--- a/src/backend/optimizer/path/equivclass.c
+++ b/src/backend/optimizer/path/equivclass.c
@@ -88,7 +88,9 @@ static RestrictInfo *ec_search_derived_clause_for_ems(PlannerInfo *root,
 													  EquivalenceClass *parent_ec);
 
 /*
- * Hash key identifying a derived clause by EquivalenceMembers and parent EC.
+ * Hash key identifying a derived clause.
+ *
+ * See fill_ec_derives_key() for details on canonical key construction.
  */
 typedef struct
 {
@@ -3450,15 +3452,22 @@ get_common_eclass_indexes(PlannerInfo *root, Relids relids1, Relids relids2)
 
 /*
  * ec_build_derives_hash
- *	  Construct the auxiliary hash table for derived clauses.
+ *	  Construct the auxiliary hash table for derived clause lookups.
  */
 static void
 ec_build_derives_hash(PlannerInfo *root, EquivalenceClass *ec)
 {
 	Assert(!ec->ec_derives_hash);
 
-	/* Create the hash table */
-	ec->ec_derives_hash = derives_create(root->planner_cxt, 256, NULL);
+	/*
+	 * Create the hash table.
+	 *
+	 * We set the initial size to twice the number of clauses in
+	 * ec_derives_list to avoid immediate resizing during insertion.
+	 */
+	ec->ec_derives_hash = derives_create(root->planner_cxt,
+										 list_length(ec->ec_derives_list) * 2,
+										 NULL);
 
 	foreach_node(RestrictInfo, rinfo, ec->ec_derives_list)
 		ec_add_clause_to_derives_hash(ec, rinfo);
@@ -3495,10 +3504,56 @@ ec_add_derived_clauses(EquivalenceClass *ec, List *clauses)
 			ec_add_clause_to_derives_hash(ec, rinfo);
 }
 
+/*
+ * fill_ec_derives_key
+ *		Compute a canonical key for ec_derives_hash lookup or insertion.
+ *
+ * Derived clauses are looked up using a pair of EquivalenceMembers and a
+ * parent EquivalenceClass. To avoid storing or searching for both EM orderings,
+ * we canonicalize the key:
+ *
+ * - For clauses involving two non-constant EMs, we order the EMs by address
+ *   and place the lower one first.
+ * - For clauses involving a constant EM, the caller must pass the non-constant
+ *   EM as leftem; we then set em1 = NULL and em2 = leftem.
+ */
+static inline void
+fill_ec_derives_key(ECDerivesKey *key,
+					EquivalenceMember *leftem,
+					EquivalenceMember *rightem,
+					EquivalenceClass *parent_ec)
+{
+	Assert(leftem);				/* Always required for lookup or insertion */
+
+	/*
+	 * Clauses with a constant EM are always stored and looked up using only
+	 * the non-constant EM, with the other key slot set to NULL.
+	 */
+	if (rightem == NULL)
+	{
+		key->em1 = NULL;
+		key->em2 = leftem;
+	}
+	else if (leftem < rightem)
+	{
+		key->em1 = leftem;
+		key->em2 = rightem;
+	}
+	else
+	{
+		key->em1 = rightem;
+		key->em2 = leftem;
+	}
+	key->parent_ec = parent_ec;
+}
+
 /*
  * ec_add_clause_to_derives_hash
- *      Add a derived clause to the ec_derives_hash of the given
- *      EquivalenceClass.
+ *		Add a derived clause to ec_derives_hash for the given EquivalenceClass.
+ *
+ * Each clause is inserted under a canonicalized key. For constant-containing
+ * clauses, only the non-constant EM is used for lookup; see comments in
+ * fill_ec_derives_key().
  */
 static void
 ec_add_clause_to_derives_hash(EquivalenceClass *ec, RestrictInfo *rinfo)
@@ -3513,49 +3568,24 @@ ec_add_clause_to_derives_hash(EquivalenceClass *ec, RestrictInfo *rinfo)
 	 */
 	Assert(!rinfo->left_em->em_is_const);
 
-	if (rinfo->right_em->em_is_const)
-	{
-		/*
-		 * Clauses containing a constant are never considered redundant, so
-		 * parent_ec is not set.
-		 */
-		Assert(!rinfo->parent_ec);
-
-		/*
-		 * find_derived_clause_for_ec_member() performs lookup of a clause
-		 * involving a constant using only the non-constant EM and NULL for
-		 * the RHS. Since there's only one constant EM per EC, we don't need
-		 * to store or match it during lookup.  We set key.em2 = NULL to
-		 * reflect this.
-		 */
-		key.em1 = rinfo->left_em;
-		key.em2 = NULL;
-		key.parent_ec = rinfo->parent_ec;
-		entry = derives_insert(ec->ec_derives_hash, key, &found);
-		Assert(!found);
-		entry->rinfo = rinfo;
-	}
-	else
-	{
-		key.em1 = rinfo->left_em;
-		key.em2 = rinfo->right_em;
-		key.parent_ec = rinfo->parent_ec;
-		entry = derives_insert(ec->ec_derives_hash, key, &found);
-		Assert(!found);
-		entry->rinfo = rinfo;
+	/*
+	 * Clauses containing a constant are never considered redundant, so
+	 * parent_ec is not set.
+	 */
+	Assert(!rinfo->parent_ec || !rinfo->right_em->em_is_const);
 
-		/*
-		 * Insert the clause under the given EM pair key, and also under the
-		 * reverse order. This ensures we can find the clause regardless of
-		 * the order in which EMs are passed to the lookup function.
-		 */
-		key.em1 = rinfo->right_em;
-		key.em2 = rinfo->left_em;
-		key.parent_ec = rinfo->parent_ec;
-		entry = derives_insert(ec->ec_derives_hash, key, &found);
-		Assert(!found);
-		entry->rinfo = rinfo;
-	}
+	/*
+	 * See fill_ec_derives_key() for details: we use a canonicalized key
+	 * to avoid storing both EM orderings. For constant EMs, only the
+	 * non-constant EM is included in the key.
+	 */
+	fill_ec_derives_key(&key,
+						rinfo->left_em,
+						rinfo->right_em->em_is_const ? NULL : rinfo->right_em,
+						rinfo->parent_ec);
+	entry = derives_insert(ec->ec_derives_hash, key, &found);
+	Assert(!found);
+	entry->rinfo = rinfo;
 }
 
 /*
@@ -3623,13 +3653,12 @@ ec_search_clause_for_ems(PlannerInfo *root, EquivalenceClass *ec,
  * If the number of derived clauses exceeds a threshold, switch to hash table
  * lookup; otherwise, scan ec_derives_list linearly.
  *
- * Clauses involving constants are looked up using only the non-const EM
- * (leftem) and a NULL rightem. In that case, we expect to find a clause with
- * a constant on the RHS.
+ * Clauses involving constants are looked up using only the non-constant EM
+ * (leftem), with rightem set to NULL. In that case, we expect to find a
+ * clause with a constant on the RHS.
  *
- * We do not attempt a second lookup with EMs swapped when using the hash
- * table; such clauses are inserted under both orderings at the time of
- * insertion.
+ * We do not attempt a second lookup with EMs swapped, because the key is
+ * canonicalized during both insertion and lookup.
  */
 static RestrictInfo *
 ec_search_derived_clause_for_ems(PlannerInfo *root, EquivalenceClass *ec,
@@ -3651,14 +3680,7 @@ ec_search_derived_clause_for_ems(PlannerInfo *root, EquivalenceClass *ec,
 		RestrictInfo *rinfo;
 		ECDerivesEntry *entry;
 
-		/*
-		 * See ec_add_clause_to_derives_hash() for rationale: derived clauses
-		 * are inserted into the hash table under both (em1, em2) and (em2,
-		 * em1), so a single lookup with the original order is sufficient.
-		 */
-		key.em1 = leftem;
-		key.em2 = rightem;
-		key.parent_ec = parent_ec;
+		fill_ec_derives_key(&key, leftem, rightem, parent_ec);
 		entry = derives_lookup(ec->ec_derives_hash, key);
 		if (entry)
 		{
-- 
2.43.0



  [application/octet-stream] v6-0001-Add-assertion-to-verify-derived-clause-has-consta.patch (1.5K, ../../CA+HiwqHfheOxVxHTZXzdkhdkKw2LM83+wuudJRp86bz4UkPMtA@mail.gmail.com/3-v6-0001-Add-assertion-to-verify-derived-clause-has-consta.patch)
  download | inline diff:
From d9335e86fc37dfd9d2c810ec4e03df9fa10c2b81 Mon Sep 17 00:00:00 2001
From: Amit Langote <[email protected]>
Date: Mon, 24 Mar 2025 21:17:46 +0900
Subject: [PATCH v6 1/3] Add assertion to verify derived clause has constant
 RHS

find_derived_clause_for_ec_member() searches for a previously-derived
clause that equates a non-constant EquivalenceMember to a constant.
It is only called for EquivalenceClasses with ec_has_const set, and
with a non-constant member as the target.

The matched clause is expected to have the non-constant member on the
left-hand side and the constant EquivalenceMember on the right.

Assert that the RHS is indeed a constant, to catch violations of this
structure and enforce assumptions made by
generate_base_implied_equalities_const().

Author: Ashutosh Bapat <[email protected]>
Discussion: https://postgr.es/m/CAExHW5scMxyFRqOFE6ODmBiW2rnVBEmeEcA-p4W_CyuEikURdA@mail.gmail.com
---
 src/backend/optimizer/path/equivclass.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c
index 0f9ecf5ee8b..493a95d26cc 100644
--- a/src/backend/optimizer/path/equivclass.c
+++ b/src/backend/optimizer/path/equivclass.c
@@ -2664,7 +2664,10 @@ find_derived_clause_for_ec_member(EquivalenceClass *ec,
 		 * members on the left side of derived clauses.
 		 */
 		if (rinfo->left_em == em)
+		{
+			Assert(rinfo->right_em->em_is_const);
 			return rinfo;
+		}
 	}
 	return NULL;
 }
-- 
2.43.0



  [application/octet-stream] v6-0002-Make-derived-clause-lookup-in-EquivalenceClass-mo.patch (25.5K, ../../CA+HiwqHfheOxVxHTZXzdkhdkKw2LM83+wuudJRp86bz4UkPMtA@mail.gmail.com/4-v6-0002-Make-derived-clause-lookup-in-EquivalenceClass-mo.patch)
  download | inline diff:
From 3362bded420d011e4ac446d9958c71ab9b19cc8f Mon Sep 17 00:00:00 2001
From: Amit Langote <[email protected]>
Date: Tue, 25 Mar 2025 20:36:26 +0900
Subject: [PATCH v6 2/3] Make derived clause lookup in EquivalenceClass more
 efficient

Previously, derived clauses were stored in ec_derives, a List of
RestrictInfos. These clauses are looked up later using the left and
right EquivalenceMembers and the clause's parent EC, typically during
join clause generation.

This lookup becomes expensive in queries with many partitions or
joins, where ec_derives may contain thousands of entries. In
particular, create_join_clause() can spend significant time scanning
this list.

To improve performance, a hash table (ec_derives_hash) is now built
when ec_derives_list exceeds 32 entries.  This is the same threshold
used for join_rel_hash. To reflect this dual structure, the
ec_derives field is renamed to ec_derives_list. The list is retained
alongside the hash table to support _outEquivalenceClass() and
EquivalenceClass merging, which both operate on ec_derives_list.

Lookup logic is updated to consult the hash table when available. For
ECs with a constant EM, lookups are done using only the non-constant
EM; the constant EM is assumed to be unique and may not be available
at lookup time. In such cases, the hash table stores em2 = NULL, which
can be matched by using NULL for em2 in the lookup key.

An assertion originally placed in find_derived_clause_for_ec_member()
is moved into ec_search_derived_clause_for_ems() to enforce the same
invariant in both hash and list lookup paths.

The new ec_clear_derived_clauses() always frees ec_derives_list, even
though some of the original code paths that cleared the old ec_derives
field did not. This ensures consistent cleanup and avoids leaking
memory when the list grows large.

Author: Ashutosh Bapat <[email protected]>
Reviewed-by: Amit Langote <[email protected]>
Tested-by: Dmitry Dolgov <[email protected]>
Tested-by: Alvaro Herrera <[email protected]>
Tested-by: Amit Langote <[email protected]>
Discussion: https://postgr.es/m/CAExHW5vZiQtWU6moszLP5iZ8gLX_ZAUbgEX0DxGLx9PGWCtqUg@mail.gmail.com
---
 src/backend/nodes/outfuncs.c              |   3 +-
 src/backend/optimizer/path/costsize.c     |   3 +-
 src/backend/optimizer/path/equivclass.c   | 403 ++++++++++++++++++----
 src/backend/optimizer/plan/analyzejoins.c |   5 +-
 src/include/nodes/pathnodes.h             |  17 +-
 src/include/optimizer/paths.h             |   4 +-
 src/tools/pgindent/typedefs.list          |   2 +
 7 files changed, 357 insertions(+), 80 deletions(-)

diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index bb9bdd67192..557f06e344f 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -467,7 +467,8 @@ _outEquivalenceClass(StringInfo str, const EquivalenceClass *node)
 	WRITE_OID_FIELD(ec_collation);
 	WRITE_NODE_FIELD(ec_members);
 	WRITE_NODE_FIELD(ec_sources);
-	WRITE_NODE_FIELD(ec_derives);
+	/* Only ec_derives_list is written; hash is not serialized. */
+	WRITE_NODE_FIELD(ec_derives_list);
 	WRITE_BITMAPSET_FIELD(ec_relids);
 	WRITE_BOOL_FIELD(ec_has_const);
 	WRITE_BOOL_FIELD(ec_has_volatile);
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index f6f77b8fe19..c474c7a06af 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -5848,7 +5848,8 @@ get_foreign_key_join_selectivity(PlannerInfo *root,
 				if (ec && ec->ec_has_const)
 				{
 					EquivalenceMember *em = fkinfo->fk_eclass_member[i];
-					RestrictInfo *rinfo = find_derived_clause_for_ec_member(ec,
+					RestrictInfo *rinfo = find_derived_clause_for_ec_member(root,
+																			ec,
 																			em);
 
 					if (rinfo)
diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c
index 493a95d26cc..b988db4d7b3 100644
--- a/src/backend/optimizer/path/equivclass.c
+++ b/src/backend/optimizer/path/equivclass.c
@@ -20,6 +20,7 @@
 
 #include "access/stratnum.h"
 #include "catalog/pg_type.h"
+#include "common/hashfn.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/appendinfo.h"
@@ -72,7 +73,50 @@ static Bitmapset *get_eclass_indexes_for_relids(PlannerInfo *root,
 												Relids relids);
 static Bitmapset *get_common_eclass_indexes(PlannerInfo *root, Relids relids1,
 											Relids relids2);
+static void ec_build_derives_hash(PlannerInfo *root, EquivalenceClass *ec);
+static void ec_add_derived_clauses(EquivalenceClass *ec, List *clauses);
+static void ec_add_derived_clause(EquivalenceClass *ec, RestrictInfo *clause);
+static void ec_add_clause_to_derives_hash(EquivalenceClass *ec, RestrictInfo *rinfo);
+static RestrictInfo *ec_search_clause_for_ems(PlannerInfo *root, EquivalenceClass *ec,
+											  EquivalenceMember *leftem,
+											  EquivalenceMember *rightem,
+											  EquivalenceClass *parent_ec);
+static RestrictInfo *ec_search_derived_clause_for_ems(PlannerInfo *root,
+													  EquivalenceClass *ec,
+													  EquivalenceMember *leftem,
+													  EquivalenceMember *rightem,
+													  EquivalenceClass *parent_ec);
 
+/*
+ * Hash key identifying a derived clause by EquivalenceMembers and parent EC.
+ */
+typedef struct
+{
+	EquivalenceMember *em1;
+	EquivalenceMember *em2;
+	EquivalenceClass *parent_ec;
+} ECDerivesKey;
+
+/* Hash table entry in ec_derives_hash. */
+typedef struct
+{
+	uint32		status;
+	ECDerivesKey key;
+	RestrictInfo *rinfo;
+} ECDerivesEntry;
+
+#define SH_PREFIX               derives
+#define SH_ELEMENT_TYPE			ECDerivesEntry
+#define SH_KEY_TYPE             ECDerivesKey
+#define SH_KEY                  key
+#define SH_HASH_KEY(tb, key)	\
+	hash_bytes((const unsigned char *) &(key), sizeof(ECDerivesKey))
+#define SH_EQUAL(tb, a, b)	\
+	((a).em1 == (b).em1 && (a).em2 == (b).em2 && (a).parent_ec == (b).parent_ec)
+#define SH_SCOPE                static inline
+#define SH_DECLARE
+#define SH_DEFINE
+#include "lib/simplehash.h"
 
 /*
  * process_equivalence
@@ -342,7 +386,12 @@ process_equivalence(PlannerInfo *root,
 		 */
 		ec1->ec_members = list_concat(ec1->ec_members, ec2->ec_members);
 		ec1->ec_sources = list_concat(ec1->ec_sources, ec2->ec_sources);
-		ec1->ec_derives = list_concat(ec1->ec_derives, ec2->ec_derives);
+
+		/*
+		 * Appends ec2's derived clauses to ec1->ec_derives_list and adds them
+		 * to ec1->ec_derives_hash if present.
+		 */
+		ec_add_derived_clauses(ec1, ec2->ec_derives_list);
 		ec1->ec_relids = bms_join(ec1->ec_relids, ec2->ec_relids);
 		ec1->ec_has_const |= ec2->ec_has_const;
 		/* can't need to set has_volatile */
@@ -355,7 +404,7 @@ process_equivalence(PlannerInfo *root,
 		/* just to avoid debugging confusion w/ dangling pointers: */
 		ec2->ec_members = NIL;
 		ec2->ec_sources = NIL;
-		ec2->ec_derives = NIL;
+		ec_clear_derived_clauses(ec2);
 		ec2->ec_relids = NULL;
 		ec1->ec_sources = lappend(ec1->ec_sources, restrictinfo);
 		ec1->ec_min_security = Min(ec1->ec_min_security,
@@ -412,7 +461,8 @@ process_equivalence(PlannerInfo *root,
 		ec->ec_collation = collation;
 		ec->ec_members = NIL;
 		ec->ec_sources = list_make1(restrictinfo);
-		ec->ec_derives = NIL;
+		ec->ec_derives_list = NIL;
+		ec->ec_derives_hash = NULL;
 		ec->ec_relids = NULL;
 		ec->ec_has_const = false;
 		ec->ec_has_volatile = false;
@@ -671,7 +721,8 @@ get_eclass_for_sort_expr(PlannerInfo *root,
 	newec->ec_collation = collation;
 	newec->ec_members = NIL;
 	newec->ec_sources = NIL;
-	newec->ec_derives = NIL;
+	newec->ec_derives_list = NIL;
+	newec->ec_derives_hash = NULL;
 	newec->ec_relids = NULL;
 	newec->ec_has_const = false;
 	newec->ec_has_volatile = contain_volatile_functions((Node *) expr);
@@ -1026,8 +1077,8 @@ relation_can_be_sorted_early(PlannerInfo *root, RelOptInfo *rel,
  * scanning of the quals and before Path construction begins.
  *
  * We make no attempt to avoid generating duplicate RestrictInfos here: we
- * don't search ec_sources or ec_derives for matches.  It doesn't really
- * seem worth the trouble to do so.
+ * don't search existing source or derived clauses in the EC for matches.  It
+ * doesn't really seem worth the trouble to do so.
  */
 void
 generate_base_implied_equalities(PlannerInfo *root)
@@ -1188,11 +1239,11 @@ generate_base_implied_equalities_const(PlannerInfo *root,
 
 		/*
 		 * If the clause didn't degenerate to a constant, fill in the correct
-		 * markings for a mergejoinable clause, and save it in ec_derives. (We
-		 * will not re-use such clauses directly, but selectivity estimation
-		 * may consult the list later.  Note that this use of ec_derives does
-		 * not overlap with its use for join clauses, since we never generate
-		 * join clauses from an ec_has_const eclass.)
+		 * markings for a mergejoinable clause, and save it as a derived
+		 * clause. (We will not re-use such clauses directly, but selectivity
+		 * estimation may consult those later.  Note that this use of derived
+		 * clauses does not overlap with its use for join clauses, since we
+		 * never generate join clauses from an ec_has_const eclass.)
 		 */
 		if (rinfo && rinfo->mergeopfamilies)
 		{
@@ -1200,7 +1251,7 @@ generate_base_implied_equalities_const(PlannerInfo *root,
 			rinfo->left_ec = rinfo->right_ec = ec;
 			rinfo->left_em = cur_em;
 			rinfo->right_em = const_em;
-			ec->ec_derives = lappend(ec->ec_derives, rinfo);
+			ec_add_derived_clause(ec, rinfo);
 		}
 	}
 }
@@ -1265,10 +1316,10 @@ generate_base_implied_equalities_no_const(PlannerInfo *root,
 
 			/*
 			 * If the clause didn't degenerate to a constant, fill in the
-			 * correct markings for a mergejoinable clause.  We don't put it
-			 * in ec_derives however; we don't currently need to re-find such
-			 * clauses, and we don't want to clutter that list with non-join
-			 * clauses.
+			 * correct markings for a mergejoinable clause.  We don't record
+			 * it as a derived clause, since we don't currently need to
+			 * re-find such clauses, and don't want to clutter the
+			 * derived-clause set with non-join clauses.
 			 */
 			if (rinfo && rinfo->mergeopfamilies)
 			{
@@ -1369,7 +1420,7 @@ generate_base_implied_equalities_broken(PlannerInfo *root,
  * we consider different join paths, we avoid generating multiple copies:
  * whenever we select a particular pair of EquivalenceMembers to join,
  * we check to see if the pair matches any original clause (in ec_sources)
- * or previously-built clause (in ec_derives).  This saves memory and allows
+ * or previously-built derived clause.  This saves memory and allows
  * re-use of information cached in RestrictInfos.  We also avoid generating
  * commutative duplicates, i.e. if the algorithm selects "a.x = b.y" but
  * we already have "b.y = a.x", we return the existing clause.
@@ -1754,9 +1805,9 @@ generate_join_implied_equalities_broken(PlannerInfo *root,
 	/*
 	 * If we have to translate, just brute-force apply adjust_appendrel_attrs
 	 * to all the RestrictInfos at once.  This will result in returning
-	 * RestrictInfos that are not listed in ec_derives, but there shouldn't be
-	 * any duplication, and it's a sufficiently narrow corner case that we
-	 * shouldn't sweat too much over it anyway.
+	 * RestrictInfos that are not included in EC's derived clauses, but there
+	 * shouldn't be any duplication, and it's a sufficiently narrow corner
+	 * case that we shouldn't sweat too much over it anyway.
 	 *
 	 * Since inner_rel might be an indirect descendant of the baserel
 	 * mentioned in the ec_sources clauses, we have to be prepared to apply
@@ -1823,43 +1874,11 @@ create_join_clause(PlannerInfo *root,
 {
 	RestrictInfo *rinfo;
 	RestrictInfo *parent_rinfo = NULL;
-	ListCell   *lc;
 	MemoryContext oldcontext;
 
-	/*
-	 * Search to see if we already built a RestrictInfo for this pair of
-	 * EquivalenceMembers.  We can use either original source clauses or
-	 * previously-derived clauses, and a commutator clause is acceptable.
-	 *
-	 * We used to verify that opno matches, but that seems redundant: even if
-	 * it's not identical, it'd better have the same effects, or the operator
-	 * families we're using are broken.
-	 */
-	foreach(lc, ec->ec_sources)
-	{
-		rinfo = (RestrictInfo *) lfirst(lc);
-		if (rinfo->left_em == leftem &&
-			rinfo->right_em == rightem &&
-			rinfo->parent_ec == parent_ec)
-			return rinfo;
-		if (rinfo->left_em == rightem &&
-			rinfo->right_em == leftem &&
-			rinfo->parent_ec == parent_ec)
-			return rinfo;
-	}
-
-	foreach(lc, ec->ec_derives)
-	{
-		rinfo = (RestrictInfo *) lfirst(lc);
-		if (rinfo->left_em == leftem &&
-			rinfo->right_em == rightem &&
-			rinfo->parent_ec == parent_ec)
-			return rinfo;
-		if (rinfo->left_em == rightem &&
-			rinfo->right_em == leftem &&
-			rinfo->parent_ec == parent_ec)
-			return rinfo;
-	}
+	rinfo = ec_search_clause_for_ems(root, ec, leftem, rightem, parent_ec);
+	if (rinfo)
+		return rinfo;
 
 	/*
 	 * Not there, so build it, in planner context so we can re-use it. (Not
@@ -1923,7 +1942,7 @@ create_join_clause(PlannerInfo *root,
 	rinfo->left_em = leftem;
 	rinfo->right_em = rightem;
 	/* and save it for possible re-use */
-	ec->ec_derives = lappend(ec->ec_derives, rinfo);
+	ec_add_derived_clause(ec, rinfo);
 
 	MemoryContextSwitchTo(oldcontext);
 
@@ -2648,28 +2667,14 @@ match_eclasses_to_foreign_key_col(PlannerInfo *root,
  * Returns NULL if no such clause can be found.
  */
 RestrictInfo *
-find_derived_clause_for_ec_member(EquivalenceClass *ec,
+find_derived_clause_for_ec_member(PlannerInfo *root,
+								  EquivalenceClass *ec,
 								  EquivalenceMember *em)
 {
-	ListCell   *lc;
-
 	Assert(ec->ec_has_const);
 	Assert(!em->em_is_const);
-	foreach(lc, ec->ec_derives)
-	{
-		RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
 
-		/*
-		 * generate_base_implied_equalities_const will have put non-const
-		 * members on the left side of derived clauses.
-		 */
-		if (rinfo->left_em == em)
-		{
-			Assert(rinfo->right_em->em_is_const);
-			return rinfo;
-		}
-	}
-	return NULL;
+	return ec_search_derived_clause_for_ems(root, ec, em, NULL, NULL);
 }
 
 
@@ -3442,3 +3447,255 @@ get_common_eclass_indexes(PlannerInfo *root, Relids relids1, Relids relids2)
 	/* Calculate and return the common EC indexes, recycling the left input. */
 	return bms_int_members(rel1ecs, rel2ecs);
 }
+
+/*
+ * ec_build_derives_hash
+ *	  Construct the auxiliary hash table for derived clauses.
+ */
+static void
+ec_build_derives_hash(PlannerInfo *root, EquivalenceClass *ec)
+{
+	Assert(!ec->ec_derives_hash);
+
+	/* Create the hash table */
+	ec->ec_derives_hash = derives_create(root->planner_cxt, 256, NULL);
+
+	foreach_node(RestrictInfo, rinfo, ec->ec_derives_list)
+		ec_add_clause_to_derives_hash(ec, rinfo);
+}
+
+/*
+ * ec_add_derived_clause
+ *		Add a clause to the set of derived clauses for the given
+ *		EquivalenceClass. Always appends to ec_derives_list; also adds
+ *		to ec_derives_hash if it exists.
+ */
+static void
+ec_add_derived_clause(EquivalenceClass *ec, RestrictInfo *clause)
+{
+	ec->ec_derives_list = lappend(ec->ec_derives_list, clause);
+	if (ec->ec_derives_hash)
+		ec_add_clause_to_derives_hash(ec, clause);
+}
+
+/*
+ * ec_add_derived_clauses
+ *		Add a list of clauses to the set of clauses derived from the given
+ *		EquivalenceClass; adding to the list and hash table if needed.
+ *
+ * This function is similar to above function but optimized for adding multiple
+ * clauses at a time to the ec_derives_list.
+ */
+static void
+ec_add_derived_clauses(EquivalenceClass *ec, List *clauses)
+{
+	ec->ec_derives_list = list_concat(ec->ec_derives_list, clauses);
+	if (ec->ec_derives_hash)
+		foreach_node(RestrictInfo, rinfo, clauses)
+			ec_add_clause_to_derives_hash(ec, rinfo);
+}
+
+/*
+ * ec_add_clause_to_derives_hash
+ *      Add a derived clause to the ec_derives_hash of the given
+ *      EquivalenceClass.
+ */
+static void
+ec_add_clause_to_derives_hash(EquivalenceClass *ec, RestrictInfo *rinfo)
+{
+	ECDerivesKey key;
+	ECDerivesEntry *entry;
+	bool		found;
+
+	/*
+	 * Constants are always placed on the RHS; see
+	 * generate_base_implied_equalities_const().
+	 */
+	Assert(!rinfo->left_em->em_is_const);
+
+	if (rinfo->right_em->em_is_const)
+	{
+		/*
+		 * Clauses containing a constant are never considered redundant, so
+		 * parent_ec is not set.
+		 */
+		Assert(!rinfo->parent_ec);
+
+		/*
+		 * find_derived_clause_for_ec_member() performs lookup of a clause
+		 * involving a constant using only the non-constant EM and NULL for
+		 * the RHS. Since there's only one constant EM per EC, we don't need
+		 * to store or match it during lookup.  We set key.em2 = NULL to
+		 * reflect this.
+		 */
+		key.em1 = rinfo->left_em;
+		key.em2 = NULL;
+		key.parent_ec = rinfo->parent_ec;
+		entry = derives_insert(ec->ec_derives_hash, key, &found);
+		Assert(!found);
+		entry->rinfo = rinfo;
+	}
+	else
+	{
+		key.em1 = rinfo->left_em;
+		key.em2 = rinfo->right_em;
+		key.parent_ec = rinfo->parent_ec;
+		entry = derives_insert(ec->ec_derives_hash, key, &found);
+		Assert(!found);
+		entry->rinfo = rinfo;
+
+		/*
+		 * Insert the clause under the given EM pair key, and also under the
+		 * reverse order. This ensures we can find the clause regardless of
+		 * the order in which EMs are passed to the lookup function.
+		 */
+		key.em1 = rinfo->right_em;
+		key.em2 = rinfo->left_em;
+		key.parent_ec = rinfo->parent_ec;
+		entry = derives_insert(ec->ec_derives_hash, key, &found);
+		Assert(!found);
+		entry->rinfo = rinfo;
+	}
+}
+
+/*
+ * ec_clear_derived_clauses
+ *      Reset ec_derives_list and ec_derives_hash.
+ *
+ * We destroy the hash table explicitly, since it may consume significant
+ * space. The list holds the same set of entries and can become equally large
+ * when thousands of partitions are involved, so we free it as well -- even
+ * though we do not typically free lists.
+ */
+void
+ec_clear_derived_clauses(EquivalenceClass *ec)
+{
+	list_free(ec->ec_derives_list);
+	ec->ec_derives_list = NIL;
+
+	if (ec->ec_derives_hash)
+	{
+		derives_destroy(ec->ec_derives_hash);
+		ec->ec_derives_hash = NULL;
+	}
+}
+
+/*
+ * ec_search_clause_for_ems
+ *		Search for an existing RestrictInfo that equates the given pair
+ *		of EquivalenceMembers, either from ec_sources or ec_derives.
+ *
+ * We accept clauses in either operand order, so commutators are matched. We
+ * used to require matching operator OIDs, but dropped that since any
+ * semantically different operator here would indicate a broken operator
+ * family.
+ *
+ * Returns NULL if no matching clause is found.
+ */
+
+static RestrictInfo *
+ec_search_clause_for_ems(PlannerInfo *root, EquivalenceClass *ec,
+						 EquivalenceMember *leftem, EquivalenceMember *rightem,
+						 EquivalenceClass *parent_ec)
+{
+	/* Check original source clauses */
+	foreach_node(RestrictInfo, rinfo, ec->ec_sources)
+	{
+		if (rinfo->left_em == leftem &&
+			rinfo->right_em == rightem &&
+			rinfo->parent_ec == parent_ec)
+			return rinfo;
+		if (rinfo->left_em == rightem &&
+			rinfo->right_em == leftem &&
+			rinfo->parent_ec == parent_ec)
+			return rinfo;
+	}
+
+	/* Not found in ec_sources; search derived clauses */
+	return ec_search_derived_clause_for_ems(root, ec, leftem, rightem,
+											parent_ec);
+}
+
+/*
+ * ec_search_derived_clause_for_ems
+ *		Search for an existing derived clause between two EquivalenceMembers.
+ *
+ * If the number of derived clauses exceeds a threshold, switch to hash table
+ * lookup; otherwise, scan ec_derives_list linearly.
+ *
+ * Clauses involving constants are looked up using only the non-const EM
+ * (leftem) and a NULL rightem. In that case, we expect to find a clause with
+ * a constant on the RHS.
+ *
+ * We do not attempt a second lookup with EMs swapped when using the hash
+ * table; such clauses are inserted under both orderings at the time of
+ * insertion.
+ */
+static RestrictInfo *
+ec_search_derived_clause_for_ems(PlannerInfo *root, EquivalenceClass *ec,
+								 EquivalenceMember *leftem,
+								 EquivalenceMember *rightem,
+								 EquivalenceClass *parent_ec)
+{
+	/*
+	 * Switch to using hash lookup when list grows "too long". The threshold
+	 * is arbitrary and is known only here.
+	 */
+	if (!ec->ec_derives_hash && list_length(ec->ec_derives_list) >= 32)
+		ec_build_derives_hash(root, ec);
+
+	/* Perform hash table lookup if available */
+	if (ec->ec_derives_hash)
+	{
+		ECDerivesKey key;
+		RestrictInfo *rinfo;
+		ECDerivesEntry *entry;
+
+		/*
+		 * See ec_add_clause_to_derives_hash() for rationale: derived clauses
+		 * are inserted into the hash table under both (em1, em2) and (em2,
+		 * em1), so a single lookup with the original order is sufficient.
+		 */
+		key.em1 = leftem;
+		key.em2 = rightem;
+		key.parent_ec = parent_ec;
+		entry = derives_lookup(ec->ec_derives_hash, key);
+		if (entry)
+		{
+			rinfo = entry->rinfo;
+			Assert(rinfo);
+
+			/*
+			 * If this is a lookup in a const-containing EC, the RHS must be a
+			 * constant. The caller signals this by passing NULL for rightem.
+			 */
+			Assert(rightem || rinfo->right_em->em_is_const);
+			return rinfo;
+		}
+	}
+	else
+	{
+		/* Fallback to linear search over ec_derives_list */
+		foreach_node(RestrictInfo, rinfo, ec->ec_derives_list)
+		{
+			/* Handle special case: lookup by non-const EM alone */
+			if (!rightem &&
+				rinfo->left_em == leftem)
+			{
+				/* See the comment above in hash path for rationale. */
+				Assert(rinfo->right_em->em_is_const);
+				return rinfo;
+			}
+			if (rinfo->left_em == leftem &&
+				rinfo->right_em == rightem &&
+				rinfo->parent_ec == parent_ec)
+				return rinfo;
+			if (rinfo->left_em == rightem &&
+				rinfo->right_em == leftem &&
+				rinfo->parent_ec == parent_ec)
+				return rinfo;
+		}
+	}
+
+	return NULL;
+}
diff --git a/src/backend/optimizer/plan/analyzejoins.c b/src/backend/optimizer/plan/analyzejoins.c
index 8a8d4a2af33..ae20691ca91 100644
--- a/src/backend/optimizer/plan/analyzejoins.c
+++ b/src/backend/optimizer/plan/analyzejoins.c
@@ -749,7 +749,7 @@ remove_rel_from_eclass(EquivalenceClass *ec, SpecialJoinInfo *sjinfo,
 	 * drop them.  (At this point, any such clauses would be base restriction
 	 * clauses, which we'd not need anymore anyway.)
 	 */
-	ec->ec_derives = NIL;
+	ec_clear_derived_clauses(ec);
 }
 
 /*
@@ -1544,8 +1544,7 @@ update_eclasses(EquivalenceClass *ec, int from, int to)
 	list_free(ec->ec_members);
 	ec->ec_members = new_members;
 
-	list_free(ec->ec_derives);
-	ec->ec_derives = NULL;
+	ec_clear_derived_clauses(ec);
 
 	/* Update EC source expressions */
 	foreach_node(RestrictInfo, rinfo, ec->ec_sources)
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index c24a1fc8514..e29d26a1814 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -1403,6 +1403,18 @@ typedef struct JoinDomain
  * entry: consider SELECT random() AS a, random() AS b ... ORDER BY b,a.
  * So we record the SortGroupRef of the originating sort clause.
  *
+ * Derived equality clauses are stored in ec_derives_list. For small queries,
+ * this list is scanned directly during lookup. For larger queries -- e.g.,
+ * with many partitions or joins -- a hash table (ec_derives_hash) is built
+ * when the list grows beyond a threshold, for faster lookup. When present,
+ * the hash table contains the same RestrictInfos and is maintained alongside
+ * the list. We retain the list even when the hash is used to simplify
+ * serialization (e.g., in _outEquivalenceClass()) and support
+ * EquivalenceClass merging.
+ *
+ * In contrast, ec_sources holds equality clauses that appear directly in the
+ * query. These are typically few and do not require a hash table for lookup.
+ *
  * NB: if ec_merged isn't NULL, this class has been merged into another, and
  * should be ignored in favor of using the pointed-to class.
  *
@@ -1422,7 +1434,10 @@ typedef struct EquivalenceClass
 	Oid			ec_collation;	/* collation, if datatypes are collatable */
 	List	   *ec_members;		/* list of EquivalenceMembers */
 	List	   *ec_sources;		/* list of generating RestrictInfos */
-	List	   *ec_derives;		/* list of derived RestrictInfos */
+	List	   *ec_derives_list;	/* list of derived RestrictInfos */
+	struct derives_hash *ec_derives_hash;	/* optional hash table for fast
+											 * lookup; contains same
+											 * RestrictInfos as list */
 	Relids		ec_relids;		/* all relids appearing in ec_members, except
 								 * for child members (see below) */
 	bool		ec_has_const;	/* any pseudoconstants in ec_members? */
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index bc5dfd7db41..16dc4d5ee82 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -167,7 +167,8 @@ extern bool exprs_known_equal(PlannerInfo *root, Node *item1, Node *item2,
 extern EquivalenceClass *match_eclasses_to_foreign_key_col(PlannerInfo *root,
 														   ForeignKeyOptInfo *fkinfo,
 														   int colno);
-extern RestrictInfo *find_derived_clause_for_ec_member(EquivalenceClass *ec,
+extern RestrictInfo *find_derived_clause_for_ec_member(PlannerInfo *root,
+													   EquivalenceClass *ec,
 													   EquivalenceMember *em);
 extern void add_child_rel_equivalences(PlannerInfo *root,
 									   AppendRelInfo *appinfo,
@@ -197,6 +198,7 @@ extern bool eclass_useful_for_merging(PlannerInfo *root,
 extern bool is_redundant_derived_clause(RestrictInfo *rinfo, List *clauselist);
 extern bool is_redundant_with_indexclauses(RestrictInfo *rinfo,
 										   List *indexclauses);
+extern void ec_clear_derived_clauses(EquivalenceClass *ec);
 
 /*
  * pathkeys.c
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b66cecd8799..997abe1427c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -667,6 +667,8 @@ DumpableObjectWithAcl
 DynamicFileList
 DynamicZoneAbbrev
 EC_KEY
+ECDerivesKey
+ECDerivesEntry
 EDGE
 ENGINE
 EOM_flatten_into_method
-- 
2.43.0



^ permalink  raw  reply  [nested|flat] 14+ messages in thread

* Re: Reducing memory consumed by RestrictInfo list translations in partitionwise join planning
@ 2025-04-01 16:58  Ashutosh Bapat <[email protected]>
  parent: Amit Langote <[email protected]>
  0 siblings, 1 reply; 14+ messages in thread

From: Ashutosh Bapat @ 2025-04-01 16:58 UTC (permalink / raw)
  To: Amit Langote <[email protected]>; +Cc: David Rowley <[email protected]>; Alvaro Herrera <[email protected]>; Dmitry Dolgov <[email protected]>; [email protected]; vignesh C <[email protected]>; pgsql-hackers; Richard Guo <[email protected]>

On Tue, Apr 1, 2025 at 1:32 PM Amit Langote <[email protected]> wrote:
>
> I think David’s suggestion to use 64 as the fixed initial size is
> simpler and more predictable. Since list_length * 2 will always be >=
> 64 anyway, unless we expect clause counts to grow significantly right
> after the threshold, the fixed size avoids the need to reason about
> sizing heuristics and keeps the logic clearer.
>
> It also lines up well with David’s point -- 64 strikes a good balance
> for both memory usage and CPU efficiency. Unless there’s a strong
> reason to favor dynamic sizing, I’d prefer to stick with the fixed 64.

Consider following table and query (based on a similar table in
join.sql, which exercises find_derived_clause_for_ec_member() code
path.
#create table fkest (x integer, x10 integer, x10b integer, x100
integer, unique(x, x10, x100), foreign key (x, x10b, x100) references
fkest(x, x10, x100));
#select 'select * from fkest f1 join ' || string_agg(format('fkest f%s
on (f1.x = f%s.x and f1.x10 = f%s.x10b and f1.x100 = f%s.x100)', i, i,
i , i), ' join ') || ' where f1.x100 = 2' query from
generate_series(2, 100) i; \gset
#explain (summary) :query;

This is a 100-way self-join between foreign key and referenced key and
one of the foreign keys being set to a constant. This exercises the
case of derived clauses with constant EM.

When planning this query, all the 100 derived clauses containing the
constant EM are created first and then they are searched one by one
for every EM. Thus when the hash table is created in
find_derived_clause_for_ec_member()->ec_search_derived_clause_for_ems(),
the length of ec_derives_list is already 100, so the hash table will
be expanded while it's being filled up the first time, if we use
constant 64 as initial hash table size. This can be avoided if we use
list_length() * 2. The pattern for create_join_clause() however is
search then insert - so it will always create the hash table with
initial size 64 right when the list length is 32. Both these cases are
served well if we base the initial hash table size as a multiple of
list_length(ec_derives_list).

For the record, without the patch this query takes about 5800ms on
average for planning on my laptop. With the patch the planning time
reduces to about 5400ms - 6-7% of improvement if my approximate math
is correct.

>
> As David suggested off-list, it seems better to have a static inline
> function for the key canonicalization logic than to duplicate it in
> the insert and lookup paths, as done in your 0004.

Static inline function to fill the key in canonical form is a good
idea. Thanks for the patch.

>
> * Replaces literal values for threshold and initial size with macros,
> defined near the key and entry types.

I don't see this in your attached patches. Am I missing something?
It's still using list_length() for initial hash table size. But with
my explanation above, I believe we will keep it that way.

>
> I wasn’t sure why you removed this comment:
>
> - * We do not attempt a second lookup with EMs swapped when using the hash
> - * table; such clauses are inserted under both orderings at the time of
> - * insertion.

When I read it, I didn't understand why we mentioned a second lookup,
so I dropped it. But now I see that the comment is in the context of
two comparisons being done when using list. I have rephrased the
comment a bit to make this comparison clear.


>
> 0003’s commit message includes a blurb I plan to paste into the main
> patch’s message (with minor tweaks) when we squash the patches.

Slight correction in the first sentence of that blurb message.

Derived clauses are now stored in the ec_derives_hash using canonicalized
keys: the EquivalenceMember with lower memory address is always placed
in em1, and ...

+/*
+ * fill_ec_derives_key
+ * Compute a canonical key for ec_derives_hash lookup or insertion.
+ *
+ * Derived clauses are looked up using a pair of EquivalenceMembers and a
+ * parent EquivalenceClass. To avoid storing or searching for both EM
orderings,
+ * we canonicalize the key:
+ *
+ * - For clauses involving two non-constant EMs, we order the EMs by address
+ * and place the lower one first.
+ * - For clauses involving a constant EM, the caller must pass the non-constant
+ * EM as leftem; we then set em1 = NULL and em2 = leftem.
+ */
+static inline void
+fill_ec_derives_key(ECDerivesKey *key,
+ EquivalenceMember *leftem,
+ EquivalenceMember *rightem,
+ EquivalenceClass *parent_ec)
+{
+ Assert(leftem); /* Always required for lookup or insertion */
+
+ /*
+ * Clauses with a constant EM are always stored and looked up using only
+ * the non-constant EM, with the other key slot set to NULL.
+ */

This comment seems to overlap with what's already there in the
prologue. However "Clauses with a constant EM are always stored and
looked up using the non-constant EM" is non-overlapping part. This
part is also repeated in ec_add_clause_to_derives_hash(), which I
think is a better place for this comment. Changed in the attached
patch.

*/
Assert(!rinfo->left_em->em_is_const);
+ /*
+ * Clauses containing a constant are never considered redundant, so
+ * parent_ec is not set.
+ */
+ Assert(!rinfo->parent_ec || !rinfo->right_em->em_is_const);

These two Assertions are applicable to all the derived clauses but are
added to a function which deals with only hash table. If an EC doesn't
have enough derived clauses to create a hash table these assertions
won't be checked. The reason they are here is because we are using
these properties to create canonical hash table key. Should we move
them to ec_add_derived_clause() for better coverage?

I have also removed some comments repeated in the function prologue
and function body.

0001, 0002 and 0003 are the same as your set. 0004 contains my changes
in a separate patch so that it's easy to review those.

--
Best Wishes,
Ashutosh Bapat


Attachments:

  [text/x-patch] 0001-Add-assertion-to-verify-derived-clause-has--20250401.patch (1.5K, ../../CAExHW5sFgbPCh25fkb1cUmN0cVeEum9zfa0Vf2p8RcekS2_B1g@mail.gmail.com/2-0001-Add-assertion-to-verify-derived-clause-has--20250401.patch)
  download | inline diff:
From 49c6c25df01d830f1dc367048a4db013291107c9 Mon Sep 17 00:00:00 2001
From: Amit Langote <[email protected]>
Date: Mon, 24 Mar 2025 21:17:46 +0900
Subject: [PATCH 1/4] Add assertion to verify derived clause has constant RHS

find_derived_clause_for_ec_member() searches for a previously-derived
clause that equates a non-constant EquivalenceMember to a constant.
It is only called for EquivalenceClasses with ec_has_const set, and
with a non-constant member as the target.

The matched clause is expected to have the non-constant member on the
left-hand side and the constant EquivalenceMember on the right.

Assert that the RHS is indeed a constant, to catch violations of this
structure and enforce assumptions made by
generate_base_implied_equalities_const().

Author: Ashutosh Bapat <[email protected]>
Discussion: https://postgr.es/m/CAExHW5scMxyFRqOFE6ODmBiW2rnVBEmeEcA-p4W_CyuEikURdA@mail.gmail.com
---
 src/backend/optimizer/path/equivclass.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c
index 0f9ecf5ee8b..493a95d26cc 100644
--- a/src/backend/optimizer/path/equivclass.c
+++ b/src/backend/optimizer/path/equivclass.c
@@ -2664,7 +2664,10 @@ find_derived_clause_for_ec_member(EquivalenceClass *ec,
 		 * members on the left side of derived clauses.
 		 */
 		if (rinfo->left_em == em)
+		{
+			Assert(rinfo->right_em->em_is_const);
 			return rinfo;
+		}
 	}
 	return NULL;
 }

base-commit: 764d501d24baab8ead6dc3bf7bb0dbd13ea86084
-- 
2.34.1



  [text/x-patch] 0004-Minor-fixes-20250401.patch (6.4K, ../../CAExHW5sFgbPCh25fkb1cUmN0cVeEum9zfa0Vf2p8RcekS2_B1g@mail.gmail.com/3-0004-Minor-fixes-20250401.patch)
  download | inline diff:
From ff721aeed52f15e9aad22f03777e37225ff4183f Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <[email protected]>
Date: Tue, 1 Apr 2025 22:13:08 +0530
Subject: [PATCH 4/4] Minor fixes

1. Assert movement
2. grammar fixes
3. removed duplicate comments
4. comment rephrased

Author: Ashutosh Bapat
---
 src/backend/optimizer/path/equivclass.c | 68 +++++++++++--------------
 1 file changed, 30 insertions(+), 38 deletions(-)

diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c
index 12155c0c957..3249f966573 100644
--- a/src/backend/optimizer/path/equivclass.c
+++ b/src/backend/optimizer/path/equivclass.c
@@ -90,7 +90,8 @@ static RestrictInfo *ec_search_derived_clause_for_ems(PlannerInfo *root,
 /*
  * Hash key identifying a derived clause.
  *
- * See fill_ec_derives_key() for details on canonical key construction.
+ * The structure is not supposed to be filled manually. Please use
+ * fill_ec_derives_key() to setup key in canonical form.
  */
 typedef struct
 {
@@ -3478,10 +3479,24 @@ ec_build_derives_hash(PlannerInfo *root, EquivalenceClass *ec)
  *		Add a clause to the set of derived clauses for the given
  *		EquivalenceClass. Always appends to ec_derives_list; also adds
  *		to ec_derives_hash if it exists.
+ *
+ * While at it, also check some derived clause invariants.
  */
 static void
 ec_add_derived_clause(EquivalenceClass *ec, RestrictInfo *clause)
 {
+	/*
+	 * Constant, if present, is always placed on the RHS; see
+	 * generate_base_implied_equalities_const(). LHS is never a constant.
+	 */
+	Assert(!clause->left_em->em_is_const);
+
+	/*
+	 * Clauses containing a constant are never considered redundant, so
+	 * parent_ec is not set.
+	 */
+	Assert(!clause->parent_ec || !clause->right_em->em_is_const);
+
 	ec->ec_derives_list = lappend(ec->ec_derives_list, clause);
 	if (ec->ec_derives_hash)
 		ec_add_clause_to_derives_hash(ec, clause);
@@ -3492,8 +3507,10 @@ ec_add_derived_clause(EquivalenceClass *ec, RestrictInfo *clause)
  *		Add a list of clauses to the set of clauses derived from the given
  *		EquivalenceClass; adding to the list and hash table if needed.
  *
- * This function is similar to above function but optimized for adding multiple
- * clauses at a time to the ec_derives_list.
+ * This function is similar to ec_add_derived_clause() but optimized for adding
+ * multiple clauses at a time to the ec_derives_list. However we don't need to
+ * repeat the Asserts in that function since they must have been applied when
+ * the clauses were inserted in the derived clause list from where they came.
  */
 static void
 ec_add_derived_clauses(EquivalenceClass *ec, List *clauses)
@@ -3506,16 +3523,16 @@ ec_add_derived_clauses(EquivalenceClass *ec, List *clauses)
 
 /*
  * fill_ec_derives_key
- *		Compute a canonical key for ec_derives_hash lookup or insertion.
+ *		Setup canonical key for ec_derives_hash lookup or insertion.
  *
  * Derived clauses are looked up using a pair of EquivalenceMembers and a
  * parent EquivalenceClass. To avoid storing or searching for both EM orderings,
  * we canonicalize the key:
  *
- * - For clauses involving two non-constant EMs, we order the EMs by address
- *   and place the lower one first.
+ * - For clauses involving two non-constant EMs, em1 is set to the EM with lower
+ *   memory address and em2 is set to the other one.
  * - For clauses involving a constant EM, the caller must pass the non-constant
- *   EM as leftem; we then set em1 = NULL and em2 = leftem.
+ *   EM as leftem and NULL as rightem; we then set em1 = NULL and em2 = leftem.
  */
 static inline void
 fill_ec_derives_key(ECDerivesKey *key,
@@ -3525,10 +3542,6 @@ fill_ec_derives_key(ECDerivesKey *key,
 {
 	Assert(leftem);				/* Always required for lookup or insertion */
 
-	/*
-	 * Clauses with a constant EM are always stored and looked up using only
-	 * the non-constant EM, with the other key slot set to NULL.
-	 */
 	if (rightem == NULL)
 	{
 		key->em1 = NULL;
@@ -3549,9 +3562,9 @@ fill_ec_derives_key(ECDerivesKey *key,
 
 /*
  * ec_add_clause_to_derives_hash
- *		Add a derived clause to ec_derives_hash for the given EquivalenceClass.
+ *		Add a derived clause to ec_derives_hash in the given EquivalenceClass.
  *
- * Each clause is inserted under a canonicalized key. For constant-containing
+ * Each clause is associated with a canonicalized key. For constant-containing
  * clauses, only the non-constant EM is used for lookup; see comments in
  * fill_ec_derives_key().
  */
@@ -3562,23 +3575,6 @@ ec_add_clause_to_derives_hash(EquivalenceClass *ec, RestrictInfo *rinfo)
 	ECDerivesEntry *entry;
 	bool		found;
 
-	/*
-	 * Constants are always placed on the RHS; see
-	 * generate_base_implied_equalities_const().
-	 */
-	Assert(!rinfo->left_em->em_is_const);
-
-	/*
-	 * Clauses containing a constant are never considered redundant, so
-	 * parent_ec is not set.
-	 */
-	Assert(!rinfo->parent_ec || !rinfo->right_em->em_is_const);
-
-	/*
-	 * See fill_ec_derives_key() for details: we use a canonicalized key to
-	 * avoid storing both EM orderings. For constant EMs, only the
-	 * non-constant EM is included in the key.
-	 */
 	fill_ec_derives_key(&key,
 						rinfo->left_em,
 						rinfo->right_em->em_is_const ? NULL : rinfo->right_em,
@@ -3654,11 +3650,12 @@ ec_search_clause_for_ems(PlannerInfo *root, EquivalenceClass *ec,
  * lookup; otherwise, scan ec_derives_list linearly.
  *
  * Clauses involving constants are looked up using only the non-constant EM
- * (leftem), with rightem set to NULL. In that case, we expect to find a
+ * passed as leftem and rightem set to NULL. In that case, we expect to find a
  * clause with a constant on the RHS.
  *
- * We do not attempt a second lookup with EMs swapped, because the key is
- * canonicalized during both insertion and lookup.
+ * While looking up the derived clause in the list, we match each given EM with
+ * both sides of the clause respectively.  But hash table look up is carried out
+ * only once using a canonicalized key.
  */
 static RestrictInfo *
 ec_search_derived_clause_for_ems(PlannerInfo *root, EquivalenceClass *ec,
@@ -3686,11 +3683,6 @@ ec_search_derived_clause_for_ems(PlannerInfo *root, EquivalenceClass *ec,
 		{
 			rinfo = entry->rinfo;
 			Assert(rinfo);
-
-			/*
-			 * If this is a lookup in a const-containing EC, the RHS must be a
-			 * constant. The caller signals this by passing NULL for rightem.
-			 */
 			Assert(rightem || rinfo->right_em->em_is_const);
 			return rinfo;
 		}
-- 
2.34.1



  [text/x-patch] 0002-Make-derived-clause-lookup-in-EquivalenceCl-20250401.patch (25.5K, ../../CAExHW5sFgbPCh25fkb1cUmN0cVeEum9zfa0Vf2p8RcekS2_B1g@mail.gmail.com/4-0002-Make-derived-clause-lookup-in-EquivalenceCl-20250401.patch)
  download | inline diff:
From d13e8975522ec8b90b33bd0749c370b3ac8ac196 Mon Sep 17 00:00:00 2001
From: Amit Langote <[email protected]>
Date: Tue, 25 Mar 2025 20:36:26 +0900
Subject: [PATCH 2/4] Make derived clause lookup in EquivalenceClass more
 efficient

Previously, derived clauses were stored in ec_derives, a List of
RestrictInfos. These clauses are looked up later using the left and
right EquivalenceMembers and the clause's parent EC, typically during
join clause generation.

This lookup becomes expensive in queries with many partitions or
joins, where ec_derives may contain thousands of entries. In
particular, create_join_clause() can spend significant time scanning
this list.

To improve performance, a hash table (ec_derives_hash) is now built
when ec_derives_list exceeds 32 entries.  This is the same threshold
used for join_rel_hash. To reflect this dual structure, the
ec_derives field is renamed to ec_derives_list. The list is retained
alongside the hash table to support _outEquivalenceClass() and
EquivalenceClass merging, which both operate on ec_derives_list.

Lookup logic is updated to consult the hash table when available. For
ECs with a constant EM, lookups are done using only the non-constant
EM; the constant EM is assumed to be unique and may not be available
at lookup time. In such cases, the hash table stores em2 = NULL, which
can be matched by using NULL for em2 in the lookup key.

An assertion originally placed in find_derived_clause_for_ec_member()
is moved into ec_search_derived_clause_for_ems() to enforce the same
invariant in both hash and list lookup paths.

The new ec_clear_derived_clauses() always frees ec_derives_list, even
though some of the original code paths that cleared the old ec_derives
field did not. This ensures consistent cleanup and avoids leaking
memory when the list grows large.

Author: Ashutosh Bapat <[email protected]>
Reviewed-by: Amit Langote <[email protected]>
Tested-by: Dmitry Dolgov <[email protected]>
Tested-by: Alvaro Herrera <[email protected]>
Tested-by: Amit Langote <[email protected]>
Discussion: https://postgr.es/m/CAExHW5vZiQtWU6moszLP5iZ8gLX_ZAUbgEX0DxGLx9PGWCtqUg@mail.gmail.com
---
 src/backend/nodes/outfuncs.c              |   3 +-
 src/backend/optimizer/path/costsize.c     |   3 +-
 src/backend/optimizer/path/equivclass.c   | 403 ++++++++++++++++++----
 src/backend/optimizer/plan/analyzejoins.c |   5 +-
 src/include/nodes/pathnodes.h             |  17 +-
 src/include/optimizer/paths.h             |   4 +-
 src/tools/pgindent/typedefs.list          |   2 +
 7 files changed, 357 insertions(+), 80 deletions(-)

diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index bb9bdd67192..557f06e344f 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -467,7 +467,8 @@ _outEquivalenceClass(StringInfo str, const EquivalenceClass *node)
 	WRITE_OID_FIELD(ec_collation);
 	WRITE_NODE_FIELD(ec_members);
 	WRITE_NODE_FIELD(ec_sources);
-	WRITE_NODE_FIELD(ec_derives);
+	/* Only ec_derives_list is written; hash is not serialized. */
+	WRITE_NODE_FIELD(ec_derives_list);
 	WRITE_BITMAPSET_FIELD(ec_relids);
 	WRITE_BOOL_FIELD(ec_has_const);
 	WRITE_BOOL_FIELD(ec_has_volatile);
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index f6f77b8fe19..c474c7a06af 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -5848,7 +5848,8 @@ get_foreign_key_join_selectivity(PlannerInfo *root,
 				if (ec && ec->ec_has_const)
 				{
 					EquivalenceMember *em = fkinfo->fk_eclass_member[i];
-					RestrictInfo *rinfo = find_derived_clause_for_ec_member(ec,
+					RestrictInfo *rinfo = find_derived_clause_for_ec_member(root,
+																			ec,
 																			em);
 
 					if (rinfo)
diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c
index 493a95d26cc..b988db4d7b3 100644
--- a/src/backend/optimizer/path/equivclass.c
+++ b/src/backend/optimizer/path/equivclass.c
@@ -20,6 +20,7 @@
 
 #include "access/stratnum.h"
 #include "catalog/pg_type.h"
+#include "common/hashfn.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/appendinfo.h"
@@ -72,7 +73,50 @@ static Bitmapset *get_eclass_indexes_for_relids(PlannerInfo *root,
 												Relids relids);
 static Bitmapset *get_common_eclass_indexes(PlannerInfo *root, Relids relids1,
 											Relids relids2);
+static void ec_build_derives_hash(PlannerInfo *root, EquivalenceClass *ec);
+static void ec_add_derived_clauses(EquivalenceClass *ec, List *clauses);
+static void ec_add_derived_clause(EquivalenceClass *ec, RestrictInfo *clause);
+static void ec_add_clause_to_derives_hash(EquivalenceClass *ec, RestrictInfo *rinfo);
+static RestrictInfo *ec_search_clause_for_ems(PlannerInfo *root, EquivalenceClass *ec,
+											  EquivalenceMember *leftem,
+											  EquivalenceMember *rightem,
+											  EquivalenceClass *parent_ec);
+static RestrictInfo *ec_search_derived_clause_for_ems(PlannerInfo *root,
+													  EquivalenceClass *ec,
+													  EquivalenceMember *leftem,
+													  EquivalenceMember *rightem,
+													  EquivalenceClass *parent_ec);
 
+/*
+ * Hash key identifying a derived clause by EquivalenceMembers and parent EC.
+ */
+typedef struct
+{
+	EquivalenceMember *em1;
+	EquivalenceMember *em2;
+	EquivalenceClass *parent_ec;
+} ECDerivesKey;
+
+/* Hash table entry in ec_derives_hash. */
+typedef struct
+{
+	uint32		status;
+	ECDerivesKey key;
+	RestrictInfo *rinfo;
+} ECDerivesEntry;
+
+#define SH_PREFIX               derives
+#define SH_ELEMENT_TYPE			ECDerivesEntry
+#define SH_KEY_TYPE             ECDerivesKey
+#define SH_KEY                  key
+#define SH_HASH_KEY(tb, key)	\
+	hash_bytes((const unsigned char *) &(key), sizeof(ECDerivesKey))
+#define SH_EQUAL(tb, a, b)	\
+	((a).em1 == (b).em1 && (a).em2 == (b).em2 && (a).parent_ec == (b).parent_ec)
+#define SH_SCOPE                static inline
+#define SH_DECLARE
+#define SH_DEFINE
+#include "lib/simplehash.h"
 
 /*
  * process_equivalence
@@ -342,7 +386,12 @@ process_equivalence(PlannerInfo *root,
 		 */
 		ec1->ec_members = list_concat(ec1->ec_members, ec2->ec_members);
 		ec1->ec_sources = list_concat(ec1->ec_sources, ec2->ec_sources);
-		ec1->ec_derives = list_concat(ec1->ec_derives, ec2->ec_derives);
+
+		/*
+		 * Appends ec2's derived clauses to ec1->ec_derives_list and adds them
+		 * to ec1->ec_derives_hash if present.
+		 */
+		ec_add_derived_clauses(ec1, ec2->ec_derives_list);
 		ec1->ec_relids = bms_join(ec1->ec_relids, ec2->ec_relids);
 		ec1->ec_has_const |= ec2->ec_has_const;
 		/* can't need to set has_volatile */
@@ -355,7 +404,7 @@ process_equivalence(PlannerInfo *root,
 		/* just to avoid debugging confusion w/ dangling pointers: */
 		ec2->ec_members = NIL;
 		ec2->ec_sources = NIL;
-		ec2->ec_derives = NIL;
+		ec_clear_derived_clauses(ec2);
 		ec2->ec_relids = NULL;
 		ec1->ec_sources = lappend(ec1->ec_sources, restrictinfo);
 		ec1->ec_min_security = Min(ec1->ec_min_security,
@@ -412,7 +461,8 @@ process_equivalence(PlannerInfo *root,
 		ec->ec_collation = collation;
 		ec->ec_members = NIL;
 		ec->ec_sources = list_make1(restrictinfo);
-		ec->ec_derives = NIL;
+		ec->ec_derives_list = NIL;
+		ec->ec_derives_hash = NULL;
 		ec->ec_relids = NULL;
 		ec->ec_has_const = false;
 		ec->ec_has_volatile = false;
@@ -671,7 +721,8 @@ get_eclass_for_sort_expr(PlannerInfo *root,
 	newec->ec_collation = collation;
 	newec->ec_members = NIL;
 	newec->ec_sources = NIL;
-	newec->ec_derives = NIL;
+	newec->ec_derives_list = NIL;
+	newec->ec_derives_hash = NULL;
 	newec->ec_relids = NULL;
 	newec->ec_has_const = false;
 	newec->ec_has_volatile = contain_volatile_functions((Node *) expr);
@@ -1026,8 +1077,8 @@ relation_can_be_sorted_early(PlannerInfo *root, RelOptInfo *rel,
  * scanning of the quals and before Path construction begins.
  *
  * We make no attempt to avoid generating duplicate RestrictInfos here: we
- * don't search ec_sources or ec_derives for matches.  It doesn't really
- * seem worth the trouble to do so.
+ * don't search existing source or derived clauses in the EC for matches.  It
+ * doesn't really seem worth the trouble to do so.
  */
 void
 generate_base_implied_equalities(PlannerInfo *root)
@@ -1188,11 +1239,11 @@ generate_base_implied_equalities_const(PlannerInfo *root,
 
 		/*
 		 * If the clause didn't degenerate to a constant, fill in the correct
-		 * markings for a mergejoinable clause, and save it in ec_derives. (We
-		 * will not re-use such clauses directly, but selectivity estimation
-		 * may consult the list later.  Note that this use of ec_derives does
-		 * not overlap with its use for join clauses, since we never generate
-		 * join clauses from an ec_has_const eclass.)
+		 * markings for a mergejoinable clause, and save it as a derived
+		 * clause. (We will not re-use such clauses directly, but selectivity
+		 * estimation may consult those later.  Note that this use of derived
+		 * clauses does not overlap with its use for join clauses, since we
+		 * never generate join clauses from an ec_has_const eclass.)
 		 */
 		if (rinfo && rinfo->mergeopfamilies)
 		{
@@ -1200,7 +1251,7 @@ generate_base_implied_equalities_const(PlannerInfo *root,
 			rinfo->left_ec = rinfo->right_ec = ec;
 			rinfo->left_em = cur_em;
 			rinfo->right_em = const_em;
-			ec->ec_derives = lappend(ec->ec_derives, rinfo);
+			ec_add_derived_clause(ec, rinfo);
 		}
 	}
 }
@@ -1265,10 +1316,10 @@ generate_base_implied_equalities_no_const(PlannerInfo *root,
 
 			/*
 			 * If the clause didn't degenerate to a constant, fill in the
-			 * correct markings for a mergejoinable clause.  We don't put it
-			 * in ec_derives however; we don't currently need to re-find such
-			 * clauses, and we don't want to clutter that list with non-join
-			 * clauses.
+			 * correct markings for a mergejoinable clause.  We don't record
+			 * it as a derived clause, since we don't currently need to
+			 * re-find such clauses, and don't want to clutter the
+			 * derived-clause set with non-join clauses.
 			 */
 			if (rinfo && rinfo->mergeopfamilies)
 			{
@@ -1369,7 +1420,7 @@ generate_base_implied_equalities_broken(PlannerInfo *root,
  * we consider different join paths, we avoid generating multiple copies:
  * whenever we select a particular pair of EquivalenceMembers to join,
  * we check to see if the pair matches any original clause (in ec_sources)
- * or previously-built clause (in ec_derives).  This saves memory and allows
+ * or previously-built derived clause.  This saves memory and allows
  * re-use of information cached in RestrictInfos.  We also avoid generating
  * commutative duplicates, i.e. if the algorithm selects "a.x = b.y" but
  * we already have "b.y = a.x", we return the existing clause.
@@ -1754,9 +1805,9 @@ generate_join_implied_equalities_broken(PlannerInfo *root,
 	/*
 	 * If we have to translate, just brute-force apply adjust_appendrel_attrs
 	 * to all the RestrictInfos at once.  This will result in returning
-	 * RestrictInfos that are not listed in ec_derives, but there shouldn't be
-	 * any duplication, and it's a sufficiently narrow corner case that we
-	 * shouldn't sweat too much over it anyway.
+	 * RestrictInfos that are not included in EC's derived clauses, but there
+	 * shouldn't be any duplication, and it's a sufficiently narrow corner
+	 * case that we shouldn't sweat too much over it anyway.
 	 *
 	 * Since inner_rel might be an indirect descendant of the baserel
 	 * mentioned in the ec_sources clauses, we have to be prepared to apply
@@ -1823,43 +1874,11 @@ create_join_clause(PlannerInfo *root,
 {
 	RestrictInfo *rinfo;
 	RestrictInfo *parent_rinfo = NULL;
-	ListCell   *lc;
 	MemoryContext oldcontext;
 
-	/*
-	 * Search to see if we already built a RestrictInfo for this pair of
-	 * EquivalenceMembers.  We can use either original source clauses or
-	 * previously-derived clauses, and a commutator clause is acceptable.
-	 *
-	 * We used to verify that opno matches, but that seems redundant: even if
-	 * it's not identical, it'd better have the same effects, or the operator
-	 * families we're using are broken.
-	 */
-	foreach(lc, ec->ec_sources)
-	{
-		rinfo = (RestrictInfo *) lfirst(lc);
-		if (rinfo->left_em == leftem &&
-			rinfo->right_em == rightem &&
-			rinfo->parent_ec == parent_ec)
-			return rinfo;
-		if (rinfo->left_em == rightem &&
-			rinfo->right_em == leftem &&
-			rinfo->parent_ec == parent_ec)
-			return rinfo;
-	}
-
-	foreach(lc, ec->ec_derives)
-	{
-		rinfo = (RestrictInfo *) lfirst(lc);
-		if (rinfo->left_em == leftem &&
-			rinfo->right_em == rightem &&
-			rinfo->parent_ec == parent_ec)
-			return rinfo;
-		if (rinfo->left_em == rightem &&
-			rinfo->right_em == leftem &&
-			rinfo->parent_ec == parent_ec)
-			return rinfo;
-	}
+	rinfo = ec_search_clause_for_ems(root, ec, leftem, rightem, parent_ec);
+	if (rinfo)
+		return rinfo;
 
 	/*
 	 * Not there, so build it, in planner context so we can re-use it. (Not
@@ -1923,7 +1942,7 @@ create_join_clause(PlannerInfo *root,
 	rinfo->left_em = leftem;
 	rinfo->right_em = rightem;
 	/* and save it for possible re-use */
-	ec->ec_derives = lappend(ec->ec_derives, rinfo);
+	ec_add_derived_clause(ec, rinfo);
 
 	MemoryContextSwitchTo(oldcontext);
 
@@ -2648,28 +2667,14 @@ match_eclasses_to_foreign_key_col(PlannerInfo *root,
  * Returns NULL if no such clause can be found.
  */
 RestrictInfo *
-find_derived_clause_for_ec_member(EquivalenceClass *ec,
+find_derived_clause_for_ec_member(PlannerInfo *root,
+								  EquivalenceClass *ec,
 								  EquivalenceMember *em)
 {
-	ListCell   *lc;
-
 	Assert(ec->ec_has_const);
 	Assert(!em->em_is_const);
-	foreach(lc, ec->ec_derives)
-	{
-		RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
 
-		/*
-		 * generate_base_implied_equalities_const will have put non-const
-		 * members on the left side of derived clauses.
-		 */
-		if (rinfo->left_em == em)
-		{
-			Assert(rinfo->right_em->em_is_const);
-			return rinfo;
-		}
-	}
-	return NULL;
+	return ec_search_derived_clause_for_ems(root, ec, em, NULL, NULL);
 }
 
 
@@ -3442,3 +3447,255 @@ get_common_eclass_indexes(PlannerInfo *root, Relids relids1, Relids relids2)
 	/* Calculate and return the common EC indexes, recycling the left input. */
 	return bms_int_members(rel1ecs, rel2ecs);
 }
+
+/*
+ * ec_build_derives_hash
+ *	  Construct the auxiliary hash table for derived clauses.
+ */
+static void
+ec_build_derives_hash(PlannerInfo *root, EquivalenceClass *ec)
+{
+	Assert(!ec->ec_derives_hash);
+
+	/* Create the hash table */
+	ec->ec_derives_hash = derives_create(root->planner_cxt, 256, NULL);
+
+	foreach_node(RestrictInfo, rinfo, ec->ec_derives_list)
+		ec_add_clause_to_derives_hash(ec, rinfo);
+}
+
+/*
+ * ec_add_derived_clause
+ *		Add a clause to the set of derived clauses for the given
+ *		EquivalenceClass. Always appends to ec_derives_list; also adds
+ *		to ec_derives_hash if it exists.
+ */
+static void
+ec_add_derived_clause(EquivalenceClass *ec, RestrictInfo *clause)
+{
+	ec->ec_derives_list = lappend(ec->ec_derives_list, clause);
+	if (ec->ec_derives_hash)
+		ec_add_clause_to_derives_hash(ec, clause);
+}
+
+/*
+ * ec_add_derived_clauses
+ *		Add a list of clauses to the set of clauses derived from the given
+ *		EquivalenceClass; adding to the list and hash table if needed.
+ *
+ * This function is similar to above function but optimized for adding multiple
+ * clauses at a time to the ec_derives_list.
+ */
+static void
+ec_add_derived_clauses(EquivalenceClass *ec, List *clauses)
+{
+	ec->ec_derives_list = list_concat(ec->ec_derives_list, clauses);
+	if (ec->ec_derives_hash)
+		foreach_node(RestrictInfo, rinfo, clauses)
+			ec_add_clause_to_derives_hash(ec, rinfo);
+}
+
+/*
+ * ec_add_clause_to_derives_hash
+ *      Add a derived clause to the ec_derives_hash of the given
+ *      EquivalenceClass.
+ */
+static void
+ec_add_clause_to_derives_hash(EquivalenceClass *ec, RestrictInfo *rinfo)
+{
+	ECDerivesKey key;
+	ECDerivesEntry *entry;
+	bool		found;
+
+	/*
+	 * Constants are always placed on the RHS; see
+	 * generate_base_implied_equalities_const().
+	 */
+	Assert(!rinfo->left_em->em_is_const);
+
+	if (rinfo->right_em->em_is_const)
+	{
+		/*
+		 * Clauses containing a constant are never considered redundant, so
+		 * parent_ec is not set.
+		 */
+		Assert(!rinfo->parent_ec);
+
+		/*
+		 * find_derived_clause_for_ec_member() performs lookup of a clause
+		 * involving a constant using only the non-constant EM and NULL for
+		 * the RHS. Since there's only one constant EM per EC, we don't need
+		 * to store or match it during lookup.  We set key.em2 = NULL to
+		 * reflect this.
+		 */
+		key.em1 = rinfo->left_em;
+		key.em2 = NULL;
+		key.parent_ec = rinfo->parent_ec;
+		entry = derives_insert(ec->ec_derives_hash, key, &found);
+		Assert(!found);
+		entry->rinfo = rinfo;
+	}
+	else
+	{
+		key.em1 = rinfo->left_em;
+		key.em2 = rinfo->right_em;
+		key.parent_ec = rinfo->parent_ec;
+		entry = derives_insert(ec->ec_derives_hash, key, &found);
+		Assert(!found);
+		entry->rinfo = rinfo;
+
+		/*
+		 * Insert the clause under the given EM pair key, and also under the
+		 * reverse order. This ensures we can find the clause regardless of
+		 * the order in which EMs are passed to the lookup function.
+		 */
+		key.em1 = rinfo->right_em;
+		key.em2 = rinfo->left_em;
+		key.parent_ec = rinfo->parent_ec;
+		entry = derives_insert(ec->ec_derives_hash, key, &found);
+		Assert(!found);
+		entry->rinfo = rinfo;
+	}
+}
+
+/*
+ * ec_clear_derived_clauses
+ *      Reset ec_derives_list and ec_derives_hash.
+ *
+ * We destroy the hash table explicitly, since it may consume significant
+ * space. The list holds the same set of entries and can become equally large
+ * when thousands of partitions are involved, so we free it as well -- even
+ * though we do not typically free lists.
+ */
+void
+ec_clear_derived_clauses(EquivalenceClass *ec)
+{
+	list_free(ec->ec_derives_list);
+	ec->ec_derives_list = NIL;
+
+	if (ec->ec_derives_hash)
+	{
+		derives_destroy(ec->ec_derives_hash);
+		ec->ec_derives_hash = NULL;
+	}
+}
+
+/*
+ * ec_search_clause_for_ems
+ *		Search for an existing RestrictInfo that equates the given pair
+ *		of EquivalenceMembers, either from ec_sources or ec_derives.
+ *
+ * We accept clauses in either operand order, so commutators are matched. We
+ * used to require matching operator OIDs, but dropped that since any
+ * semantically different operator here would indicate a broken operator
+ * family.
+ *
+ * Returns NULL if no matching clause is found.
+ */
+
+static RestrictInfo *
+ec_search_clause_for_ems(PlannerInfo *root, EquivalenceClass *ec,
+						 EquivalenceMember *leftem, EquivalenceMember *rightem,
+						 EquivalenceClass *parent_ec)
+{
+	/* Check original source clauses */
+	foreach_node(RestrictInfo, rinfo, ec->ec_sources)
+	{
+		if (rinfo->left_em == leftem &&
+			rinfo->right_em == rightem &&
+			rinfo->parent_ec == parent_ec)
+			return rinfo;
+		if (rinfo->left_em == rightem &&
+			rinfo->right_em == leftem &&
+			rinfo->parent_ec == parent_ec)
+			return rinfo;
+	}
+
+	/* Not found in ec_sources; search derived clauses */
+	return ec_search_derived_clause_for_ems(root, ec, leftem, rightem,
+											parent_ec);
+}
+
+/*
+ * ec_search_derived_clause_for_ems
+ *		Search for an existing derived clause between two EquivalenceMembers.
+ *
+ * If the number of derived clauses exceeds a threshold, switch to hash table
+ * lookup; otherwise, scan ec_derives_list linearly.
+ *
+ * Clauses involving constants are looked up using only the non-const EM
+ * (leftem) and a NULL rightem. In that case, we expect to find a clause with
+ * a constant on the RHS.
+ *
+ * We do not attempt a second lookup with EMs swapped when using the hash
+ * table; such clauses are inserted under both orderings at the time of
+ * insertion.
+ */
+static RestrictInfo *
+ec_search_derived_clause_for_ems(PlannerInfo *root, EquivalenceClass *ec,
+								 EquivalenceMember *leftem,
+								 EquivalenceMember *rightem,
+								 EquivalenceClass *parent_ec)
+{
+	/*
+	 * Switch to using hash lookup when list grows "too long". The threshold
+	 * is arbitrary and is known only here.
+	 */
+	if (!ec->ec_derives_hash && list_length(ec->ec_derives_list) >= 32)
+		ec_build_derives_hash(root, ec);
+
+	/* Perform hash table lookup if available */
+	if (ec->ec_derives_hash)
+	{
+		ECDerivesKey key;
+		RestrictInfo *rinfo;
+		ECDerivesEntry *entry;
+
+		/*
+		 * See ec_add_clause_to_derives_hash() for rationale: derived clauses
+		 * are inserted into the hash table under both (em1, em2) and (em2,
+		 * em1), so a single lookup with the original order is sufficient.
+		 */
+		key.em1 = leftem;
+		key.em2 = rightem;
+		key.parent_ec = parent_ec;
+		entry = derives_lookup(ec->ec_derives_hash, key);
+		if (entry)
+		{
+			rinfo = entry->rinfo;
+			Assert(rinfo);
+
+			/*
+			 * If this is a lookup in a const-containing EC, the RHS must be a
+			 * constant. The caller signals this by passing NULL for rightem.
+			 */
+			Assert(rightem || rinfo->right_em->em_is_const);
+			return rinfo;
+		}
+	}
+	else
+	{
+		/* Fallback to linear search over ec_derives_list */
+		foreach_node(RestrictInfo, rinfo, ec->ec_derives_list)
+		{
+			/* Handle special case: lookup by non-const EM alone */
+			if (!rightem &&
+				rinfo->left_em == leftem)
+			{
+				/* See the comment above in hash path for rationale. */
+				Assert(rinfo->right_em->em_is_const);
+				return rinfo;
+			}
+			if (rinfo->left_em == leftem &&
+				rinfo->right_em == rightem &&
+				rinfo->parent_ec == parent_ec)
+				return rinfo;
+			if (rinfo->left_em == rightem &&
+				rinfo->right_em == leftem &&
+				rinfo->parent_ec == parent_ec)
+				return rinfo;
+		}
+	}
+
+	return NULL;
+}
diff --git a/src/backend/optimizer/plan/analyzejoins.c b/src/backend/optimizer/plan/analyzejoins.c
index 8a8d4a2af33..ae20691ca91 100644
--- a/src/backend/optimizer/plan/analyzejoins.c
+++ b/src/backend/optimizer/plan/analyzejoins.c
@@ -749,7 +749,7 @@ remove_rel_from_eclass(EquivalenceClass *ec, SpecialJoinInfo *sjinfo,
 	 * drop them.  (At this point, any such clauses would be base restriction
 	 * clauses, which we'd not need anymore anyway.)
 	 */
-	ec->ec_derives = NIL;
+	ec_clear_derived_clauses(ec);
 }
 
 /*
@@ -1544,8 +1544,7 @@ update_eclasses(EquivalenceClass *ec, int from, int to)
 	list_free(ec->ec_members);
 	ec->ec_members = new_members;
 
-	list_free(ec->ec_derives);
-	ec->ec_derives = NULL;
+	ec_clear_derived_clauses(ec);
 
 	/* Update EC source expressions */
 	foreach_node(RestrictInfo, rinfo, ec->ec_sources)
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index c24a1fc8514..e29d26a1814 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -1403,6 +1403,18 @@ typedef struct JoinDomain
  * entry: consider SELECT random() AS a, random() AS b ... ORDER BY b,a.
  * So we record the SortGroupRef of the originating sort clause.
  *
+ * Derived equality clauses are stored in ec_derives_list. For small queries,
+ * this list is scanned directly during lookup. For larger queries -- e.g.,
+ * with many partitions or joins -- a hash table (ec_derives_hash) is built
+ * when the list grows beyond a threshold, for faster lookup. When present,
+ * the hash table contains the same RestrictInfos and is maintained alongside
+ * the list. We retain the list even when the hash is used to simplify
+ * serialization (e.g., in _outEquivalenceClass()) and support
+ * EquivalenceClass merging.
+ *
+ * In contrast, ec_sources holds equality clauses that appear directly in the
+ * query. These are typically few and do not require a hash table for lookup.
+ *
  * NB: if ec_merged isn't NULL, this class has been merged into another, and
  * should be ignored in favor of using the pointed-to class.
  *
@@ -1422,7 +1434,10 @@ typedef struct EquivalenceClass
 	Oid			ec_collation;	/* collation, if datatypes are collatable */
 	List	   *ec_members;		/* list of EquivalenceMembers */
 	List	   *ec_sources;		/* list of generating RestrictInfos */
-	List	   *ec_derives;		/* list of derived RestrictInfos */
+	List	   *ec_derives_list;	/* list of derived RestrictInfos */
+	struct derives_hash *ec_derives_hash;	/* optional hash table for fast
+											 * lookup; contains same
+											 * RestrictInfos as list */
 	Relids		ec_relids;		/* all relids appearing in ec_members, except
 								 * for child members (see below) */
 	bool		ec_has_const;	/* any pseudoconstants in ec_members? */
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index bc5dfd7db41..16dc4d5ee82 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -167,7 +167,8 @@ extern bool exprs_known_equal(PlannerInfo *root, Node *item1, Node *item2,
 extern EquivalenceClass *match_eclasses_to_foreign_key_col(PlannerInfo *root,
 														   ForeignKeyOptInfo *fkinfo,
 														   int colno);
-extern RestrictInfo *find_derived_clause_for_ec_member(EquivalenceClass *ec,
+extern RestrictInfo *find_derived_clause_for_ec_member(PlannerInfo *root,
+													   EquivalenceClass *ec,
 													   EquivalenceMember *em);
 extern void add_child_rel_equivalences(PlannerInfo *root,
 									   AppendRelInfo *appinfo,
@@ -197,6 +198,7 @@ extern bool eclass_useful_for_merging(PlannerInfo *root,
 extern bool is_redundant_derived_clause(RestrictInfo *rinfo, List *clauselist);
 extern bool is_redundant_with_indexclauses(RestrictInfo *rinfo,
 										   List *indexclauses);
+extern void ec_clear_derived_clauses(EquivalenceClass *ec);
 
 /*
  * pathkeys.c
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b66cecd8799..997abe1427c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -667,6 +667,8 @@ DumpableObjectWithAcl
 DynamicFileList
 DynamicZoneAbbrev
 EC_KEY
+ECDerivesKey
+ECDerivesEntry
 EDGE
 ENGINE
 EOM_flatten_into_method
-- 
2.34.1



  [text/x-patch] 0003-Canonicalize-keys-for-derived-clause-hash-t-20250401.patch (7.9K, ../../CAExHW5sFgbPCh25fkb1cUmN0cVeEum9zfa0Vf2p8RcekS2_B1g@mail.gmail.com/5-0003-Canonicalize-keys-for-derived-clause-hash-t-20250401.patch)
  download | inline diff:
From aeb2e9e7058dc90851123185135176ebfd24317d Mon Sep 17 00:00:00 2001
From: Amit Langote <[email protected]>
Date: Tue, 1 Apr 2025 16:06:48 +0900
Subject: [PATCH 3/4] Canonicalize keys for derived clause hash table

Derived clauses are now stored in the ec_derives_hash using canonicalized
keys: the lower-addressed EquivalenceMember is always placed in em1, and
the higher-addressed one in em2. This avoids storing or searching for
both permutations of the same clause and eliminates the need for
redundant lookups.

For clauses involving a constant EM, only the non-constant EM is included
in the key; em1 is set to NULL and em2 to the non-constant EM.

The initial hash table size is set to twice the length of ec_derives_list
to avoid immediate resizing.

This design follows suggestions by David Rowley, who proposed both the
key canonicalization to reduce entry count and guidance on sizing the
hash table to balance memory usage and CPU efficiency.
---
 src/backend/optimizer/path/equivclass.c | 146 ++++++++++++++----------
 1 file changed, 84 insertions(+), 62 deletions(-)

diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c
index b988db4d7b3..12155c0c957 100644
--- a/src/backend/optimizer/path/equivclass.c
+++ b/src/backend/optimizer/path/equivclass.c
@@ -88,7 +88,9 @@ static RestrictInfo *ec_search_derived_clause_for_ems(PlannerInfo *root,
 													  EquivalenceClass *parent_ec);
 
 /*
- * Hash key identifying a derived clause by EquivalenceMembers and parent EC.
+ * Hash key identifying a derived clause.
+ *
+ * See fill_ec_derives_key() for details on canonical key construction.
  */
 typedef struct
 {
@@ -3450,15 +3452,22 @@ get_common_eclass_indexes(PlannerInfo *root, Relids relids1, Relids relids2)
 
 /*
  * ec_build_derives_hash
- *	  Construct the auxiliary hash table for derived clauses.
+ *	  Construct the auxiliary hash table for derived clause lookups.
  */
 static void
 ec_build_derives_hash(PlannerInfo *root, EquivalenceClass *ec)
 {
 	Assert(!ec->ec_derives_hash);
 
-	/* Create the hash table */
-	ec->ec_derives_hash = derives_create(root->planner_cxt, 256, NULL);
+	/*
+	 * Create the hash table.
+	 *
+	 * We set the initial size to twice the number of clauses in
+	 * ec_derives_list to avoid immediate resizing during insertion.
+	 */
+	ec->ec_derives_hash = derives_create(root->planner_cxt,
+										 list_length(ec->ec_derives_list) * 2,
+										 NULL);
 
 	foreach_node(RestrictInfo, rinfo, ec->ec_derives_list)
 		ec_add_clause_to_derives_hash(ec, rinfo);
@@ -3495,10 +3504,56 @@ ec_add_derived_clauses(EquivalenceClass *ec, List *clauses)
 			ec_add_clause_to_derives_hash(ec, rinfo);
 }
 
+/*
+ * fill_ec_derives_key
+ *		Compute a canonical key for ec_derives_hash lookup or insertion.
+ *
+ * Derived clauses are looked up using a pair of EquivalenceMembers and a
+ * parent EquivalenceClass. To avoid storing or searching for both EM orderings,
+ * we canonicalize the key:
+ *
+ * - For clauses involving two non-constant EMs, we order the EMs by address
+ *   and place the lower one first.
+ * - For clauses involving a constant EM, the caller must pass the non-constant
+ *   EM as leftem; we then set em1 = NULL and em2 = leftem.
+ */
+static inline void
+fill_ec_derives_key(ECDerivesKey *key,
+					EquivalenceMember *leftem,
+					EquivalenceMember *rightem,
+					EquivalenceClass *parent_ec)
+{
+	Assert(leftem);				/* Always required for lookup or insertion */
+
+	/*
+	 * Clauses with a constant EM are always stored and looked up using only
+	 * the non-constant EM, with the other key slot set to NULL.
+	 */
+	if (rightem == NULL)
+	{
+		key->em1 = NULL;
+		key->em2 = leftem;
+	}
+	else if (leftem < rightem)
+	{
+		key->em1 = leftem;
+		key->em2 = rightem;
+	}
+	else
+	{
+		key->em1 = rightem;
+		key->em2 = leftem;
+	}
+	key->parent_ec = parent_ec;
+}
+
 /*
  * ec_add_clause_to_derives_hash
- *      Add a derived clause to the ec_derives_hash of the given
- *      EquivalenceClass.
+ *		Add a derived clause to ec_derives_hash for the given EquivalenceClass.
+ *
+ * Each clause is inserted under a canonicalized key. For constant-containing
+ * clauses, only the non-constant EM is used for lookup; see comments in
+ * fill_ec_derives_key().
  */
 static void
 ec_add_clause_to_derives_hash(EquivalenceClass *ec, RestrictInfo *rinfo)
@@ -3513,49 +3568,24 @@ ec_add_clause_to_derives_hash(EquivalenceClass *ec, RestrictInfo *rinfo)
 	 */
 	Assert(!rinfo->left_em->em_is_const);
 
-	if (rinfo->right_em->em_is_const)
-	{
-		/*
-		 * Clauses containing a constant are never considered redundant, so
-		 * parent_ec is not set.
-		 */
-		Assert(!rinfo->parent_ec);
-
-		/*
-		 * find_derived_clause_for_ec_member() performs lookup of a clause
-		 * involving a constant using only the non-constant EM and NULL for
-		 * the RHS. Since there's only one constant EM per EC, we don't need
-		 * to store or match it during lookup.  We set key.em2 = NULL to
-		 * reflect this.
-		 */
-		key.em1 = rinfo->left_em;
-		key.em2 = NULL;
-		key.parent_ec = rinfo->parent_ec;
-		entry = derives_insert(ec->ec_derives_hash, key, &found);
-		Assert(!found);
-		entry->rinfo = rinfo;
-	}
-	else
-	{
-		key.em1 = rinfo->left_em;
-		key.em2 = rinfo->right_em;
-		key.parent_ec = rinfo->parent_ec;
-		entry = derives_insert(ec->ec_derives_hash, key, &found);
-		Assert(!found);
-		entry->rinfo = rinfo;
+	/*
+	 * Clauses containing a constant are never considered redundant, so
+	 * parent_ec is not set.
+	 */
+	Assert(!rinfo->parent_ec || !rinfo->right_em->em_is_const);
 
-		/*
-		 * Insert the clause under the given EM pair key, and also under the
-		 * reverse order. This ensures we can find the clause regardless of
-		 * the order in which EMs are passed to the lookup function.
-		 */
-		key.em1 = rinfo->right_em;
-		key.em2 = rinfo->left_em;
-		key.parent_ec = rinfo->parent_ec;
-		entry = derives_insert(ec->ec_derives_hash, key, &found);
-		Assert(!found);
-		entry->rinfo = rinfo;
-	}
+	/*
+	 * See fill_ec_derives_key() for details: we use a canonicalized key to
+	 * avoid storing both EM orderings. For constant EMs, only the
+	 * non-constant EM is included in the key.
+	 */
+	fill_ec_derives_key(&key,
+						rinfo->left_em,
+						rinfo->right_em->em_is_const ? NULL : rinfo->right_em,
+						rinfo->parent_ec);
+	entry = derives_insert(ec->ec_derives_hash, key, &found);
+	Assert(!found);
+	entry->rinfo = rinfo;
 }
 
 /*
@@ -3623,13 +3653,12 @@ ec_search_clause_for_ems(PlannerInfo *root, EquivalenceClass *ec,
  * If the number of derived clauses exceeds a threshold, switch to hash table
  * lookup; otherwise, scan ec_derives_list linearly.
  *
- * Clauses involving constants are looked up using only the non-const EM
- * (leftem) and a NULL rightem. In that case, we expect to find a clause with
- * a constant on the RHS.
+ * Clauses involving constants are looked up using only the non-constant EM
+ * (leftem), with rightem set to NULL. In that case, we expect to find a
+ * clause with a constant on the RHS.
  *
- * We do not attempt a second lookup with EMs swapped when using the hash
- * table; such clauses are inserted under both orderings at the time of
- * insertion.
+ * We do not attempt a second lookup with EMs swapped, because the key is
+ * canonicalized during both insertion and lookup.
  */
 static RestrictInfo *
 ec_search_derived_clause_for_ems(PlannerInfo *root, EquivalenceClass *ec,
@@ -3651,14 +3680,7 @@ ec_search_derived_clause_for_ems(PlannerInfo *root, EquivalenceClass *ec,
 		RestrictInfo *rinfo;
 		ECDerivesEntry *entry;
 
-		/*
-		 * See ec_add_clause_to_derives_hash() for rationale: derived clauses
-		 * are inserted into the hash table under both (em1, em2) and (em2,
-		 * em1), so a single lookup with the original order is sufficient.
-		 */
-		key.em1 = leftem;
-		key.em2 = rightem;
-		key.parent_ec = parent_ec;
+		fill_ec_derives_key(&key, leftem, rightem, parent_ec);
 		entry = derives_lookup(ec->ec_derives_hash, key);
 		if (entry)
 		{
-- 
2.34.1



^ permalink  raw  reply  [nested|flat] 14+ messages in thread

* Re: Reducing memory consumed by RestrictInfo list translations in partitionwise join planning
@ 2025-04-02 07:30  Amit Langote <[email protected]>
  parent: Ashutosh Bapat <[email protected]>
  0 siblings, 1 reply; 14+ messages in thread

From: Amit Langote @ 2025-04-02 07:30 UTC (permalink / raw)
  To: Ashutosh Bapat <[email protected]>; +Cc: David Rowley <[email protected]>; Alvaro Herrera <[email protected]>; Dmitry Dolgov <[email protected]>; [email protected]; vignesh C <[email protected]>; pgsql-hackers; Richard Guo <[email protected]>

On Wed, Apr 2, 2025 at 1:58 AM Ashutosh Bapat
<[email protected]> wrote:
> On Tue, Apr 1, 2025 at 1:32 PM Amit Langote <[email protected]> wrote:
> >
> > I think David’s suggestion to use 64 as the fixed initial size is
> > simpler and more predictable. Since list_length * 2 will always be >=
> > 64 anyway, unless we expect clause counts to grow significantly right
> > after the threshold, the fixed size avoids the need to reason about
> > sizing heuristics and keeps the logic clearer.
> >
> > It also lines up well with David’s point -- 64 strikes a good balance
> > for both memory usage and CPU efficiency. Unless there’s a strong
> > reason to favor dynamic sizing, I’d prefer to stick with the fixed 64.
>
> Consider following table and query (based on a similar table in
> join.sql, which exercises find_derived_clause_for_ec_member() code
> path.
> #create table fkest (x integer, x10 integer, x10b integer, x100
> integer, unique(x, x10, x100), foreign key (x, x10b, x100) references
> fkest(x, x10, x100));
> #select 'select * from fkest f1 join ' || string_agg(format('fkest f%s
> on (f1.x = f%s.x and f1.x10 = f%s.x10b and f1.x100 = f%s.x100)', i, i,
> i , i), ' join ') || ' where f1.x100 = 2' query from
> generate_series(2, 100) i; \gset
> #explain (summary) :query;
>
> This is a 100-way self-join between foreign key and referenced key and
> one of the foreign keys being set to a constant. This exercises the
> case of derived clauses with constant EM.
>
> When planning this query, all the 100 derived clauses containing the
> constant EM are created first and then they are searched one by one
> for every EM. Thus when the hash table is created in
> find_derived_clause_for_ec_member()->ec_search_derived_clause_for_ems(),
> the length of ec_derives_list is already 100, so the hash table will
> be expanded while it's being filled up the first time, if we use
> constant 64 as initial hash table size. This can be avoided if we use
> list_length() * 2. The pattern for create_join_clause() however is
> search then insert - so it will always create the hash table with
> initial size 64 right when the list length is 32. Both these cases are
> served well if we base the initial hash table size as a multiple of
> list_length(ec_derives_list).
>
> For the record, without the patch this query takes about 5800ms on
> average for planning on my laptop. With the patch the planning time
> reduces to about 5400ms - 6-7% of improvement if my approximate math
> is correct.

Yeah, I figured the constant EM case might end up adding a bunch of
derived clauses before the hash table is built, but this example
really helps illustrate that, thanks.

It does seem like a special case --
generate_base_implied_equalities_const() dumps in a whole bunch of
clauses up front, unlike the usual search-then-insert pattern in
create_join_clause().

After our chat with David, I think using list_length(ec_derives_list)
to size the hash table is reasonable. Given how simplehash rounds up
-- dividing by the fillfactor and rounding to the next power of two --
we still end up with 64 buckets around the threshold. So the dynamic
sizing behaves pretty predictably and doesn't seem like a bad choice
after all.

> > * Replaces literal values for threshold and initial size with macros,
> > defined near the key and entry types.
>
> I don't see this in your attached patches. Am I missing something?
> It's still using list_length() for initial hash table size. But with
> my explanation above, I believe we will keep it that way.

Oops, looks like I created those (v6) patches before adding those macro changes.

> > I wasn’t sure why you removed this comment:
> >
> > - * We do not attempt a second lookup with EMs swapped when using the hash
> > - * table; such clauses are inserted under both orderings at the time of
> > - * insertion.
>
> When I read it, I didn't understand why we mentioned a second lookup,
> so I dropped it. But now I see that the comment is in the context of
> two comparisons being done when using list. I have rephrased the
> comment a bit to make this comparison clear.

Looks good.

> > 0003’s commit message includes a blurb I plan to paste into the main
> > patch’s message (with minor tweaks) when we squash the patches.
>
> Slight correction in the first sentence of that blurb message.
>
> Derived clauses are now stored in the ec_derives_hash using canonicalized
> keys: the EquivalenceMember with lower memory address is always placed
> in em1, and ...

Looks good.

> +/*
> + * fill_ec_derives_key
> + * Compute a canonical key for ec_derives_hash lookup or insertion.
> + *
> + * Derived clauses are looked up using a pair of EquivalenceMembers and a
> + * parent EquivalenceClass. To avoid storing or searching for both EM
> orderings,
> + * we canonicalize the key:
> + *
> + * - For clauses involving two non-constant EMs, we order the EMs by address
> + * and place the lower one first.
> + * - For clauses involving a constant EM, the caller must pass the non-constant
> + * EM as leftem; we then set em1 = NULL and em2 = leftem.
> + */
> +static inline void
> +fill_ec_derives_key(ECDerivesKey *key,
> + EquivalenceMember *leftem,
> + EquivalenceMember *rightem,
> + EquivalenceClass *parent_ec)
> +{
> + Assert(leftem); /* Always required for lookup or insertion */
> +
> + /*
> + * Clauses with a constant EM are always stored and looked up using only
> + * the non-constant EM, with the other key slot set to NULL.
> + */
>
> This comment seems to overlap with what's already there in the
> prologue. However "Clauses with a constant EM are always stored and
> looked up using the non-constant EM" is non-overlapping part. This
> part is also repeated in ec_add_clause_to_derives_hash(), which I
> think is a better place for this comment. Changed in the attached
> patch.

Ok, those comment changes look good overall.

> */
> Assert(!rinfo->left_em->em_is_const);
> + /*
> + * Clauses containing a constant are never considered redundant, so
> + * parent_ec is not set.
> + */
> + Assert(!rinfo->parent_ec || !rinfo->right_em->em_is_const);
>
> These two Assertions are applicable to all the derived clauses but are
> added to a function which deals with only hash table. If an EC doesn't
> have enough derived clauses to create a hash table these assertions
> won't be checked. The reason they are here is because we are using
> these properties to create canonical hash table key. Should we move
> them to ec_add_derived_clause() for better coverage?

Yes, good idea.

> I have also removed some comments repeated in the function prologue
> and function body.
>
> 0001, 0002 and 0003 are the same as your set. 0004 contains my changes
> in a separate patch so that it's easy to review those.

Here's v7 in which I have merged 0003 and 0004 into 0002.

You'll see that threshold now uses a macro and
list_length(ec->ec_derives_list) is passed as initial size.

I'm feeling good about this version, but let me know if you have any
further thoughts / comments.

--
Thanks, Amit Langote


Attachments:

  [application/octet-stream] v7-0001-Add-assertion-to-verify-derived-clause-has-consta.patch (1.5K, ../../CA+HiwqG3RrsRDX+0gjVbF6AKib9UFWchd1J3FQUbDuf8Ey-M6Q@mail.gmail.com/2-v7-0001-Add-assertion-to-verify-derived-clause-has-consta.patch)
  download | inline diff:
From d9335e86fc37dfd9d2c810ec4e03df9fa10c2b81 Mon Sep 17 00:00:00 2001
From: Amit Langote <[email protected]>
Date: Mon, 24 Mar 2025 21:17:46 +0900
Subject: [PATCH v7 1/2] Add assertion to verify derived clause has constant
 RHS

find_derived_clause_for_ec_member() searches for a previously-derived
clause that equates a non-constant EquivalenceMember to a constant.
It is only called for EquivalenceClasses with ec_has_const set, and
with a non-constant member as the target.

The matched clause is expected to have the non-constant member on the
left-hand side and the constant EquivalenceMember on the right.

Assert that the RHS is indeed a constant, to catch violations of this
structure and enforce assumptions made by
generate_base_implied_equalities_const().

Author: Ashutosh Bapat <[email protected]>
Discussion: https://postgr.es/m/CAExHW5scMxyFRqOFE6ODmBiW2rnVBEmeEcA-p4W_CyuEikURdA@mail.gmail.com
---
 src/backend/optimizer/path/equivclass.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c
index 0f9ecf5ee8b..493a95d26cc 100644
--- a/src/backend/optimizer/path/equivclass.c
+++ b/src/backend/optimizer/path/equivclass.c
@@ -2664,7 +2664,10 @@ find_derived_clause_for_ec_member(EquivalenceClass *ec,
 		 * members on the left side of derived clauses.
 		 */
 		if (rinfo->left_em == em)
+		{
+			Assert(rinfo->right_em->em_is_const);
 			return rinfo;
+		}
 	}
 	return NULL;
 }
-- 
2.43.0



  [application/octet-stream] v7-0002-Make-derived-clause-lookup-in-EquivalenceClass-mo.patch (27.4K, ../../CA+HiwqG3RrsRDX+0gjVbF6AKib9UFWchd1J3FQUbDuf8Ey-M6Q@mail.gmail.com/3-v7-0002-Make-derived-clause-lookup-in-EquivalenceClass-mo.patch)
  download | inline diff:
From 553abf40ecd8f696bc64db4e106b1c8ad1d28098 Mon Sep 17 00:00:00 2001
From: Amit Langote <[email protected]>
Date: Wed, 2 Apr 2025 15:31:30 +0900
Subject: [PATCH v7 2/2] Make derived clause lookup in EquivalenceClass more
 efficient

Derived clauses are stored in ec_derives, a List of RestrictInfos.
These clauses are looked up later using the left and right
EquivalenceMembers and the clause's parent EC, typically during join
clause generation.

This linear search becomes expensive in queries with many joins or
partitions, where ec_derives may contain thousands of entries. In
particular, create_join_clause() can spend significant time scanning
this list.

To improve performance, introduce a hash table (ec_derives_hash) that
is built when the list reaches 32 entries -- the same threshold used
for join_rel_hash. The original list is retained alongside the hash
table to support EC merging and serialization
(_outEquivalenceClass()).

Each clause is stored in the hash table using a canonicalized key: the
EquivalenceMember with the lower memory address is placed in em1, and
the other in em2. This avoids storing or searching for both
permutations of the same clause. For clauses involving a constant EM,
the key stores em1 = NULL and em2 as the non-constant EM.

The hash table is initialized using list_length(ec_derives_list) as
the size hint. simplehash internally adjusts this to the next power of
two after dividing by the fillfactor, so this typically results in at
least 64 buckets near the threshold -- avoiding immediate resizing
while adapting to the actual number of entries.

The lookup logic for derived clauses is now centralized in
ec_search_derived_clause_for_ems(), which consults the hash table when
available and falls back to the list otherwise.

The new ec_clear_derived_clauses() always frees ec_derives_list, even
though some of the original code paths that cleared the old
ec_derives field did not. This ensures consistent cleanup and avoids
leaking memory when the list grows large.

An assertion originally placed in find_derived_clause_for_ec_member()
is moved into ec_search_derived_clause_for_ems() so that it is
enforced consistently, regardless of whether the hash table or list is
used for lookup.

This design incorporates suggestions by David Rowley, who proposed
both the key canonicalization and guidance on initial sizing to
balance memory usage and CPU efficiency.

Author: Ashutosh Bapat <[email protected]>
Reviewed-by: Amit Langote <[email protected]>
Reviewed-by: David Rowley <[email protected]>
Tested-by: Dmitry Dolgov <[email protected]>
Tested-by: Alvaro Herrera <[email protected]>
Tested-by: Amit Langote <[email protected]>
Tested-by: David Rowley <[email protected]>
Discussion: https://postgr.es/m/CAExHW5vZiQtWU6moszLP5iZ8gLX_ZAUbgEX0DxGLx9PGWCtqUg@mail.gmail.com
---
 src/backend/nodes/outfuncs.c              |   3 +-
 src/backend/optimizer/path/costsize.c     |   3 +-
 src/backend/optimizer/path/equivclass.c   | 438 ++++++++++++++++++----
 src/backend/optimizer/plan/analyzejoins.c |   5 +-
 src/include/nodes/pathnodes.h             |  17 +-
 src/include/optimizer/paths.h             |   4 +-
 src/tools/pgindent/typedefs.list          |   2 +
 7 files changed, 392 insertions(+), 80 deletions(-)

diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index bb9bdd67192..557f06e344f 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -467,7 +467,8 @@ _outEquivalenceClass(StringInfo str, const EquivalenceClass *node)
 	WRITE_OID_FIELD(ec_collation);
 	WRITE_NODE_FIELD(ec_members);
 	WRITE_NODE_FIELD(ec_sources);
-	WRITE_NODE_FIELD(ec_derives);
+	/* Only ec_derives_list is written; hash is not serialized. */
+	WRITE_NODE_FIELD(ec_derives_list);
 	WRITE_BITMAPSET_FIELD(ec_relids);
 	WRITE_BOOL_FIELD(ec_has_const);
 	WRITE_BOOL_FIELD(ec_has_volatile);
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index f6f77b8fe19..c474c7a06af 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -5848,7 +5848,8 @@ get_foreign_key_join_selectivity(PlannerInfo *root,
 				if (ec && ec->ec_has_const)
 				{
 					EquivalenceMember *em = fkinfo->fk_eclass_member[i];
-					RestrictInfo *rinfo = find_derived_clause_for_ec_member(ec,
+					RestrictInfo *rinfo = find_derived_clause_for_ec_member(root,
+																			ec,
 																			em);
 
 					if (rinfo)
diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c
index 493a95d26cc..853d3904577 100644
--- a/src/backend/optimizer/path/equivclass.c
+++ b/src/backend/optimizer/path/equivclass.c
@@ -20,6 +20,7 @@
 
 #include "access/stratnum.h"
 #include "catalog/pg_type.h"
+#include "common/hashfn.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/appendinfo.h"
@@ -72,7 +73,56 @@ static Bitmapset *get_eclass_indexes_for_relids(PlannerInfo *root,
 												Relids relids);
 static Bitmapset *get_common_eclass_indexes(PlannerInfo *root, Relids relids1,
 											Relids relids2);
+static void ec_build_derives_hash(PlannerInfo *root, EquivalenceClass *ec);
+static void ec_add_derived_clauses(EquivalenceClass *ec, List *clauses);
+static void ec_add_derived_clause(EquivalenceClass *ec, RestrictInfo *clause);
+static void ec_add_clause_to_derives_hash(EquivalenceClass *ec, RestrictInfo *rinfo);
+static RestrictInfo *ec_search_clause_for_ems(PlannerInfo *root, EquivalenceClass *ec,
+											  EquivalenceMember *leftem,
+											  EquivalenceMember *rightem,
+											  EquivalenceClass *parent_ec);
+static RestrictInfo *ec_search_derived_clause_for_ems(PlannerInfo *root,
+													  EquivalenceClass *ec,
+													  EquivalenceMember *leftem,
+													  EquivalenceMember *rightem,
+													  EquivalenceClass *parent_ec);
 
+/*
+ * Hash key identifying a derived clause.
+ *
+ * This structure should not be filled manually. Use fill_ec_derives_key() to
+ * set it up in canonical form.
+ */
+typedef struct
+{
+	EquivalenceMember *em1;
+	EquivalenceMember *em2;
+	EquivalenceClass *parent_ec;
+} ECDerivesKey;
+
+/* Hash table entry in ec_derives_hash. */
+typedef struct
+{
+	uint32		status;
+	ECDerivesKey key;
+	RestrictInfo *rinfo;
+} ECDerivesEntry;
+
+/* Threshold for switching from list to hash table */
+#define EC_DERIVES_HASH_THRESHOLD 32
+
+#define SH_PREFIX               derives
+#define SH_ELEMENT_TYPE			ECDerivesEntry
+#define SH_KEY_TYPE             ECDerivesKey
+#define SH_KEY                  key
+#define SH_HASH_KEY(tb, key)	\
+	hash_bytes((const unsigned char *) &(key), sizeof(ECDerivesKey))
+#define SH_EQUAL(tb, a, b)	\
+	((a).em1 == (b).em1 && (a).em2 == (b).em2 && (a).parent_ec == (b).parent_ec)
+#define SH_SCOPE                static inline
+#define SH_DECLARE
+#define SH_DEFINE
+#include "lib/simplehash.h"
 
 /*
  * process_equivalence
@@ -342,7 +392,12 @@ process_equivalence(PlannerInfo *root,
 		 */
 		ec1->ec_members = list_concat(ec1->ec_members, ec2->ec_members);
 		ec1->ec_sources = list_concat(ec1->ec_sources, ec2->ec_sources);
-		ec1->ec_derives = list_concat(ec1->ec_derives, ec2->ec_derives);
+
+		/*
+		 * Appends ec2's derived clauses to ec1->ec_derives_list and adds them
+		 * to ec1->ec_derives_hash if present.
+		 */
+		ec_add_derived_clauses(ec1, ec2->ec_derives_list);
 		ec1->ec_relids = bms_join(ec1->ec_relids, ec2->ec_relids);
 		ec1->ec_has_const |= ec2->ec_has_const;
 		/* can't need to set has_volatile */
@@ -355,7 +410,7 @@ process_equivalence(PlannerInfo *root,
 		/* just to avoid debugging confusion w/ dangling pointers: */
 		ec2->ec_members = NIL;
 		ec2->ec_sources = NIL;
-		ec2->ec_derives = NIL;
+		ec_clear_derived_clauses(ec2);
 		ec2->ec_relids = NULL;
 		ec1->ec_sources = lappend(ec1->ec_sources, restrictinfo);
 		ec1->ec_min_security = Min(ec1->ec_min_security,
@@ -412,7 +467,8 @@ process_equivalence(PlannerInfo *root,
 		ec->ec_collation = collation;
 		ec->ec_members = NIL;
 		ec->ec_sources = list_make1(restrictinfo);
-		ec->ec_derives = NIL;
+		ec->ec_derives_list = NIL;
+		ec->ec_derives_hash = NULL;
 		ec->ec_relids = NULL;
 		ec->ec_has_const = false;
 		ec->ec_has_volatile = false;
@@ -671,7 +727,8 @@ get_eclass_for_sort_expr(PlannerInfo *root,
 	newec->ec_collation = collation;
 	newec->ec_members = NIL;
 	newec->ec_sources = NIL;
-	newec->ec_derives = NIL;
+	newec->ec_derives_list = NIL;
+	newec->ec_derives_hash = NULL;
 	newec->ec_relids = NULL;
 	newec->ec_has_const = false;
 	newec->ec_has_volatile = contain_volatile_functions((Node *) expr);
@@ -1026,8 +1083,8 @@ relation_can_be_sorted_early(PlannerInfo *root, RelOptInfo *rel,
  * scanning of the quals and before Path construction begins.
  *
  * We make no attempt to avoid generating duplicate RestrictInfos here: we
- * don't search ec_sources or ec_derives for matches.  It doesn't really
- * seem worth the trouble to do so.
+ * don't search existing source or derived clauses in the EC for matches.  It
+ * doesn't really seem worth the trouble to do so.
  */
 void
 generate_base_implied_equalities(PlannerInfo *root)
@@ -1188,11 +1245,11 @@ generate_base_implied_equalities_const(PlannerInfo *root,
 
 		/*
 		 * If the clause didn't degenerate to a constant, fill in the correct
-		 * markings for a mergejoinable clause, and save it in ec_derives. (We
-		 * will not re-use such clauses directly, but selectivity estimation
-		 * may consult the list later.  Note that this use of ec_derives does
-		 * not overlap with its use for join clauses, since we never generate
-		 * join clauses from an ec_has_const eclass.)
+		 * markings for a mergejoinable clause, and save it as a derived
+		 * clause. (We will not re-use such clauses directly, but selectivity
+		 * estimation may consult those later.  Note that this use of derived
+		 * clauses does not overlap with its use for join clauses, since we
+		 * never generate join clauses from an ec_has_const eclass.)
 		 */
 		if (rinfo && rinfo->mergeopfamilies)
 		{
@@ -1200,7 +1257,7 @@ generate_base_implied_equalities_const(PlannerInfo *root,
 			rinfo->left_ec = rinfo->right_ec = ec;
 			rinfo->left_em = cur_em;
 			rinfo->right_em = const_em;
-			ec->ec_derives = lappend(ec->ec_derives, rinfo);
+			ec_add_derived_clause(ec, rinfo);
 		}
 	}
 }
@@ -1265,10 +1322,10 @@ generate_base_implied_equalities_no_const(PlannerInfo *root,
 
 			/*
 			 * If the clause didn't degenerate to a constant, fill in the
-			 * correct markings for a mergejoinable clause.  We don't put it
-			 * in ec_derives however; we don't currently need to re-find such
-			 * clauses, and we don't want to clutter that list with non-join
-			 * clauses.
+			 * correct markings for a mergejoinable clause.  We don't record
+			 * it as a derived clause, since we don't currently need to
+			 * re-find such clauses, and don't want to clutter the
+			 * derived-clause set with non-join clauses.
 			 */
 			if (rinfo && rinfo->mergeopfamilies)
 			{
@@ -1369,7 +1426,7 @@ generate_base_implied_equalities_broken(PlannerInfo *root,
  * we consider different join paths, we avoid generating multiple copies:
  * whenever we select a particular pair of EquivalenceMembers to join,
  * we check to see if the pair matches any original clause (in ec_sources)
- * or previously-built clause (in ec_derives).  This saves memory and allows
+ * or previously-built derived clause.  This saves memory and allows
  * re-use of information cached in RestrictInfos.  We also avoid generating
  * commutative duplicates, i.e. if the algorithm selects "a.x = b.y" but
  * we already have "b.y = a.x", we return the existing clause.
@@ -1754,9 +1811,9 @@ generate_join_implied_equalities_broken(PlannerInfo *root,
 	/*
 	 * If we have to translate, just brute-force apply adjust_appendrel_attrs
 	 * to all the RestrictInfos at once.  This will result in returning
-	 * RestrictInfos that are not listed in ec_derives, but there shouldn't be
-	 * any duplication, and it's a sufficiently narrow corner case that we
-	 * shouldn't sweat too much over it anyway.
+	 * RestrictInfos that are not included in EC's derived clauses, but there
+	 * shouldn't be any duplication, and it's a sufficiently narrow corner
+	 * case that we shouldn't sweat too much over it anyway.
 	 *
 	 * Since inner_rel might be an indirect descendant of the baserel
 	 * mentioned in the ec_sources clauses, we have to be prepared to apply
@@ -1823,43 +1880,11 @@ create_join_clause(PlannerInfo *root,
 {
 	RestrictInfo *rinfo;
 	RestrictInfo *parent_rinfo = NULL;
-	ListCell   *lc;
 	MemoryContext oldcontext;
 
-	/*
-	 * Search to see if we already built a RestrictInfo for this pair of
-	 * EquivalenceMembers.  We can use either original source clauses or
-	 * previously-derived clauses, and a commutator clause is acceptable.
-	 *
-	 * We used to verify that opno matches, but that seems redundant: even if
-	 * it's not identical, it'd better have the same effects, or the operator
-	 * families we're using are broken.
-	 */
-	foreach(lc, ec->ec_sources)
-	{
-		rinfo = (RestrictInfo *) lfirst(lc);
-		if (rinfo->left_em == leftem &&
-			rinfo->right_em == rightem &&
-			rinfo->parent_ec == parent_ec)
-			return rinfo;
-		if (rinfo->left_em == rightem &&
-			rinfo->right_em == leftem &&
-			rinfo->parent_ec == parent_ec)
-			return rinfo;
-	}
-
-	foreach(lc, ec->ec_derives)
-	{
-		rinfo = (RestrictInfo *) lfirst(lc);
-		if (rinfo->left_em == leftem &&
-			rinfo->right_em == rightem &&
-			rinfo->parent_ec == parent_ec)
-			return rinfo;
-		if (rinfo->left_em == rightem &&
-			rinfo->right_em == leftem &&
-			rinfo->parent_ec == parent_ec)
-			return rinfo;
-	}
+	rinfo = ec_search_clause_for_ems(root, ec, leftem, rightem, parent_ec);
+	if (rinfo)
+		return rinfo;
 
 	/*
 	 * Not there, so build it, in planner context so we can re-use it. (Not
@@ -1923,7 +1948,7 @@ create_join_clause(PlannerInfo *root,
 	rinfo->left_em = leftem;
 	rinfo->right_em = rightem;
 	/* and save it for possible re-use */
-	ec->ec_derives = lappend(ec->ec_derives, rinfo);
+	ec_add_derived_clause(ec, rinfo);
 
 	MemoryContextSwitchTo(oldcontext);
 
@@ -2648,28 +2673,14 @@ match_eclasses_to_foreign_key_col(PlannerInfo *root,
  * Returns NULL if no such clause can be found.
  */
 RestrictInfo *
-find_derived_clause_for_ec_member(EquivalenceClass *ec,
+find_derived_clause_for_ec_member(PlannerInfo *root,
+								  EquivalenceClass *ec,
 								  EquivalenceMember *em)
 {
-	ListCell   *lc;
-
 	Assert(ec->ec_has_const);
 	Assert(!em->em_is_const);
-	foreach(lc, ec->ec_derives)
-	{
-		RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
 
-		/*
-		 * generate_base_implied_equalities_const will have put non-const
-		 * members on the left side of derived clauses.
-		 */
-		if (rinfo->left_em == em)
-		{
-			Assert(rinfo->right_em->em_is_const);
-			return rinfo;
-		}
-	}
-	return NULL;
+	return ec_search_derived_clause_for_ems(root, ec, em, NULL, NULL);
 }
 
 
@@ -3442,3 +3453,284 @@ get_common_eclass_indexes(PlannerInfo *root, Relids relids1, Relids relids2)
 	/* Calculate and return the common EC indexes, recycling the left input. */
 	return bms_int_members(rel1ecs, rel2ecs);
 }
+
+/*
+ * ec_build_derives_hash
+ *	  Construct the auxiliary hash table for derived clause lookups.
+ */
+static void
+ec_build_derives_hash(PlannerInfo *root, EquivalenceClass *ec)
+{
+	Assert(!ec->ec_derives_hash);
+
+	/*
+	 * Create the hash table.
+	 *
+	 * We pass list_length(ec->ec_derives_list) as the initial size.
+	 * Simplehash will divide this by the fillfactor (typically 0.9) and round
+	 * up to the next power of two, so this will usually give us at least 64
+	 * buckets around the threshold. That avoids immediate resizing without
+	 * hardcoding a specific size.
+	 */
+	ec->ec_derives_hash = derives_create(root->planner_cxt,
+										 list_length(ec->ec_derives_list),
+										 NULL);
+
+	foreach_node(RestrictInfo, rinfo, ec->ec_derives_list)
+		ec_add_clause_to_derives_hash(ec, rinfo);
+}
+
+/*
+ * ec_add_derived_clause
+ *		Add a clause to the set of derived clauses for the given
+ *		EquivalenceClass. Always appends to ec_derives_list; also adds
+ *		to ec_derives_hash if it exists.
+ *
+ * Also asserts expected invariants of derived clauses.
+ */
+static void
+ec_add_derived_clause(EquivalenceClass *ec, RestrictInfo *clause)
+{
+	/*
+	 * Constant, if present, is always placed on the RHS; see
+	 * generate_base_implied_equalities_const(). LHS is never a constant.
+	 */
+	Assert(!clause->left_em->em_is_const);
+
+	/*
+	 * Clauses containing a constant are never considered redundant, so
+	 * parent_ec is not set.
+	 */
+	Assert(!clause->parent_ec || !clause->right_em->em_is_const);
+
+	ec->ec_derives_list = lappend(ec->ec_derives_list, clause);
+	if (ec->ec_derives_hash)
+		ec_add_clause_to_derives_hash(ec, clause);
+}
+
+/*
+ * ec_add_derived_clauses
+ *		Add a list of clauses to the set of clauses derived from the given
+ *		EquivalenceClass; adding to the list and hash table if needed.
+ *
+ * This function is similar to ec_add_derived_clause() but optimized for adding
+ * multiple clauses at a time to the ec_derives_list.  The assertions from
+ * ec_add_derived_clause() are not repeated here, as the input clauses are
+ * assumed to have already been validated.
+ */
+static void
+ec_add_derived_clauses(EquivalenceClass *ec, List *clauses)
+{
+	ec->ec_derives_list = list_concat(ec->ec_derives_list, clauses);
+	if (ec->ec_derives_hash)
+		foreach_node(RestrictInfo, rinfo, clauses)
+			ec_add_clause_to_derives_hash(ec, rinfo);
+}
+
+/*
+ * fill_ec_derives_key
+ *		Compute a canonical key for ec_derives_hash lookup or insertion.
+ *
+ * Derived clauses are looked up using a pair of EquivalenceMembers and a
+ * parent EquivalenceClass. To avoid storing or searching for both EM orderings,
+ * we canonicalize the key:
+ *
+ * - For clauses involving two non-constant EMs, em1 is set to the EM with lower
+ *   memory address and em2 is set to the other one.
+ * - For clauses involving a constant EM, the caller must pass the non-constant
+ *   EM as leftem and NULL as rightem; we then set em1 = NULL and em2 = leftem.
+ */
+static inline void
+fill_ec_derives_key(ECDerivesKey *key,
+					EquivalenceMember *leftem,
+					EquivalenceMember *rightem,
+					EquivalenceClass *parent_ec)
+{
+	Assert(leftem);				/* Always required for lookup or insertion */
+
+	if (rightem == NULL)
+	{
+		key->em1 = NULL;
+		key->em2 = leftem;
+	}
+	else if (leftem < rightem)
+	{
+		key->em1 = leftem;
+		key->em2 = rightem;
+	}
+	else
+	{
+		key->em1 = rightem;
+		key->em2 = leftem;
+	}
+	key->parent_ec = parent_ec;
+}
+
+/*
+ * ec_add_clause_to_derives_hash
+ *		Add a derived clause to ec_derives_hash in the given EquivalenceClass.
+ *
+ * Each clause is associated with a canonicalized key. For constant-containing
+ * clauses, only the non-constant EM is used for lookup; see comments in
+ * fill_ec_derives_key().
+ */
+static void
+ec_add_clause_to_derives_hash(EquivalenceClass *ec, RestrictInfo *rinfo)
+{
+	ECDerivesKey key;
+	ECDerivesEntry *entry;
+	bool		found;
+
+	/*
+	 * Constants are always placed on the RHS; see
+	 * generate_base_implied_equalities_const().
+	 */
+	Assert(!rinfo->left_em->em_is_const);
+
+	/*
+	 * Clauses containing a constant are never considered redundant, so
+	 * parent_ec is not set.
+	 */
+	Assert(!rinfo->parent_ec || !rinfo->right_em->em_is_const);
+
+	/*
+	 * See fill_ec_derives_key() for details: we use a canonicalized key to
+	 * avoid storing both EM orderings. For constant EMs, only the
+	 * non-constant EM is included in the key.
+	 */
+	fill_ec_derives_key(&key,
+						rinfo->left_em,
+						rinfo->right_em->em_is_const ? NULL : rinfo->right_em,
+						rinfo->parent_ec);
+	entry = derives_insert(ec->ec_derives_hash, key, &found);
+	Assert(!found);
+	entry->rinfo = rinfo;
+}
+
+/*
+ * ec_clear_derived_clauses
+ *      Reset ec_derives_list and ec_derives_hash.
+ *
+ * We destroy the hash table explicitly, since it may consume significant
+ * space. The list holds the same set of entries and can become equally large
+ * when thousands of partitions are involved, so we free it as well -- even
+ * though we do not typically free lists.
+ */
+void
+ec_clear_derived_clauses(EquivalenceClass *ec)
+{
+	list_free(ec->ec_derives_list);
+	ec->ec_derives_list = NIL;
+
+	if (ec->ec_derives_hash)
+	{
+		derives_destroy(ec->ec_derives_hash);
+		ec->ec_derives_hash = NULL;
+	}
+}
+
+/*
+ * ec_search_clause_for_ems
+ *		Search for an existing RestrictInfo that equates the given pair
+ *		of EquivalenceMembers, either from ec_sources or ec_derives.
+ *
+ * We accept clauses in either operand order, so commutators are matched. We
+ * used to require matching operator OIDs, but dropped that since any
+ * semantically different operator here would indicate a broken operator
+ * family.
+ *
+ * Returns NULL if no matching clause is found.
+ */
+
+static RestrictInfo *
+ec_search_clause_for_ems(PlannerInfo *root, EquivalenceClass *ec,
+						 EquivalenceMember *leftem, EquivalenceMember *rightem,
+						 EquivalenceClass *parent_ec)
+{
+	/* Check original source clauses */
+	foreach_node(RestrictInfo, rinfo, ec->ec_sources)
+	{
+		if (rinfo->left_em == leftem &&
+			rinfo->right_em == rightem &&
+			rinfo->parent_ec == parent_ec)
+			return rinfo;
+		if (rinfo->left_em == rightem &&
+			rinfo->right_em == leftem &&
+			rinfo->parent_ec == parent_ec)
+			return rinfo;
+	}
+
+	/* Not found in ec_sources; search derived clauses */
+	return ec_search_derived_clause_for_ems(root, ec, leftem, rightem,
+											parent_ec);
+}
+
+/*
+ * ec_search_derived_clause_for_ems
+ *		Search for an existing derived clause between two EquivalenceMembers.
+ *
+ * If the number of derived clauses exceeds a threshold, switch to hash table
+ * lookup; otherwise, scan ec_derives_list linearly.
+ *
+ * Clauses involving constants are looked up by passing the non-constant EM
+ * as leftem and setting rightem to NULL. In that case, we expect to find a
+ * clause with a constant on the RHS.
+ *
+ * While searching the list, we compare each given EM with both sides of each
+ * clause. But for hash table lookups, we construct a canonicalized key and
+ * perform a single lookup.
+ */
+static RestrictInfo *
+ec_search_derived_clause_for_ems(PlannerInfo *root, EquivalenceClass *ec,
+								 EquivalenceMember *leftem,
+								 EquivalenceMember *rightem,
+								 EquivalenceClass *parent_ec)
+{
+	/* Switch to using hash lookup when list grows "too long". */
+	if (!ec->ec_derives_hash &&
+		list_length(ec->ec_derives_list) >= EC_DERIVES_HASH_THRESHOLD)
+		ec_build_derives_hash(root, ec);
+
+	/* Perform hash table lookup if available */
+	if (ec->ec_derives_hash)
+	{
+		ECDerivesKey key;
+		RestrictInfo *rinfo;
+		ECDerivesEntry *entry;
+
+		fill_ec_derives_key(&key, leftem, rightem, parent_ec);
+		entry = derives_lookup(ec->ec_derives_hash, key);
+		if (entry)
+		{
+			rinfo = entry->rinfo;
+			Assert(rinfo);
+			Assert(rightem || rinfo->right_em->em_is_const);
+			return rinfo;
+		}
+	}
+	else
+	{
+		/* Fallback to linear search over ec_derives_list */
+		foreach_node(RestrictInfo, rinfo, ec->ec_derives_list)
+		{
+			/* Handle special case: lookup by non-const EM alone */
+			if (!rightem &&
+				rinfo->left_em == leftem)
+			{
+				/* See the comment above in hash path for rationale. */
+				Assert(rinfo->right_em->em_is_const);
+				return rinfo;
+			}
+			if (rinfo->left_em == leftem &&
+				rinfo->right_em == rightem &&
+				rinfo->parent_ec == parent_ec)
+				return rinfo;
+			if (rinfo->left_em == rightem &&
+				rinfo->right_em == leftem &&
+				rinfo->parent_ec == parent_ec)
+				return rinfo;
+		}
+	}
+
+	return NULL;
+}
diff --git a/src/backend/optimizer/plan/analyzejoins.c b/src/backend/optimizer/plan/analyzejoins.c
index 8a8d4a2af33..ae20691ca91 100644
--- a/src/backend/optimizer/plan/analyzejoins.c
+++ b/src/backend/optimizer/plan/analyzejoins.c
@@ -749,7 +749,7 @@ remove_rel_from_eclass(EquivalenceClass *ec, SpecialJoinInfo *sjinfo,
 	 * drop them.  (At this point, any such clauses would be base restriction
 	 * clauses, which we'd not need anymore anyway.)
 	 */
-	ec->ec_derives = NIL;
+	ec_clear_derived_clauses(ec);
 }
 
 /*
@@ -1544,8 +1544,7 @@ update_eclasses(EquivalenceClass *ec, int from, int to)
 	list_free(ec->ec_members);
 	ec->ec_members = new_members;
 
-	list_free(ec->ec_derives);
-	ec->ec_derives = NULL;
+	ec_clear_derived_clauses(ec);
 
 	/* Update EC source expressions */
 	foreach_node(RestrictInfo, rinfo, ec->ec_sources)
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index c24a1fc8514..e29d26a1814 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -1403,6 +1403,18 @@ typedef struct JoinDomain
  * entry: consider SELECT random() AS a, random() AS b ... ORDER BY b,a.
  * So we record the SortGroupRef of the originating sort clause.
  *
+ * Derived equality clauses are stored in ec_derives_list. For small queries,
+ * this list is scanned directly during lookup. For larger queries -- e.g.,
+ * with many partitions or joins -- a hash table (ec_derives_hash) is built
+ * when the list grows beyond a threshold, for faster lookup. When present,
+ * the hash table contains the same RestrictInfos and is maintained alongside
+ * the list. We retain the list even when the hash is used to simplify
+ * serialization (e.g., in _outEquivalenceClass()) and support
+ * EquivalenceClass merging.
+ *
+ * In contrast, ec_sources holds equality clauses that appear directly in the
+ * query. These are typically few and do not require a hash table for lookup.
+ *
  * NB: if ec_merged isn't NULL, this class has been merged into another, and
  * should be ignored in favor of using the pointed-to class.
  *
@@ -1422,7 +1434,10 @@ typedef struct EquivalenceClass
 	Oid			ec_collation;	/* collation, if datatypes are collatable */
 	List	   *ec_members;		/* list of EquivalenceMembers */
 	List	   *ec_sources;		/* list of generating RestrictInfos */
-	List	   *ec_derives;		/* list of derived RestrictInfos */
+	List	   *ec_derives_list;	/* list of derived RestrictInfos */
+	struct derives_hash *ec_derives_hash;	/* optional hash table for fast
+											 * lookup; contains same
+											 * RestrictInfos as list */
 	Relids		ec_relids;		/* all relids appearing in ec_members, except
 								 * for child members (see below) */
 	bool		ec_has_const;	/* any pseudoconstants in ec_members? */
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index bc5dfd7db41..16dc4d5ee82 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -167,7 +167,8 @@ extern bool exprs_known_equal(PlannerInfo *root, Node *item1, Node *item2,
 extern EquivalenceClass *match_eclasses_to_foreign_key_col(PlannerInfo *root,
 														   ForeignKeyOptInfo *fkinfo,
 														   int colno);
-extern RestrictInfo *find_derived_clause_for_ec_member(EquivalenceClass *ec,
+extern RestrictInfo *find_derived_clause_for_ec_member(PlannerInfo *root,
+													   EquivalenceClass *ec,
 													   EquivalenceMember *em);
 extern void add_child_rel_equivalences(PlannerInfo *root,
 									   AppendRelInfo *appinfo,
@@ -197,6 +198,7 @@ extern bool eclass_useful_for_merging(PlannerInfo *root,
 extern bool is_redundant_derived_clause(RestrictInfo *rinfo, List *clauselist);
 extern bool is_redundant_with_indexclauses(RestrictInfo *rinfo,
 										   List *indexclauses);
+extern void ec_clear_derived_clauses(EquivalenceClass *ec);
 
 /*
  * pathkeys.c
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b66cecd8799..997abe1427c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -667,6 +667,8 @@ DumpableObjectWithAcl
 DynamicFileList
 DynamicZoneAbbrev
 EC_KEY
+ECDerivesKey
+ECDerivesEntry
 EDGE
 ENGINE
 EOM_flatten_into_method
-- 
2.43.0



^ permalink  raw  reply  [nested|flat] 14+ messages in thread

* Re: Reducing memory consumed by RestrictInfo list translations in partitionwise join planning
@ 2025-04-02 12:51  Ashutosh Bapat <[email protected]>
  parent: Amit Langote <[email protected]>
  0 siblings, 1 reply; 14+ messages in thread

From: Ashutosh Bapat @ 2025-04-02 12:51 UTC (permalink / raw)
  To: Amit Langote <[email protected]>; +Cc: David Rowley <[email protected]>; Alvaro Herrera <[email protected]>; Dmitry Dolgov <[email protected]>; [email protected]; vignesh C <[email protected]>; pgsql-hackers; Richard Guo <[email protected]>

On Wed, Apr 2, 2025 at 1:00 PM Amit Langote <[email protected]> wrote:

>
> I'm feeling good about this version, but let me know if you have any
> further thoughts / comments.

Thanks for incorporating the changes and fixing initial hash table size.

+ #define EC_DERIVES_HASH_THRESHOLD 32

Given that the constant is being used only at a single place, we don't
need a macro. But I am not against the macro.

PFA patch set with some minor edits in 0003. Also I have edited commit
message of 0001 and 0002.

In the commit messages of 0002,
1. mentioning that the lookup happens only for join clause generation
is not accurate, since we lookup EM = constant clauses as well which
are not join clauses.
2. In the second paragraph em1 and em2 are mentioned without
mentioning what are they. I have rephrased it so as to avoid
mentioning names of structure member.

--
Best Wishes,
Ashutosh Bapat


Attachments:

  [text/x-patch] 0001-Add-assertion-to-verify-derived-clause-has--20250402.patch (1.5K, ../../CAExHW5vfcQYS2ChCoBQV5BYWPE-RgQFkD-+myVgJm8Tdd-j1Fw@mail.gmail.com/2-0001-Add-assertion-to-verify-derived-clause-has--20250402.patch)
  download | inline diff:
From 208f231a57431bfa126dc6c41af302fd5d761132 Mon Sep 17 00:00:00 2001
From: Amit Langote <[email protected]>
Date: Mon, 24 Mar 2025 21:17:46 +0900
Subject: [PATCH 1/3] Add assertion to verify derived clause has constant RHS

find_derived_clause_for_ec_member() searches for a previously-derived
clause that equates a non-constant EquivalenceMember to a constant.
It is only called for EquivalenceClasses with ec_has_const set, and
with a non-constant member the EquivalenceMember to search for.

The matched clause is expected to have the non-constant member on the
left-hand side and the constant EquivalenceMember on the right.

Assert that the RHS is indeed a constant, to catch violations of this
structure and enforce assumptions made by
generate_base_implied_equalities_const().

Author: Ashutosh Bapat <[email protected]>
Discussion: https://postgr.es/m/CAExHW5scMxyFRqOFE6ODmBiW2rnVBEmeEcA-p4W_CyuEikURdA@mail.gmail.com
---
 src/backend/optimizer/path/equivclass.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c
index 0f9ecf5ee8b..493a95d26cc 100644
--- a/src/backend/optimizer/path/equivclass.c
+++ b/src/backend/optimizer/path/equivclass.c
@@ -2664,7 +2664,10 @@ find_derived_clause_for_ec_member(EquivalenceClass *ec,
 		 * members on the left side of derived clauses.
 		 */
 		if (rinfo->left_em == em)
+		{
+			Assert(rinfo->right_em->em_is_const);
 			return rinfo;
+		}
 	}
 	return NULL;
 }

base-commit: 43dca8a11624d02dde2b4bd348d77b7045c0dfbc
-- 
2.34.1



  [text/x-patch] 0002-Make-derived-clause-lookup-in-EquivalenceCl-20250402.patch (27.4K, ../../CAExHW5vfcQYS2ChCoBQV5BYWPE-RgQFkD-+myVgJm8Tdd-j1Fw@mail.gmail.com/3-0002-Make-derived-clause-lookup-in-EquivalenceCl-20250402.patch)
  download | inline diff:
From e484154330d2a6ed90ad542f4d9ffa463f1ab9be Mon Sep 17 00:00:00 2001
From: Amit Langote <[email protected]>
Date: Wed, 2 Apr 2025 15:31:30 +0900
Subject: [PATCH 2/3] Make derived clause lookup in EquivalenceClass more
 efficient

Derived clauses are stored in ec_derives, a List of RestrictInfos.
These clauses are looked up later using the left and right
EquivalenceMembers and the clause's parent EC.

This linear search becomes expensive in queries with many joins or
partitions, where ec_derives may contain thousands of entries. In
particular, create_join_clause() can spend significant time scanning
this list.

To improve performance, introduce a hash table (ec_derives_hash) that
is built when the list reaches 32 entries -- the same threshold used
for join_rel_hash. The original list is retained alongside the hash
table to support EC merging and serialization
(_outEquivalenceClass()).

Each clause is stored in the hash table using a canonicalized key: the
EquivalenceMember with the lower memory address is placed in the key
before the one with the higher memory address. This avoids storing or
searching for both permutations of the same clause. For clauses
involving a constant EM, the key stores NULL followed by the
non-constant EM.

The hash table is initialized using list_length(ec_derives_list) as
the size hint. simplehash internally adjusts this to the next power of
two after dividing by the fillfactor, so this typically results in at
least 64 buckets near the threshold -- avoiding immediate resizing
while adapting to the actual number of entries.

The lookup logic for derived clauses is now centralized in
ec_search_derived_clause_for_ems(), which consults the hash table when
available and falls back to the list otherwise.

The new ec_clear_derived_clauses() always frees ec_derives_list, even
though some of the original code paths that cleared the old
ec_derives field did not. This ensures consistent cleanup and avoids
leaking memory when the list grows large.

An assertion originally placed in find_derived_clause_for_ec_member()
is moved into ec_search_derived_clause_for_ems() so that it is
enforced consistently, regardless of whether the hash table or list is
used for lookup.

This design incorporates suggestions by David Rowley, who proposed
both the key canonicalization and guidance on initial sizing to
balance memory usage and CPU efficiency.

Author: Ashutosh Bapat <[email protected]>
Reviewed-by: Amit Langote <[email protected]>
Reviewed-by: David Rowley <[email protected]>
Tested-by: Dmitry Dolgov <[email protected]>
Tested-by: Alvaro Herrera <[email protected]>
Tested-by: Amit Langote <[email protected]>
Tested-by: David Rowley <[email protected]>
Discussion: https://postgr.es/m/CAExHW5vZiQtWU6moszLP5iZ8gLX_ZAUbgEX0DxGLx9PGWCtqUg@mail.gmail.com
---
 src/backend/nodes/outfuncs.c              |   3 +-
 src/backend/optimizer/path/costsize.c     |   3 +-
 src/backend/optimizer/path/equivclass.c   | 438 ++++++++++++++++++----
 src/backend/optimizer/plan/analyzejoins.c |   5 +-
 src/include/nodes/pathnodes.h             |  17 +-
 src/include/optimizer/paths.h             |   4 +-
 src/tools/pgindent/typedefs.list          |   2 +
 7 files changed, 392 insertions(+), 80 deletions(-)

diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index bb9bdd67192..557f06e344f 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -467,7 +467,8 @@ _outEquivalenceClass(StringInfo str, const EquivalenceClass *node)
 	WRITE_OID_FIELD(ec_collation);
 	WRITE_NODE_FIELD(ec_members);
 	WRITE_NODE_FIELD(ec_sources);
-	WRITE_NODE_FIELD(ec_derives);
+	/* Only ec_derives_list is written; hash is not serialized. */
+	WRITE_NODE_FIELD(ec_derives_list);
 	WRITE_BITMAPSET_FIELD(ec_relids);
 	WRITE_BOOL_FIELD(ec_has_const);
 	WRITE_BOOL_FIELD(ec_has_volatile);
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index f6f77b8fe19..c474c7a06af 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -5848,7 +5848,8 @@ get_foreign_key_join_selectivity(PlannerInfo *root,
 				if (ec && ec->ec_has_const)
 				{
 					EquivalenceMember *em = fkinfo->fk_eclass_member[i];
-					RestrictInfo *rinfo = find_derived_clause_for_ec_member(ec,
+					RestrictInfo *rinfo = find_derived_clause_for_ec_member(root,
+																			ec,
 																			em);
 
 					if (rinfo)
diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c
index 493a95d26cc..853d3904577 100644
--- a/src/backend/optimizer/path/equivclass.c
+++ b/src/backend/optimizer/path/equivclass.c
@@ -20,6 +20,7 @@
 
 #include "access/stratnum.h"
 #include "catalog/pg_type.h"
+#include "common/hashfn.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/appendinfo.h"
@@ -72,7 +73,56 @@ static Bitmapset *get_eclass_indexes_for_relids(PlannerInfo *root,
 												Relids relids);
 static Bitmapset *get_common_eclass_indexes(PlannerInfo *root, Relids relids1,
 											Relids relids2);
+static void ec_build_derives_hash(PlannerInfo *root, EquivalenceClass *ec);
+static void ec_add_derived_clauses(EquivalenceClass *ec, List *clauses);
+static void ec_add_derived_clause(EquivalenceClass *ec, RestrictInfo *clause);
+static void ec_add_clause_to_derives_hash(EquivalenceClass *ec, RestrictInfo *rinfo);
+static RestrictInfo *ec_search_clause_for_ems(PlannerInfo *root, EquivalenceClass *ec,
+											  EquivalenceMember *leftem,
+											  EquivalenceMember *rightem,
+											  EquivalenceClass *parent_ec);
+static RestrictInfo *ec_search_derived_clause_for_ems(PlannerInfo *root,
+													  EquivalenceClass *ec,
+													  EquivalenceMember *leftem,
+													  EquivalenceMember *rightem,
+													  EquivalenceClass *parent_ec);
 
+/*
+ * Hash key identifying a derived clause.
+ *
+ * This structure should not be filled manually. Use fill_ec_derives_key() to
+ * set it up in canonical form.
+ */
+typedef struct
+{
+	EquivalenceMember *em1;
+	EquivalenceMember *em2;
+	EquivalenceClass *parent_ec;
+} ECDerivesKey;
+
+/* Hash table entry in ec_derives_hash. */
+typedef struct
+{
+	uint32		status;
+	ECDerivesKey key;
+	RestrictInfo *rinfo;
+} ECDerivesEntry;
+
+/* Threshold for switching from list to hash table */
+#define EC_DERIVES_HASH_THRESHOLD 32
+
+#define SH_PREFIX               derives
+#define SH_ELEMENT_TYPE			ECDerivesEntry
+#define SH_KEY_TYPE             ECDerivesKey
+#define SH_KEY                  key
+#define SH_HASH_KEY(tb, key)	\
+	hash_bytes((const unsigned char *) &(key), sizeof(ECDerivesKey))
+#define SH_EQUAL(tb, a, b)	\
+	((a).em1 == (b).em1 && (a).em2 == (b).em2 && (a).parent_ec == (b).parent_ec)
+#define SH_SCOPE                static inline
+#define SH_DECLARE
+#define SH_DEFINE
+#include "lib/simplehash.h"
 
 /*
  * process_equivalence
@@ -342,7 +392,12 @@ process_equivalence(PlannerInfo *root,
 		 */
 		ec1->ec_members = list_concat(ec1->ec_members, ec2->ec_members);
 		ec1->ec_sources = list_concat(ec1->ec_sources, ec2->ec_sources);
-		ec1->ec_derives = list_concat(ec1->ec_derives, ec2->ec_derives);
+
+		/*
+		 * Appends ec2's derived clauses to ec1->ec_derives_list and adds them
+		 * to ec1->ec_derives_hash if present.
+		 */
+		ec_add_derived_clauses(ec1, ec2->ec_derives_list);
 		ec1->ec_relids = bms_join(ec1->ec_relids, ec2->ec_relids);
 		ec1->ec_has_const |= ec2->ec_has_const;
 		/* can't need to set has_volatile */
@@ -355,7 +410,7 @@ process_equivalence(PlannerInfo *root,
 		/* just to avoid debugging confusion w/ dangling pointers: */
 		ec2->ec_members = NIL;
 		ec2->ec_sources = NIL;
-		ec2->ec_derives = NIL;
+		ec_clear_derived_clauses(ec2);
 		ec2->ec_relids = NULL;
 		ec1->ec_sources = lappend(ec1->ec_sources, restrictinfo);
 		ec1->ec_min_security = Min(ec1->ec_min_security,
@@ -412,7 +467,8 @@ process_equivalence(PlannerInfo *root,
 		ec->ec_collation = collation;
 		ec->ec_members = NIL;
 		ec->ec_sources = list_make1(restrictinfo);
-		ec->ec_derives = NIL;
+		ec->ec_derives_list = NIL;
+		ec->ec_derives_hash = NULL;
 		ec->ec_relids = NULL;
 		ec->ec_has_const = false;
 		ec->ec_has_volatile = false;
@@ -671,7 +727,8 @@ get_eclass_for_sort_expr(PlannerInfo *root,
 	newec->ec_collation = collation;
 	newec->ec_members = NIL;
 	newec->ec_sources = NIL;
-	newec->ec_derives = NIL;
+	newec->ec_derives_list = NIL;
+	newec->ec_derives_hash = NULL;
 	newec->ec_relids = NULL;
 	newec->ec_has_const = false;
 	newec->ec_has_volatile = contain_volatile_functions((Node *) expr);
@@ -1026,8 +1083,8 @@ relation_can_be_sorted_early(PlannerInfo *root, RelOptInfo *rel,
  * scanning of the quals and before Path construction begins.
  *
  * We make no attempt to avoid generating duplicate RestrictInfos here: we
- * don't search ec_sources or ec_derives for matches.  It doesn't really
- * seem worth the trouble to do so.
+ * don't search existing source or derived clauses in the EC for matches.  It
+ * doesn't really seem worth the trouble to do so.
  */
 void
 generate_base_implied_equalities(PlannerInfo *root)
@@ -1188,11 +1245,11 @@ generate_base_implied_equalities_const(PlannerInfo *root,
 
 		/*
 		 * If the clause didn't degenerate to a constant, fill in the correct
-		 * markings for a mergejoinable clause, and save it in ec_derives. (We
-		 * will not re-use such clauses directly, but selectivity estimation
-		 * may consult the list later.  Note that this use of ec_derives does
-		 * not overlap with its use for join clauses, since we never generate
-		 * join clauses from an ec_has_const eclass.)
+		 * markings for a mergejoinable clause, and save it as a derived
+		 * clause. (We will not re-use such clauses directly, but selectivity
+		 * estimation may consult those later.  Note that this use of derived
+		 * clauses does not overlap with its use for join clauses, since we
+		 * never generate join clauses from an ec_has_const eclass.)
 		 */
 		if (rinfo && rinfo->mergeopfamilies)
 		{
@@ -1200,7 +1257,7 @@ generate_base_implied_equalities_const(PlannerInfo *root,
 			rinfo->left_ec = rinfo->right_ec = ec;
 			rinfo->left_em = cur_em;
 			rinfo->right_em = const_em;
-			ec->ec_derives = lappend(ec->ec_derives, rinfo);
+			ec_add_derived_clause(ec, rinfo);
 		}
 	}
 }
@@ -1265,10 +1322,10 @@ generate_base_implied_equalities_no_const(PlannerInfo *root,
 
 			/*
 			 * If the clause didn't degenerate to a constant, fill in the
-			 * correct markings for a mergejoinable clause.  We don't put it
-			 * in ec_derives however; we don't currently need to re-find such
-			 * clauses, and we don't want to clutter that list with non-join
-			 * clauses.
+			 * correct markings for a mergejoinable clause.  We don't record
+			 * it as a derived clause, since we don't currently need to
+			 * re-find such clauses, and don't want to clutter the
+			 * derived-clause set with non-join clauses.
 			 */
 			if (rinfo && rinfo->mergeopfamilies)
 			{
@@ -1369,7 +1426,7 @@ generate_base_implied_equalities_broken(PlannerInfo *root,
  * we consider different join paths, we avoid generating multiple copies:
  * whenever we select a particular pair of EquivalenceMembers to join,
  * we check to see if the pair matches any original clause (in ec_sources)
- * or previously-built clause (in ec_derives).  This saves memory and allows
+ * or previously-built derived clause.  This saves memory and allows
  * re-use of information cached in RestrictInfos.  We also avoid generating
  * commutative duplicates, i.e. if the algorithm selects "a.x = b.y" but
  * we already have "b.y = a.x", we return the existing clause.
@@ -1754,9 +1811,9 @@ generate_join_implied_equalities_broken(PlannerInfo *root,
 	/*
 	 * If we have to translate, just brute-force apply adjust_appendrel_attrs
 	 * to all the RestrictInfos at once.  This will result in returning
-	 * RestrictInfos that are not listed in ec_derives, but there shouldn't be
-	 * any duplication, and it's a sufficiently narrow corner case that we
-	 * shouldn't sweat too much over it anyway.
+	 * RestrictInfos that are not included in EC's derived clauses, but there
+	 * shouldn't be any duplication, and it's a sufficiently narrow corner
+	 * case that we shouldn't sweat too much over it anyway.
 	 *
 	 * Since inner_rel might be an indirect descendant of the baserel
 	 * mentioned in the ec_sources clauses, we have to be prepared to apply
@@ -1823,43 +1880,11 @@ create_join_clause(PlannerInfo *root,
 {
 	RestrictInfo *rinfo;
 	RestrictInfo *parent_rinfo = NULL;
-	ListCell   *lc;
 	MemoryContext oldcontext;
 
-	/*
-	 * Search to see if we already built a RestrictInfo for this pair of
-	 * EquivalenceMembers.  We can use either original source clauses or
-	 * previously-derived clauses, and a commutator clause is acceptable.
-	 *
-	 * We used to verify that opno matches, but that seems redundant: even if
-	 * it's not identical, it'd better have the same effects, or the operator
-	 * families we're using are broken.
-	 */
-	foreach(lc, ec->ec_sources)
-	{
-		rinfo = (RestrictInfo *) lfirst(lc);
-		if (rinfo->left_em == leftem &&
-			rinfo->right_em == rightem &&
-			rinfo->parent_ec == parent_ec)
-			return rinfo;
-		if (rinfo->left_em == rightem &&
-			rinfo->right_em == leftem &&
-			rinfo->parent_ec == parent_ec)
-			return rinfo;
-	}
-
-	foreach(lc, ec->ec_derives)
-	{
-		rinfo = (RestrictInfo *) lfirst(lc);
-		if (rinfo->left_em == leftem &&
-			rinfo->right_em == rightem &&
-			rinfo->parent_ec == parent_ec)
-			return rinfo;
-		if (rinfo->left_em == rightem &&
-			rinfo->right_em == leftem &&
-			rinfo->parent_ec == parent_ec)
-			return rinfo;
-	}
+	rinfo = ec_search_clause_for_ems(root, ec, leftem, rightem, parent_ec);
+	if (rinfo)
+		return rinfo;
 
 	/*
 	 * Not there, so build it, in planner context so we can re-use it. (Not
@@ -1923,7 +1948,7 @@ create_join_clause(PlannerInfo *root,
 	rinfo->left_em = leftem;
 	rinfo->right_em = rightem;
 	/* and save it for possible re-use */
-	ec->ec_derives = lappend(ec->ec_derives, rinfo);
+	ec_add_derived_clause(ec, rinfo);
 
 	MemoryContextSwitchTo(oldcontext);
 
@@ -2648,28 +2673,14 @@ match_eclasses_to_foreign_key_col(PlannerInfo *root,
  * Returns NULL if no such clause can be found.
  */
 RestrictInfo *
-find_derived_clause_for_ec_member(EquivalenceClass *ec,
+find_derived_clause_for_ec_member(PlannerInfo *root,
+								  EquivalenceClass *ec,
 								  EquivalenceMember *em)
 {
-	ListCell   *lc;
-
 	Assert(ec->ec_has_const);
 	Assert(!em->em_is_const);
-	foreach(lc, ec->ec_derives)
-	{
-		RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
 
-		/*
-		 * generate_base_implied_equalities_const will have put non-const
-		 * members on the left side of derived clauses.
-		 */
-		if (rinfo->left_em == em)
-		{
-			Assert(rinfo->right_em->em_is_const);
-			return rinfo;
-		}
-	}
-	return NULL;
+	return ec_search_derived_clause_for_ems(root, ec, em, NULL, NULL);
 }
 
 
@@ -3442,3 +3453,284 @@ get_common_eclass_indexes(PlannerInfo *root, Relids relids1, Relids relids2)
 	/* Calculate and return the common EC indexes, recycling the left input. */
 	return bms_int_members(rel1ecs, rel2ecs);
 }
+
+/*
+ * ec_build_derives_hash
+ *	  Construct the auxiliary hash table for derived clause lookups.
+ */
+static void
+ec_build_derives_hash(PlannerInfo *root, EquivalenceClass *ec)
+{
+	Assert(!ec->ec_derives_hash);
+
+	/*
+	 * Create the hash table.
+	 *
+	 * We pass list_length(ec->ec_derives_list) as the initial size.
+	 * Simplehash will divide this by the fillfactor (typically 0.9) and round
+	 * up to the next power of two, so this will usually give us at least 64
+	 * buckets around the threshold. That avoids immediate resizing without
+	 * hardcoding a specific size.
+	 */
+	ec->ec_derives_hash = derives_create(root->planner_cxt,
+										 list_length(ec->ec_derives_list),
+										 NULL);
+
+	foreach_node(RestrictInfo, rinfo, ec->ec_derives_list)
+		ec_add_clause_to_derives_hash(ec, rinfo);
+}
+
+/*
+ * ec_add_derived_clause
+ *		Add a clause to the set of derived clauses for the given
+ *		EquivalenceClass. Always appends to ec_derives_list; also adds
+ *		to ec_derives_hash if it exists.
+ *
+ * Also asserts expected invariants of derived clauses.
+ */
+static void
+ec_add_derived_clause(EquivalenceClass *ec, RestrictInfo *clause)
+{
+	/*
+	 * Constant, if present, is always placed on the RHS; see
+	 * generate_base_implied_equalities_const(). LHS is never a constant.
+	 */
+	Assert(!clause->left_em->em_is_const);
+
+	/*
+	 * Clauses containing a constant are never considered redundant, so
+	 * parent_ec is not set.
+	 */
+	Assert(!clause->parent_ec || !clause->right_em->em_is_const);
+
+	ec->ec_derives_list = lappend(ec->ec_derives_list, clause);
+	if (ec->ec_derives_hash)
+		ec_add_clause_to_derives_hash(ec, clause);
+}
+
+/*
+ * ec_add_derived_clauses
+ *		Add a list of clauses to the set of clauses derived from the given
+ *		EquivalenceClass; adding to the list and hash table if needed.
+ *
+ * This function is similar to ec_add_derived_clause() but optimized for adding
+ * multiple clauses at a time to the ec_derives_list.  The assertions from
+ * ec_add_derived_clause() are not repeated here, as the input clauses are
+ * assumed to have already been validated.
+ */
+static void
+ec_add_derived_clauses(EquivalenceClass *ec, List *clauses)
+{
+	ec->ec_derives_list = list_concat(ec->ec_derives_list, clauses);
+	if (ec->ec_derives_hash)
+		foreach_node(RestrictInfo, rinfo, clauses)
+			ec_add_clause_to_derives_hash(ec, rinfo);
+}
+
+/*
+ * fill_ec_derives_key
+ *		Compute a canonical key for ec_derives_hash lookup or insertion.
+ *
+ * Derived clauses are looked up using a pair of EquivalenceMembers and a
+ * parent EquivalenceClass. To avoid storing or searching for both EM orderings,
+ * we canonicalize the key:
+ *
+ * - For clauses involving two non-constant EMs, em1 is set to the EM with lower
+ *   memory address and em2 is set to the other one.
+ * - For clauses involving a constant EM, the caller must pass the non-constant
+ *   EM as leftem and NULL as rightem; we then set em1 = NULL and em2 = leftem.
+ */
+static inline void
+fill_ec_derives_key(ECDerivesKey *key,
+					EquivalenceMember *leftem,
+					EquivalenceMember *rightem,
+					EquivalenceClass *parent_ec)
+{
+	Assert(leftem);				/* Always required for lookup or insertion */
+
+	if (rightem == NULL)
+	{
+		key->em1 = NULL;
+		key->em2 = leftem;
+	}
+	else if (leftem < rightem)
+	{
+		key->em1 = leftem;
+		key->em2 = rightem;
+	}
+	else
+	{
+		key->em1 = rightem;
+		key->em2 = leftem;
+	}
+	key->parent_ec = parent_ec;
+}
+
+/*
+ * ec_add_clause_to_derives_hash
+ *		Add a derived clause to ec_derives_hash in the given EquivalenceClass.
+ *
+ * Each clause is associated with a canonicalized key. For constant-containing
+ * clauses, only the non-constant EM is used for lookup; see comments in
+ * fill_ec_derives_key().
+ */
+static void
+ec_add_clause_to_derives_hash(EquivalenceClass *ec, RestrictInfo *rinfo)
+{
+	ECDerivesKey key;
+	ECDerivesEntry *entry;
+	bool		found;
+
+	/*
+	 * Constants are always placed on the RHS; see
+	 * generate_base_implied_equalities_const().
+	 */
+	Assert(!rinfo->left_em->em_is_const);
+
+	/*
+	 * Clauses containing a constant are never considered redundant, so
+	 * parent_ec is not set.
+	 */
+	Assert(!rinfo->parent_ec || !rinfo->right_em->em_is_const);
+
+	/*
+	 * See fill_ec_derives_key() for details: we use a canonicalized key to
+	 * avoid storing both EM orderings. For constant EMs, only the
+	 * non-constant EM is included in the key.
+	 */
+	fill_ec_derives_key(&key,
+						rinfo->left_em,
+						rinfo->right_em->em_is_const ? NULL : rinfo->right_em,
+						rinfo->parent_ec);
+	entry = derives_insert(ec->ec_derives_hash, key, &found);
+	Assert(!found);
+	entry->rinfo = rinfo;
+}
+
+/*
+ * ec_clear_derived_clauses
+ *      Reset ec_derives_list and ec_derives_hash.
+ *
+ * We destroy the hash table explicitly, since it may consume significant
+ * space. The list holds the same set of entries and can become equally large
+ * when thousands of partitions are involved, so we free it as well -- even
+ * though we do not typically free lists.
+ */
+void
+ec_clear_derived_clauses(EquivalenceClass *ec)
+{
+	list_free(ec->ec_derives_list);
+	ec->ec_derives_list = NIL;
+
+	if (ec->ec_derives_hash)
+	{
+		derives_destroy(ec->ec_derives_hash);
+		ec->ec_derives_hash = NULL;
+	}
+}
+
+/*
+ * ec_search_clause_for_ems
+ *		Search for an existing RestrictInfo that equates the given pair
+ *		of EquivalenceMembers, either from ec_sources or ec_derives.
+ *
+ * We accept clauses in either operand order, so commutators are matched. We
+ * used to require matching operator OIDs, but dropped that since any
+ * semantically different operator here would indicate a broken operator
+ * family.
+ *
+ * Returns NULL if no matching clause is found.
+ */
+
+static RestrictInfo *
+ec_search_clause_for_ems(PlannerInfo *root, EquivalenceClass *ec,
+						 EquivalenceMember *leftem, EquivalenceMember *rightem,
+						 EquivalenceClass *parent_ec)
+{
+	/* Check original source clauses */
+	foreach_node(RestrictInfo, rinfo, ec->ec_sources)
+	{
+		if (rinfo->left_em == leftem &&
+			rinfo->right_em == rightem &&
+			rinfo->parent_ec == parent_ec)
+			return rinfo;
+		if (rinfo->left_em == rightem &&
+			rinfo->right_em == leftem &&
+			rinfo->parent_ec == parent_ec)
+			return rinfo;
+	}
+
+	/* Not found in ec_sources; search derived clauses */
+	return ec_search_derived_clause_for_ems(root, ec, leftem, rightem,
+											parent_ec);
+}
+
+/*
+ * ec_search_derived_clause_for_ems
+ *		Search for an existing derived clause between two EquivalenceMembers.
+ *
+ * If the number of derived clauses exceeds a threshold, switch to hash table
+ * lookup; otherwise, scan ec_derives_list linearly.
+ *
+ * Clauses involving constants are looked up by passing the non-constant EM
+ * as leftem and setting rightem to NULL. In that case, we expect to find a
+ * clause with a constant on the RHS.
+ *
+ * While searching the list, we compare each given EM with both sides of each
+ * clause. But for hash table lookups, we construct a canonicalized key and
+ * perform a single lookup.
+ */
+static RestrictInfo *
+ec_search_derived_clause_for_ems(PlannerInfo *root, EquivalenceClass *ec,
+								 EquivalenceMember *leftem,
+								 EquivalenceMember *rightem,
+								 EquivalenceClass *parent_ec)
+{
+	/* Switch to using hash lookup when list grows "too long". */
+	if (!ec->ec_derives_hash &&
+		list_length(ec->ec_derives_list) >= EC_DERIVES_HASH_THRESHOLD)
+		ec_build_derives_hash(root, ec);
+
+	/* Perform hash table lookup if available */
+	if (ec->ec_derives_hash)
+	{
+		ECDerivesKey key;
+		RestrictInfo *rinfo;
+		ECDerivesEntry *entry;
+
+		fill_ec_derives_key(&key, leftem, rightem, parent_ec);
+		entry = derives_lookup(ec->ec_derives_hash, key);
+		if (entry)
+		{
+			rinfo = entry->rinfo;
+			Assert(rinfo);
+			Assert(rightem || rinfo->right_em->em_is_const);
+			return rinfo;
+		}
+	}
+	else
+	{
+		/* Fallback to linear search over ec_derives_list */
+		foreach_node(RestrictInfo, rinfo, ec->ec_derives_list)
+		{
+			/* Handle special case: lookup by non-const EM alone */
+			if (!rightem &&
+				rinfo->left_em == leftem)
+			{
+				/* See the comment above in hash path for rationale. */
+				Assert(rinfo->right_em->em_is_const);
+				return rinfo;
+			}
+			if (rinfo->left_em == leftem &&
+				rinfo->right_em == rightem &&
+				rinfo->parent_ec == parent_ec)
+				return rinfo;
+			if (rinfo->left_em == rightem &&
+				rinfo->right_em == leftem &&
+				rinfo->parent_ec == parent_ec)
+				return rinfo;
+		}
+	}
+
+	return NULL;
+}
diff --git a/src/backend/optimizer/plan/analyzejoins.c b/src/backend/optimizer/plan/analyzejoins.c
index 8a8d4a2af33..ae20691ca91 100644
--- a/src/backend/optimizer/plan/analyzejoins.c
+++ b/src/backend/optimizer/plan/analyzejoins.c
@@ -749,7 +749,7 @@ remove_rel_from_eclass(EquivalenceClass *ec, SpecialJoinInfo *sjinfo,
 	 * drop them.  (At this point, any such clauses would be base restriction
 	 * clauses, which we'd not need anymore anyway.)
 	 */
-	ec->ec_derives = NIL;
+	ec_clear_derived_clauses(ec);
 }
 
 /*
@@ -1544,8 +1544,7 @@ update_eclasses(EquivalenceClass *ec, int from, int to)
 	list_free(ec->ec_members);
 	ec->ec_members = new_members;
 
-	list_free(ec->ec_derives);
-	ec->ec_derives = NULL;
+	ec_clear_derived_clauses(ec);
 
 	/* Update EC source expressions */
 	foreach_node(RestrictInfo, rinfo, ec->ec_sources)
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index c24a1fc8514..e29d26a1814 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -1403,6 +1403,18 @@ typedef struct JoinDomain
  * entry: consider SELECT random() AS a, random() AS b ... ORDER BY b,a.
  * So we record the SortGroupRef of the originating sort clause.
  *
+ * Derived equality clauses are stored in ec_derives_list. For small queries,
+ * this list is scanned directly during lookup. For larger queries -- e.g.,
+ * with many partitions or joins -- a hash table (ec_derives_hash) is built
+ * when the list grows beyond a threshold, for faster lookup. When present,
+ * the hash table contains the same RestrictInfos and is maintained alongside
+ * the list. We retain the list even when the hash is used to simplify
+ * serialization (e.g., in _outEquivalenceClass()) and support
+ * EquivalenceClass merging.
+ *
+ * In contrast, ec_sources holds equality clauses that appear directly in the
+ * query. These are typically few and do not require a hash table for lookup.
+ *
  * NB: if ec_merged isn't NULL, this class has been merged into another, and
  * should be ignored in favor of using the pointed-to class.
  *
@@ -1422,7 +1434,10 @@ typedef struct EquivalenceClass
 	Oid			ec_collation;	/* collation, if datatypes are collatable */
 	List	   *ec_members;		/* list of EquivalenceMembers */
 	List	   *ec_sources;		/* list of generating RestrictInfos */
-	List	   *ec_derives;		/* list of derived RestrictInfos */
+	List	   *ec_derives_list;	/* list of derived RestrictInfos */
+	struct derives_hash *ec_derives_hash;	/* optional hash table for fast
+											 * lookup; contains same
+											 * RestrictInfos as list */
 	Relids		ec_relids;		/* all relids appearing in ec_members, except
 								 * for child members (see below) */
 	bool		ec_has_const;	/* any pseudoconstants in ec_members? */
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index bc5dfd7db41..16dc4d5ee82 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -167,7 +167,8 @@ extern bool exprs_known_equal(PlannerInfo *root, Node *item1, Node *item2,
 extern EquivalenceClass *match_eclasses_to_foreign_key_col(PlannerInfo *root,
 														   ForeignKeyOptInfo *fkinfo,
 														   int colno);
-extern RestrictInfo *find_derived_clause_for_ec_member(EquivalenceClass *ec,
+extern RestrictInfo *find_derived_clause_for_ec_member(PlannerInfo *root,
+													   EquivalenceClass *ec,
 													   EquivalenceMember *em);
 extern void add_child_rel_equivalences(PlannerInfo *root,
 									   AppendRelInfo *appinfo,
@@ -197,6 +198,7 @@ extern bool eclass_useful_for_merging(PlannerInfo *root,
 extern bool is_redundant_derived_clause(RestrictInfo *rinfo, List *clauselist);
 extern bool is_redundant_with_indexclauses(RestrictInfo *rinfo,
 										   List *indexclauses);
+extern void ec_clear_derived_clauses(EquivalenceClass *ec);
 
 /*
  * pathkeys.c
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 449bafc123c..d0ed895bea2 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -668,6 +668,8 @@ DumpableObjectWithAcl
 DynamicFileList
 DynamicZoneAbbrev
 EC_KEY
+ECDerivesKey
+ECDerivesEntry
 EDGE
 ENGINE
 EOM_flatten_into_method
-- 
2.34.1



  [text/x-patch] 0003-Minor-edits-20250402.patch (1.9K, ../../CAExHW5vfcQYS2ChCoBQV5BYWPE-RgQFkD-+myVgJm8Tdd-j1Fw@mail.gmail.com/4-0003-Minor-edits-20250402.patch)
  download | inline diff:
From 13476a46a146ba81f0be6c12bb4c0504f33afcd7 Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <[email protected]>
Date: Wed, 2 Apr 2025 17:31:35 +0530
Subject: [PATCH 3/3] Minor edits 1. Fixed indentation in SH_ declarations 2.
 Removed a redundant comment 3. Fix sentences in a function prologue.

Ashutosh Bapat
---
 src/backend/optimizer/path/equivclass.c | 8 +++-----
 1 file changed, 3 insertions(+), 5 deletions(-)

diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c
index 853d3904577..3f164fa2b3e 100644
--- a/src/backend/optimizer/path/equivclass.c
+++ b/src/backend/optimizer/path/equivclass.c
@@ -117,7 +117,7 @@ typedef struct
 #define SH_KEY                  key
 #define SH_HASH_KEY(tb, key)	\
 	hash_bytes((const unsigned char *) &(key), sizeof(ECDerivesKey))
-#define SH_EQUAL(tb, a, b)	\
+#define SH_EQUAL(tb, a, b)		\
 	((a).em1 == (b).em1 && (a).em2 == (b).em2 && (a).parent_ec == (b).parent_ec)
 #define SH_SCOPE                static inline
 #define SH_DECLARE
@@ -3634,10 +3634,9 @@ ec_clear_derived_clauses(EquivalenceClass *ec)
  *		Search for an existing RestrictInfo that equates the given pair
  *		of EquivalenceMembers, either from ec_sources or ec_derives.
  *
- * We accept clauses in either operand order, so commutators are matched. We
+ * Return a clause with matching operands in given order or commuted order. We
  * used to require matching operator OIDs, but dropped that since any
- * semantically different operator here would indicate a broken operator
- * family.
+ * semantically different operator here would indicate a broken operator family.
  *
  * Returns NULL if no matching clause is found.
  */
@@ -3717,7 +3716,6 @@ ec_search_derived_clause_for_ems(PlannerInfo *root, EquivalenceClass *ec,
 			if (!rightem &&
 				rinfo->left_em == leftem)
 			{
-				/* See the comment above in hash path for rationale. */
 				Assert(rinfo->right_em->em_is_const);
 				return rinfo;
 			}
-- 
2.34.1



^ permalink  raw  reply  [nested|flat] 14+ messages in thread

* Re: Reducing memory consumed by RestrictInfo list translations in partitionwise join planning
@ 2025-04-03 03:28  Amit Langote <[email protected]>
  parent: Ashutosh Bapat <[email protected]>
  0 siblings, 0 replies; 14+ messages in thread

From: Amit Langote @ 2025-04-03 03:28 UTC (permalink / raw)
  To: Ashutosh Bapat <[email protected]>; +Cc: David Rowley <[email protected]>; Alvaro Herrera <[email protected]>; Dmitry Dolgov <[email protected]>; [email protected]; vignesh C <[email protected]>; pgsql-hackers; Richard Guo <[email protected]>

On Wed, Apr 2, 2025 at 9:52 PM Ashutosh Bapat
<[email protected]> wrote:
> On Wed, Apr 2, 2025 at 1:00 PM Amit Langote <[email protected]> wrote:
> > I'm feeling good about this version, but let me know if you have any
> > further thoughts / comments.
>
> Thanks for incorporating the changes and fixing initial hash table size.
>
> + #define EC_DERIVES_HASH_THRESHOLD 32
>
> Given that the constant is being used only at a single place, we don't
> need a macro. But I am not against the macro.

Yeah, let's keep it, because it documents well.

> PFA patch set with some minor edits in 0003. Also I have edited commit
> message of 0001 and 0002.
>
> In the commit messages of 0002,
> 1. mentioning that the lookup happens only for join clause generation
> is not accurate, since we lookup EM = constant clauses as well which
> are not join clauses.
> 2. In the second paragraph em1 and em2 are mentioned without
> mentioning what are they. I have rephrased it so as to avoid
> mentioning names of structure member.

Incorporated, thanks.

I'll plan to commit these tomorrow barring objections.

-- 
Thanks, Amit Langote


Attachments:

  [application/octet-stream] v9-0002-Make-derived-clause-lookup-in-EquivalenceClass-mo.patch (27.4K, ../../CA+HiwqH8OaVD8LOpON=h3UyzXBwj=hHzTF_46DGvoeXzRrkx9A@mail.gmail.com/2-v9-0002-Make-derived-clause-lookup-in-EquivalenceClass-mo.patch)
  download | inline diff:
From 41d83a418d88b8769e13aaa34801d280ddcba9bd Mon Sep 17 00:00:00 2001
From: Amit Langote <[email protected]>
Date: Thu, 3 Apr 2025 12:02:56 +0900
Subject: [PATCH v9 2/2] Make derived clause lookup in EquivalenceClass more
 efficient

Derived clauses are stored in ec_derives, a List of RestrictInfos.
These clauses are later looked up by matching the left and right
EquivalenceMembers along with the clause's parent EC.

This linear search becomes expensive in queries with many joins or
partitions, where ec_derives may contain thousands of entries. In
particular, create_join_clause() can spend significant time scanning
this list.

To improve performance, introduce a hash table (ec_derives_hash) that
is built when the list reaches 32 entries -- the same threshold used
for join_rel_hash. The original list is retained alongside the hash
table to support EC merging and serialization
(_outEquivalenceClass()).

Each clause is stored in the hash table using a canonicalized key: the
EquivalenceMember with the lower memory address is placed in the key
before the one with the higher memory address. This avoids storing or
searching for both permutations of the same clause. For clauses
involving a constant EM, the key places NULL in the first slot and the
non-constant EM in the second.

The hash table is initialized using list_length(ec_derives_list) as
the size hint. simplehash internally adjusts this to the next power of
two after dividing by the fillfactor, so this typically results in at
least 64 buckets near the threshold -- avoiding immediate resizing
while adapting to the actual number of entries.

The lookup logic for derived clauses is now centralized in
ec_search_derived_clause_for_ems(), which consults the hash table when
available and falls back to the list otherwise.

The new ec_clear_derived_clauses() always frees ec_derives_list, even
though some of the original code paths that cleared the old
ec_derives field did not. This ensures consistent cleanup and avoids
leaking memory when large lists are discarded.

An assertion originally placed in find_derived_clause_for_ec_member()
is moved into ec_search_derived_clause_for_ems() so that it is
enforced consistently, regardless of whether the hash table or list is
used for lookup.

This design incorporates suggestions by David Rowley, who proposed
both the key canonicalization and the initial sizing approach to
balance memory usage and CPU efficiency.

Author: Ashutosh Bapat <[email protected]>
Reviewed-by: Amit Langote <[email protected]>
Reviewed-by: David Rowley <[email protected]>
Tested-by: Dmitry Dolgov <[email protected]>
Tested-by: Alvaro Herrera <[email protected]>
Tested-by: Amit Langote <[email protected]>
Tested-by: David Rowley <[email protected]>
Discussion: https://postgr.es/m/CAExHW5vZiQtWU6moszLP5iZ8gLX_ZAUbgEX0DxGLx9PGWCtqUg@mail.gmail.com
---
 src/backend/nodes/outfuncs.c              |   3 +-
 src/backend/optimizer/path/costsize.c     |   3 +-
 src/backend/optimizer/path/equivclass.c   | 435 ++++++++++++++++++----
 src/backend/optimizer/plan/analyzejoins.c |   5 +-
 src/include/nodes/pathnodes.h             |  17 +-
 src/include/optimizer/paths.h             |   4 +-
 src/tools/pgindent/typedefs.list          |   2 +
 7 files changed, 389 insertions(+), 80 deletions(-)

diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index bb9bdd67192..557f06e344f 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -467,7 +467,8 @@ _outEquivalenceClass(StringInfo str, const EquivalenceClass *node)
 	WRITE_OID_FIELD(ec_collation);
 	WRITE_NODE_FIELD(ec_members);
 	WRITE_NODE_FIELD(ec_sources);
-	WRITE_NODE_FIELD(ec_derives);
+	/* Only ec_derives_list is written; hash is not serialized. */
+	WRITE_NODE_FIELD(ec_derives_list);
 	WRITE_BITMAPSET_FIELD(ec_relids);
 	WRITE_BOOL_FIELD(ec_has_const);
 	WRITE_BOOL_FIELD(ec_has_volatile);
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index f6f77b8fe19..c474c7a06af 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -5848,7 +5848,8 @@ get_foreign_key_join_selectivity(PlannerInfo *root,
 				if (ec && ec->ec_has_const)
 				{
 					EquivalenceMember *em = fkinfo->fk_eclass_member[i];
-					RestrictInfo *rinfo = find_derived_clause_for_ec_member(ec,
+					RestrictInfo *rinfo = find_derived_clause_for_ec_member(root,
+																			ec,
 																			em);
 
 					if (rinfo)
diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c
index 493a95d26cc..9cd54c573a8 100644
--- a/src/backend/optimizer/path/equivclass.c
+++ b/src/backend/optimizer/path/equivclass.c
@@ -20,6 +20,7 @@
 
 #include "access/stratnum.h"
 #include "catalog/pg_type.h"
+#include "common/hashfn.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/appendinfo.h"
@@ -72,7 +73,56 @@ static Bitmapset *get_eclass_indexes_for_relids(PlannerInfo *root,
 												Relids relids);
 static Bitmapset *get_common_eclass_indexes(PlannerInfo *root, Relids relids1,
 											Relids relids2);
+static void ec_build_derives_hash(PlannerInfo *root, EquivalenceClass *ec);
+static void ec_add_derived_clauses(EquivalenceClass *ec, List *clauses);
+static void ec_add_derived_clause(EquivalenceClass *ec, RestrictInfo *clause);
+static void ec_add_clause_to_derives_hash(EquivalenceClass *ec, RestrictInfo *rinfo);
+static RestrictInfo *ec_search_clause_for_ems(PlannerInfo *root, EquivalenceClass *ec,
+											  EquivalenceMember *leftem,
+											  EquivalenceMember *rightem,
+											  EquivalenceClass *parent_ec);
+static RestrictInfo *ec_search_derived_clause_for_ems(PlannerInfo *root,
+													  EquivalenceClass *ec,
+													  EquivalenceMember *leftem,
+													  EquivalenceMember *rightem,
+													  EquivalenceClass *parent_ec);
 
+/*
+ * Hash key identifying a derived clause.
+ *
+ * This structure should not be filled manually. Use fill_ec_derives_key() to
+ * set it up in canonical form.
+ */
+typedef struct
+{
+	EquivalenceMember *em1;
+	EquivalenceMember *em2;
+	EquivalenceClass *parent_ec;
+} ECDerivesKey;
+
+/* Hash table entry in ec_derives_hash. */
+typedef struct
+{
+	uint32		status;
+	ECDerivesKey key;
+	RestrictInfo *rinfo;
+} ECDerivesEntry;
+
+/* Threshold for switching from list to hash table */
+#define EC_DERIVES_HASH_THRESHOLD 32
+
+#define SH_PREFIX               derives
+#define SH_ELEMENT_TYPE			ECDerivesEntry
+#define SH_KEY_TYPE             ECDerivesKey
+#define SH_KEY                  key
+#define SH_HASH_KEY(tb, key)	\
+	hash_bytes((const unsigned char *) &(key), sizeof(ECDerivesKey))
+#define SH_EQUAL(tb, a, b)		\
+	((a).em1 == (b).em1 && (a).em2 == (b).em2 && (a).parent_ec == (b).parent_ec)
+#define SH_SCOPE                static inline
+#define SH_DECLARE
+#define SH_DEFINE
+#include "lib/simplehash.h"
 
 /*
  * process_equivalence
@@ -342,7 +392,12 @@ process_equivalence(PlannerInfo *root,
 		 */
 		ec1->ec_members = list_concat(ec1->ec_members, ec2->ec_members);
 		ec1->ec_sources = list_concat(ec1->ec_sources, ec2->ec_sources);
-		ec1->ec_derives = list_concat(ec1->ec_derives, ec2->ec_derives);
+
+		/*
+		 * Appends ec2's derived clauses to ec1->ec_derives_list and adds them
+		 * to ec1->ec_derives_hash if present.
+		 */
+		ec_add_derived_clauses(ec1, ec2->ec_derives_list);
 		ec1->ec_relids = bms_join(ec1->ec_relids, ec2->ec_relids);
 		ec1->ec_has_const |= ec2->ec_has_const;
 		/* can't need to set has_volatile */
@@ -355,7 +410,7 @@ process_equivalence(PlannerInfo *root,
 		/* just to avoid debugging confusion w/ dangling pointers: */
 		ec2->ec_members = NIL;
 		ec2->ec_sources = NIL;
-		ec2->ec_derives = NIL;
+		ec_clear_derived_clauses(ec2);
 		ec2->ec_relids = NULL;
 		ec1->ec_sources = lappend(ec1->ec_sources, restrictinfo);
 		ec1->ec_min_security = Min(ec1->ec_min_security,
@@ -412,7 +467,8 @@ process_equivalence(PlannerInfo *root,
 		ec->ec_collation = collation;
 		ec->ec_members = NIL;
 		ec->ec_sources = list_make1(restrictinfo);
-		ec->ec_derives = NIL;
+		ec->ec_derives_list = NIL;
+		ec->ec_derives_hash = NULL;
 		ec->ec_relids = NULL;
 		ec->ec_has_const = false;
 		ec->ec_has_volatile = false;
@@ -671,7 +727,8 @@ get_eclass_for_sort_expr(PlannerInfo *root,
 	newec->ec_collation = collation;
 	newec->ec_members = NIL;
 	newec->ec_sources = NIL;
-	newec->ec_derives = NIL;
+	newec->ec_derives_list = NIL;
+	newec->ec_derives_hash = NULL;
 	newec->ec_relids = NULL;
 	newec->ec_has_const = false;
 	newec->ec_has_volatile = contain_volatile_functions((Node *) expr);
@@ -1026,8 +1083,8 @@ relation_can_be_sorted_early(PlannerInfo *root, RelOptInfo *rel,
  * scanning of the quals and before Path construction begins.
  *
  * We make no attempt to avoid generating duplicate RestrictInfos here: we
- * don't search ec_sources or ec_derives for matches.  It doesn't really
- * seem worth the trouble to do so.
+ * don't search existing source or derived clauses in the EC for matches.  It
+ * doesn't really seem worth the trouble to do so.
  */
 void
 generate_base_implied_equalities(PlannerInfo *root)
@@ -1188,11 +1245,11 @@ generate_base_implied_equalities_const(PlannerInfo *root,
 
 		/*
 		 * If the clause didn't degenerate to a constant, fill in the correct
-		 * markings for a mergejoinable clause, and save it in ec_derives. (We
-		 * will not re-use such clauses directly, but selectivity estimation
-		 * may consult the list later.  Note that this use of ec_derives does
-		 * not overlap with its use for join clauses, since we never generate
-		 * join clauses from an ec_has_const eclass.)
+		 * markings for a mergejoinable clause, and save it as a derived
+		 * clause. (We will not re-use such clauses directly, but selectivity
+		 * estimation may consult those later.  Note that this use of derived
+		 * clauses does not overlap with its use for join clauses, since we
+		 * never generate join clauses from an ec_has_const eclass.)
 		 */
 		if (rinfo && rinfo->mergeopfamilies)
 		{
@@ -1200,7 +1257,7 @@ generate_base_implied_equalities_const(PlannerInfo *root,
 			rinfo->left_ec = rinfo->right_ec = ec;
 			rinfo->left_em = cur_em;
 			rinfo->right_em = const_em;
-			ec->ec_derives = lappend(ec->ec_derives, rinfo);
+			ec_add_derived_clause(ec, rinfo);
 		}
 	}
 }
@@ -1265,10 +1322,10 @@ generate_base_implied_equalities_no_const(PlannerInfo *root,
 
 			/*
 			 * If the clause didn't degenerate to a constant, fill in the
-			 * correct markings for a mergejoinable clause.  We don't put it
-			 * in ec_derives however; we don't currently need to re-find such
-			 * clauses, and we don't want to clutter that list with non-join
-			 * clauses.
+			 * correct markings for a mergejoinable clause.  We don't record
+			 * it as a derived clause, since we don't currently need to
+			 * re-find such clauses, and don't want to clutter the
+			 * derived-clause set with non-join clauses.
 			 */
 			if (rinfo && rinfo->mergeopfamilies)
 			{
@@ -1369,7 +1426,7 @@ generate_base_implied_equalities_broken(PlannerInfo *root,
  * we consider different join paths, we avoid generating multiple copies:
  * whenever we select a particular pair of EquivalenceMembers to join,
  * we check to see if the pair matches any original clause (in ec_sources)
- * or previously-built clause (in ec_derives).  This saves memory and allows
+ * or previously-built derived clause.  This saves memory and allows
  * re-use of information cached in RestrictInfos.  We also avoid generating
  * commutative duplicates, i.e. if the algorithm selects "a.x = b.y" but
  * we already have "b.y = a.x", we return the existing clause.
@@ -1754,9 +1811,9 @@ generate_join_implied_equalities_broken(PlannerInfo *root,
 	/*
 	 * If we have to translate, just brute-force apply adjust_appendrel_attrs
 	 * to all the RestrictInfos at once.  This will result in returning
-	 * RestrictInfos that are not listed in ec_derives, but there shouldn't be
-	 * any duplication, and it's a sufficiently narrow corner case that we
-	 * shouldn't sweat too much over it anyway.
+	 * RestrictInfos that are not included in EC's derived clauses, but there
+	 * shouldn't be any duplication, and it's a sufficiently narrow corner
+	 * case that we shouldn't sweat too much over it anyway.
 	 *
 	 * Since inner_rel might be an indirect descendant of the baserel
 	 * mentioned in the ec_sources clauses, we have to be prepared to apply
@@ -1823,43 +1880,11 @@ create_join_clause(PlannerInfo *root,
 {
 	RestrictInfo *rinfo;
 	RestrictInfo *parent_rinfo = NULL;
-	ListCell   *lc;
 	MemoryContext oldcontext;
 
-	/*
-	 * Search to see if we already built a RestrictInfo for this pair of
-	 * EquivalenceMembers.  We can use either original source clauses or
-	 * previously-derived clauses, and a commutator clause is acceptable.
-	 *
-	 * We used to verify that opno matches, but that seems redundant: even if
-	 * it's not identical, it'd better have the same effects, or the operator
-	 * families we're using are broken.
-	 */
-	foreach(lc, ec->ec_sources)
-	{
-		rinfo = (RestrictInfo *) lfirst(lc);
-		if (rinfo->left_em == leftem &&
-			rinfo->right_em == rightem &&
-			rinfo->parent_ec == parent_ec)
-			return rinfo;
-		if (rinfo->left_em == rightem &&
-			rinfo->right_em == leftem &&
-			rinfo->parent_ec == parent_ec)
-			return rinfo;
-	}
-
-	foreach(lc, ec->ec_derives)
-	{
-		rinfo = (RestrictInfo *) lfirst(lc);
-		if (rinfo->left_em == leftem &&
-			rinfo->right_em == rightem &&
-			rinfo->parent_ec == parent_ec)
-			return rinfo;
-		if (rinfo->left_em == rightem &&
-			rinfo->right_em == leftem &&
-			rinfo->parent_ec == parent_ec)
-			return rinfo;
-	}
+	rinfo = ec_search_clause_for_ems(root, ec, leftem, rightem, parent_ec);
+	if (rinfo)
+		return rinfo;
 
 	/*
 	 * Not there, so build it, in planner context so we can re-use it. (Not
@@ -1923,7 +1948,7 @@ create_join_clause(PlannerInfo *root,
 	rinfo->left_em = leftem;
 	rinfo->right_em = rightem;
 	/* and save it for possible re-use */
-	ec->ec_derives = lappend(ec->ec_derives, rinfo);
+	ec_add_derived_clause(ec, rinfo);
 
 	MemoryContextSwitchTo(oldcontext);
 
@@ -2648,28 +2673,14 @@ match_eclasses_to_foreign_key_col(PlannerInfo *root,
  * Returns NULL if no such clause can be found.
  */
 RestrictInfo *
-find_derived_clause_for_ec_member(EquivalenceClass *ec,
+find_derived_clause_for_ec_member(PlannerInfo *root,
+								  EquivalenceClass *ec,
 								  EquivalenceMember *em)
 {
-	ListCell   *lc;
-
 	Assert(ec->ec_has_const);
 	Assert(!em->em_is_const);
-	foreach(lc, ec->ec_derives)
-	{
-		RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
 
-		/*
-		 * generate_base_implied_equalities_const will have put non-const
-		 * members on the left side of derived clauses.
-		 */
-		if (rinfo->left_em == em)
-		{
-			Assert(rinfo->right_em->em_is_const);
-			return rinfo;
-		}
-	}
-	return NULL;
+	return ec_search_derived_clause_for_ems(root, ec, em, NULL, NULL);
 }
 
 
@@ -3442,3 +3453,281 @@ get_common_eclass_indexes(PlannerInfo *root, Relids relids1, Relids relids2)
 	/* Calculate and return the common EC indexes, recycling the left input. */
 	return bms_int_members(rel1ecs, rel2ecs);
 }
+
+/*
+ * ec_build_derives_hash
+ *	  Construct the auxiliary hash table for derived clause lookups.
+ */
+static void
+ec_build_derives_hash(PlannerInfo *root, EquivalenceClass *ec)
+{
+	Assert(!ec->ec_derives_hash);
+
+	/*
+	 * Create the hash table.
+	 *
+	 * We pass list_length(ec->ec_derives_list) as the initial size.
+	 * Simplehash will divide this by the fillfactor (typically 0.9) and round
+	 * up to the next power of two, so this will usually give us at least 64
+	 * buckets around the threshold. That avoids immediate resizing without
+	 * hardcoding a specific size.
+	 */
+	ec->ec_derives_hash = derives_create(root->planner_cxt,
+										 list_length(ec->ec_derives_list),
+										 NULL);
+
+	foreach_node(RestrictInfo, rinfo, ec->ec_derives_list)
+		ec_add_clause_to_derives_hash(ec, rinfo);
+}
+
+/*
+ * ec_add_derived_clause
+ *		Add a clause to the set of derived clauses for the given
+ *		EquivalenceClass. Always appends to ec_derives_list; also adds
+ *		to ec_derives_hash if it exists.
+ *
+ * Also asserts expected invariants of derived clauses.
+ */
+static void
+ec_add_derived_clause(EquivalenceClass *ec, RestrictInfo *clause)
+{
+	/*
+	 * Constant, if present, is always placed on the RHS; see
+	 * generate_base_implied_equalities_const(). LHS is never a constant.
+	 */
+	Assert(!clause->left_em->em_is_const);
+
+	/*
+	 * Clauses containing a constant are never considered redundant, so
+	 * parent_ec is not set.
+	 */
+	Assert(!clause->parent_ec || !clause->right_em->em_is_const);
+
+	ec->ec_derives_list = lappend(ec->ec_derives_list, clause);
+	if (ec->ec_derives_hash)
+		ec_add_clause_to_derives_hash(ec, clause);
+}
+
+/*
+ * ec_add_derived_clauses
+ *		Add a list of clauses to the set of clauses derived from the given
+ *		EquivalenceClass; adding to the list and hash table if needed.
+ *
+ * This function is similar to ec_add_derived_clause() but optimized for adding
+ * multiple clauses at a time to the ec_derives_list.  The assertions from
+ * ec_add_derived_clause() are not repeated here, as the input clauses are
+ * assumed to have already been validated.
+ */
+static void
+ec_add_derived_clauses(EquivalenceClass *ec, List *clauses)
+{
+	ec->ec_derives_list = list_concat(ec->ec_derives_list, clauses);
+	if (ec->ec_derives_hash)
+		foreach_node(RestrictInfo, rinfo, clauses)
+			ec_add_clause_to_derives_hash(ec, rinfo);
+}
+
+/*
+ * fill_ec_derives_key
+ *		Compute a canonical key for ec_derives_hash lookup or insertion.
+ *
+ * Derived clauses are looked up using a pair of EquivalenceMembers and a
+ * parent EquivalenceClass. To avoid storing or searching for both EM orderings,
+ * we canonicalize the key:
+ *
+ * - For clauses involving two non-constant EMs, em1 is set to the EM with lower
+ *   memory address and em2 is set to the other one.
+ * - For clauses involving a constant EM, the caller must pass the non-constant
+ *   EM as leftem and NULL as rightem; we then set em1 = NULL and em2 = leftem.
+ */
+static inline void
+fill_ec_derives_key(ECDerivesKey *key,
+					EquivalenceMember *leftem,
+					EquivalenceMember *rightem,
+					EquivalenceClass *parent_ec)
+{
+	Assert(leftem);				/* Always required for lookup or insertion */
+
+	if (rightem == NULL)
+	{
+		key->em1 = NULL;
+		key->em2 = leftem;
+	}
+	else if (leftem < rightem)
+	{
+		key->em1 = leftem;
+		key->em2 = rightem;
+	}
+	else
+	{
+		key->em1 = rightem;
+		key->em2 = leftem;
+	}
+	key->parent_ec = parent_ec;
+}
+
+/*
+ * ec_add_clause_to_derives_hash
+ *		Add a derived clause to ec_derives_hash in the given EquivalenceClass.
+ *
+ * Each clause is associated with a canonicalized key. For constant-containing
+ * clauses, only the non-constant EM is used for lookup; see comments in
+ * fill_ec_derives_key().
+ */
+static void
+ec_add_clause_to_derives_hash(EquivalenceClass *ec, RestrictInfo *rinfo)
+{
+	ECDerivesKey key;
+	ECDerivesEntry *entry;
+	bool		found;
+
+	/*
+	 * Constants are always placed on the RHS; see
+	 * generate_base_implied_equalities_const().
+	 */
+	Assert(!rinfo->left_em->em_is_const);
+
+	/*
+	 * Clauses containing a constant are never considered redundant, so
+	 * parent_ec is not set.
+	 */
+	Assert(!rinfo->parent_ec || !rinfo->right_em->em_is_const);
+
+	/*
+	 * See fill_ec_derives_key() for details: we use a canonicalized key to
+	 * avoid storing both EM orderings. For constant EMs, only the
+	 * non-constant EM is included in the key.
+	 */
+	fill_ec_derives_key(&key,
+						rinfo->left_em,
+						rinfo->right_em->em_is_const ? NULL : rinfo->right_em,
+						rinfo->parent_ec);
+	entry = derives_insert(ec->ec_derives_hash, key, &found);
+	Assert(!found);
+	entry->rinfo = rinfo;
+}
+
+/*
+ * ec_clear_derived_clauses
+ *      Reset ec_derives_list and ec_derives_hash.
+ *
+ * We destroy the hash table explicitly, since it may consume significant
+ * space. The list holds the same set of entries and can become equally large
+ * when thousands of partitions are involved, so we free it as well -- even
+ * though we do not typically free lists.
+ */
+void
+ec_clear_derived_clauses(EquivalenceClass *ec)
+{
+	list_free(ec->ec_derives_list);
+	ec->ec_derives_list = NIL;
+
+	if (ec->ec_derives_hash)
+	{
+		derives_destroy(ec->ec_derives_hash);
+		ec->ec_derives_hash = NULL;
+	}
+}
+
+/*
+ * ec_search_clause_for_ems
+ *		Search for an existing RestrictInfo that equates the given pair
+ *		of EquivalenceMembers, either from ec_sources or ec_derives.
+ *
+ * Returns a clause with matching operands in either given order or commuted
+ * order. We used to require matching operator OIDs, but dropped that since any
+ * semantically different operator here would indicate a broken operator family.
+ *
+ * Returns NULL if no matching clause is found.
+ */
+static RestrictInfo *
+ec_search_clause_for_ems(PlannerInfo *root, EquivalenceClass *ec,
+						 EquivalenceMember *leftem, EquivalenceMember *rightem,
+						 EquivalenceClass *parent_ec)
+{
+	/* Check original source clauses */
+	foreach_node(RestrictInfo, rinfo, ec->ec_sources)
+	{
+		if (rinfo->left_em == leftem &&
+			rinfo->right_em == rightem &&
+			rinfo->parent_ec == parent_ec)
+			return rinfo;
+		if (rinfo->left_em == rightem &&
+			rinfo->right_em == leftem &&
+			rinfo->parent_ec == parent_ec)
+			return rinfo;
+	}
+
+	/* Not found in ec_sources; search derived clauses */
+	return ec_search_derived_clause_for_ems(root, ec, leftem, rightem,
+											parent_ec);
+}
+
+/*
+ * ec_search_derived_clause_for_ems
+ *		Search for an existing derived clause between two EquivalenceMembers.
+ *
+ * If the number of derived clauses exceeds a threshold, switch to hash table
+ * lookup; otherwise, scan ec_derives_list linearly.
+ *
+ * Clauses involving constants are looked up by passing the non-constant EM
+ * as leftem and setting rightem to NULL. In that case, we expect to find a
+ * clause with a constant on the RHS.
+ *
+ * While searching the list, we compare each given EM with both sides of each
+ * clause. But for hash table lookups, we construct a canonicalized key and
+ * perform a single lookup.
+ */
+static RestrictInfo *
+ec_search_derived_clause_for_ems(PlannerInfo *root, EquivalenceClass *ec,
+								 EquivalenceMember *leftem,
+								 EquivalenceMember *rightem,
+								 EquivalenceClass *parent_ec)
+{
+	/* Switch to using hash lookup when list grows "too long". */
+	if (!ec->ec_derives_hash &&
+		list_length(ec->ec_derives_list) >= EC_DERIVES_HASH_THRESHOLD)
+		ec_build_derives_hash(root, ec);
+
+	/* Perform hash table lookup if available */
+	if (ec->ec_derives_hash)
+	{
+		ECDerivesKey key;
+		RestrictInfo *rinfo;
+		ECDerivesEntry *entry;
+
+		fill_ec_derives_key(&key, leftem, rightem, parent_ec);
+		entry = derives_lookup(ec->ec_derives_hash, key);
+		if (entry)
+		{
+			rinfo = entry->rinfo;
+			Assert(rinfo);
+			Assert(rightem || rinfo->right_em->em_is_const);
+			return rinfo;
+		}
+	}
+	else
+	{
+		/* Fallback to linear search over ec_derives_list */
+		foreach_node(RestrictInfo, rinfo, ec->ec_derives_list)
+		{
+			/* Handle special case: lookup by non-const EM alone */
+			if (!rightem &&
+				rinfo->left_em == leftem)
+			{
+				Assert(rinfo->right_em->em_is_const);
+				return rinfo;
+			}
+			if (rinfo->left_em == leftem &&
+				rinfo->right_em == rightem &&
+				rinfo->parent_ec == parent_ec)
+				return rinfo;
+			if (rinfo->left_em == rightem &&
+				rinfo->right_em == leftem &&
+				rinfo->parent_ec == parent_ec)
+				return rinfo;
+		}
+	}
+
+	return NULL;
+}
diff --git a/src/backend/optimizer/plan/analyzejoins.c b/src/backend/optimizer/plan/analyzejoins.c
index 8a8d4a2af33..ae20691ca91 100644
--- a/src/backend/optimizer/plan/analyzejoins.c
+++ b/src/backend/optimizer/plan/analyzejoins.c
@@ -749,7 +749,7 @@ remove_rel_from_eclass(EquivalenceClass *ec, SpecialJoinInfo *sjinfo,
 	 * drop them.  (At this point, any such clauses would be base restriction
 	 * clauses, which we'd not need anymore anyway.)
 	 */
-	ec->ec_derives = NIL;
+	ec_clear_derived_clauses(ec);
 }
 
 /*
@@ -1544,8 +1544,7 @@ update_eclasses(EquivalenceClass *ec, int from, int to)
 	list_free(ec->ec_members);
 	ec->ec_members = new_members;
 
-	list_free(ec->ec_derives);
-	ec->ec_derives = NULL;
+	ec_clear_derived_clauses(ec);
 
 	/* Update EC source expressions */
 	foreach_node(RestrictInfo, rinfo, ec->ec_sources)
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index ac3af528bc6..6e034960896 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -1402,6 +1402,18 @@ typedef struct JoinDomain
  * entry: consider SELECT random() AS a, random() AS b ... ORDER BY b,a.
  * So we record the SortGroupRef of the originating sort clause.
  *
+ * Derived equality clauses are stored in ec_derives_list. For small queries,
+ * this list is scanned directly during lookup. For larger queries -- e.g.,
+ * with many partitions or joins -- a hash table (ec_derives_hash) is built
+ * when the list grows beyond a threshold, for faster lookup. When present,
+ * the hash table contains the same RestrictInfos and is maintained alongside
+ * the list. We retain the list even when the hash is used to simplify
+ * serialization (e.g., in _outEquivalenceClass()) and support
+ * EquivalenceClass merging.
+ *
+ * In contrast, ec_sources holds equality clauses that appear directly in the
+ * query. These are typically few and do not require a hash table for lookup.
+ *
  * NB: if ec_merged isn't NULL, this class has been merged into another, and
  * should be ignored in favor of using the pointed-to class.
  *
@@ -1421,7 +1433,10 @@ typedef struct EquivalenceClass
 	Oid			ec_collation;	/* collation, if datatypes are collatable */
 	List	   *ec_members;		/* list of EquivalenceMembers */
 	List	   *ec_sources;		/* list of generating RestrictInfos */
-	List	   *ec_derives;		/* list of derived RestrictInfos */
+	List	   *ec_derives_list;	/* list of derived RestrictInfos */
+	struct derives_hash *ec_derives_hash;	/* optional hash table for fast
+											 * lookup; contains same
+											 * RestrictInfos as list */
 	Relids		ec_relids;		/* all relids appearing in ec_members, except
 								 * for child members (see below) */
 	bool		ec_has_const;	/* any pseudoconstants in ec_members? */
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index bc5dfd7db41..16dc4d5ee82 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -167,7 +167,8 @@ extern bool exprs_known_equal(PlannerInfo *root, Node *item1, Node *item2,
 extern EquivalenceClass *match_eclasses_to_foreign_key_col(PlannerInfo *root,
 														   ForeignKeyOptInfo *fkinfo,
 														   int colno);
-extern RestrictInfo *find_derived_clause_for_ec_member(EquivalenceClass *ec,
+extern RestrictInfo *find_derived_clause_for_ec_member(PlannerInfo *root,
+													   EquivalenceClass *ec,
 													   EquivalenceMember *em);
 extern void add_child_rel_equivalences(PlannerInfo *root,
 									   AppendRelInfo *appinfo,
@@ -197,6 +198,7 @@ extern bool eclass_useful_for_merging(PlannerInfo *root,
 extern bool is_redundant_derived_clause(RestrictInfo *rinfo, List *clauselist);
 extern bool is_redundant_with_indexclauses(RestrictInfo *rinfo,
 										   List *indexclauses);
+extern void ec_clear_derived_clauses(EquivalenceClass *ec);
 
 /*
  * pathkeys.c
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 8f28d8ff28e..c3f05796a7c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -673,6 +673,8 @@ DumpableObjectWithAcl
 DynamicFileList
 DynamicZoneAbbrev
 EC_KEY
+ECDerivesKey
+ECDerivesEntry
 EDGE
 ENGINE
 EOM_flatten_into_method
-- 
2.43.0



  [application/octet-stream] v9-0001-Add-assertion-to-verify-derived-clause-has-consta.patch (1.5K, ../../CA+HiwqH8OaVD8LOpON=h3UyzXBwj=hHzTF_46DGvoeXzRrkx9A@mail.gmail.com/3-v9-0001-Add-assertion-to-verify-derived-clause-has-consta.patch)
  download | inline diff:
From a5af7f603859496ecd26664e8fd3c384ad558295 Mon Sep 17 00:00:00 2001
From: Amit Langote <[email protected]>
Date: Mon, 24 Mar 2025 21:17:46 +0900
Subject: [PATCH v9 1/2] Add assertion to verify derived clause has constant
 RHS

find_derived_clause_for_ec_member() searches for a previously-derived
clause that equates a non-constant EquivalenceMember to a constant.
It is only called for EquivalenceClasses with ec_has_const set, and
with a non-constant member the EquivalenceMember to search for.

The matched clause is expected to have the non-constant member on the
left-hand side and the constant EquivalenceMember on the right.

Assert that the RHS is indeed a constant, to catch violations of this
structure and enforce assumptions made by
generate_base_implied_equalities_const().

Author: Ashutosh Bapat <[email protected]>
Discussion: https://postgr.es/m/CAExHW5scMxyFRqOFE6ODmBiW2rnVBEmeEcA-p4W_CyuEikURdA@mail.gmail.com
---
 src/backend/optimizer/path/equivclass.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c
index 0f9ecf5ee8b..493a95d26cc 100644
--- a/src/backend/optimizer/path/equivclass.c
+++ b/src/backend/optimizer/path/equivclass.c
@@ -2664,7 +2664,10 @@ find_derived_clause_for_ec_member(EquivalenceClass *ec,
 		 * members on the left side of derived clauses.
 		 */
 		if (rinfo->left_em == em)
+		{
+			Assert(rinfo->right_em->em_is_const);
 			return rinfo;
+		}
 	}
 	return NULL;
 }
-- 
2.43.0



^ permalink  raw  reply  [nested|flat] 14+ messages in thread


end of thread, other threads:[~2025-04-03 03:28 UTC | newest]

Thread overview: 14+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2020-12-12 06:40 [PATCH 4/7] cmdline parser for compression alg/level/opts Justin Pryzby <[email protected]>
2020-12-12 06:40 [PATCH 4/7] cmdline parser for compression alg/level/opts Justin Pryzby <[email protected]>
2021-12-05 18:45 ExecTypeSetColNames is fundamentally broken Tom Lane <[email protected]>
2021-12-06 20:30 ` Re: ExecTypeSetColNames is fundamentally broken Robert Haas <[email protected]>
2021-12-06 21:05   ` Re: ExecTypeSetColNames is fundamentally broken Tom Lane <[email protected]>
2021-12-07 16:19     ` Re: ExecTypeSetColNames is fundamentally broken Robert Haas <[email protected]>
2021-12-07 17:30       ` Re: ExecTypeSetColNames is fundamentally broken Tom Lane <[email protected]>
2021-12-07 17:58         ` Re: ExecTypeSetColNames is fundamentally broken Robert Haas <[email protected]>
2021-12-07 18:19           ` Re: ExecTypeSetColNames is fundamentally broken Tom Lane <[email protected]>
2025-04-01 08:01 Re: Reducing memory consumed by RestrictInfo list translations in partitionwise join planning Amit Langote <[email protected]>
2025-04-01 16:58 ` Re: Reducing memory consumed by RestrictInfo list translations in partitionwise join planning Ashutosh Bapat <[email protected]>
2025-04-02 07:30   ` Re: Reducing memory consumed by RestrictInfo list translations in partitionwise join planning Amit Langote <[email protected]>
2025-04-02 12:51     ` Re: Reducing memory consumed by RestrictInfo list translations in partitionwise join planning Ashutosh Bapat <[email protected]>
2025-04-03 03:28       ` Re: Reducing memory consumed by RestrictInfo list translations in partitionwise join planning Amit Langote <[email protected]>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox