From 4a2c3e72a646f51d1a2527e1dc486bc33b944fcc Mon Sep 17 00:00:00 2001
From: Tomas Vondra <tomas@vondra.me>
Date: Wed, 1 Oct 2025 02:44:37 +0200
Subject: [PATCH v20251001 23/25] experimental: zstd compression

---
 src/backend/storage/file/buffile.c  | 50 +++++++++++++++++++++++++++++
 src/backend/utils/misc/guc_tables.c |  3 ++
 src/include/storage/buffile.h       |  3 +-
 3 files changed, 55 insertions(+), 1 deletion(-)

diff --git a/src/backend/storage/file/buffile.c b/src/backend/storage/file/buffile.c
index 380537fb126..cdb3f150ebf 100644
--- a/src/backend/storage/file/buffile.c
+++ b/src/backend/storage/file/buffile.c
@@ -64,11 +64,16 @@
 #include <zlib.h>
 #endif
 
+#ifdef USE_ZSTD
+#include <zstd.h>
+#endif
+
 /* Compression types */
 #define TEMP_NONE_COMPRESSION  0
 #define TEMP_PGLZ_COMPRESSION  1
 #define TEMP_LZ4_COMPRESSION   2
 #define TEMP_GZIP_COMPRESSION  3
+#define TEMP_ZSTD_COMPRESSION  4
 
 /*
  * We break BufFiles into gigabyte-sized segments, regardless of RELSEG_SIZE.
@@ -308,6 +313,14 @@ BufFileCreateCompressTemp(bool interXact)
 					break;
 				}
 
+			case TEMP_ZSTD_COMPRESSION:
+				{
+#ifdef USE_ZSTD
+					size = ZSTD_COMPRESSBOUND(BLCKSZ) + sizeof(CompressHeader);
+#endif
+					break;
+				}
+
 			case TEMP_PGLZ_COMPRESSION:
 				size = pglz_maximum_compressed_size(BLCKSZ, BLCKSZ) + sizeof(CompressHeader);
 				break;
@@ -704,6 +717,7 @@ BufFileLoadBuffer(BufFile *file)
 						break;
 
 					case TEMP_GZIP_COMPRESSION:
+					{
 #ifdef HAVE_LIBZ
 						int		ret;
 						size_t	len = sizeof(file->buffer);
@@ -716,7 +730,21 @@ BufFileLoadBuffer(BufFile *file)
 						file->nbytes = len;
 #endif
 						break;
+					}
+					case TEMP_ZSTD_COMPRESSION:
+					{
+#ifdef USE_ZSTD
+						size_t	ret;
+
+						ret = ZSTD_decompress(file->buffer.data, sizeof(file->buffer),
+											  buff, header.len);
+						if (ZSTD_isError(ret))
+							elog(ERROR, "ZSTD_decompress failed");
 
+						file->nbytes = ret;
+#endif
+						break;
+					}
 					case TEMP_PGLZ_COMPRESSION:
 						file->nbytes = pglz_decompress(buff, header.len,
 													   file->buffer.data, header.raw_len, false);
@@ -835,6 +863,28 @@ BufFileDumpBuffer(BufFile *file)
 					}
 
 					cSize = len;
+#endif
+					break;
+				}
+			case TEMP_ZSTD_COMPRESSION:
+				{
+#ifdef USE_ZSTD
+					size_t		ret;
+					size_t		len = ZSTD_compressBound(file->nbytes);
+
+					/* XXX maybe lower level? the default is pretty slow ... */
+					ret = ZSTD_compress(cData + sizeof(CompressHeader), len,
+										file->buffer.data, file->nbytes,
+										ZSTD_defaultCLevel());
+					if (ZSTD_isError(ret))
+					{
+						ereport(ERROR,
+								(errcode(ERRCODE_DATA_CORRUPTED),
+								 errmsg_internal("compression failed, compressed size %d, original size %d",
+												 cSize, nbytesOriginal)));
+					}
+
+					cSize = ret;
 #endif
 					break;
 				}
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 88fbe405d59..9410e31ce00 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -476,6 +476,9 @@ static const struct config_enum_entry temp_file_compression_options[] = {
 #endif
 #ifdef  HAVE_LIBZ
 	{"gzip", TEMP_GZIP_COMPRESSION, false},
+#endif
+#ifdef  USE_ZSTD
+	{"zstd", TEMP_ZSTD_COMPRESSION, false},
 #endif
 	{NULL, 0, false}
 };
diff --git a/src/include/storage/buffile.h b/src/include/storage/buffile.h
index 7a1bb1d6085..ac6fe602939 100644
--- a/src/include/storage/buffile.h
+++ b/src/include/storage/buffile.h
@@ -37,7 +37,8 @@ typedef enum
 	TEMP_NONE_COMPRESSION,
 	TEMP_PGLZ_COMPRESSION,
 	TEMP_LZ4_COMPRESSION,
-	TEMP_GZIP_COMPRESSION
+	TEMP_GZIP_COMPRESSION,
+	TEMP_ZSTD_COMPRESSION
 } TempCompression;
 
 extern PGDLLIMPORT int temp_file_compression;
-- 
2.51.0

