Repository: segmentio/parquet-go
Pull request #116 (2022-03-29 07:42:22)

add parquet.Dictionary.Page method
Following up from user feedback, this PR adds the `parquet.Dictionary.Page` method, allowing the dictionary to be converted to a parquet page to read its content.

I first modified the `parquet.Dictionary` interface to add a `Values() ValueReader` method (in the same way we have on `parquet.Page`, and then realized that the interface and implementation started to overlap too much with pages, to the point that I estimated that it would be better to reduce the code surface to simply leverage the page APIs.

Prior to this change there was a symmetry between the `ReadFrom` and `WriteTo` methods of the `parquet.Dictionary` interface. By removing `WriteTo` (since it's now available by calling `Page().WriteTo`), this symmetry was broken so I replaced `ReadFrom` with a `parquet.Type.ReadDictionary` method instead. 

Please take a look and let me know if you have any comments!

achille-roussel: Thanks for the useful comments!

Patch:
diff --git a/buffer.go b/buffer.go
index 8698881..4496a44 100644
--- a/buffer.go
+++ b/buffer.go
@@ -59,6 +59,7 @@ func (buf *Buffer) configure(schema *Schema) {
 
 	forEachLeafColumnOf(schema, func(leaf leafColumn) {
 		nullOrdering := nullsGoLast
+		columnIndex := int(leaf.columnIndex)
 		columnType := leaf.node.Type()
 		bufferSize := buf.config.ColumnBufferSize
 		dictionary := (Dictionary)(nil)
@@ -66,11 +67,11 @@ func (buf *Buffer) configure(schema *Schema) {
 
 		if isDictionaryEncoding(encoding) {
 			bufferSize /= 2
-			dictionary = columnType.NewDictionary(bufferSize)
+			dictionary = columnType.NewDictionary(columnIndex, bufferSize)
 			columnType = dictionary.Type()
 		}
 
-		column := columnType.NewColumnBuffer(int(leaf.columnIndex), bufferSize)
+		column := columnType.NewColumnBuffer(columnIndex, bufferSize)
 		switch {
 		case leaf.maxRepetitionLevel > 0:
 			column = newRepeatedColumnBuffer(column, leaf.maxRepetitionLevel, leaf.maxDefinitionLevel, nullOrdering)
diff --git a/column_buffer_go18.go b/column_buffer_go18.go
index d61f971..c4fa1d1 100644
--- a/column_buffer_go18.go
+++ b/column_buffer_go18.go
@@ -76,7 +76,8 @@ func (col *columnBuffer[T]) Write(b []byte) (int, error) {
 	if (len(b) % sizeof[T]()) != 0 {
 		return 0, fmt.Errorf("cannot write %s values from input of size %d", col.class.name, len(b))
 	}
-	return col.WriteRequired(cast.BytesToSlice[T](b))
+	n, err := col.WriteRequired(cast.BytesToSlice[T](b))
+	return sizeof[T]() * n, err
 }
 
 func (col *columnBuffer[T]) WriteRequired(values []T) (int, error) {
diff --git a/dictionary.go b/dictionary.go
index 83de7d6..8e848e6 100644
--- a/dictionary.go
+++ b/dictionary.go
@@ -47,17 +47,14 @@ type Dictionary interface {
 	// Returns the min and max values found in the given indexes.
 	Bounds(indexed []int32) (min, max Value)
 
-	// Reads the dictionary from the decoder passed as argument.
-	//
-	// The dictionary is cleared prior to loading the values so that its final
-	// content contains only the entries read from the decoder.
-	ReadFrom(encoding.Decoder) error
-
-	// Writes the dictionary to the encoder passed as argument.
-	WriteTo(encoding.Encoder) error
-
 	// Resets the dictionary to its initial state, removing all values.
 	Reset()
+
+	// Returns a BufferedPage representing the content of the dictionary.
+	//
+	// The returned page shares the underlying memory of the buffer, it remains
+	// valid to use until the dictionary's Reset method is called.
+	Page() BufferedPage
 }
 
 func dictCap(bufferSize, valueItemSize int) int {
@@ -66,15 +63,41 @@ func dictCap(bufferSize, valueItemSize int) int {
 }
 
 type byteArrayDictionary struct {
-	typ    Type
-	values encoding.ByteArrayList
-	index  map[string]int32
+	byteArrayPage
+	typ   Type
+	index map[string]int32
 }
 
-func newByteArrayDictionary(typ Type, bufferSize int) *byteArrayDictionary {
+func newByteArrayDictionary(typ Type, columnIndex int16, bufferSize int) *byteArrayDictionary {
 	return &byteArrayDictionary{
-		typ:    typ,
-		values: encoding.MakeByteArrayList(dictCap(bufferSize, 16)),
+		typ: typ,
+		byteArrayPage: byteArrayPage{
+			values:      encoding.MakeByteArrayList(dictCap(bufferSize, 16)),
+			columnIndex: columnIndex,
+		},
+	}
+}
+
+func readByteArrayDictionary(typ Type, columnIndex int16, numValues int, decoder encoding.Decoder) (Dictionary, error) {
+	d := &byteArrayDictionary{
+		typ: typ,
+		byteArrayPage: byteArrayPage{
+			values:      encoding.MakeByteArrayList(atLeastOne(numValues)),
+			columnIndex: columnIndex,
+		},
+	}
+
+	for {
+		if d.values.Len() == d.values.Cap() {
+			d.values.Grow(d.values.Len())
+		}
+		_, err := decoder.DecodeByteArray(&d.values)
+		if err != nil {
+			if err == io.EOF {
+				err = nil
+			}
+			return d, err
+		}
 	}
 }
 
@@ -141,69 +164,86 @@ func (d *byteArrayDictionary) Bounds(indexes []int32) (min, max Value) {
 	return min, max
 }
 
-func (d *byteArrayDictionary) ReadFrom(decoder encoding.Decoder) error {
-	d.Reset()
-	for {
-		if d.values.Len() == d.values.Cap() {
-			d.values.Grow(d.values.Len())
-		}
-		_, err := decoder.DecodeByteArray(&d.values)
-		if err != nil {
-			if err == io.EOF {
-				err = nil
-			}
-			return err
-		}
-	}
-}
-
-func (d *byteArrayDictionary) WriteTo(encoder encoding.Encoder) error {
-	if err := encoder.EncodeByteArray(d.values); err != nil {
-		return fmt.Errorf("writing parquet dictionary of %d binary values: %w", d.Len(), err)
-	}
-	return nil
-}
-
 func (d *byteArrayDictionary) Reset() {
 	d.values.Reset()
 	d.index = nil
 }
 
+func (d *byteArrayDictionary) Page() BufferedPage {
+	return &d.byteArrayPage
+}
+
 type fixedLenByteArrayDictionary struct {
-	typ    Type
-	size   int
-	values []byte
-	index  map[string]int32
+	fixedLenByteArrayPage
+	typ   Type
+	index map[string]int32
 }
 
-func newFixedLenByteArrayDictionary(typ Type, bufferSize int) *fixedLenByteArrayDictionary {
+func newFixedLenByteArrayDictionary(typ Type, columnIndex int16, bufferSize int) *fixedLenByteArrayDictionary {
 	size := typ.Length()
 	return &fixedLenByteArrayDictionary{
-		typ:    typ,
-		size:   size,
-		values: make([]byte, 0, dictCap(bufferSize, size)*size),
+		typ: typ,
+		fixedLenByteArrayPage: fixedLenByteArrayPage{
+			size:        size,
+			data:        make([]byte, 0, dictCap(bufferSize, size)*size),
+			columnIndex: columnIndex,
+		},
 	}
 }
 
+func readFixedLenByteArrayDictionary(typ Type, columnIndex int16, numValues int, decoder encoding.Decoder) (Dictionary, error) {
+	size := typ.Length()
+
+	d := &fixedLenByteArrayDictionary{
+		typ: typ,
+		fixedLenByteArrayPage: fixedLenByteArrayPage{
+			size:        size,
+			data:        make([]byte, 0, atLeastOne(numValues)*size),
+			columnIndex: columnIndex,
+		},
+	}
+
+	for {
+		if len(d.data) == cap(d.data) {
+			newValues := make([]byte, len(d.data), 2*cap(d.data))
+			copy(newValues, d.data)
+			d.data = newValues
+		}
+
+		n, err := decoder.DecodeFixedLenByteArray(d.size, d.data[len(d.data):cap(d.data)])
+		if n > 0 {
+			d.data = d.data[:len(d.data)+(n*d.size)]
+		}
+
+		if err == io.EOF {
+			return d, nil
+		}
+		if err != nil {
+			return nil, fmt.Errorf("reading parquet dictionary of fixed-length binary values of size %d: %w", d.size, err)
+		}
+	}
+
+}
+
 func (d *fixedLenByteArrayDictionary) Type() Type { return newIndexedType(d.typ, d) }
 
-func (d *fixedLenByteArrayDictionary) Len() int { return len(d.values) / d.size }
+func (d *fixedLenByteArrayDictionary) Len() int { return len(d.data) / d.size }
 
 func (d *fixedLenByteArrayDictionary) Index(i int32) Value {
 	return makeValueBytes(FixedLenByteArray, d.value(i))
 }
 
 func (d *fixedLenByteArrayDictionary) value(i int32) []byte {
-	return d.values[int(i)*d.size : int(i+1)*d.size]
+	return d.data[int(i)*d.size : int(i+1)*d.size]
 }
 
 func (d *fixedLenByteArrayDictionary) Insert(indexes []int32, values []Value) {
 	_ = indexes[:len(values)]
 
 	if d.index == nil {
-		d.index = make(map[string]int32, cap(d.values)/d.size)
-		for i, j := 0, int32(0); i < len(d.values); i += d.size {
-			d.index[bits.BytesToString(d.values[i:i+d.size])] = j
+		d.index = make(map[string]int32, cap(d.data)/d.size)
+		for i, j := 0, int32(0); i < len(d.data); i += d.size {
+			d.index[bits.BytesToString(d.data[i:i+d.size])] = j
 			j++
 		}
 	}
@@ -214,9 +254,9 @@ func (d *fixedLenByteArrayDictionary) Insert(indexes []int32, values []Value) {
 		index, exists := d.index[string(value)]
 		if !exists {
 			index = int32(d.Len())
-			start := len(d.values)
-			d.values = append(d.values, value...)
-			d.index[bits.BytesToString(d.values[start:])] = index
+			start := len(d.data)
+			d.data = append(d.data, value...)
+			d.index[bits.BytesToString(d.data[start:])] = index
 		}
 
 		indexes[i] = index
@@ -250,43 +290,15 @@ func (d *fixedLenByteArrayDictionary) Bounds(indexes []int32) (min, max Value) {
 	return min, max
 }
 
-func (d *fixedLenByteArrayDictionary) ReadFrom(decoder encoding.Decoder) error {
-	d.Reset()
-	for {
-		if len(d.values) == cap(d.values) {
-			newValues := make([]byte, len(d.values), 2*cap(d.values))
-			copy(newValues, d.values)
-			d.values = newValues
-		}
-
-		n, err := decoder.DecodeFixedLenByteArray(d.size, d.values[len(d.values):cap(d.values)])
-		if n > 0 {
-			d.values = d.values[:len(d.values)+(n*d.size)]
-		}
-
-		if err != nil {
-			if err == io.EOF {
-				err = nil
-			} else {
-				err = fmt.Errorf("reading parquet dictionary of fixed-length binary values of size %d: %w", d.size, err)
-			}
-			return err
-		}
-	}
-}
-
-func (d *fixedLenByteArrayDictionary) WriteTo(encoder encoding.Encoder) error {
-	if err := encoder.EncodeFixedLenByteArray(d.size, d.values); err != nil {
-		return fmt.Errorf("writing parquet dictionary of %d fixed-length binary values of size %d: %w", d.Len(), d.size, err)
-	}
-	return nil
-}
-
 func (d *fixedLenByteArrayDictionary) Reset() {
-	d.values = d.values[:0]
+	d.data = d.data[:0]
 	d.index = nil
 }
 
+func (d *fixedLenByteArrayDictionary) Page() BufferedPage {
+	return &d.fixedLenByteArrayPage
+}
+
 type indexedType struct {
 	Type
 	dict Dictionary
diff --git a/dictionary_default.go b/dictionary_default.go
index 08da320..7b3f915 100644
--- a/dictionary_default.go
+++ b/dictionary_default.go
@@ -12,14 +12,50 @@ import (
 
 // The boolean dictionary always contains two values for true and false.
 type booleanDictionary struct {
-	typ    Type
-	values [2]bool
+	booleanPage
+	typ   Type
+	index map[bool]int32
 }
 
-func newBooleanDictionary(typ Type) *booleanDictionary {
+func newBooleanDictionary(typ Type, columnIndex int16, bufferSize int) *booleanDictionary {
 	return &booleanDictionary{
-		typ:    typ,
-		values: [2]bool{false, true},
+		typ: typ,
+		booleanPage: booleanPage{
+			values:      make([]bool, 0, atLeastOne(bufferSize)),
+			columnIndex: columnIndex,
+		},
+	}
+}
+
+func readBooleanDictionary(typ Type, columnIndex int16, numValues int, decoder encoding.Decoder) (*booleanDictionary, error) {
+	d := &booleanDictionary{
+		typ: typ,
+		booleanPage: booleanPage{
+			values:      make([]bool, 0, atLeastOne(numValues)),
+			columnIndex: columnIndex,
+		},
+	}
+
+	for {
+		if len(d.values) == cap(d.values) {
+			newValues := make([]bool, len(d.values), 2*cap(d.values))
+			copy(newValues, d.values)
+			d.values = newValues
+		}
+
+		n, err := decoder.DecodeBoolean(d.values[len(d.values):cap(d.values)])
+		if n > 0 {
+			d.values = d.values[:len(d.values)+n]
+		}
+
+		if err != nil {
+			if err == io.EOF {
+				err = nil
+			} else {
+				err = fmt.Errorf("reading parquet dictionary of int32 values: %w", err)
+			}
+			return d, err
+		}
 	}
 }
 
@@ -32,12 +68,24 @@ func (d *booleanDictionary) Index(i int32) Value { return makeValueBoolean(d.val
 func (d *booleanDictionary) Insert(indexes []int32, values []Value) {
 	_ = indexes[:len(values)]
 
+	if d.index == nil {
+		d.index = make(map[bool]int32, cap(d.values))
+		for i, v := range d.values {
+			d.index[v] = int32(i)
+		}
+	}
+
 	for i, v := range values {
-		if v.Boolean() {
-			indexes[i] = 1
-		} else {
-			indexes[i] = 0
+		value := v.Boolean()
+
+		index, exists := d.index[value]
+		if !exists {
+			index = int32(len(d.values))
+			d.values = append(d.values, value)
+			d.index[value] = index
 		}
+
+		indexes[i] = index
 	}
 }
 
@@ -68,40 +116,60 @@ func (d *booleanDictionary) Bounds(indexes []int32) (min, max Value) {
 	return min, max
 }
 
-func (d *booleanDictionary) ReadFrom(decoder encoding.Decoder) error {
-	_, err := decoder.DecodeBoolean(d.values[:])
-	d.Reset()
-	if err != nil {
-		if err == io.EOF {
-			err = nil
-		} else {
-			err = fmt.Errorf("reading parquet dictionary of boolean values: %w", err)
-		}
-	}
-	return err
-}
-
-func (d *booleanDictionary) WriteTo(encoder encoding.Encoder) error {
-	if err := encoder.EncodeBoolean(d.values[:]); err != nil {
-		return fmt.Errorf("writing parquet dictionary of %d boolean values: %w", d.Len(), err)
-	}
-	return nil
+func (d *booleanDictionary) Reset() {
+	d.values = d.values[:0]
+	d.index = nil
 }
 
-func (d *booleanDictionary) Reset() {
-	d.values = [2]bool{false, true}
+func (d *booleanDictionary) Page() BufferedPage {
+	return &d.booleanPage
 }
 
 type int32Dictionary struct {
-	typ    Type
-	values []int32
-	index  map[int32]int32
+	int32Page
+	typ   Type
+	index map[int32]int32
 }
 
-func newInt32Dictionary(typ Type, bufferSize int) *int32Dictionary {
+func newInt32Dictionary(typ Type, columnIndex int16, bufferSize int) *int32Dictionary {
 	return &int32Dictionary{
-		typ:    typ,
-		values: make([]int32, 0, dictCap(bufferSize, 4)),
+		typ: typ,
+		int32Page: int32Page{
+			values:      make([]int32, 0, dictCap(bufferSize, 4)),
+			columnIndex: columnIndex,
+		},
+	}
+}
+
+func readInt32Dictionary(typ Type, columnIndex int16, numValues int, decoder encoding.Decoder) (*int32Dictionary, error) {
+	d := &int32Dictionary{
+		typ: typ,
+		int32Page: int32Page{
+			values:      make([]int32, 0, atLeastOne(numValues)),
+			columnIndex: columnIndex,
+		},
+	}
+
+	for {
+		if len(d.values) == cap(d.values) {
+			newValues := make([]int32, len(d.values), 2*cap(d.values))
+			copy(newValues, d.values)
+			d.values = newValues
+		}
+
+		n, err := decoder.DecodeInt32(d.values[len(d.values):cap(d.values)])
+		if n > 0 {
+			d.values = d.values[:len(d.values)+n]
+		}
+
+		if err != nil {
+			if err == io.EOF {
+				err = nil
+			} else {
+				err = fmt.Errorf("reading parquet dictionary of int32 values: %w", err)
+			}
+			return d, err
+		}
 	}
 }
 
@@ -162,16 +230,48 @@ func (d *int32Dictionary) Bounds(indexes []int32) (min, max Value) {
 	return min, max
 }
 
-func (d *int32Dictionary) ReadFrom(decoder encoding.Decoder) error {
-	d.Reset()
+func (d *int32Dictionary) Reset() {
+	d.values = d.values[:0]
+	d.index = nil
+}
+
+func (d *int32Dictionary) Page() BufferedPage {
+	return &d.int32Page
+}
+
+type int64Dictionary struct {
+	int64Page
+	typ   Type
+	index map[int64]int32
+}
+
+func newInt64Dictionary(typ Type, columnIndex int16, bufferSize int) *int64Dictionary {
+	return &int64Dictionary{
+		typ: typ,
+		int64Page: int64Page{
+			values:      make([]int64, 0, dictCap(bufferSize, 8)),
+			columnIndex: columnIndex,
+		},
+	}
+}
+
+func readInt64Dictionary(typ Type, columnIndex int16, numValues int, decoder encoding.Decoder) (*int64Dictionary, error) {
+	d := &int64Dictionary{
+		typ: typ,
+		int64Page: int64Page{
+			values:      make([]int64, 0, atLeastOne(numValues)),
+			columnIndex: columnIndex,
+		},
+	}
+
 	for {
 		if len(d.values) == cap(d.values) {
-			newValues := make([]int32, len(d.values), 2*cap(d.values))
+			newValues := make([]int64, len(d.values), 2*cap(d.values))
 			copy(newValues, d.values)
 			d.values = newValues
 		}
 
-		n, err := decoder.DecodeInt32(d.values[len(d.values):cap(d.values)])
+		n, err := decoder.DecodeInt64(d.values[len(d.values):cap(d.values)])
 		if n > 0 {
 			d.values = d.values[:len(d.values)+n]
 		}
@@ -180,38 +280,13 @@ func (d *int32Dictionary) ReadFrom(decoder encoding.Decoder) error {
 			if err == io.EOF {
 				err = nil
 			} else {
-				err = fmt.Errorf("reading parquet dictionary of int32 values: %w", err)
+				err = fmt.Errorf("reading parquet dictionary of int64 values: %w", err)
 			}
-			return err
+			return d, err
 		}
 	}
 }
 
-func (d *int32Dictionary) WriteTo(encoder encoding.Encoder) error {
-	if err := encoder.EncodeInt32(d.values); err != nil {
-		return fmt.Errorf("writing parquet dictionary of %d int32 values: %w", d.Len(), err)
-	}
-	return nil
-}
-
-func (d *int32Dictionary) Reset() {
-	d.values = d.values[:0]
-	d.index = nil
-}
-
-type int64Dictionary struct {
-	typ    Type
-	values []int64
-	index  map[int64]int32
-}
-
-func newInt64Dictionary(typ Type, bufferSize int) *int64Dictionary {
-	return &int64Dictionary{
-		typ:    typ,
-		values: make([]int64, 0, dictCap(bufferSize, 8)),
-	}
-}
-
 func (d *int64Dictionary) Type() Type { return newIndexedType(d.typ, d) }
 
 func (d *int64Dictionary) Len() int { return len(d.values) }
@@ -269,16 +344,48 @@ func (d *int64Dictionary) Bounds(indexes []int32) (min, max Value) {
 	return min, max
 }
 
-func (d *int64Dictionary) ReadFrom(decoder encoding.Decoder) error {
-	d.Reset()
+func (d *int64Dictionary) Reset() {
+	d.values = d.values[:0]
+	d.index = nil
+}
+
+func (d *int64Dictionary) Page() BufferedPage {
+	return &d.int64Page
+}
+
+type int96Dictionary struct {
+	int96Page
+	typ   Type
+	index map[deprecated.Int96]int32
+}
+
+func newInt96Dictionary(typ Type, columnIndex int16, bufferSize int) *int96Dictionary {
+	return &int96Dictionary{
+		typ: typ,
+		int96Page: int96Page{
+			values:      make([]deprecated.Int96, 0, dictCap(bufferSize, 12)),
+			columnIndex: columnIndex,
+		},
+	}
+}
+
+func readInt96Dictionary(typ Type, columnIndex int16, numValues int, decoder encoding.Decoder) (*int96Dictionary, error) {
+	d := &int96Dictionary{
+		typ: typ,
+		int96Page: int96Page{
+			values:      make([]deprecated.Int96, 0, atLeastOne(numValues)),
+			columnIndex: columnIndex,
+		},
+	}
+
 	for {
 		if len(d.values) == cap(d.values) {
-			newValues := make([]int64, len(d.values), 2*cap(d.values))
+			newValues := make([]deprecated.Int96, len(d.values), 2*cap(d.values))
 			copy(newValues, d.values)
 			d.values = newValues
 		}
 
-		n, err := decoder.DecodeInt64(d.values[len(d.values):cap(d.values)])
+		n, err := decoder.DecodeInt96(d.values[len(d.values):cap(d.values)])
 		if n > 0 {
 			d.values = d.values[:len(d.values)+n]
 		}
@@ -287,38 +394,13 @@ func (d *int64Dictionary) ReadFrom(decoder encoding.Decoder) error {
 			if err == io.EOF {
 				err = nil
 			} else {
-				err = fmt.Errorf("reading parquet dictionary of int64 values: %w", err)
+				err = fmt.Errorf("reading parquet dictionary of int96 values: %w", err)
 			}
-			return err
+			return d, err
 		}
 	}
 }
 
-func (d *int64Dictionary) WriteTo(encoder encoding.Encoder) error {
-	if err := encoder.EncodeInt64(d.values); err != nil {
-		return fmt.Errorf("writing parquet dictionary of %d int64 values: %w", d.Len(), err)
-	}
-	return nil
-}
-
-func (d *int64Dictionary) Reset() {
-	d.values = d.values[:0]
-	d.index = nil
-}
-
-type int96Dictionary struct {
-	typ    Type
-	values []deprecated.Int96
-	index  map[deprecated.Int96]int32
-}
-
-func newInt96Dictionary(typ Type, bufferSize int) *int96Dictionary {
-	return &int96Dictionary{
-		typ:    typ,
-		values: make([]deprecated.Int96, 0, dictCap(bufferSize, 12)),
-	}
-}
-
 func (d *int96Dictionary) Type() Type { return newIndexedType(d.typ, d) }
 
 func (d *int96Dictionary) Len() int { return len(d.values) }
@@ -376,16 +458,48 @@ func (d *int96Dictionary) Bounds(indexes []int32) (min, max Value) {
 	return min, max
 }
 
-func (d *int96Dictionary) ReadFrom(decoder encoding.Decoder) error {
-	d.Reset()
+func (d *int96Dictionary) Reset() {
+	d.values = d.values[:0]
+	d.index = nil
+}
+
+func (d *int96Dictionary) Page() BufferedPage {
+	return &d.int96Page
+}
+
+type floatDictionary struct {
+	floatPage
+	typ   Type
+	index map[float32]int32
+}
+
+func newFloatDictionary(typ Type, columnIndex int16, bufferSize int) *floatDictionary {
+	return &floatDictionary{
+		typ: typ,
+		floatPage: floatPage{
+			values:      make([]float32, 0, dictCap(bufferSize, 4)),
+			columnIndex: columnIndex,
+		},
+	}
+}
+
+func readFloatDictionary(typ Type, columnIndex int16, numValues int, decoder encoding.Decoder) (*floatDictionary, error) {
+	d := &floatDictionary{
+		typ: typ,
+		floatPage: floatPage{
+			values:      make([]float32, 0, atLeastOne(numValues)),
+			columnIndex: columnIndex,
+		},
+	}
+
 	for {
 		if len(d.values) == cap(d.values) {
-			newValues := make([]deprecated.Int96, len(d.values), 2*cap(d.values))
+			newValues := make([]float32, len(d.values), 2*cap(d.values))
 			copy(newValues, d.values)
 			d.values = newValues
 		}
 
-		n, err := decoder.DecodeInt96(d.values[len(d.values):cap(d.values)])
+		n, err := decoder.DecodeFloat(d.values[len(d.values):cap(d.values)])
 		if n > 0 {
 			d.values = d.values[:len(d.values)+n]
 		}
@@ -394,38 +508,13 @@ func (d *int96Dictionary) ReadFrom(decoder encoding.Decoder) error {
 			if err == io.EOF {
 				err = nil
 			} else {
-				err = fmt.Errorf("reading parquet dictionary of int96 values: %w", err)
+				err = fmt.Errorf("reading parquet dictionary of float values: %w", err)
 			}
-			return err
+			return d, err
 		}
 	}
 }
 
-func (d *int96Dictionary) WriteTo(encoder encoding.Encoder) error {
-	if err := encoder.EncodeInt96(d.values); err != nil {
-		return fmt.Errorf("writing parquet dictionary of %d int96 values: %w", d.Len(), err)
-	}
-	return nil
-}
-
-func (d *int96Dictionary) Reset() {
-	d.values = d.values[:0]
-	d.index = nil
-}
-
-type floatDictionary struct {
-	typ    Type
-	values []float32
-	index  map[float32]int32
-}
-
-func newFloatDictionary(typ Type, bufferSize int) *floatDictionary {
-	return &floatDictionary{
-		typ:    typ,
-		values: make([]float32, 0, dictCap(bufferSize, 4)),
-	}
-}
-
 func (d *floatDictionary) Type() Type { return newIndexedType(d.typ, d) }
 
 func (d *floatDictionary) Len() int { return len(d.values) }
@@ -483,16 +572,48 @@ func (d *floatDictionary) Bounds(indexes []int32) (min, max Value) {
 	return min, max
 }
 
-func (d *floatDictionary) ReadFrom(decoder encoding.Decoder) error {
-	d.Reset()
+func (d *floatDictionary) Reset() {
+	d.values = d.values[:0]
+	d.index = nil
+}
+
+func (d *floatDictionary) Page() BufferedPage {
+	return &d.floatPage
+}
+
+type doubleDictionary struct {
+	doublePage
+	typ   Type
+	index map[float64]int32
+}
+
+func newDoubleDictionary(typ Type, columnIndex int16, bufferSize int) *doubleDictionary {
+	return &doubleDictionary{
+		typ: typ,
+		doublePage: doublePage{
+			values:      make([]float64, 0, dictCap(bufferSize, 8)),
+			columnIndex: columnIndex,
+		},
+	}
+}
+
+func readDoubleDictionary(typ Type, columnIndex int16, numValues int, decoder encoding.Decoder) (*doubleDictionary, error) {
+	d := &doubleDictionary{
+		typ: typ,
+		doublePage: doublePage{
+			values:      make([]float64, 0, atLeastOne(numValues)),
+			columnIndex: columnIndex,
+		},
+	}
+
 	for {
 		if len(d.values) == cap(d.values) {
-			newValues := make([]float32, len(d.values), 2*cap(d.values))
+			newValues := make([]float64, len(d.values), 2*cap(d.values))
 			copy(newValues, d.values)
 			d.values = newValues
 		}
 
-		n, err := decoder.DecodeFloat(d.values[len(d.values):cap(d.values)])
+		n, err := decoder.DecodeDouble(d.values[len(d.values):cap(d.values)])
 		if n > 0 {
 			d.values = d.values[:len(d.values)+n]
 		}
@@ -501,38 +622,13 @@ func (d *floatDictionary) ReadFrom(decoder encoding.Decoder) error {
 			if err == io.EOF {
 				err = nil
 			} else {
-				err = fmt.Errorf("reading parquet dictionary of float values: %w", err)
+				err = fmt.Errorf("reading parquet dictionary of double values: %w", err)
 			}
-			return err
+			return d, err
 		}
 	}
 }
 
-func (d *floatDictionary) WriteTo(encoder encoding.Encoder) error {
-	if err := encoder.EncodeFloat(d.values); err != nil {
-		return fmt.Errorf("writing parquet dictionary of %d float values: %w", d.Len(), err)
-	}
-	return nil
-}
-
-func (d *floatDictionary) Reset() {
-	d.values = d.values[:0]
-	d.index = nil
-}
-
-type doubleDictionary struct {
-	typ    Type
-	values []float64
-	index  map[float64]int32
-}
-
-func newDoubleDictionary(typ Type, bufferSize int) *doubleDictionary {
-	return &doubleDictionary{
-		typ:    typ,
-		values: make([]float64, 0, dictCap(bufferSize, 8)),
-	}
-}
-
 func (d *doubleDictionary) Type() Type { return newIndexedType(d.typ, d) }
 
 func (d *doubleDictionary) Len() int { return len(d.values) }
@@ -590,47 +686,24 @@ func (d *doubleDictionary) Bounds(indexes []int32) (min, max Value) {
 	return min, max
 }
 
-func (d *doubleDictionary) ReadFrom(decoder encoding.Decoder) error {
-	d.Reset()
-	for {
-		if len(d.values) == cap(d.values) {
-			newValues := make([]float64, len(d.values), 2*cap(d.values))
-			copy(newValues, d.values)
-			d.values = newValues
-		}
-
-		n, err := decoder.DecodeDouble(d.values[len(d.values):cap(d.values)])
-		if n > 0 {
-			d.values = d.values[:len(d.values)+n]
-		}
-
-		if err != nil {
-			if err == io.EOF {
-				err = nil
-			} else {
-				err = fmt.Errorf("reading parquet dictionary of double values: %w", err)
-			}
-			return err
-		}
-	}
-}
-
-func (d *doubleDictionary) WriteTo(encoder encoding.Encoder) error {
-	if err := encoder.EncodeDouble(d.values); err != nil {
-		return fmt.Errorf("writing parquet dictionary of %d double values: %w", d.Len(), err)
-	}
-	return nil
-}
-
 func (d *doubleDictionary) Reset() {
 	d.values = d.values[:0]
 	d.index = nil
 }
 
+func (d *doubleDictionary) Page() BufferedPage {
+	return &d.doublePage
+}
+
 type uint32Dictionary struct{ *int32Dictionary }
 
-func newUint32Dictionary(typ Type, bufferSize int) uint32Dictionary {
-	return uint32Dictionary{newInt32Dictionary(typ, bufferSize)}
+func newUint32Dictionary(typ Type, columnIndex int16, bufferSize int) uint32Dictionary {
+	return uint32Dictionary{newInt32Dictionary(typ, columnIndex, bufferSize)}
+}
+
+func readUint32Dictionary(typ Type, columnIndex int16, numValues int, decoder encoding.Decoder) (uint32Dictionary, error) {
+	d, err := readInt32Dictionary(typ, columnIndex, numValues, decoder)
+	return uint32Dictionary{d}, err
 }
 
 func (d uint32Dictionary) Type() Type { return newIndexedType(d.typ, d) }
@@ -656,10 +729,19 @@ func (d uint32Dictionary) Bounds(indexes []int32) (min, max Value) {
 	return min, max
 }
 
+func (d uint32Dictionary) Page() BufferedPage {
+	return uint32Page{&d.int32Page}
+}
+
 type uint64Dictionary struct{ *int64Dictionary }
 
-func newUint64Dictionary(typ Type, bufferSize int) uint64Dictionary {
-	return uint64Dictionary{newInt64Dictionary(typ, bufferSize)}
+func newUint64Dictionary(typ Type, columnIndex int16, bufferSize int) uint64Dictionary {
+	return uint64Dictionary{newInt64Dictionary(typ, columnIndex, bufferSize)}
+}
+
+func readUint64Dictionary(typ Type, columnIndex int16, numValues int, decoder encoding.Decoder) (uint64Dictionary, error) {
+	d, err := readInt64Dictionary(typ, columnIndex, numValues, decoder)
+	return uint64Dictionary{d}, err
 }
 
 func (d uint64Dictionary) Type() Type { return newIndexedType(d.typ, d) }
@@ -684,3 +766,7 @@ func (d uint64Dictionary) Bounds(indexes []int32) (min, max Value) {
 	}
 	return min, max
 }
+
+func (d uint64Dictionary) Page() BufferedPage {
+	return uint64Page{&d.int64Page}
+}
diff --git a/dictionary_go18.go b/dictionary_go18.go
index c663c08..6f68290 100644
--- a/dictionary_go18.go
+++ b/dictionary_go18.go
@@ -10,17 +10,52 @@ import (
 )
 
 type dictionary[T primitive] struct {
-	class  *class[T]
-	typ    Type
-	values []T
-	index  map[T]int32
+	page[T]
+	typ   Type
+	index map[T]int32
 }
 
-func newDictionary[T primitive](typ Type, bufferSize int, class *class[T]) *dictionary[T] {
+func newDictionary[T primitive](typ Type, columnIndex int16, bufferSize int, class *class[T]) *dictionary[T] {
 	return &dictionary[T]{
-		class:  class,
-		typ:    typ,
-		values: make([]T, 0, dictCap(bufferSize, sizeof[T]())),
+		typ: typ,
+		page: page[T]{
+			class:       class,
+			values:      make([]T, 0, dictCap(bufferSize, sizeof[T]())),
+			columnIndex: columnIndex,
+		},
+	}
+}
+
+func readDictionary[T primitive](typ Type, columnIndex int16, numValues int, decoder encoding.Decoder, class *class[T]) (*dictionary[T], error) {
+	d := &dictionary[T]{
+		typ: typ,
+		page: page[T]{
+			class:       class,
+			values:      make([]T, 0, atLeastOne(numValues)),
+			columnIndex: columnIndex,
+		},
+	}
+
+	for {
+		if len(d.values) == cap(d.values) {
+			newValues := make([]T, len(d.values), 2*cap(d.values))
+			copy(newValues, d.values)
+			d.values = newValues
+		}
+
+		n, err := d.class.decode(decoder, d.values[len(d.values):cap(d.values)])
+		if n > 0 {
+			d.values = d.values[:len(d.values)+n]
+		}
+
+		if err != nil {
+			if err == io.EOF {
+				err = nil
+			} else {
+				err = fmt.Errorf("reading parquet dictionary of %s values: %w", d.class.name, err)
+			}
+			return d, err
+		}
 	}
 }
 
@@ -85,39 +120,11 @@ func (d *dictionary[T]) Bounds(indexes []int32) (min, max Value) {
 	return min, max
 }
 
-func (d *dictionary[T]) ReadFrom(decoder encoding.Decoder) error {
-	d.Reset()
-	for {
-		if len(d.values) == cap(d.values) {
-			newValues := make([]T, len(d.values), 2*cap(d.values))
-			copy(newValues, d.values)
-			d.values = newValues
-		}
-
-		n, err := d.class.decode(decoder, d.values[len(d.values):cap(d.values)])
-		if n > 0 {
-			d.values = d.values[:len(d.values)+n]
-		}
-
-		if err != nil {
-			if err == io.EOF {
-				err = nil
-			} else {
-				err = fmt.Errorf("reading parquet dictionary of %s values: %w", d.class.name, err)
-			}
-			return err
-		}
-	}
-}
-
-func (d *dictionary[T]) WriteTo(encoder encoding.Encoder) error {
-	if err := d.class.encode(encoder, d.values); err != nil {
-		return fmt.Errorf("writing parquet dictionary of %d %s values: %w", d.Len(), d.class.name, err)
-	}
-	return nil
-}
-
 func (d *dictionary[T]) Reset() {
 	d.values = d.values[:0]
 	d.index = nil
 }
+
+func (d *dictionary[T]) Page() BufferedPage {
+	return &d.page
+}
diff --git a/file.go b/file.go
index 7035911..7a5cbce 100644
--- a/file.go
+++ b/file.go
@@ -17,6 +17,7 @@ import (
 )
 
 const (
+	defaultDictBufferSize  = 8192
 	defaultReadBufferSize  = 4096
 	defaultLevelBufferSize = 1024
 )
@@ -541,8 +542,6 @@ func (r *filePages) readPage() (*filePage, error) {
 }
 
 func (r *filePages) readDictionary() error {
-	r.page.dictionary = r.page.columnType.NewDictionary(0) // TODO: configure buffer size
-	r.page.columnType = r.page.dictionary.Type()
 	currentOffset, _ := r.section.Seek(0, io.SeekCurrent)
 	defer func() {
 		r.section.Seek(currentOffset, io.SeekStart)
@@ -559,19 +558,20 @@ func (r *filePages) readDictionary() error {
 		return err
 	}
 
-	dict := r.page.dictionary
 	page := acquireCompressedPageReader(p.codec, &p.data)
-
 	enc := r.page.header.DictionaryPageHeader.Encoding
 	dec := LookupEncoding(enc).NewDecoder(page)
 
-	dict.Reset()
-	err = dict.ReadFrom(dec)
+	columnIndex := r.column.Column()
+	numValues := int(p.NumValues())
+	dict, err := r.page.columnType.ReadDictionary(columnIndex, numValues, dec)
 	releaseCompressedPageReader(page)
 
 	if err != nil {
 		return fmt.Errorf("reading dictionary of column %q: %w", p.columnPath(), err)
 	}
+	r.page.dictionary = dict
+	r.page.columnType = dict.Type()
 	return nil
 }
 
diff --git a/page_go18.go b/page_go18.go
index b398994..4c9dbd5 100644
--- a/page_go18.go
+++ b/page_go18.go
@@ -74,7 +74,8 @@ type pageReader[T primitive] struct {
 }
 
 func (r *pageReader[T]) Read(b []byte) (n int, err error) {
-	return r.ReadRequired(cast.BytesToSlice[T](b))
+	n, err = r.ReadRequired(cast.BytesToSlice[T](b))
+	return sizeof[T]() * n, err
 }
 
 func (r *pageReader[T]) ReadRequired(values []T) (n int, err error) {
diff --git a/type.go b/type.go
index 9757aa5..3f80fb5 100644
--- a/type.go
+++ b/type.go
@@ -8,6 +8,7 @@ import (
 
 	"github.com/google/uuid"
 	"github.com/segmentio/parquet-go/deprecated"
+	"github.com/segmentio/parquet-go/encoding"
 	"github.com/segmentio/parquet-go/format"
 	"github.com/segmentio/parquet-go/internal/bits"
 )
@@ -119,7 +120,7 @@ type Type interface {
 	// Creates a dictionary holding values of this type.
 	//
 	// The method panics if it is called on a group type.
-	NewDictionary(bufferSize int) Dictionary
+	NewDictionary(columnIndex, bufferSize int) Dictionary
 
 	// Creates a row group buffer column for values of this type.
 	//
@@ -155,6 +156,14 @@ type Type interface {
 	// parquet.Int32Reader interface to allow programs to more efficiently read
 	// columns of INT32 values.
 	NewColumnReader(columnIndex, bufferSize int) ColumnReader
+
+	// Reads a dictionary with values of this type from the decoder passed as
+	// argument.
+	//
+	// The number of values is a hint to optimize the allocation of memory
+	// buffers for the dictionary. Callers that don't know how many values will
+	// be decoded should pass zero for numValues.
+	ReadDictionary(columnIndex, numValues int, decoder encoding.Decoder) (Dictionary, error)
 }
 
 // In the current parquet version supported by this library, only type-defined
@@ -375,8 +384,8 @@ func (t *stringType) NewColumnIndexer(sizeLimit int) ColumnIndexer {
 	return newByteArrayColumnIndexer(sizeLimit)
 }
 
-func (t *stringType) NewDictionary(bufferSize int) Dictionary {
-	return newByteArrayDictionary(t, bufferSize)
+func (t *stringType) NewDictionary(columnIndex, bufferSize int) Dictionary {
+	return newByteArrayDictionary(t, makeColumnIndex(columnIndex), bufferSize)
 }
 
 func (t *stringType) NewColumnBuffer(columnIndex, bufferSize int) ColumnBuffer {
@@ -387,6 +396,10 @@ func (t *stringType) NewColumnReader(columnIndex, bufferSize int) ColumnReader {
 	return newByteArrayColumnReader(t, makeColumnIndex(columnIndex), bufferSize)
 }
 
+func (t *stringType) ReadDictionary(columnIndex, numValues int, decoder encoding.Decoder) (Dictionary, error) {
+	return readByteArrayDictionary(t, makeColumnIndex(columnIndex), numValues, decoder)
+}
+
 func (t *stringType) GoType() reflect.Type {
 	return reflect.TypeOf("")
 }
@@ -426,8 +439,8 @@ func (t *uuidType) NewColumnIndexer(sizeLimit int) ColumnIndexer {
 	return newFixedLenByteArrayColumnIndexer(16, sizeLimit)
 }
 
-func (t *uuidType) NewDictionary(bufferSize int) Dictionary {
-	return newFixedLenByteArrayDictionary(t, bufferSize)
+func (t *uuidType) NewDictionary(columnIndex, bufferSize int) Dictionary {
+	return newFixedLenByteArrayDictionary(t, makeColumnIndex(columnIndex), bufferSize)
 }
 
 func (t *uuidType) NewColumnBuffer(columnIndex, bufferSize int) ColumnBuffer {
@@ -438,6 +451,10 @@ func (t *uuidType) NewColumnReader(columnIndex, bufferSize int) ColumnReader {
 	return newFixedLenByteArrayColumnReader(t, makeColumnIndex(columnIndex), bufferSize)
 }
 
+func (t *uuidType) ReadDictionary(columnIndex, numValues int, decoder encoding.Decoder) (Dictionary, error) {
+	return readFixedLenByteArrayDictionary(t, makeColumnIndex(columnIndex), numValues, decoder)
+}
+
 func (t *uuidType) GoType() reflect.Type {
 	return reflect.TypeOf(uuid.UUID{})
 }
@@ -479,8 +496,8 @@ func (t *enumType) NewColumnIndexer(sizeLimit int) ColumnIndexer {
 	return newByteArrayColumnIndexer(sizeLimit)
 }
 
-func (t *enumType) NewDictionary(bufferSize int) Dictionary {
-	return newByteArrayDictionary(t, bufferSize)
+func (t *enumType) NewDictionary(columnIndex, bufferSize int) Dictionary {
+	return newByteArrayDictionary(t, makeColumnIndex(columnIndex), bufferSize)
 }
 
 func (t *enumType) NewColumnBuffer(columnIndex, bufferSize int) ColumnBuffer {
@@ -491,6 +508,10 @@ func (t *enumType) NewColumnReader(columnIndex, bufferSize int) ColumnReader {
 	return newByteArrayColumnReader(t, makeColumnIndex(columnIndex), bufferSize)
 }
 
+func (t *enumType) ReadDictionary(columnIndex, numValues int, decoder encoding.Decoder) (Dictionary, error) {
+	return readByteArrayDictionary(t, makeColumnIndex(columnIndex), numValues, decoder)
+}
+
 func (t *enumType) GoType() reflect.Type {
 	return reflect.TypeOf("")
 }
@@ -532,8 +553,8 @@ func (t *jsonType) NewColumnIndexer(sizeLimit int) ColumnIndexer {
 	return newByteArrayColumnIndexer(sizeLimit)
 }
 
-func (t *jsonType) NewDictionary(bufferSize int) Dictionary {
-	return newByteArrayDictionary(t, bufferSize)
+func (t *jsonType) NewDictionary(columnIndex, bufferSize int) Dictionary {
+	return newByteArrayDictionary(t, makeColumnIndex(columnIndex), bufferSize)
 }
 
 func (t *jsonType) NewColumnBuffer(columnIndex, bufferSize int) ColumnBuffer {
@@ -544,6 +565,10 @@ func (t *jsonType) NewColumnReader(columnIndex, bufferSize int) ColumnReader {
 	return newByteArrayColumnReader(t, makeColumnIndex(columnIndex), bufferSize)
 }
 
+func (t *jsonType) ReadDictionary(columnIndex, numValues int, decoder encoding.Decoder) (Dictionary, error) {
+	return readByteArrayDictionary(t, makeColumnIndex(columnIndex), numValues, decoder)
+}
+
 // BSON constructs a leaf node of BSON logical type.
 //
 // https://github.com/apache/parquet-format/blob/master/LogicalTypes.md#bson
@@ -581,8 +606,8 @@ func (t *bsonType) NewColumnIndexer(sizeLimit int) ColumnIndexer {
 	return newByteArrayColumnIndexer(sizeLimit)
 }
 
-func (t *bsonType) NewDictionary(bufferSize int) Dictionary {
-	return newByteArrayDictionary(t, bufferSize)
+func (t *bsonType) NewDictionary(columnIndex, bufferSize int) Dictionary {
+	return newByteArrayDictionary(t, makeColumnIndex(columnIndex), bufferSize)
 }
 
 func (t *bsonType) NewColumnBuffer(columnIndex, bufferSize int) ColumnBuffer {
@@ -593,6 +618,10 @@ func (t *bsonType) NewColumnReader(columnIndex, bufferSize int) ColumnReader {
 	return newByteArrayColumnReader(t, makeColumnIndex(columnIndex), bufferSize)
 }
 
+func (t *bsonType) ReadDictionary(columnIndex, numValues int, decoder encoding.Decoder) (Dictionary, error) {
+	return readByteArrayDictionary(t, makeColumnIndex(columnIndex), numValues, decoder)
+}
+
 // Date constructs a leaf node of DATE logical type.
 //
 // https://github.com/apache/parquet-format/blob/master/LogicalTypes.md#date
@@ -667,12 +696,20 @@ func Time(unit TimeUnit) Node {
 
 type timeType format.TimeType
 
+func (t *timeType) useInt32() bool {
+	return t.Unit.Millis != nil
+}
+
+func (t *timeType) useInt64() bool {
+	return t.Unit.Micros != nil
+}
+
 func (t *timeType) String() string {
 	return (*format.TimeType)(t).String()
 }
 
 func (t *timeType) Kind() Kind {
-	if t.Unit.Millis != nil {
+	if t.useInt32() {
 		return Int32
 	} else {
 		return Int64
@@ -680,7 +717,7 @@ func (t *timeType) Kind() Kind {
 }
 
 func (t *timeType) Length() int {
-	if t.Unit.Millis != nil {
+	if t.useInt32() {
 		return 32
 	} else {
 		return 64
@@ -688,7 +725,7 @@ func (t *timeType) Length() int {
 }
 
 func (t *timeType) Compare(a, b Value) int {
-	if t.Unit.Millis != nil {
+	if t.useInt32() {
 		return compareInt32(a.Int32(), b.Int32())
 	} else {
 		return compareInt64(a.Int64(), b.Int64())
@@ -700,7 +737,7 @@ func (t *timeType) ColumnOrder() *format.ColumnOrder {
 }
 
 func (t *timeType) PhysicalType() *format.Type {
-	if t.Unit.Millis != nil {
+	if t.useInt32() {
 		return &physicalTypes[Int32]
 	} else {
 		return &physicalTypes[Int64]
@@ -713,9 +750,9 @@ func (t *timeType) LogicalType() *format.LogicalType {
 
 func (t *timeType) ConvertedType() *deprecated.ConvertedType {
 	switch {
-	case t.Unit.Millis != nil:
+	case t.useInt32():
 		return &convertedTypes[deprecated.TimeMillis]
-	case t.Unit.Micros != nil:
+	case t.useInt64():
 		return &convertedTypes[deprecated.TimeMicros]
 	default:
 		return nil
@@ -791,20 +828,24 @@ func (t *listType) ConvertedType() *deprecated.ConvertedType {
 	return &convertedTypes[deprecated.List]
 }
 
-func (t *listType) NewColumnIndexer(sizeLimit int) ColumnIndexer {
-	panic("create create a column indexer from parquet LIST type")
+func (t *listType) NewColumnIndexer(int) ColumnIndexer {
+	panic("create create column indexer from parquet LIST type")
 }
 
-func (t *listType) NewDictionary(bufferSize int) Dictionary {
+func (t *listType) NewDictionary(int, int) Dictionary {
 	panic("cannot create dictionary from parquet LIST type")
 }
 
-func (t *listType) NewColumnBuffer(columnIndex, bufferSize int) ColumnBuffer {
-	panic("cannot create row group column from parquet LIST type")
+func (t *listType) NewColumnBuffer(int, int) ColumnBuffer {
+	panic("cannot create column buffer from parquet LIST type")
+}
+
+func (t *listType) NewColumnReader(int, int) ColumnReader {
+	panic("cannot create column reader from parquet LIST type")
 }
 
-func (t *listType) NewColumnReader(columnIndex, bufferSize int) ColumnReader {
-	panic("cannot create row group column from parquet LIST type")
+func (t *listType) ReadDictionary(int, int, encoding.Decoder) (Dictionary, error) {
+	panic("cannot read dictionary from parquet LIST type")
 }
 
 // Map constructs a node of MAP logical type.
@@ -845,20 +886,24 @@ func (t *mapType) ConvertedType() *deprecated.ConvertedType {
 	return &convertedTypes[deprecated.Map]
 }
 
-func (t *mapType) NewColumnIndexer(sizeLimit int) ColumnIndexer {
-	panic("create create a column indexer from parquet MAP type")
+func (t *mapType) NewColumnIndexer(int) ColumnIndexer {
+	panic("create create column indexer from parquet MAP type")
 }
 
-func (t *mapType) NewDictionary(bufferSize int) Dictionary {
+func (t *mapType) NewDictionary(int, int) Dictionary {
 	panic("cannot create dictionary from parquet MAP type")
 }
 
-func (t *mapType) NewColumnBuffer(columnIndex, bufferSize int) ColumnBuffer {
-	panic("cannot create row group column from parquet MAP type")
+func (t *mapType) NewColumnBuffer(int, int) ColumnBuffer {
+	panic("cannot create column buffer from parquet MAP type")
 }
 
-func (t *mapType) NewColumnReader(columnIndex, bufferSize int) ColumnReader {
-	panic("cannot create row group column from parquet MAP type")
+func (t *mapType) NewColumnReader(int, int) ColumnReader {
+	panic("cannot create column reader from parquet MAP type")
+}
+
+func (t *mapType) ReadDictionary(int, int, encoding.Decoder) (Dictionary, error) {
+	panic("cannot read dictionary from parquet MAP type")
 }
 
 type nullType format.NullType
@@ -882,19 +927,23 @@ func (t *nullType) LogicalType() *format.LogicalType {
 func (t *nullType) ConvertedType() *deprecated.ConvertedType { return nil }
 
 func (t *nullType) NewColumnIndexer(int) ColumnIndexer {
-	panic("create create a column indexer from parquet NULL type")
+	panic("create create column indexer from parquet NULL type")
 }
 
-func (t *nullType) NewDictionary(int) Dictionary {
+func (t *nullType) NewDictionary(int, int) Dictionary {
 	panic("cannot create dictionary from parquet NULL type")
 }
 
 func (t *nullType) NewColumnBuffer(int, int) ColumnBuffer {
-	panic("cannot create row group column from parquet NULL type")
+	panic("cannot create column buffer from parquet NULL type")
 }
 
 func (t *nullType) NewColumnReader(int, int) ColumnReader {
-	panic("cannot create row group column from parquet NULL type")
+	panic("cannot create column reader from parquet NULL type")
+}
+
+func (t *nullType) ReadDictionary(int, int, encoding.Decoder) (Dictionary, error) {
+	panic("cannot read dictionary from parquet NULL type")
 }
 
 type groupType struct{}
@@ -913,16 +962,20 @@ func (groupType) NewColumnIndexer(int) ColumnIndexer {
 	panic("cannot create column indexer from parquet group")
 }
 
-func (groupType) NewDictionary(int) Dictionary {
+func (groupType) NewDictionary(int, int) Dictionary {
 	panic("cannot create dictionary from parquet group")
 }
 
 func (t groupType) NewColumnBuffer(int, int) ColumnBuffer {
-	panic("cannot create row group column from parquet group")
+	panic("cannot create column buffer from parquet group")
 }
 
 func (t groupType) NewColumnReader(int, int) ColumnReader {
-	panic("cannot create row group column from parquet group")
+	panic("cannot create column reader from parquet group")
+}
+
+func (t groupType) ReadDictionary(int, int, encoding.Decoder) (Dictionary, error) {
+	panic("cannot read dictionary from parquet group")
 }
 
 func (groupType) Length() int { return 0 }
diff --git a/type_default.go b/type_default.go
index 9856e7b..0961cd1 100644
--- a/type_default.go
+++ b/type_default.go
@@ -7,6 +7,7 @@ import (
 	"fmt"
 
 	"github.com/segmentio/parquet-go/deprecated"
+	"github.com/segmentio/parquet-go/encoding"
 	"github.com/segmentio/parquet-go/format"
 )
 
@@ -48,8 +49,8 @@ func (t booleanType) NewColumnIndexer(sizeLimit int) ColumnIndexer {
 	return newBooleanColumnIndexer()
 }
 
-func (t booleanType) NewDictionary(bufferSize int) Dictionary {
-	return newBooleanDictionary(t)
+func (t booleanType) NewDictionary(columnIndex, bufferSize int) Dictionary {
+	return newBooleanDictionary(t, makeColumnIndex(columnIndex), bufferSize)
 }
 
 func (t booleanType) NewColumnBuffer(columnIndex, bufferSize int) ColumnBuffer {
@@ -60,6 +61,10 @@ func (t booleanType) NewColumnReader(columnIndex, bufferSize int) ColumnReader {
 	return newBooleanColumnReader(t, makeColumnIndex(columnIndex), bufferSize)
 }
 
+func (t booleanType) ReadDictionary(columnIndex, numValues int, decoder encoding.Decoder) (Dictionary, error) {
+	return readBooleanDictionary(t, makeColumnIndex(columnIndex), numValues, decoder)
+}
+
 type int32Type struct{ primitiveType }
 
 func (t int32Type) String() string { return "INT32" }
@@ -80,8 +85,8 @@ func (t int32Type) NewColumnIndexer(sizeLimit int) ColumnIndexer {
 	return newInt32ColumnIndexer()
 }
 
-func (t int32Type) NewDictionary(bufferSize int) Dictionary {
-	return newInt32Dictionary(t, bufferSize)
+func (t int32Type) NewDictionary(columnIndex, bufferSize int) Dictionary {
+	return newInt32Dictionary(t, makeColumnIndex(columnIndex), bufferSize)
 }
 
 func (t int32Type) NewColumnBuffer(columnIndex, bufferSize int) ColumnBuffer {
@@ -92,6 +97,10 @@ func (t int32Type) NewColumnReader(columnIndex, bufferSize int) ColumnReader {
 	return newInt32ColumnReader(t, makeColumnIndex(columnIndex), bufferSize)
 }
 
+func (t int32Type) ReadDictionary(columnIndex, numValues int, decoder encoding.Decoder) (Dictionary, error) {
+	return readInt32Dictionary(t, makeColumnIndex(columnIndex), numValues, decoder)
+}
+
 type int64Type struct{ primitiveType }
 
 func (t int64Type) String() string { return "INT64" }
@@ -112,8 +121,8 @@ func (t int64Type) NewColumnIndexer(sizeLimit int) ColumnIndexer {
 	return newInt64ColumnIndexer()
 }
 
-func (t int64Type) NewDictionary(bufferSize int) Dictionary {
-	return newInt64Dictionary(t, bufferSize)
+func (t int64Type) NewDictionary(columnIndex, bufferSize int) Dictionary {
+	return newInt64Dictionary(t, makeColumnIndex(columnIndex), bufferSize)
 }
 
 func (t int64Type) NewColumnBuffer(columnIndex, bufferSize int) ColumnBuffer {
@@ -124,6 +133,10 @@ func (t int64Type) NewColumnReader(columnIndex, bufferSize int) ColumnReader {
 	return newInt64ColumnReader(t, makeColumnIndex(columnIndex), bufferSize)
 }
 
+func (t int64Type) ReadDictionary(columnIndex, numValues int, decoder encoding.Decoder) (Dictionary, error) {
+	return readInt64Dictionary(t, makeColumnIndex(columnIndex), numValues, decoder)
+}
+
 type int96Type struct{ primitiveType }
 
 func (t int96Type) String() string { return "INT96" }
@@ -144,8 +157,8 @@ func (t int96Type) NewColumnIndexer(sizeLimit int) ColumnIndexer {
 	return newInt96ColumnIndexer()
 }
 
-func (t int96Type) NewDictionary(bufferSize int) Dictionary {
-	return newInt96Dictionary(t, bufferSize)
+func (t int96Type) NewDictionary(columnIndex, bufferSize int) Dictionary {
+	return newInt96Dictionary(t, makeColumnIndex(columnIndex), bufferSize)
 }
 
 func (t int96Type) NewColumnBuffer(columnIndex, bufferSize int) ColumnBuffer {
@@ -156,6 +169,10 @@ func (t int96Type) NewColumnReader(columnIndex, bufferSize int) ColumnReader {
 	return newInt96ColumnReader(t, makeColumnIndex(columnIndex), bufferSize)
 }
 
+func (t int96Type) ReadDictionary(columnIndex, numValues int, decoder encoding.Decoder) (Dictionary, error) {
+	return readInt96Dictionary(t, makeColumnIndex(columnIndex), numValues, decoder)
+}
+
 type floatType struct{ primitiveType }
 
 func (t floatType) String() string { return "FLOAT" }
@@ -176,8 +193,8 @@ func (t floatType) NewColumnIndexer(sizeLimit int) ColumnIndexer {
 	return newFloatColumnIndexer()
 }
 
-func (t floatType) NewDictionary(bufferSize int) Dictionary {
-	return newFloatDictionary(t, bufferSize)
+func (t floatType) NewDictionary(columnIndex, bufferSize int) Dictionary {
+	return newFloatDictionary(t, makeColumnIndex(columnIndex), bufferSize)
 }
 
 func (t floatType) NewColumnBuffer(columnIndex, bufferSize int) ColumnBuffer {
@@ -188,6 +205,10 @@ func (t floatType) NewColumnReader(columnIndex, bufferSize int) ColumnReader {
 	return newFloatColumnReader(t, makeColumnIndex(columnIndex), bufferSize)
 }
 
+func (t floatType) ReadDictionary(columnIndex, numValues int, decoder encoding.Decoder) (Dictionary, error) {
+	return readFloatDictionary(t, makeColumnIndex(columnIndex), numValues, decoder)
+}
+
 type doubleType struct{ primitiveType }
 
 func (t doubleType) String() string { return "DOUBLE" }
@@ -206,8 +227,8 @@ func (t doubleType) NewColumnIndexer(sizeLimit int) ColumnIndexer {
 	return newDoubleColumnIndexer()
 }
 
-func (t doubleType) NewDictionary(bufferSize int) Dictionary {
-	return newDoubleDictionary(t, bufferSize)
+func (t doubleType) NewDictionary(columnIndex, bufferSize int) Dictionary {
+	return newDoubleDictionary(t, makeColumnIndex(columnIndex), bufferSize)
 }
 
 func (t doubleType) NewColumnBuffer(columnIndex, bufferSize int) ColumnBuffer {
@@ -218,6 +239,10 @@ func (t doubleType) NewColumnReader(columnIndex, bufferSize int) ColumnReader {
 	return newDoubleColumnReader(t, makeColumnIndex(columnIndex), bufferSize)
 }
 
+func (t doubleType) ReadDictionary(columnIndex, numValues int, decoder encoding.Decoder) (Dictionary, error) {
+	return readDoubleDictionary(t, makeColumnIndex(columnIndex), numValues, decoder)
+}
+
 type byteArrayType struct{ primitiveType }
 
 func (t byteArrayType) String() string { return "BYTE_ARRAY" }
@@ -238,8 +263,8 @@ func (t byteArrayType) NewColumnIndexer(sizeLimit int) ColumnIndexer {
 	return newByteArrayColumnIndexer(sizeLimit)
 }
 
-func (t byteArrayType) NewDictionary(bufferSize int) Dictionary {
-	return newByteArrayDictionary(t, bufferSize)
+func (t byteArrayType) NewDictionary(columnIndex, bufferSize int) Dictionary {
+	return newByteArrayDictionary(t, makeColumnIndex(columnIndex), bufferSize)
 }
 
 func (t byteArrayType) NewColumnBuffer(columnIndex, bufferSize int) ColumnBuffer {
@@ -250,6 +275,10 @@ func (t byteArrayType) NewColumnReader(columnIndex, bufferSize int) ColumnReader
 	return newByteArrayColumnReader(t, makeColumnIndex(columnIndex), bufferSize)
 }
 
+func (t byteArrayType) ReadDictionary(columnIndex, numValues int, decoder encoding.Decoder) (Dictionary, error) {
+	return readByteArrayDictionary(t, makeColumnIndex(columnIndex), numValues, decoder)
+}
+
 type fixedLenByteArrayType struct {
 	primitiveType
 	length int
@@ -275,8 +304,8 @@ func (t *fixedLenByteArrayType) NewColumnIndexer(sizeLimit int) ColumnIndexer {
 	return newFixedLenByteArrayColumnIndexer(t.length, sizeLimit)
 }
 
-func (t *fixedLenByteArrayType) NewDictionary(bufferSize int) Dictionary {
-	return newFixedLenByteArrayDictionary(t, bufferSize)
+func (t *fixedLenByteArrayType) NewDictionary(columnIndex, bufferSize int) Dictionary {
+	return newFixedLenByteArrayDictionary(t, makeColumnIndex(columnIndex), bufferSize)
 }
 
 func (t *fixedLenByteArrayType) NewColumnBuffer(columnIndex, bufferSize int) ColumnBuffer {
@@ -287,6 +316,10 @@ func (t *fixedLenByteArrayType) NewColumnReader(columnIndex, bufferSize int) Col
 	return newFixedLenByteArrayColumnReader(t, makeColumnIndex(columnIndex), bufferSize)
 }
 
+func (t *fixedLenByteArrayType) ReadDictionary(columnIndex, numValues int, decoder encoding.Decoder) (Dictionary, error) {
+	return readFixedLenByteArrayDictionary(t, makeColumnIndex(columnIndex), numValues, decoder)
+}
+
 // FixedLenByteArrayType constructs a type for fixed-length values of the given
 // size (in bytes).
 func FixedLenByteArrayType(length int) Type {
@@ -309,18 +342,18 @@ func (t *intType) NewColumnIndexer(sizeLimit int) ColumnIndexer {
 	}
 }
 
-func (t *intType) NewDictionary(bufferSize int) Dictionary {
+func (t *intType) NewDictionary(columnIndex, bufferSize int) Dictionary {
 	if t.IsSigned {
 		if t.BitWidth == 64 {
-			return newInt64Dictionary(t, bufferSize)
+			return newInt64Dictionary(t, makeColumnIndex(columnIndex), bufferSize)
 		} else {
-			return newInt32Dictionary(t, bufferSize)
+			return newInt32Dictionary(t, makeColumnIndex(columnIndex), bufferSize)
 		}
 	} else {
 		if t.BitWidth == 64 {
-			return newUint64Dictionary(t, bufferSize)
+			return newUint64Dictionary(t, makeColumnIndex(columnIndex), bufferSize)
 		} else {
-			return newUint32Dictionary(t, bufferSize)
+			return newUint32Dictionary(t, makeColumnIndex(columnIndex), bufferSize)
 		}
 	}
 }
@@ -349,12 +382,28 @@ func (t *intType) NewColumnReader(columnIndex, bufferSize int) ColumnReader {
 	}
 }
 
+func (t *intType) ReadDictionary(columnIndex, numValues int, decoder encoding.Decoder) (Dictionary, error) {
+	if t.IsSigned {
+		if t.BitWidth == 64 {
+			return readInt64Dictionary(t, makeColumnIndex(columnIndex), numValues, decoder)
+		} else {
+			return readInt32Dictionary(t, makeColumnIndex(columnIndex), numValues, decoder)
+		}
+	} else {
+		if t.BitWidth == 64 {
+			return readUint64Dictionary(t, makeColumnIndex(columnIndex), numValues, decoder)
+		} else {
+			return readUint32Dictionary(t, makeColumnIndex(columnIndex), numValues, decoder)
+		}
+	}
+}
+
 func (t *dateType) NewColumnIndexer(sizeLimit int) ColumnIndexer {
 	return newInt32ColumnIndexer()
 }
 
-func (t *dateType) NewDictionary(bufferSize int) Dictionary {
-	return newInt32Dictionary(t, bufferSize)
+func (t *dateType) NewDictionary(columnIndex, bufferSize int) Dictionary {
+	return newInt32Dictionary(t, makeColumnIndex(columnIndex), bufferSize)
 }
 
 func (t *dateType) NewColumnBuffer(columnIndex, bufferSize int) ColumnBuffer {
@@ -365,6 +414,10 @@ func (t *dateType) NewColumnReader(columnIndex, bufferSize int) ColumnReader {
 	return newInt32ColumnReader(t, makeColumnIndex(columnIndex), bufferSize)
 }
 
+func (t *dateType) ReadDictionary(columnIndex, numValues int, decoder encoding.Decoder) (Dictionary, error) {
+	return readInt32Dictionary(t, makeColumnIndex(columnIndex), numValues, decoder)
+}
+
 func (t *timeType) NewColumnIndexer(sizeLimit int) ColumnIndexer {
 	if t.Unit.Millis != nil {
 		return newInt32ColumnIndexer()
@@ -373,11 +426,11 @@ func (t *timeType) NewColumnIndexer(sizeLimit int) ColumnIndexer {
 	}
 }
 
-func (t *timeType) NewDictionary(bufferSize int) Dictionary {
+func (t *timeType) NewDictionary(columnIndex, bufferSize int) Dictionary {
 	if t.Unit.Millis != nil {
-		return newInt32Dictionary(t, bufferSize)
+		return newInt32Dictionary(t, makeColumnIndex(columnIndex), bufferSize)
 	} else {
-		return newInt64Dictionary(t, bufferSize)
+		return newInt64Dictionary(t, makeColumnIndex(columnIndex), bufferSize)
 	}
 }
 
@@ -397,12 +450,20 @@ func (t *timeType) NewColumnReader(columnIndex, bufferSize int) ColumnReader {
 	}
 }
 
+func (t *timeType) ReadDictionary(columnIndex, numValues int, decoder encoding.Decoder) (Dictionary, error) {
+	if t.Unit.Millis != nil {
+		return readInt32Dictionary(t, makeColumnIndex(columnIndex), numValues, decoder)
+	} else {
+		return readInt64Dictionary(t, makeColumnIndex(columnIndex), numValues, decoder)
+	}
+}
+
 func (t *timestampType) NewColumnIndexer(sizeLimit int) ColumnIndexer {
 	return newInt64ColumnIndexer()
 }
 
-func (t *timestampType) NewDictionary(bufferSize int) Dictionary {
-	return newInt64Dictionary(t, bufferSize)
+func (t *timestampType) NewDictionary(columnIndex, bufferSize int) Dictionary {
+	return newInt64Dictionary(t, makeColumnIndex(columnIndex), bufferSize)
 }
 
 func (t *timestampType) NewColumnBuffer(columnIndex, bufferSize int) ColumnBuffer {
@@ -412,3 +473,7 @@ func (t *timestampType) NewColumnBuffer(columnIndex, bufferSize int) ColumnBuffe
 func (t *timestampType) NewColumnReader(columnIndex, bufferSize int) ColumnReader {
 	return newInt64ColumnReader(t, makeColumnIndex(columnIndex), bufferSize)
 }
+
+func (t *timestampType) ReadDictionary(columnIndex, numValues int, decoder encoding.Decoder) (Dictionary, error) {
+	return readInt64Dictionary(t, makeColumnIndex(columnIndex), numValues, decoder)
+}
diff --git a/type_go18.go b/type_go18.go
index 1a229b8..31890d1 100644
--- a/type_go18.go
+++ b/type_go18.go
@@ -7,6 +7,7 @@ import (
 	"fmt"
 
 	"github.com/segmentio/parquet-go/deprecated"
+	"github.com/segmentio/parquet-go/encoding"
 	"github.com/segmentio/parquet-go/format"
 )
 
@@ -44,8 +45,8 @@ func (t primitiveType[T]) NewColumnIndexer(sizeLimit int) ColumnIndexer {
 	return newColumnIndexer(t.class)
 }
 
-func (t primitiveType[T]) NewDictionary(bufferSize int) Dictionary {
-	return newDictionary(t, bufferSize, t.class)
+func (t primitiveType[T]) NewDictionary(columnIndex, bufferSize int) Dictionary {
+	return newDictionary(t, makeColumnIndex(columnIndex), bufferSize, t.class)
 }
 
 func (t primitiveType[T]) NewColumnBuffer(columnIndex, bufferSize int) ColumnBuffer {
@@ -56,6 +57,10 @@ func (t primitiveType[T]) NewColumnReader(columnIndex, bufferSize int) ColumnRea
 	return newColumnReader(t, makeColumnIndex(columnIndex), bufferSize, t.class)
 }
 
+func (t primitiveType[T]) ReadDictionary(columnIndex, numValues int, decoder encoding.Decoder) (Dictionary, error) {
+	return readDictionary(t, makeColumnIndex(columnIndex), numValues, decoder, t.class)
+}
+
 type byteArrayType struct{}
 
 func (t byteArrayType) String() string { return "BYTE_ARRAY" }
@@ -82,8 +87,8 @@ func (t byteArrayType) NewColumnIndexer(sizeLimit int) ColumnIndexer {
 	return newByteArrayColumnIndexer(sizeLimit)
 }
 
-func (t byteArrayType) NewDictionary(bufferSize int) Dictionary {
-	return newByteArrayDictionary(t, bufferSize)
+func (t byteArrayType) NewDictionary(columnIndex, bufferSize int) Dictionary {
+	return newByteArrayDictionary(t, makeColumnIndex(columnIndex), bufferSize)
 }
 
 func (t byteArrayType) NewColumnBuffer(columnIndex, bufferSize int) ColumnBuffer {
@@ -94,6 +99,10 @@ func (t byteArrayType) NewColumnReader(columnIndex, bufferSize int) ColumnReader
 	return newByteArrayColumnReader(t, makeColumnIndex(columnIndex), bufferSize)
 }
 
+func (t byteArrayType) ReadDictionary(columnIndex, numValues int, decoder encoding.Decoder) (Dictionary, error) {
+	return readByteArrayDictionary(t, makeColumnIndex(columnIndex), numValues, decoder)
+}
+
 type fixedLenByteArrayType struct{ length int }
 
 func (t *fixedLenByteArrayType) String() string {
@@ -120,8 +129,8 @@ func (t *fixedLenByteArrayType) NewColumnIndexer(sizeLimit int) ColumnIndexer {
 	return newFixedLenByteArrayColumnIndexer(t.length, sizeLimit)
 }
 
-func (t *fixedLenByteArrayType) NewDictionary(bufferSize int) Dictionary {
-	return newFixedLenByteArrayDictionary(t, bufferSize)
+func (t *fixedLenByteArrayType) NewDictionary(columnIndex, bufferSize int) Dictionary {
+	return newFixedLenByteArrayDictionary(t, makeColumnIndex(columnIndex), bufferSize)
 }
 
 func (t *fixedLenByteArrayType) NewColumnBuffer(columnIndex, bufferSize int) ColumnBuffer {
@@ -132,6 +141,10 @@ func (t *fixedLenByteArrayType) NewColumnReader(columnIndex, bufferSize int) Col
 	return newFixedLenByteArrayColumnReader(t, makeColumnIndex(columnIndex), bufferSize)
 }
 
+func (t *fixedLenByteArrayType) ReadDictionary(columnIndex, numValues int, decoder encoding.Decoder) (Dictionary, error) {
+	return readFixedLenByteArrayDictionary(t, makeColumnIndex(columnIndex), numValues, decoder)
+}
+
 // FixedLenByteArrayType constructs a type for fixed-length values of the given
 // size (in bytes).
 func FixedLenByteArrayType(length int) Type { return &fixedLenByteArrayType{length: length} }
@@ -152,18 +165,18 @@ func (t *intType) NewColumnIndexer(sizeLimit int) ColumnIndexer {
 	}
 }
 
-func (t *intType) NewDictionary(bufferSize int) Dictionary {
+func (t *intType) NewDictionary(columnIndex, bufferSize int) Dictionary {
 	if t.IsSigned {
 		if t.BitWidth == 64 {
-			return newDictionary(t, bufferSize, &int64Class)
+			return newDictionary(t, makeColumnIndex(columnIndex), bufferSize, &int64Class)
 		} else {
-			return newDictionary(t, bufferSize, &int32Class)
+			return newDictionary(t, makeColumnIndex(columnIndex), bufferSize, &int32Class)
 		}
 	} else {
 		if t.BitWidth == 64 {
-			return newDictionary(t, bufferSize, &uint64Class)
+			return newDictionary(t, makeColumnIndex(columnIndex), bufferSize, &uint64Class)
 		} else {
-			return newDictionary(t, bufferSize, &uint32Class)
+			return newDictionary(t, makeColumnIndex(columnIndex), bufferSize, &uint32Class)
 		}
 	}
 }
@@ -200,12 +213,28 @@ func (t *intType) NewColumnReader(columnIndex, bufferSize int) ColumnReader {
 	}
 }
 
+func (t *intType) ReadDictionary(columnIndex, numValues int, decoder encoding.Decoder) (Dictionary, error) {
+	if t.IsSigned {
+		if t.BitWidth == 64 {
+			return readDictionary(t, makeColumnIndex(columnIndex), numValues, decoder, &int64Class)
+		} else {
+			return readDictionary(t, makeColumnIndex(columnIndex), numValues, decoder, &int32Class)
+		}
+	} else {
+		if t.BitWidth == 64 {
+			return readDictionary(t, makeColumnIndex(columnIndex), numValues, decoder, &uint64Class)
+		} else {
+			return readDictionary(t, makeColumnIndex(columnIndex), numValues, decoder, &uint32Class)
+		}
+	}
+}
+
 func (t *dateType) NewColumnIndexer(sizeLimit int) ColumnIndexer {
 	return newColumnIndexer(&int32Class)
 }
 
-func (t *dateType) NewDictionary(bufferSize int) Dictionary {
-	return newDictionary(t, bufferSize, &int32Class)
+func (t *dateType) NewDictionary(columnIndex, bufferSize int) Dictionary {
+	return newDictionary(t, makeColumnIndex(columnIndex), bufferSize, &int32Class)
 }
 
 func (t *dateType) NewColumnBuffer(columnIndex, bufferSize int) ColumnBuffer {
@@ -216,6 +245,10 @@ func (t *dateType) NewColumnReader(columnIndex, bufferSize int) ColumnReader {
 	return newColumnReader(t, makeColumnIndex(columnIndex), bufferSize, &int64Class)
 }
 
+func (t *dateType) ReadDictionary(columnIndex, numValues int, decoder encoding.Decoder) (Dictionary, error) {
+	return readDictionary(t, makeColumnIndex(columnIndex), numValues, decoder, &int64Class)
+}
+
 func (t *timeType) NewColumnIndexer(sizeLimit int) ColumnIndexer {
 	if t.Unit.Millis != nil {
 		return newColumnIndexer(&int32Class)
@@ -224,11 +257,11 @@ func (t *timeType) NewColumnIndexer(sizeLimit int) ColumnIndexer {
 	}
 }
 
-func (t *timeType) NewDictionary(bufferSize int) Dictionary {
+func (t *timeType) NewDictionary(columnIndex, bufferSize int) Dictionary {
 	if t.Unit.Millis != nil {
-		return newDictionary(t, bufferSize, &int32Class)
+		return newDictionary(t, makeColumnIndex(columnIndex), bufferSize, &int32Class)
 	} else {
-		return newDictionary(t, bufferSize, &int64Class)
+		return newDictionary(t, makeColumnIndex(columnIndex), bufferSize, &int64Class)
 	}
 }
 
@@ -248,12 +281,20 @@ func (t *timeType) NewColumnReader(columnIndex, bufferSize int) ColumnReader {
 	}
 }
 
+func (t *timeType) ReadDictionary(columnIndex, numValues int, decoder encoding.Decoder) (Dictionary, error) {
+	if t.Unit.Millis != nil {
+		return readDictionary(t, makeColumnIndex(columnIndex), numValues, decoder, &int32Class)
+	} else {
+		return readDictionary(t, makeColumnIndex(columnIndex), numValues, decoder, &int64Class)
+	}
+}
+
 func (t *timestampType) NewColumnIndexer(sizeLimit int) ColumnIndexer {
 	return newColumnIndexer(&int64Class)
 }
 
-func (t *timestampType) NewDictionary(bufferSize int) Dictionary {
-	return newDictionary(t, bufferSize, &int64Class)
+func (t *timestampType) NewDictionary(columnIndex, bufferSize int) Dictionary {
+	return newDictionary(t, makeColumnIndex(columnIndex), bufferSize, &int64Class)
 }
 
 func (t *timestampType) NewColumnBuffer(columnIndex, bufferSize int) ColumnBuffer {
@@ -263,3 +304,7 @@ func (t *timestampType) NewColumnBuffer(columnIndex, bufferSize int) ColumnBuffe
 func (t *timestampType) NewColumnReader(columnIndex, bufferSize int) ColumnReader {
 	return newColumnReader(t, makeColumnIndex(columnIndex), bufferSize, &int64Class)
 }
+
+func (t *timestampType) ReadDictionary(columnIndex, numValues int, decoder encoding.Decoder) (Dictionary, error) {
+	return readDictionary(t, makeColumnIndex(columnIndex), numValues, decoder, &int64Class)
+}
diff --git a/writer.go b/writer.go
index 84e9001..afc182e 100644
--- a/writer.go
+++ b/writer.go
@@ -272,9 +272,10 @@ func newWriter(output io.Writer, config *WriterConfig) *writer {
 		encoding, compression := encodingAndCompressionOf(leaf.node)
 		dictionary := Dictionary(nil)
 		columnType := leaf.node.Type()
+		columnIndex := int(leaf.columnIndex)
 
 		if isDictionaryEncoding(encoding) {
-			dictionary = columnType.NewDictionary(0)
+			dictionary = columnType.NewDictionary(columnIndex, defaultDictBufferSize)
 			columnType = dictionary.Type()
 		}
 
@@ -765,7 +766,7 @@ func (c *writerColumn) flushFilterPages() error {
 
 		// If there is a dictionary, we need to only write the dictionary.
 		if dict := c.dictionary; dict != nil {
-			return dict.WriteTo(c.page.filter)
+			return dict.Page().WriteTo(c.page.filter)
 		}
 
 		for _, page := range c.filter {
@@ -1125,7 +1126,7 @@ func (c *writerColumn) writeDictionaryPage(output io.Writer, dict Dictionary) er
 	c.page.uncompressed.Reset(p)
 	c.dict.encoder.Reset(&c.page.uncompressed)
 
-	if err := dict.WriteTo(&c.dict.encoder); err != nil {
+	if err := dict.Page().WriteTo(&c.dict.encoder); err != nil {
 		return fmt.Errorf("writing parquet dictionary page: %w", err)
 	}
 	if err := p.Close(); err != nil {


Test patch:
diff --git a/buffer_test.go b/buffer_test.go
index b5745c9..d467b94 100644
--- a/buffer_test.go
+++ b/buffer_test.go
@@ -160,7 +160,7 @@ func TestBuffer(t *testing.T) {
 				typ      parquet.Type
 			}{
 				{scenario: "plain", typ: test.typ},
-				{scenario: "indexed", typ: test.typ.NewDictionary(0).Type()},
+				{scenario: "indexed", typ: test.typ.NewDictionary(0, 0).Type()},
 			} {
 				t.Run(config.scenario, func(t *testing.T) {
 					for _, mod := range [...]struct {
diff --git a/dictionary_test.go b/dictionary_test.go
index 2ceb707..67fd023 100644
--- a/dictionary_test.go
+++ b/dictionary_test.go
@@ -42,7 +42,7 @@ func benchmarkDictionary(b *testing.B, do func(*testing.B, parquet.Type)) {
 
 func randDictionaryPage(typ parquet.Type, values []parquet.Value) parquet.BufferedPage {
 	const bufferSize = 64 * 1024
-	dict := typ.NewDictionary(4 * bufferSize)
+	dict := typ.NewDictionary(0, 4*bufferSize)
 	buf := dict.Type().NewColumnBuffer(0, bufferSize)
 	buf.WriteValues(values)
 	return buf.Page()
diff --git a/page_go18_test.go b/page_go18_test.go
index a06e62a..2b8dc5c 100644
--- a/page_go18_test.go
+++ b/page_go18_test.go
@@ -8,6 +8,7 @@ import (
 	"math/rand"
 	"reflect"
 	"testing"
+	"unsafe"
 
 	"github.com/segmentio/parquet-go"
 	"github.com/segmentio/parquet-go/deprecated"
@@ -50,6 +51,8 @@ func randValue[T plain.Type](r *rand.Rand) (v T) {
 }
 
 func testPageOf[T plain.Type](t *testing.T) {
+	var zero T
+	sizeof := int(unsafe.Sizeof(zero))
 	schema := parquet.SchemaOf(struct{ Value T }{})
 	r := rand.New(rand.NewSource(0))
 
@@ -61,12 +64,12 @@ func testPageOf[T plain.Type](t *testing.T) {
 					1: randValue[T](r),
 				}
 				n, err := w.(io.Writer).Write(cast.SliceToBytes(values))
-				return values[:n], err
+				return values[:n/sizeof], err
 			},
 			read: func(r parquet.ValueReader) ([]T, error) {
 				values := make([]T, 2)
 				n, err := r.(io.Reader).Read(cast.SliceToBytes(values))
-				return values[:n], err
+				return values[:n/sizeof], err
 			},
 		})
 	})