Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wCKrK-001sqG-0q for pgsql-hackers@arkaria.postgresql.org; Mon, 13 Apr 2026 17:14:03 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1wCKrI-0085Be-24 for pgsql-hackers@arkaria.postgresql.org; Mon, 13 Apr 2026 17:14:01 +0000 Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wCKrI-0085BW-07 for pgsql-hackers@lists.postgresql.org; Mon, 13 Apr 2026 17:14:01 +0000 Received: from fhigh-a3-smtp.messagingengine.com ([103.168.172.154]) by magus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.98.2) (envelope-from ) id 1wCKrG-00000000sDk-0rI8 for pgsql-hackers@lists.postgresql.org; Mon, 13 Apr 2026 17:14:00 +0000 Received: from phl-compute-03.internal (phl-compute-03.internal [10.202.2.43]) by mailfhigh.phl.internal (Postfix) with ESMTP id 58BF214001B9; Mon, 13 Apr 2026 13:13:56 -0400 (EDT) Received: from phl-frontend-04 ([10.202.2.163]) by phl-compute-03.internal (MEProxy); Mon, 13 Apr 2026 13:13:56 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=paquier.xyz; h= cc:cc:content-type:content-type:date:date:from:from:in-reply-to :in-reply-to:message-id:mime-version:references:reply-to:subject :subject:to:to; s=fm2; t=1776100436; x=1776186836; bh=pMOPb5o0I3 lb9ruNLA5xxiJnBNArZ1VWaXyQSqRkSgY=; b=nl53LvZF7FOqLqmNCWHBO/dZJA dIEDUho7mrgDv5083nb21OdFCCxiD7jKlaW5ZBnebIcW9LS+esgeOdd5MBc+fztY jMP/W3bVqGlIo4GoPYSwCKMp7RiaC69x0IQJch9vm15dx1cfiijLrKJQJ2PwiLoJ MEScRiI/tqMu3aNC3Cem3hkDn8k8TWNVeUF8KM2C/mVMhfJPA3qmN5/+KDSBESRc bEOmS5YfvmJgIfCIGVGkxIV3mS0guzQppfA71UFUJ7OE2YgvDvOCgHEO+0eNDL7x u9bPrLOnuiRSGRI04pyJdaxKmwc3j5v9yp6PBchH5p7kluDqsI7QVmFyaK0A== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=cc:cc:content-type:content-type:date:date :feedback-id:feedback-id:from:from:in-reply-to:in-reply-to :message-id:mime-version:references:reply-to:subject:subject:to :to:x-me-proxy:x-me-sender:x-me-sender:x-sasl-enc; s=fm2; t= 1776100436; x=1776186836; bh=pMOPb5o0I3lb9ruNLA5xxiJnBNArZ1VWaXy QSqRkSgY=; b=SqcxWTFgQW1OBGw1AbTa6cITgEdeu6fEslhBPmlAUCHhpQhOUr1 5cl0le7xCX5zuRA6ZYotNQKDeF8oiUUBjTaGAbVwlFYQ2xiwte/puty8dAuqG+DH RLVrre4LJWkayMiMDEWvO0nvguaVISeXMdwWv7T3bqch/4oqXp8biGlC1o6XGySo wy9Xox4gQFDws9xhS0G13VJiN304WjinS04B3jlW7m7SPVyyiwds2XpcnBTwWTHo 6b2BC34oRl950U6AIDAs/3hHulFQmqt1Pa2mDG/UCxt49Mlg5RtYSKrOVtbohKfE wCfsoaiNz5zgs4hQNYczyZBPkYCFzbrUyJg== X-ME-Sender: X-ME-Received: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgeefhedrtddtgdefkeektdcutefuodetggdotefrod ftvfcurfhrohhfihhlvgemucfhrghsthforghilhdpuffrtefokffrpgfnqfghnecuuegr ihhlohhuthemuceftddtnecusecvtfgvtghiphhivghnthhsucdlqddutddtmdenfghrlh cuvffnffculdejtddmnecujfgurhepfffhvfevuffkfhggtggujgesghdtreertddtvden ucfhrhhomhepofhitghhrggvlhcurfgrqhhuihgvrhcuoehmihgthhgrvghlsehprghquh hivghrrdighiiiqeenucggtffrrghtthgvrhhnpeetleeifedufffhhfdtteelgeeggeff hfekueevteeigfduudevudetgfegiedvjeenucevlhhushhtvghrufhiiigvpedtnecurf grrhgrmhepmhgrihhlfhhrohhmpehmihgthhgrvghlsehprghquhhivghrrdighiiipdhn sggprhgtphhtthhopeefpdhmohguvgepshhmthhpohhuthdprhgtphhtthhopegrnhgurh gvshesrghnrghrrgiivghlrdguvgdprhgtphhtthhopehpghhsqhhlqdhhrggtkhgvrhhs sehlihhsthhsrdhpohhsthhgrhgvshhqlhdrohhrghdprhgtphhtthhopegrnhgurhgvfi esughunhhslhgrnhgvrdhnvght X-ME-Proxy: Feedback-ID: i0fe9450f:Fastmail Received: by mail.messagingengine.com (Postfix) with ESMTPA; Mon, 13 Apr 2026 13:13:54 -0400 (EDT) Date: Tue, 14 Apr 2026 02:13:52 +0900 From: Michael Paquier To: Andres Freund Cc: Postgres hackers , Andrew Dunstan Subject: Re: test_compression, test module for low-level compression APIs (for 2b5ba2a0a141) Message-ID: References: MIME-Version: 1.0 Content-Type: multipart/signed; micalg=pgp-sha512; protocol="application/pgp-signature"; boundary="YaZnuUvOyncMcqJL" Content-Disposition: inline In-Reply-To: List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk --YaZnuUvOyncMcqJL Content-Type: multipart/mixed; boundary="wY/Kkkh+p9/WqLGS" Content-Disposition: inline --wY/Kkkh+p9/WqLGS Content-Type: text/plain; charset=us-ascii Content-Disposition: inline On Mon, Apr 13, 2026 at 10:08:16AM -0400, Andres Freund wrote: > I guess that'd work. I don't really see the point in calling these via SQL, > tbh, but if that's easier for you, using a regress.c helper woul do the trick. Okay, thanks for the opinion. It makes the addition of more tests slightly easier as there is no need to rely on an extra wrapper of what's in pg_lzcompress.h. I have done that in the attached, with additions to the main regression test suite. -- Michael --wY/Kkkh+p9/WqLGS Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0001-Add-tests-for-low-level-PGLZ-compression.patch Content-Transfer-Encoding: quoted-printable =46rom ad27ede8a798c264de961b8bc37205a2635e43c5 Mon Sep 17 00:00:00 2001 =46rom: Michael Paquier Date: Tue, 14 Apr 2026 02:01:26 +0900 Subject: [PATCH v2] Add tests for low-level PGLZ compression The goal of this module is to provide an entry point for the coverage of for low-level APIs of compression and decompression of PGLZ. This includes tests for the cases detected by fuzzing related to corrupted data, as fixed in 2b5ba2a0a141: - Control byte with match tag bit set, where no data follows. - Control byte with match tag bit set, where 1 byte follows. - Extension byte needed (len=3D18), where no data follows. As bonus points, tests are added for compress/decompress roundtrips, and for check_complete=3Dfalse/true. Backpatch-through: 14 --- .../regress/expected/compression_pglz.out | 62 +++++++++++++++++ src/test/regress/parallel_schedule | 2 +- src/test/regress/regress.c | 66 +++++++++++++++++++ src/test/regress/sql/compression_pglz.sql | 49 ++++++++++++++ 4 files changed, 178 insertions(+), 1 deletion(-) create mode 100644 src/test/regress/expected/compression_pglz.out create mode 100644 src/test/regress/sql/compression_pglz.sql diff --git a/src/test/regress/expected/compression_pglz.out b/src/test/regr= ess/expected/compression_pglz.out new file mode 100644 index 000000000000..daf457f4ac0a --- /dev/null +++ b/src/test/regress/expected/compression_pglz.out @@ -0,0 +1,62 @@ +-- +-- Tests for PGLZ compression +-- +-- directory paths and dlsuffix are passed to us in environment variables +\getenv libdir PG_LIBDIR +\getenv dlsuffix PG_DLSUFFIX +\set regresslib :libdir '/regress' :dlsuffix +CREATE FUNCTION test_pglz_compress(bytea) + RETURNS bytea + AS :'regresslib' LANGUAGE C STRICT; +CREATE FUNCTION test_pglz_decompress(bytea, int4, bool) + RETURNS bytea + AS :'regresslib' LANGUAGE C STRICT; +-- Round-trip with pglz: compress then decompress. +SELECT test_pglz_decompress(test_pglz_compress( + decode(repeat('abcd', 100), 'escape')), 400, false) =3D + decode(repeat('abcd', 100), 'escape') AS roundtrip_ok; + roundtrip_ok=20 +-------------- + t +(1 row) + +SELECT test_pglz_decompress(test_pglz_compress( + decode(repeat('abcd', 100), 'escape')), 400, true) =3D + decode(repeat('abcd', 100), 'escape') AS roundtrip_ok; + roundtrip_ok=20 +-------------- + t +(1 row) + +-- Decompression with rawsize too large, fails to fill the destination +-- buffer. +SELECT test_pglz_decompress(test_pglz_compress( + decode(repeat('abcd', 100), 'escape')), 500, true); +ERROR: pglz_decompress failed +-- Decompression with rawsize too small, fails with source not fully +-- consumed. +SELECT test_pglz_decompress(test_pglz_compress( + decode(repeat('abcd', 100), 'escape')), 100, true); +ERROR: pglz_decompress failed +-- Corrupted compressed data. The control byte is set with match tag bit, +-- but only 1 byte follows. +SELECT test_pglz_decompress('\x01ff'::bytea, 1024, false); +ERROR: pglz_decompress failed +SELECT test_pglz_decompress('\x01ff'::bytea, 1024, true); +ERROR: pglz_decompress failed +-- Corrupted compressed data. Control byte with match tag bit set, where +-- no data follows. +SELECT length(test_pglz_decompress('\x01'::bytea, 1024, false)) AS ctrl_on= ly_len; + ctrl_only_len=20 +--------------- + 0 +(1 row) + +SELECT test_pglz_decompress('\x01'::bytea, 1024, true); +ERROR: pglz_decompress failed +-- Corrupted compressed data. The match tag encodes len=3D18 (aka the +-- extension byte is needed) but there is no data. +SELECT test_pglz_decompress('\x010f01'::bytea, 1024, false); +ERROR: pglz_decompress failed +SELECT test_pglz_decompress('\x010f01'::bytea, 1024, true); +ERROR: pglz_decompress failed diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel= _schedule index cc365393bb7d..3b059906161f 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -128,7 +128,7 @@ test: partition_merge partition_split partition_join pa= rtition_prune reloptions # event_trigger depends on create_am and cannot run concurrently with # any test that runs DDL # oidjoins is read-only, though, and should run late for best coverage -test: oidjoins event_trigger +test: oidjoins event_trigger compression_pglz =20 test: role_ddl tablespace_ddl database_ddl =20 diff --git a/src/test/regress/regress.c b/src/test/regress/regress.c index 5c19f70b6e8e..2bcb5559a452 100644 --- a/src/test/regress/regress.c +++ b/src/test/regress/regress.c @@ -27,6 +27,7 @@ #include "catalog/pg_type.h" #include "commands/sequence.h" #include "commands/trigger.h" +#include "common/pg_lzcompress.h" #include "executor/executor.h" #include "executor/functions.h" #include "executor/spi.h" @@ -1422,3 +1423,68 @@ test_instr_time(PG_FUNCTION_ARGS) =20 PG_RETURN_BOOL(true); } + +/* + * test_pglz_compress + * + * Compress the input using pglz_compress(). Only the "always" strategy is + * currently supported. + * + * Returns the compressed data, or NULL if compression fails. + */ +PG_FUNCTION_INFO_V1(test_pglz_compress); +Datum +test_pglz_compress(PG_FUNCTION_ARGS) +{ + bytea *input =3D PG_GETARG_BYTEA_PP(0); + char *source =3D VARDATA_ANY(input); + int32 slen =3D VARSIZE_ANY_EXHDR(input); + int32 maxout =3D PGLZ_MAX_OUTPUT(slen); + bytea *result; + int32 clen; + + result =3D (bytea *) palloc(maxout + VARHDRSZ); + clen =3D pglz_compress(source, slen, VARDATA(result), + PGLZ_strategy_always); + if (clen < 0) + PG_RETURN_NULL(); + + SET_VARSIZE(result, clen + VARHDRSZ); + PG_RETURN_BYTEA_P(result); +} + +/* + * test_pglz_decompress + * + * Decompress the input using pglz_decompress(). + * + * The second argument is the expected uncompressed data size. The third + * argument is here for the check_complete flag. + * + * Returns the decompressed data, or raises an error if decompression fail= s. + */ +PG_FUNCTION_INFO_V1(test_pglz_decompress); +Datum +test_pglz_decompress(PG_FUNCTION_ARGS) +{ + bytea *input =3D PG_GETARG_BYTEA_PP(0); + int32 rawsize =3D PG_GETARG_INT32(1); + bool check_complete =3D PG_GETARG_BOOL(2); + char *source =3D VARDATA_ANY(input); + int32 slen =3D VARSIZE_ANY_EXHDR(input); + bytea *result; + int32 dlen; + + if (rawsize < 0) + elog(ERROR, "rawsize must not be negative"); + + result =3D (bytea *) palloc(rawsize + VARHDRSZ); + + dlen =3D pglz_decompress(source, slen, VARDATA(result), + rawsize, check_complete); + if (dlen < 0) + elog(ERROR, "pglz_decompress failed"); + + SET_VARSIZE(result, dlen + VARHDRSZ); + PG_RETURN_BYTEA_P(result); +} diff --git a/src/test/regress/sql/compression_pglz.sql b/src/test/regress/s= ql/compression_pglz.sql new file mode 100644 index 000000000000..115f1300d748 --- /dev/null +++ b/src/test/regress/sql/compression_pglz.sql @@ -0,0 +1,49 @@ +-- +-- Tests for PGLZ compression +-- + +-- directory paths and dlsuffix are passed to us in environment variables +\getenv libdir PG_LIBDIR +\getenv dlsuffix PG_DLSUFFIX + +\set regresslib :libdir '/regress' :dlsuffix + +CREATE FUNCTION test_pglz_compress(bytea) + RETURNS bytea + AS :'regresslib' LANGUAGE C STRICT; +CREATE FUNCTION test_pglz_decompress(bytea, int4, bool) + RETURNS bytea + AS :'regresslib' LANGUAGE C STRICT; + +-- Round-trip with pglz: compress then decompress. +SELECT test_pglz_decompress(test_pglz_compress( + decode(repeat('abcd', 100), 'escape')), 400, false) =3D + decode(repeat('abcd', 100), 'escape') AS roundtrip_ok; +SELECT test_pglz_decompress(test_pglz_compress( + decode(repeat('abcd', 100), 'escape')), 400, true) =3D + decode(repeat('abcd', 100), 'escape') AS roundtrip_ok; + +-- Decompression with rawsize too large, fails to fill the destination +-- buffer. +SELECT test_pglz_decompress(test_pglz_compress( + decode(repeat('abcd', 100), 'escape')), 500, true); + +-- Decompression with rawsize too small, fails with source not fully +-- consumed. +SELECT test_pglz_decompress(test_pglz_compress( + decode(repeat('abcd', 100), 'escape')), 100, true); + +-- Corrupted compressed data. The control byte is set with match tag bit, +-- but only 1 byte follows. +SELECT test_pglz_decompress('\x01ff'::bytea, 1024, false); +SELECT test_pglz_decompress('\x01ff'::bytea, 1024, true); + +-- Corrupted compressed data. Control byte with match tag bit set, where +-- no data follows. +SELECT length(test_pglz_decompress('\x01'::bytea, 1024, false)) AS ctrl_on= ly_len; +SELECT test_pglz_decompress('\x01'::bytea, 1024, true); + +-- Corrupted compressed data. The match tag encodes len=3D18 (aka the +-- extension byte is needed) but there is no data. +SELECT test_pglz_decompress('\x010f01'::bytea, 1024, false); +SELECT test_pglz_decompress('\x010f01'::bytea, 1024, true); --=20 2.53.0 --wY/Kkkh+p9/WqLGS-- --YaZnuUvOyncMcqJL Content-Type: application/pgp-signature; name=signature.asc -----BEGIN PGP SIGNATURE----- iQIzBAEBCgAdFiEEG72nH6vTowiyblFKnvQgOdbyQH0FAmndJFAACgkQnvQgOdby QH3ZyQ//a1bLL2Jjk5KGnb4s9E0JtPa8f4GgiTzJ3JFyw1coHQ3XRwJuidqZwiqu oKONJZoPel7+rvWmwLz45J1eiXiMuFdwaXaRNztnU/fJKEkY0xczmXeVxgAx94BX H5PmeYKgO1aWjX54df95nM/tM9Dlnhu/DHJvE79jdCqQnhryNCY/g+xqA1cXZCN5 Yjvk1rL6VQkNR5u3pjUwkWpY6Pu6xDKMVrgWa0kdRoTMOr7s4Qqjj7E8eBlVB1cm XLSelVi9dSxbDj1H3gdai5AHHMw+bw3xXySdW3QgHxBZf1WxR+624RRaTlOfXJkt Np3E8vUHo1QGnK6NkEWVOW1pLP0DKARsZaGTCdyyMS7KByQIKYWUm3evFQfHbuMF VDWkfFmRNc0alce8MG2yjIpii0YCmvKgZcltwN/PYnYJuwBBdvcpkeCB7HlVTop6 WWJDx0p3dIms3aHVn5h8vc/maUawwB6LDAJOolPYscpm6HLZUzW4//gXaxHZpkU3 9NXIiU96fvKtapKvqOdvNObPLYMibvvy0iaIrNc/ABYdFZ84G57/v83CHAkwYPyn QlMUr5fYJDEyqI0IiEth93YG66kVv4gCuGXObydT8d1ysxuS3sTyMXjrsH7OvqR9 f4iAhYstwhG2ydJOdEVSMVVUjagHAtG/3RBdEOcbUmiHFJnPPZc= =3aDn -----END PGP SIGNATURE----- --YaZnuUvOyncMcqJL--