1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
17package schema
18
19import (
20	"github.com/apache/arrow/go/v6/parquet"
21	format "github.com/apache/arrow/go/v6/parquet/internal/gen-go/parquet"
22	"github.com/apache/thrift/lib/go/thrift"
23	"golang.org/x/xerrors"
24)
25
26// NodeType describes whether the Node is a Primitive or Group node
27type NodeType int
28
29// the available constants for NodeType
30const (
31	Primitive NodeType = iota
32	Group
33)
34
35// Node is the interface for both Group and Primitive Nodes.
36// A logical schema type has a name, repetition level, and optionally
37// a logical type (converted type is the deprecated version of the logical
38// type concept, which is maintained for forward compatibility)
39type Node interface {
40	Name() string
41	Type() NodeType
42	RepetitionType() parquet.Repetition
43	ConvertedType() ConvertedType
44	LogicalType() LogicalType
45	FieldID() int32
46	Parent() Node
47	SetParent(Node)
48	Path() string
49	Equals(Node) bool
50	Visit(v Visitor)
51	toThrift() *format.SchemaElement
52}
53
54// Visitor is an interface for creating functionality to walk the schema tree.
55//
56// A visitor can be passed to the Visit function of a Node in order to walk
57// the tree. VisitPre is called the first time a node is encountered. If
58// it is a group node, the return is checked and if it is false, the children
59// will be skipped.
60//
61// VisitPost is called after visiting any children
62type Visitor interface {
63	VisitPre(Node) bool
64	VisitPost(Node)
65}
66
67// ColumnPathFromNode walks the parents of the given node to construct it's
68// column path
69func ColumnPathFromNode(n Node) parquet.ColumnPath {
70	if n == nil {
71		return nil
72	}
73
74	c := make([]string, 0)
75
76	// build the path in reverse order as we traverse nodes to the top
77	cursor := n
78	for cursor.Parent() != nil {
79		c = append(c, cursor.Name())
80		cursor = cursor.Parent()
81	}
82
83	// reverse the order of the list in place so that our result
84	// is in the proper, correct order.
85	for i := len(c)/2 - 1; i >= 0; i-- {
86		opp := len(c) - 1 - i
87		c[i], c[opp] = c[opp], c[i]
88	}
89
90	return c
91}
92
93// node is the base embedded struct for both group and primitive nodes
94type node struct {
95	typ    NodeType
96	parent Node
97
98	name          string
99	repetition    parquet.Repetition
100	fieldID       int32
101	logicalType   LogicalType
102	convertedType ConvertedType
103	colPath       parquet.ColumnPath
104}
105
106func (n *node) toThrift() *format.SchemaElement    { return nil }
107func (n *node) Name() string                       { return n.name }
108func (n *node) Type() NodeType                     { return n.typ }
109func (n *node) RepetitionType() parquet.Repetition { return n.repetition }
110func (n *node) ConvertedType() ConvertedType       { return n.convertedType }
111func (n *node) LogicalType() LogicalType           { return n.logicalType }
112func (n *node) FieldID() int32                     { return n.fieldID }
113func (n *node) Parent() Node                       { return n.parent }
114func (n *node) SetParent(p Node)                   { n.parent = p }
115func (n *node) Path() string {
116	return n.columnPath().String()
117}
118func (n *node) columnPath() parquet.ColumnPath {
119	if n.colPath == nil {
120		n.colPath = ColumnPathFromNode(n)
121	}
122	return n.colPath
123}
124
125func (n *node) Equals(rhs Node) bool {
126	return n.typ == rhs.Type() &&
127		n.Name() == rhs.Name() &&
128		n.RepetitionType() == rhs.RepetitionType() &&
129		n.ConvertedType() == rhs.ConvertedType() &&
130		n.FieldID() == rhs.FieldID() &&
131		n.LogicalType().Equals(rhs.LogicalType())
132}
133
134func (n *node) Visit(v Visitor) {}
135
136// A PrimitiveNode is a type that is one of the primitive Parquet storage types. In addition to
137// the other type metadata (name, repetition level, logical type), also has the
138// physical storage type and their type-specific metadata (byte width, decimal
139// parameters)
140type PrimitiveNode struct {
141	node
142
143	ColumnOrder     parquet.ColumnOrder
144	physicalType    parquet.Type
145	typeLen         int
146	decimalMetaData DecimalMetadata
147}
148
149// NewPrimitiveNodeLogical constructs a Primtive node using the provided logical type for a given
150// physical type and typelength.
151func NewPrimitiveNodeLogical(name string, repetition parquet.Repetition, logicalType LogicalType, physicalType parquet.Type, typeLen int, id int32) (*PrimitiveNode, error) {
152	n := &PrimitiveNode{
153		node:         node{typ: Primitive, name: name, repetition: repetition, logicalType: logicalType, fieldID: id},
154		physicalType: physicalType,
155		typeLen:      typeLen,
156	}
157
158	if logicalType != nil {
159		if !logicalType.IsNested() {
160			if logicalType.IsApplicable(physicalType, int32(typeLen)) {
161				n.convertedType, n.decimalMetaData = n.logicalType.ToConvertedType()
162			} else {
163				return nil, xerrors.Errorf("%s cannot be applied to primitive type %s", logicalType, physicalType)
164			}
165		} else {
166			return nil, xerrors.Errorf("nested logical type %s can not be applied to a non-group node", logicalType)
167		}
168	} else {
169		n.logicalType = NoLogicalType{}
170		n.convertedType, n.decimalMetaData = n.logicalType.ToConvertedType()
171	}
172
173	if !(n.logicalType != nil && !n.logicalType.IsNested() && n.logicalType.IsCompatible(n.convertedType, n.decimalMetaData)) {
174		return nil, xerrors.Errorf("invalid logical type %s", n.logicalType)
175	}
176
177	if n.physicalType == parquet.Types.FixedLenByteArray && n.typeLen <= 0 {
178		return nil, xerrors.New("invalid fixed length byte array length")
179	}
180	return n, nil
181}
182
183// NewPrimitiveNodeConverted constructs a primitive node from the given physical type and converted type,
184// determining the logical type from the converted type.
185func NewPrimitiveNodeConverted(name string, repetition parquet.Repetition, typ parquet.Type, converted ConvertedType, typeLen, precision, scale int, id int32) (*PrimitiveNode, error) {
186	n := &PrimitiveNode{
187		node:         node{typ: Primitive, name: name, repetition: repetition, convertedType: converted, fieldID: id},
188		physicalType: typ,
189		typeLen:      -1,
190	}
191
192	switch converted {
193	case ConvertedTypes.None:
194	case ConvertedTypes.UTF8, ConvertedTypes.JSON, ConvertedTypes.BSON:
195		if typ != parquet.Types.ByteArray {
196			return nil, xerrors.Errorf("parquet: %s can only annotate BYTE_LEN fields", typ)
197		}
198	case ConvertedTypes.Decimal:
199		switch typ {
200		case parquet.Types.Int32, parquet.Types.Int64, parquet.Types.ByteArray, parquet.Types.FixedLenByteArray:
201		default:
202			return nil, xerrors.New("parquet: DECIMAL can only annotate INT32, INT64, BYTE_ARRAY and FIXED")
203		}
204
205		switch {
206		case precision <= 0:
207			return nil, xerrors.Errorf("parquet: invalid decimal precision: %d, must be between 1 and 38 inclusive", precision)
208		case scale < 0:
209			return nil, xerrors.Errorf("parquet: invalid decimal scale: %d, must be a number between 0 and precision inclusive", scale)
210		case scale > precision:
211			return nil, xerrors.Errorf("parquet: invalid decimal scale %d, cannot be greater than precision: %d", scale, precision)
212		}
213		n.decimalMetaData.IsSet = true
214		n.decimalMetaData.Precision = int32(precision)
215		n.decimalMetaData.Scale = int32(scale)
216	case ConvertedTypes.Date,
217		ConvertedTypes.TimeMillis,
218		ConvertedTypes.Int8,
219		ConvertedTypes.Int16,
220		ConvertedTypes.Int32,
221		ConvertedTypes.Uint8,
222		ConvertedTypes.Uint16,
223		ConvertedTypes.Uint32:
224		if typ != parquet.Types.Int32 {
225			return nil, xerrors.Errorf("parquet: %s can only annotate INT32", converted)
226		}
227	case ConvertedTypes.TimeMicros,
228		ConvertedTypes.TimestampMicros,
229		ConvertedTypes.TimestampMillis,
230		ConvertedTypes.Int64,
231		ConvertedTypes.Uint64:
232		if typ != parquet.Types.Int64 {
233			return nil, xerrors.Errorf("parquet: %s can only annotate INT64", converted)
234		}
235	case ConvertedTypes.Interval:
236		if typ != parquet.Types.FixedLenByteArray || typeLen != 12 {
237			return nil, xerrors.New("parquet: INTERVAL can only annotate FIXED_LEN_BYTE_ARRAY(12)")
238		}
239	case ConvertedTypes.Enum:
240		if typ != parquet.Types.ByteArray {
241			return nil, xerrors.New("parquet: ENUM can only annotate BYTE_ARRAY fields")
242		}
243	case ConvertedTypes.NA:
244	default:
245		return nil, xerrors.Errorf("parquet: %s cannot be applied to a primitive type", converted.String())
246	}
247
248	n.logicalType = n.convertedType.ToLogicalType(n.decimalMetaData)
249	if !(n.logicalType != nil && !n.logicalType.IsNested() && n.logicalType.IsCompatible(n.convertedType, n.decimalMetaData)) {
250		return nil, xerrors.Errorf("invalid logical type %s", n.logicalType)
251	}
252
253	if n.physicalType == parquet.Types.FixedLenByteArray {
254		if typeLen <= 0 {
255			return nil, xerrors.New("invalid fixed len byte array length")
256		}
257		n.typeLen = typeLen
258	}
259
260	return n, nil
261}
262
263func PrimitiveNodeFromThrift(elem *format.SchemaElement) (*PrimitiveNode, error) {
264	fieldID := int32(-1)
265	if elem.IsSetFieldID() {
266		fieldID = elem.GetFieldID()
267	}
268
269	if elem.IsSetLogicalType() {
270		return NewPrimitiveNodeLogical(elem.GetName(), parquet.Repetition(elem.GetRepetitionType()),
271			getLogicalType(elem.GetLogicalType()), parquet.Type(elem.GetType()), int(elem.GetTypeLength()),
272			fieldID)
273	} else if elem.IsSetConvertedType() {
274		return NewPrimitiveNodeConverted(elem.GetName(), parquet.Repetition(elem.GetRepetitionType()),
275			parquet.Type(elem.GetType()), ConvertedType(elem.GetConvertedType()),
276			int(elem.GetTypeLength()), int(elem.GetPrecision()), int(elem.GetScale()), fieldID)
277	}
278	return NewPrimitiveNodeLogical(elem.GetName(), parquet.Repetition(elem.GetRepetitionType()), NoLogicalType{}, parquet.Type(elem.GetType()), int(elem.GetTypeLength()), fieldID)
279}
280
281// NewPrimitiveNode constructs a primitive node with the ConvertedType of None and no logical type.
282//
283// Use NewPrimitiveNodeLogical and NewPrimitiveNodeConverted to specify the logical or converted type.
284func NewPrimitiveNode(name string, repetition parquet.Repetition, typ parquet.Type, fieldID, typeLength int32) (*PrimitiveNode, error) {
285	return NewPrimitiveNodeLogical(name, repetition, nil, typ, int(typeLength), fieldID)
286}
287
288// Equals returns true if both nodes are primitive nodes with the same physical
289// and converted/logical types.
290func (p *PrimitiveNode) Equals(rhs Node) bool {
291	if !p.node.Equals(rhs) {
292		return false
293	}
294
295	other := rhs.(*PrimitiveNode)
296	if p == other {
297		return true
298	}
299
300	if p.PhysicalType() != other.PhysicalType() {
301		return false
302	}
303
304	equal := true
305	if p.ConvertedType() == ConvertedTypes.Decimal {
306		equal = equal &&
307			(p.decimalMetaData.Precision == other.decimalMetaData.Precision &&
308				p.decimalMetaData.Scale == other.decimalMetaData.Scale)
309	}
310	if p.PhysicalType() == parquet.Types.FixedLenByteArray {
311		equal = equal && p.TypeLength() == other.TypeLength()
312	}
313	return equal
314}
315
316// PhysicalType returns the proper Physical parquet.Type primitive that is used
317// to store the values in this column.
318func (p *PrimitiveNode) PhysicalType() parquet.Type { return p.physicalType }
319
320// SetTypeLength will change the type length of the node, has no effect if the
321// physical type is not FixedLength Byte Array
322func (p *PrimitiveNode) SetTypeLength(length int) {
323	if p.PhysicalType() == parquet.Types.FixedLenByteArray {
324		p.typeLen = length
325	}
326}
327
328// TypeLength will be -1 if not a FixedLenByteArray column, otherwise will be the
329// length of the FixedLen Byte Array
330func (p *PrimitiveNode) TypeLength() int { return p.typeLen }
331
332// DecimalMetadata returns the current metadata for the node. If not a decimal
333// typed column, the return should have IsSet == false.
334func (p *PrimitiveNode) DecimalMetadata() DecimalMetadata { return p.decimalMetaData }
335
336// Visit is for implementing a Visitor pattern handler to walk a schema's tree. One
337// example is the Schema Printer which walks the tree to print out the schema in order.
338func (p *PrimitiveNode) Visit(v Visitor) {
339	v.VisitPre(p)
340	v.VisitPost(p)
341}
342
343func (p *PrimitiveNode) toThrift() *format.SchemaElement {
344	elem := &format.SchemaElement{
345		Name:           p.Name(),
346		RepetitionType: format.FieldRepetitionTypePtr(format.FieldRepetitionType(p.RepetitionType())),
347		Type:           format.TypePtr(format.Type(p.PhysicalType())),
348	}
349	if p.ConvertedType() != ConvertedTypes.None {
350		elem.ConvertedType = format.ConvertedTypePtr(format.ConvertedType(p.ConvertedType()))
351	}
352	if p.FieldID() >= 0 {
353		elem.FieldID = thrift.Int32Ptr(p.FieldID())
354	}
355	if p.logicalType != nil && p.logicalType.IsSerialized() && !p.logicalType.Equals(IntervalLogicalType{}) {
356		elem.LogicalType = p.logicalType.toThrift()
357	}
358	if p.physicalType == parquet.Types.FixedLenByteArray {
359		elem.TypeLength = thrift.Int32Ptr(int32(p.typeLen))
360	}
361	if p.decimalMetaData.IsSet {
362		elem.Precision = &p.decimalMetaData.Precision
363		elem.Scale = &p.decimalMetaData.Scale
364	}
365	return elem
366}
367
368// FieldList is an alias for a slice of Nodes
369type FieldList []Node
370
371// Len is equivalent to len(fieldlist)
372func (f FieldList) Len() int { return len(f) }
373
374// GroupNode is for mananging nested nodes like List, Map, etc.
375type GroupNode struct {
376	node
377	fields    FieldList
378	nameToIdx strIntMultimap
379}
380
381// NewGroupNodeConverted constructs a group node with the provided fields and converted type,
382// determining the logical type from that converted type.
383func NewGroupNodeConverted(name string, repetition parquet.Repetition, fields FieldList, converted ConvertedType, id int32) (n *GroupNode, err error) {
384	n = &GroupNode{
385		node:   node{typ: Group, name: name, repetition: repetition, convertedType: converted, fieldID: id},
386		fields: fields,
387	}
388	n.logicalType = n.convertedType.ToLogicalType(DecimalMetadata{})
389	if !(n.logicalType != nil && (n.logicalType.IsNested() || n.logicalType.IsNone()) && n.logicalType.IsCompatible(n.convertedType, DecimalMetadata{})) {
390		err = xerrors.Errorf("invalid logical type %s", n.logicalType.String())
391		return
392	}
393
394	n.nameToIdx = make(strIntMultimap)
395	for idx, f := range n.fields {
396		f.SetParent(n)
397		n.nameToIdx.Add(f.Name(), idx)
398	}
399	return
400}
401
402// NewGroupNodeLogical constructs a group node with the provided fields and logical type,
403// determining the converted type from the provided logical type.
404func NewGroupNodeLogical(name string, repetition parquet.Repetition, fields FieldList, logical LogicalType, id int32) (n *GroupNode, err error) {
405	n = &GroupNode{
406		node:   node{typ: Group, name: name, repetition: repetition, logicalType: logical, fieldID: id},
407		fields: fields,
408	}
409
410	if logical != nil {
411		if logical.IsNested() {
412			n.convertedType, _ = logical.ToConvertedType()
413		} else {
414			err = xerrors.Errorf("logical type %s cannot be applied to group node", logical)
415			return
416		}
417	} else {
418		n.logicalType = NoLogicalType{}
419		n.convertedType, _ = n.logicalType.ToConvertedType()
420	}
421
422	if !(n.logicalType != nil && (n.logicalType.IsNested() || n.logicalType.IsNone()) && n.logicalType.IsCompatible(n.convertedType, DecimalMetadata{})) {
423		err = xerrors.Errorf("invalid logical type %s", n.logicalType)
424		return
425	}
426
427	n.nameToIdx = make(strIntMultimap)
428	for idx, f := range n.fields {
429		f.SetParent(n)
430		n.nameToIdx.Add(f.Name(), idx)
431	}
432	return
433}
434
435// NewGroupNode constructs a new group node with the provided fields,
436// but with converted type None and No Logical Type
437func NewGroupNode(name string, repetition parquet.Repetition, fields FieldList, fieldID int32) (*GroupNode, error) {
438	return NewGroupNodeConverted(name, repetition, fields, ConvertedTypes.None, fieldID)
439}
440
441// Must is a convenience function for the NewNode functions that return a Node
442// and an error, panic'ing if err != nil or returning the node
443func Must(n Node, err error) Node {
444	if err != nil {
445		panic(err)
446	}
447	return n
448}
449
450// MustGroup is like Must, except it casts the node to a *GroupNode, which will panic
451// if it is a primitive node.
452func MustGroup(n Node, err error) *GroupNode {
453	if err != nil {
454		panic(err)
455	}
456	return n.(*GroupNode)
457}
458
459// MustPrimitive is like Must except it casts the node to *PrimitiveNode which will panic
460// if it is a group node.
461func MustPrimitive(n Node, err error) *PrimitiveNode {
462	if err != nil {
463		panic(err)
464	}
465	return n.(*PrimitiveNode)
466}
467
468func GroupNodeFromThrift(elem *format.SchemaElement, fields FieldList) (*GroupNode, error) {
469	id := int32(-1)
470	if elem.IsSetFieldID() {
471		id = elem.GetFieldID()
472	}
473
474	if elem.IsSetLogicalType() {
475		return NewGroupNodeLogical(elem.GetName(), parquet.Repetition(elem.GetRepetitionType()), fields, getLogicalType(elem.GetLogicalType()), id)
476	}
477
478	converted := ConvertedTypes.None
479	if elem.IsSetConvertedType() {
480		converted = ConvertedType(elem.GetConvertedType())
481	}
482	return NewGroupNodeConverted(elem.GetName(), parquet.Repetition(elem.GetRepetitionType()), fields, converted, id)
483}
484
485func (g *GroupNode) toThrift() *format.SchemaElement {
486	elem := &format.SchemaElement{
487		Name:           g.name,
488		NumChildren:    thrift.Int32Ptr(int32(len(g.fields))),
489		RepetitionType: format.FieldRepetitionTypePtr(format.FieldRepetitionType(g.RepetitionType())),
490	}
491	if g.convertedType != ConvertedTypes.None {
492		elem.ConvertedType = format.ConvertedTypePtr(format.ConvertedType(g.convertedType))
493	}
494	if g.fieldID >= 0 {
495		elem.FieldID = &g.fieldID
496	}
497	if g.logicalType != nil && g.logicalType.IsSerialized() {
498		elem.LogicalType = g.logicalType.toThrift()
499	}
500	return elem
501}
502
503// Equals will compare this node to the provided node and only return true if
504// this node and all of it's children are the same as the passed in node and its
505// children.
506func (g *GroupNode) Equals(rhs Node) bool {
507	if !g.node.Equals(rhs) {
508		return false
509	}
510
511	other := rhs.(*GroupNode)
512	if g == other {
513		return true
514	}
515	if len(g.fields) != len(other.fields) {
516		return false
517	}
518
519	for idx, field := range g.fields {
520		if !field.Equals(other.fields[idx]) {
521			return false
522		}
523	}
524	return true
525}
526
527// NumFields returns the number of direct child fields for this group node
528func (g *GroupNode) NumFields() int {
529	return len(g.fields)
530}
531
532// Field returns the node in the field list which is of the provided (0-based) index
533func (g *GroupNode) Field(i int) Node {
534	return g.fields[i]
535}
536
537// FieldIndexByName provides the index for the field of the given name. Returns
538// -1 if not found.
539//
540// If there are more than one field of this name, it returns the index for the first one.
541func (g *GroupNode) FieldIndexByName(name string) int {
542	if idx, ok := g.nameToIdx[name]; ok {
543		return idx[0]
544	}
545	return -1
546}
547
548// FieldIndexByField looks up the index child of this node. Returns -1
549// if n isn't a child of this group
550func (g *GroupNode) FieldIndexByField(n Node) int {
551	if search, ok := g.nameToIdx[n.Name()]; ok {
552		for _, idx := range search {
553			if n == g.fields[idx] {
554				return idx
555			}
556		}
557	}
558	return -1
559}
560
561// Visit is for implementing a Visitor pattern handler to walk a schema's tree. One
562// example is the Schema Printer which walks the tree to print out the schema in order.
563func (g *GroupNode) Visit(v Visitor) {
564	if v.VisitPre(g) {
565		for _, field := range g.fields {
566			field.Visit(v)
567		}
568	}
569	v.VisitPost(g)
570}
571
572// HasRepeatedFields returns true if any of the children of this node have
573// Repeated as its repetition type.
574//
575// This is recursive and will check the children of any group nodes that are children.
576func (g *GroupNode) HasRepeatedFields() bool {
577	for _, field := range g.fields {
578		if field.RepetitionType() == parquet.Repetitions.Repeated {
579			return true
580		}
581		if field.Type() == Group {
582			return field.(*GroupNode).HasRepeatedFields()
583		}
584	}
585	return false
586}
587
588// NewInt32Node is a convenience factory for constructing an Int32 Primitive Node
589func NewInt32Node(name string, rep parquet.Repetition, fieldID int32) *PrimitiveNode {
590	return MustPrimitive(NewPrimitiveNode(name, rep, parquet.Types.Int32, fieldID, -1))
591}
592
593// NewInt64Node is a convenience factory for constructing an Int64 Primitive Node
594func NewInt64Node(name string, rep parquet.Repetition, fieldID int32) *PrimitiveNode {
595	return MustPrimitive(NewPrimitiveNode(name, rep, parquet.Types.Int64, fieldID, -1))
596}
597
598// NewInt96Node is a convenience factory for constructing an Int96 Primitive Node
599func NewInt96Node(name string, rep parquet.Repetition, fieldID int32) *PrimitiveNode {
600	return MustPrimitive(NewPrimitiveNode(name, rep, parquet.Types.Int96, fieldID, -1))
601}
602
603// NewFloat32Node is a convenience factory for constructing an Float Primitive Node
604func NewFloat32Node(name string, rep parquet.Repetition, fieldID int32) *PrimitiveNode {
605	return MustPrimitive(NewPrimitiveNode(name, rep, parquet.Types.Float, fieldID, -1))
606}
607
608// NewFloat64Node is a convenience factory for constructing an Double Primitive Node
609func NewFloat64Node(name string, rep parquet.Repetition, fieldID int32) *PrimitiveNode {
610	return MustPrimitive(NewPrimitiveNode(name, rep, parquet.Types.Double, fieldID, -1))
611}
612
613// NewBooleanNode is a convenience factory for constructing an Boolean Primitive Node
614func NewBooleanNode(name string, rep parquet.Repetition, fieldID int32) *PrimitiveNode {
615	return MustPrimitive(NewPrimitiveNode(name, rep, parquet.Types.Boolean, fieldID, -1))
616}
617
618// NewByteArrayNode is a convenience factory for constructing an Byte Array Primitive Node
619func NewByteArrayNode(name string, rep parquet.Repetition, fieldID int32) *PrimitiveNode {
620	return MustPrimitive(NewPrimitiveNode(name, rep, parquet.Types.ByteArray, fieldID, -1))
621}
622
623// NewFixedLenByteArrayNode is a convenience factory for constructing an Fixed Length
624// Byte Array Primitive Node of the given length
625func NewFixedLenByteArrayNode(name string, rep parquet.Repetition, length int32, fieldID int32) *PrimitiveNode {
626	return MustPrimitive(NewPrimitiveNode(name, rep, parquet.Types.FixedLenByteArray, fieldID, length))
627}
628