Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1pZiry-0002Oh-Oq for pgsql-hackers@arkaria.postgresql.org; Wed, 08 Mar 2023 01:45:31 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.92) (envelope-from ) id 1pZirx-0005LQ-63 for pgsql-hackers@arkaria.postgresql.org; Wed, 08 Mar 2023 01:45:29 +0000 Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29]) by malur.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1pZirw-0005LG-Ls for pgsql-hackers@lists.postgresql.org; Wed, 08 Mar 2023 01:45:28 +0000 Received: from mail-il1-x134.google.com ([2607:f8b0:4864:20::134]) by magus.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.92) (envelope-from ) id 1pZirs-0005Ls-GL for pgsql-hackers@lists.postgresql.org; Wed, 08 Mar 2023 01:45:27 +0000 Received: by mail-il1-x134.google.com with SMTP id y9so8687972ill.3 for ; Tue, 07 Mar 2023 17:45:24 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=telsasoft-com.20210112.gappssmtp.com; s=20210112; t=1678239922; h=in-reply-to:content-disposition:mime-version:references:message-id :subject:cc:to:from:date:from:to:cc:subject:date:message-id:reply-to; bh=9Y1kFiAJdzyyGlmBO+e32O6fJxW+4nv6d5zqyiek648=; b=tocXhpLVgYaCG2iG7C+Z1g3FR1OZpryPpGccwIrH7erMgt4YPjeO5qIvTPs1uSmGGt 54LLgareAbKckUjDD198fXM/0+DvQTxoGvgvGYvnXrfKPOmFh05NmUqteILoZ6jc0/Nx UJ4/Sw+aroH7KPxPVV0QHjdifBczJtr2Ufo/2C5GQ9AzW3DRlP5CA77Tev/EYJTCgNpJ RWVj79ezhF7XES9Wp9VoH/RlXrLSU+xQEPDGYrRmA8DHPBG698KIh9KO2/ZCByFiHjQR 2RaTTkvOPJYXWRpN5TbUgg1xOUwN3klFyS0gA2eTT6wA9wpiCjktxSUfKT7TE2QcgXhZ GMdA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; t=1678239922; h=in-reply-to:content-disposition:mime-version:references:message-id :subject:cc:to:from:date:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to; bh=9Y1kFiAJdzyyGlmBO+e32O6fJxW+4nv6d5zqyiek648=; b=AwGzJ6KOTrylmSEVZKHjagZdj8kaKBYSspyUDaoBexv+zMAKOlp89T4A3jde3lvglr VbC9c3+HjMxE3FWQ9LNJ7VJZ64+GmuaalwekNtZkWODxdFKKxK/SDpyGBLOaxPjIna34 955rp318QhYj9tU9mr443LmTFolgaXEdQJ0YyrGclZpbll7GjHDgofXV8Emh6EwZ63na PnOJiqJfhcXyvMLM1oLJWCHmUpc4gMqZj/kruxWAU/+JtU4jIc+Z5/XotBqdZ1EXeyQd C6grCUPqkgYJ7FX/eYcKIiCMvichLIdamvM218wFANkEm4qXkEmCZp6o2rXChBIhbblk illQ== X-Gm-Message-State: AO0yUKXL3tEt+0Tq7TzXHzO3J1c7bFuj1OP32bHJeVs/QcXSi/pLH3e1 RUarfjQ2jHani8qG54gocNJ+aA== X-Google-Smtp-Source: AK7set8+7EtpoEs86U/+8lMKqWOplvfZgVPFoaD1ahRJxwjrHqy7S2+lyIEoKQsqMqRILoIfIMi5Fw== X-Received: by 2002:a05:6e02:1b87:b0:315:3b94:9911 with SMTP id h7-20020a056e021b8700b003153b949911mr13919136ili.15.1678239922370; Tue, 07 Mar 2023 17:45:22 -0800 (PST) Received: from pryzbyj.telsasoft (charmander.telsasoft.com. [50.244.222.1]) by smtp.gmail.com with ESMTPSA id z3-20020a92bf03000000b00316dc3afe57sm4016971ilh.86.2023.03.07.17.45.21 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 07 Mar 2023 17:45:21 -0800 (PST) Received: by pryzbyj.telsasoft (Postfix, from userid 1000) id 82BF0800F07; Tue, 7 Mar 2023 19:45:20 -0600 (CST) Date: Tue, 7 Mar 2023 19:45:20 -0600 From: Justin Pryzby To: Tomas Vondra Cc: gkokolatos@pm.me, shiy.fnst@fujitsu.com, Michael Paquier , pgsql-hackers@lists.postgresql.org, Rachel Heaton Subject: Re: Add LZ4 compression in pg_dump Message-ID: References: <535565cd-b69f-c064-a845-15c0fc44db4a@enterprisedb.com> <22cb80ed-ec87-54c7-fd5b-cdcf79766bb1@enterprisedb.com> <8421f53d-c9f7-c02e-3278-9f9522a8e8f3@enterprisedb.com> <20230228235834.GC30529@telsasoft.com> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="Ht8JM7LT7nfa6V3p" Content-Disposition: inline In-Reply-To: List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk --Ht8JM7LT7nfa6V3p Content-Type: text/plain; charset=us-ascii Content-Disposition: inline On Wed, Mar 01, 2023 at 04:52:49PM +0100, Tomas Vondra wrote: > Thanks. That seems correct to me, but I find it somewhat confusing, > because we now have > > DeflateCompressorInit vs. InitCompressorGzip > > DeflateCompressorEnd vs. EndCompressorGzip > > DeflateCompressorData - The name doesn't really say what it does (would > be better to have a verb in there, I think). > > I wonder if we can make this somehow clearer? To move things along, I updated Georgios' patch: Rename DeflateCompressorData() to DeflateCompressorCommon(); Rearrange functions to their original order allowing a cleaner diff to the prior code; Change pg_fatal() to an assertion+comment; Update the commit message and fix a few typos; > Also, InitCompressorGzip says this: > > /* > * If the caller has defined a write function, prepare the necessary > * state. Avoid initializing during the first write call, because End > * may be called without ever writing any data. > */ > if (cs->writeF) > DeflateCompressorInit(cs); > > Does it actually make sense to not have writeF defined in some cases? InitCompressor is being called for either reading or writing, either of which could be null: src/bin/pg_dump/pg_backup_custom.c: ctx->cs = AllocateCompressor(AH->compression_spec, src/bin/pg_dump/pg_backup_custom.c- NULL, src/bin/pg_dump/pg_backup_custom.c- _CustomWriteFunc); -- src/bin/pg_dump/pg_backup_custom.c: cs = AllocateCompressor(AH->compression_spec, src/bin/pg_dump/pg_backup_custom.c- _CustomReadFunc, NULL); It's confusing that the comment says "Avoid initializing...". What it really means is "Initialize eagerly...". But that makes more sense in the context of the commit message for this bugfix than in a comment. So I changed that too. + /* If deflation was initialized, finalize it */ + if (cs->private_data) + DeflateCompressorEnd(AH, cs); Maybe it'd be more clear if this used "if (cs->writeF)", like in the init function ? -- Justin --Ht8JM7LT7nfa6V3p Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="0001-pg_dump-fix-gzip-compression-of-empty-data.patch" From 5c027aa86e8591db140093c48a58aafba7a6c28c Mon Sep 17 00:00:00 2001 From: Georgios Kokolatos Date: Wed, 1 Mar 2023 12:42:32 +0000 Subject: [PATCH] pg_dump: fix gzip compression of empty data When creating dumps with the Compressor API, it is possible to only call the Allocate and End compressor functions without ever writing any data. Since e9960732a, the gzip implementation wrongly assumed that the write function would always be called and deferred the initialization of the internal compression system until the first write call. The problem with that approach is that the End call would not finalize the internal compression system if it hadn't been initialized. This commit initializes the internal compression system during the Allocate call, whenever a write function was provided by the caller. Given that decompression does not need to keep track of any state, the compressor's private_data member is now populated only during compression. In passing, rearrange the functions to their original order, to allow usefully comparing with the previous code, like: git diff --diff-algorithm=minimal -w e9960732a~:src/bin/pg_dump/compress_io.c src/bin/pg_dump/compress_gzip.c Also replace an unreachable pg_fatal() with an assert+comment. I (Justin) argued that the new fatal shouldn't have been introduced in a refactoring commit, so this is a compromise. Report and initial patch by Justin Pryzby, test case by Georgios Kokolatos. https://www.postgresql.org/message-id/20230228235834.GC30529%40telsasoft.com --- src/bin/pg_dump/compress_gzip.c | 137 ++++++++++++++++++------------- src/bin/pg_dump/t/002_pg_dump.pl | 23 ++++++ 2 files changed, 101 insertions(+), 59 deletions(-) diff --git a/src/bin/pg_dump/compress_gzip.c b/src/bin/pg_dump/compress_gzip.c index 0af65afeb4e..3c9ac55c266 100644 --- a/src/bin/pg_dump/compress_gzip.c +++ b/src/bin/pg_dump/compress_gzip.c @@ -32,9 +32,75 @@ typedef struct GzipCompressorState size_t outsize; } GzipCompressorState; + /* Private routines that support gzip compressed data I/O */ +static void DeflateCompressorInit(CompressorState *cs); +static void DeflateCompressorEnd(ArchiveHandle *AH, CompressorState *cs); +static void DeflateCompressorCommon(ArchiveHandle *AH, CompressorState *cs, + bool flush); +static void EndCompressorGzip(ArchiveHandle *AH, CompressorState *cs); +static void WriteDataToArchiveGzip(ArchiveHandle *AH, CompressorState *cs, + const void *data, size_t dLen); +static void ReadDataFromArchiveGzip(ArchiveHandle *AH, CompressorState *cs); + static void -DeflateCompressorGzip(ArchiveHandle *AH, CompressorState *cs, bool flush) +DeflateCompressorInit(CompressorState *cs) +{ + GzipCompressorState *gzipcs; + z_streamp zp; + + gzipcs = (GzipCompressorState *) pg_malloc0(sizeof(GzipCompressorState)); + zp = gzipcs->zp = (z_streamp) pg_malloc(sizeof(z_stream)); + zp->zalloc = Z_NULL; + zp->zfree = Z_NULL; + zp->opaque = Z_NULL; + + /* + * outsize is the buffer size we tell zlib it can output to. We actually + * allocate one extra byte because some routines want to append a trailing + * zero byte to the zlib output. + */ + gzipcs->outbuf = pg_malloc(ZLIB_OUT_SIZE + 1); + gzipcs->outsize = ZLIB_OUT_SIZE; + + /* -Z 0 uses the "None" compressor -- not zlib with no compression */ + Assert(cs->compression_spec.level != 0); + + if (deflateInit(zp, cs->compression_spec.level) != Z_OK) + pg_fatal("could not initialize compression library: %s", zp->msg); + + /* Just be paranoid - maybe End is called after Start, with no Write */ + zp->next_out = gzipcs->outbuf; + zp->avail_out = gzipcs->outsize; + + /* Keep track of gzipcs */ + cs->private_data = gzipcs; +} + +static void +DeflateCompressorEnd(ArchiveHandle *AH, CompressorState *cs) +{ + GzipCompressorState *gzipcs = (GzipCompressorState *) cs->private_data; + z_streamp zp; + + zp = gzipcs->zp; + zp->next_in = NULL; + zp->avail_in = 0; + + /* Flush any remaining data from zlib buffer */ + DeflateCompressorCommon(AH, cs, true); + + if (deflateEnd(zp) != Z_OK) + pg_fatal("could not close compression stream: %s", zp->msg); + + pg_free(gzipcs->outbuf); + pg_free(gzipcs->zp); + pg_free(gzipcs); + cs->private_data = NULL; +} + +static void +DeflateCompressorCommon(ArchiveHandle *AH, CompressorState *cs, bool flush) { GzipCompressorState *gzipcs = (GzipCompressorState *) cs->private_data; z_streamp zp = gzipcs->zp; @@ -78,27 +144,9 @@ DeflateCompressorGzip(ArchiveHandle *AH, CompressorState *cs, bool flush) static void EndCompressorGzip(ArchiveHandle *AH, CompressorState *cs) { - GzipCompressorState *gzipcs = (GzipCompressorState *) cs->private_data; - z_streamp zp; - - if (gzipcs->zp) - { - zp = gzipcs->zp; - zp->next_in = NULL; - zp->avail_in = 0; - - /* Flush any remaining data from zlib buffer */ - DeflateCompressorGzip(AH, cs, true); - - if (deflateEnd(zp) != Z_OK) - pg_fatal("could not close compression stream: %s", zp->msg); - - pg_free(gzipcs->outbuf); - pg_free(gzipcs->zp); - } - - pg_free(gzipcs); - cs->private_data = NULL; + /* If deflation was initialized, finalize it */ + if (cs->private_data) + DeflateCompressorEnd(AH, cs); } static void @@ -106,41 +154,10 @@ WriteDataToArchiveGzip(ArchiveHandle *AH, CompressorState *cs, const void *data, size_t dLen) { GzipCompressorState *gzipcs = (GzipCompressorState *) cs->private_data; - z_streamp zp; - - if (!gzipcs->zp) - { - zp = gzipcs->zp = (z_streamp) pg_malloc(sizeof(z_stream)); - zp->zalloc = Z_NULL; - zp->zfree = Z_NULL; - zp->opaque = Z_NULL; - - /* - * outsize is the buffer size we tell zlib it can output to. We - * actually allocate one extra byte because some routines want to - * append a trailing zero byte to the zlib output. - */ - gzipcs->outbuf = pg_malloc(ZLIB_OUT_SIZE + 1); - gzipcs->outsize = ZLIB_OUT_SIZE; - - /* - * A level of zero simply copies the input one block at the time. This - * is probably not what the user wanted when calling this interface. - */ - if (cs->compression_spec.level == 0) - pg_fatal("requested to compress the archive yet no level was specified"); - - if (deflateInit(zp, cs->compression_spec.level) != Z_OK) - pg_fatal("could not initialize compression library: %s", zp->msg); - - /* Just be paranoid - maybe End is called after Start, with no Write */ - zp->next_out = gzipcs->outbuf; - zp->avail_out = gzipcs->outsize; - } gzipcs->zp->next_in = (void *) unconstify(void *, data); gzipcs->zp->avail_in = dLen; - DeflateCompressorGzip(AH, cs, false); + DeflateCompressorCommon(AH, cs, false); } static void @@ -214,17 +231,19 @@ void InitCompressorGzip(CompressorState *cs, const pg_compress_specification compression_spec) { - GzipCompressorState *gzipcs; - cs->readData = ReadDataFromArchiveGzip; cs->writeData = WriteDataToArchiveGzip; cs->end = EndCompressorGzip; cs->compression_spec = compression_spec; - gzipcs = (GzipCompressorState *) pg_malloc0(sizeof(GzipCompressorState)); - - cs->private_data = gzipcs; + /* + * If the caller has defined a write function, prepare the necessary + * state. Note that if the data is empty, End may be called immediately + * after Init, without ever calling Write. + */ + if (cs->writeF) + DeflateCompressorInit(cs); } diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl index 187e4b8d07d..14cd0d2d503 100644 --- a/src/bin/pg_dump/t/002_pg_dump.pl +++ b/src/bin/pg_dump/t/002_pg_dump.pl @@ -1194,6 +1194,29 @@ my %tests = ( }, }, + 'LO create (with no data)' => { + create_sql => + 'SELECT pg_catalog.lo_create(0);', + regexp => qr/^ + \QSELECT pg_catalog.lo_open\E \('\d+',\ \d+\);\n + \QSELECT pg_catalog.lo_close(0);\E + /xm, + like => { + %full_runs, + column_inserts => 1, + data_only => 1, + inserts => 1, + section_data => 1, + test_schema_plus_large_objects => 1, + }, + unlike => { + binary_upgrade => 1, + no_large_objects => 1, + schema_only => 1, + section_pre_data => 1, + }, + }, + 'COMMENT ON DATABASE postgres' => { regexp => qr/^COMMENT ON DATABASE postgres IS .+;/m, -- 2.34.1 --Ht8JM7LT7nfa6V3p--