You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@arrow.apache.org by ze...@apache.org on 2026/07/02 19:08:09 UTC

(arrow-go) branch main updated: fix(parquet/compress): Gzip CompressBound (#886)

This is an automated email from the ASF dual-hosted git repository.

zeroshade pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-go.git


The following commit(s) were added to refs/heads/main by this push:
     new 4c35e2fe fix(parquet/compress): Gzip CompressBound (#886)
4c35e2fe is described below

commit 4c35e2fe1209912b29583ef84a68b2f6e3740a09
Author: Minh Vu <vu...@gmail.com>
AuthorDate: Thu Jul 2 21:08:04 2026 +0200

    fix(parquet/compress): Gzip CompressBound (#886)
    
    ### What changed
    
    `gzipCodec.CompressBound` now includes gzip framing overhead on top of
    the existing deflate growth estimate. The bound also leaves room for the
    stateless close block accepted by `EncodeLevel`.
    
    The regression test checks empty, one-byte, small, and random
    incompressible payloads across the gzip levels accepted by the codec.
    
    ### Why
    
    The previous bound could be smaller than real gzip output for tiny
    inputs because it did not account for gzip header and trailer bytes.
    Callers using `CompressBound` to size destination buffers could
    therefore under-allocate.
    
    ### Validation
    
    - `go test ./parquet/compress`
    - `go test -tags assert ./parquet/compress`
    -
    `PARQUET_TEST_DATA=/Users/hoangvu/Code/OSS/arrow-go/parquet-testing/data
    PARQUET_TEST_BAD_DATA=/Users/hoangvu/Code/OSS/arrow-go/parquet-testing/bad_data
    go test ./parquet/...`
---
 parquet/compress/compress_test.go | 43 +++++++++++++++++++++++++++++++++++++++
 parquet/compress/gzip.go          |  9 +++++++-
 2 files changed, 51 insertions(+), 1 deletion(-)

diff --git a/parquet/compress/compress_test.go b/parquet/compress/compress_test.go
index c60c65d7..6b65f23b 100644
--- a/parquet/compress/compress_test.go
+++ b/parquet/compress/compress_test.go
@@ -24,6 +24,7 @@ import (
 	"testing"
 
 	"github.com/apache/arrow-go/v18/parquet/compress"
+	"github.com/klauspost/compress/gzip"
 	"github.com/klauspost/compress/zstd"
 	"github.com/stretchr/testify/assert"
 )
@@ -91,6 +92,48 @@ func TestCompressDataOneShot(t *testing.T) {
 	}
 }
 
+func TestGzipCompressBound(t *testing.T) {
+	codec, err := compress.GetCodec(compress.Codecs.Gzip)
+	assert.NoError(t, err)
+
+	payloads := []struct {
+		name string
+		data []byte
+	}{
+		{"empty", nil},
+		{"one byte", []byte{0xab}},
+		{"small", []byte("gzip")},
+		{"incompressible", makeRandomData(128)},
+	}
+
+	levels := []struct {
+		name  string
+		level int
+	}{
+		{"default", gzip.DefaultCompression},
+		{"no compression", gzip.NoCompression},
+		{"best speed", gzip.BestSpeed},
+		{"best compression", gzip.BestCompression},
+		{"huffman only", gzip.HuffmanOnly},
+		{"stateless", gzip.StatelessCompression},
+	}
+
+	for _, payload := range payloads {
+		t.Run(payload.name, func(t *testing.T) {
+			bound := codec.CompressBound(int64(len(payload.data)))
+			for _, level := range levels {
+				t.Run(level.name, func(t *testing.T) {
+					compressed := codec.EncodeLevel(make([]byte, 0, bound), payload.data, level.level)
+					assert.LessOrEqual(t, int64(len(compressed)), bound)
+
+					uncompressed := codec.Decode(nil, compressed)
+					assert.Equal(t, payload.data, append([]byte(nil), uncompressed...))
+				})
+			}
+		})
+	}
+}
+
 func TestCompressReaderWriter(t *testing.T) {
 	tests := []struct {
 		c compress.Compression
diff --git a/parquet/compress/gzip.go b/parquet/compress/gzip.go
index 4b43f8e9..562b5f4e 100644
--- a/parquet/compress/gzip.go
+++ b/parquet/compress/gzip.go
@@ -26,6 +26,12 @@ import (
 
 type gzipCodec struct{}
 
+const (
+	gzipHeaderSize              = 10
+	gzipTrailerSize             = 8
+	gzipStatelessCloseBlockSize = 5
+)
+
 func (gzipCodec) NewReader(r io.Reader) io.ReadCloser {
 	ret, err := gzip.NewReader(r)
 	if err != nil {
@@ -81,7 +87,8 @@ func (g gzipCodec) Encode(dst, src []byte) []byte {
 }
 
 func (gzipCodec) CompressBound(len int64) int64 {
-	return len + ((len + 7) >> 3) + ((len + 63) >> 6) + 5
+	return len + ((len + 7) >> 3) + ((len + 63) >> 6) + 5 +
+		gzipHeaderSize + gzipTrailerSize + gzipStatelessCloseBlockSize
 }
 
 func (gzipCodec) NewWriter(w io.Writer) io.WriteCloser {