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	"testing"
21
22	"github.com/apache/arrow/go/v6/parquet"
23	format "github.com/apache/arrow/go/v6/parquet/internal/gen-go/parquet"
24	"github.com/stretchr/testify/assert"
25	"github.com/stretchr/testify/suite"
26)
27
28type schemaElementConstruction struct {
29	node            Node
30	element         *format.SchemaElement
31	name            string
32	expectConverted bool
33	converted       ConvertedType
34	expectLogical   bool
35	checkLogical    func(*format.SchemaElement) bool
36}
37
38type decimalSchemaElementConstruction struct {
39	schemaElementConstruction
40	precision int
41	scale     int
42}
43
44type temporalSchemaElementConstruction struct {
45	schemaElementConstruction
46	adjusted bool
47	unit     TimeUnitType
48	getUnit  func(*format.SchemaElement) *format.TimeUnit
49}
50
51type intSchemaElementConstruction struct {
52	schemaElementConstruction
53	width  int8
54	signed bool
55}
56
57type legacySchemaElementConstructArgs struct {
58	name            string
59	physical        parquet.Type
60	len             int
61	expectConverted bool
62	converted       ConvertedType
63	expectLogical   bool
64	checkLogical    func(*format.SchemaElement) bool
65}
66
67type schemaElementConstructArgs struct {
68	name            string
69	logical         LogicalType
70	physical        parquet.Type
71	len             int
72	expectConverted bool
73	converted       ConvertedType
74	expectLogical   bool
75	checkLogical    func(*format.SchemaElement) bool
76}
77type SchemaElementConstructionSuite struct {
78	suite.Suite
79}
80
81func (s *SchemaElementConstructionSuite) reconstruct(c schemaElementConstructArgs) *schemaElementConstruction {
82	ret := &schemaElementConstruction{
83		node:            MustPrimitive(NewPrimitiveNodeLogical(c.name, parquet.Repetitions.Required, c.logical, c.physical, c.len, -1)),
84		name:            c.name,
85		expectConverted: c.expectConverted,
86		converted:       c.converted,
87		expectLogical:   c.expectLogical,
88		checkLogical:    c.checkLogical,
89	}
90	ret.element = ret.node.toThrift()
91	return ret
92}
93
94func (s *SchemaElementConstructionSuite) legacyReconstruct(c legacySchemaElementConstructArgs) *schemaElementConstruction {
95	ret := &schemaElementConstruction{
96		node:            MustPrimitive(NewPrimitiveNodeConverted(c.name, parquet.Repetitions.Required, c.physical, c.converted, c.len, 0, 0, -1)),
97		name:            c.name,
98		expectConverted: c.expectConverted,
99		converted:       c.converted,
100		expectLogical:   c.expectLogical,
101		checkLogical:    c.checkLogical,
102	}
103	ret.element = ret.node.toThrift()
104	return ret
105}
106
107func (s *SchemaElementConstructionSuite) inspect(c *schemaElementConstruction) {
108	if c.expectConverted {
109		s.True(c.element.IsSetConvertedType())
110		s.Equal(c.converted, ConvertedType(*c.element.ConvertedType))
111	} else {
112		s.False(c.element.IsSetConvertedType())
113	}
114	if c.expectLogical {
115		s.True(c.element.IsSetLogicalType())
116		s.True(c.checkLogical(c.element))
117	} else {
118		s.False(c.element.IsSetLogicalType())
119	}
120}
121
122func (s *SchemaElementConstructionSuite) TestSimple() {
123	checkNone := func(*format.SchemaElement) bool { return true }
124
125	tests := []struct {
126		name   string
127		args   *schemaElementConstructArgs
128		legacy *legacySchemaElementConstructArgs
129	}{
130		{"string", &schemaElementConstructArgs{
131			"string", StringLogicalType{}, parquet.Types.ByteArray, -1, true, ConvertedTypes.UTF8, true,
132			func(e *format.SchemaElement) bool { return e.LogicalType.IsSetSTRING() },
133		}, nil},
134		{"enum", &schemaElementConstructArgs{
135			"enum", EnumLogicalType{}, parquet.Types.ByteArray, -1, true, ConvertedTypes.Enum, true,
136			func(e *format.SchemaElement) bool { return e.LogicalType.IsSetENUM() },
137		}, nil},
138		{"date", &schemaElementConstructArgs{
139			"date", DateLogicalType{}, parquet.Types.Int32, -1, true, ConvertedTypes.Date, true,
140			func(e *format.SchemaElement) bool { return e.LogicalType.IsSetDATE() },
141		}, nil},
142		{"interval", &schemaElementConstructArgs{
143			"interval", IntervalLogicalType{}, parquet.Types.FixedLenByteArray, 12, true, ConvertedTypes.Interval, false,
144			checkNone,
145		}, nil},
146		{"null", &schemaElementConstructArgs{
147			"null", NullLogicalType{}, parquet.Types.Double, -1, false, ConvertedTypes.NA, true,
148			func(e *format.SchemaElement) bool { return e.LogicalType.IsSetUNKNOWN() },
149		}, nil},
150		{"json", &schemaElementConstructArgs{
151			"json", JSONLogicalType{}, parquet.Types.ByteArray, -1, true, ConvertedTypes.JSON, true,
152			func(e *format.SchemaElement) bool { return e.LogicalType.IsSetJSON() },
153		}, nil},
154		{"bson", &schemaElementConstructArgs{
155			"bson", BSONLogicalType{}, parquet.Types.ByteArray, -1, true, ConvertedTypes.BSON, true,
156			func(e *format.SchemaElement) bool { return e.LogicalType.IsSetBSON() },
157		}, nil},
158		{"uuid", &schemaElementConstructArgs{
159			"uuid", UUIDLogicalType{}, parquet.Types.FixedLenByteArray, 16, false, ConvertedTypes.NA, true,
160			func(e *format.SchemaElement) bool { return e.LogicalType.IsSetUUID() },
161		}, nil},
162		{"none", &schemaElementConstructArgs{
163			"none", NoLogicalType{}, parquet.Types.Int64, -1, false, ConvertedTypes.NA, false,
164			checkNone,
165		}, nil},
166		{"unknown", &schemaElementConstructArgs{
167			"unknown", UnknownLogicalType{}, parquet.Types.Int64, -1, true, ConvertedTypes.NA, false,
168			checkNone,
169		}, nil},
170		{"timestamp_ms", nil, &legacySchemaElementConstructArgs{
171			"timestamp_ms", parquet.Types.Int64, -1, true, ConvertedTypes.TimestampMillis, false, checkNone}},
172		{"timestamp_us", nil, &legacySchemaElementConstructArgs{
173			"timestamp_us", parquet.Types.Int64, -1, true, ConvertedTypes.TimestampMicros, false, checkNone}},
174	}
175	for _, tt := range tests {
176		s.Run(tt.name, func() {
177			var sc *schemaElementConstruction
178			if tt.args != nil {
179				sc = s.reconstruct(*tt.args)
180			} else {
181				sc = s.legacyReconstruct(*tt.legacy)
182			}
183			s.Equal(tt.name, sc.element.Name)
184			s.inspect(sc)
185		})
186	}
187}
188
189func (s *SchemaElementConstructionSuite) reconstructDecimal(c schemaElementConstructArgs) *decimalSchemaElementConstruction {
190	ret := s.reconstruct(c)
191	dec := c.logical.(*DecimalLogicalType)
192	return &decimalSchemaElementConstruction{*ret, int(dec.Precision()), int(dec.Scale())}
193}
194
195func (s *SchemaElementConstructionSuite) inspectDecimal(d *decimalSchemaElementConstruction) {
196	s.inspect(&d.schemaElementConstruction)
197	s.EqualValues(d.precision, d.element.GetPrecision())
198	s.EqualValues(d.scale, d.element.GetScale())
199	s.EqualValues(d.precision, d.element.LogicalType.DECIMAL.Precision)
200	s.EqualValues(d.scale, d.element.LogicalType.DECIMAL.Scale)
201}
202
203func (s *SchemaElementConstructionSuite) TestDecimal() {
204	checkDecimal := func(p *format.SchemaElement) bool { return p.LogicalType.IsSetDECIMAL() }
205
206	tests := []schemaElementConstructArgs{
207		{
208			name: "decimal16_6", logical: NewDecimalLogicalType(16 /* precision */, 6 /* scale */),
209			physical: parquet.Types.Int64, len: -1, expectConverted: true, converted: ConvertedTypes.Decimal,
210			expectLogical: true, checkLogical: checkDecimal,
211		},
212		{
213			name: "decimal1_0", logical: NewDecimalLogicalType(1 /* precision */, 0 /* scale */),
214			physical: parquet.Types.Int32, len: -1, expectConverted: true, converted: ConvertedTypes.Decimal,
215			expectLogical: true, checkLogical: checkDecimal,
216		},
217		{
218			name: "decimal10", logical: NewDecimalLogicalType(10 /* precision */, 0 /* scale */),
219			physical: parquet.Types.Int64, len: -1, expectConverted: true, converted: ConvertedTypes.Decimal,
220			expectLogical: true, checkLogical: checkDecimal,
221		},
222		{
223			name: "decimal11_11", logical: NewDecimalLogicalType(11 /* precision */, 11 /* scale */),
224			physical: parquet.Types.Int64, len: -1, expectConverted: true, converted: ConvertedTypes.Decimal,
225			expectLogical: true, checkLogical: checkDecimal,
226		},
227	}
228	for _, tt := range tests {
229		s.Run(tt.name, func() {
230			d := s.reconstructDecimal(tt)
231			s.Equal(tt.name, d.element.Name)
232			s.inspectDecimal(d)
233		})
234	}
235}
236
237func (s *SchemaElementConstructionSuite) reconstructTemporal(c schemaElementConstructArgs, getUnit func(*format.SchemaElement) *format.TimeUnit) *temporalSchemaElementConstruction {
238	base := s.reconstruct(c)
239	t := c.logical.(TemporalLogicalType)
240	return &temporalSchemaElementConstruction{
241		*base,
242		t.IsAdjustedToUTC(),
243		t.TimeUnit(),
244		getUnit,
245	}
246}
247
248func (s *SchemaElementConstructionSuite) inspectTemporal(t *temporalSchemaElementConstruction) {
249	s.inspect(&t.schemaElementConstruction)
250	switch t.unit {
251	case TimeUnitMillis:
252		s.True(t.getUnit(t.element).IsSetMILLIS())
253	case TimeUnitMicros:
254		s.True(t.getUnit(t.element).IsSetMICROS())
255	case TimeUnitNanos:
256		s.True(t.getUnit(t.element).IsSetNANOS())
257	case TimeUnitUnknown:
258		fallthrough
259	default:
260		s.Fail("invalid time unit in test case")
261	}
262}
263
264func (s *SchemaElementConstructionSuite) TestTemporal() {
265	checkTime := func(p *format.SchemaElement) bool {
266		return p.LogicalType.IsSetTIME()
267	}
268	checkTimestamp := func(p *format.SchemaElement) bool {
269		return p.LogicalType.IsSetTIMESTAMP()
270	}
271
272	getTimeUnit := func(p *format.SchemaElement) *format.TimeUnit {
273		return p.LogicalType.TIME.Unit
274	}
275	getTimestampUnit := func(p *format.SchemaElement) *format.TimeUnit {
276		return p.LogicalType.TIMESTAMP.Unit
277	}
278
279	timeTests := []schemaElementConstructArgs{
280		{
281			name: "time_T_ms", logical: NewTimeLogicalType(true, TimeUnitMillis), physical: parquet.Types.Int32, len: -1,
282			expectConverted: true, converted: ConvertedTypes.TimeMillis, expectLogical: true, checkLogical: checkTime,
283		},
284		{
285			name: "time_F_ms", logical: NewTimeLogicalType(false, TimeUnitMillis), physical: parquet.Types.Int32, len: -1,
286			expectConverted: false, converted: ConvertedTypes.NA, expectLogical: true, checkLogical: checkTime,
287		},
288		{
289			name: "time_T_us", logical: NewTimeLogicalType(true, TimeUnitMicros), physical: parquet.Types.Int64, len: -1,
290			expectConverted: true, converted: ConvertedTypes.TimeMicros, expectLogical: true, checkLogical: checkTime,
291		},
292		{
293			name: "time_F_us", logical: NewTimeLogicalType(false, TimeUnitMicros), physical: parquet.Types.Int64, len: -1,
294			expectConverted: false, converted: ConvertedTypes.NA, expectLogical: true, checkLogical: checkTime,
295		},
296		{
297			name: "time_T_ns", logical: NewTimeLogicalType(true, TimeUnitNanos), physical: parquet.Types.Int64, len: -1,
298			expectConverted: false, converted: ConvertedTypes.NA, expectLogical: true, checkLogical: checkTime,
299		},
300		{
301			name: "time_F_ns", logical: NewTimeLogicalType(false, TimeUnitNanos), physical: parquet.Types.Int64, len: -1,
302			expectConverted: false, converted: ConvertedTypes.NA, expectLogical: true, checkLogical: checkTime,
303		},
304	}
305	timeStampTests := []schemaElementConstructArgs{
306		{
307			name: "timestamp_T_ms", logical: NewTimestampLogicalType(true, TimeUnitMillis), physical: parquet.Types.Int64, len: -1,
308			expectConverted: true, converted: ConvertedTypes.TimestampMillis, expectLogical: true, checkLogical: checkTimestamp,
309		},
310		{
311			name: "timestamp_F_ms", logical: NewTimestampLogicalType(false, TimeUnitMillis), physical: parquet.Types.Int64, len: -1,
312			expectConverted: false, converted: ConvertedTypes.NA, expectLogical: true, checkLogical: checkTimestamp,
313		},
314		{
315			name: "timestamp_F_ms_force", logical: NewTimestampLogicalTypeForce(false, TimeUnitMillis), physical: parquet.Types.Int64, len: -1,
316			expectConverted: true, converted: ConvertedTypes.TimestampMillis, expectLogical: true, checkLogical: checkTimestamp,
317		},
318		{
319			name: "timestamp_T_us", logical: NewTimestampLogicalType(true, TimeUnitMicros), physical: parquet.Types.Int64, len: -1,
320			expectConverted: true, converted: ConvertedTypes.TimestampMicros, expectLogical: true, checkLogical: checkTimestamp,
321		},
322		{
323			name: "timestamp_F_us", logical: NewTimestampLogicalType(false, TimeUnitMicros), physical: parquet.Types.Int64, len: -1,
324			expectConverted: false, converted: ConvertedTypes.NA, expectLogical: true, checkLogical: checkTimestamp,
325		},
326		{
327			name: "timestamp_F_us_force", logical: NewTimestampLogicalTypeForce(false, TimeUnitMicros), physical: parquet.Types.Int64, len: -1,
328			expectConverted: true, converted: ConvertedTypes.TimestampMicros, expectLogical: true, checkLogical: checkTimestamp,
329		},
330		{
331			name: "timestamp_T_ns", logical: NewTimestampLogicalType(true, TimeUnitNanos), physical: parquet.Types.Int64, len: -1,
332			expectConverted: false, converted: ConvertedTypes.NA, expectLogical: true, checkLogical: checkTimestamp,
333		},
334		{
335			name: "timestamp_F_ns", logical: NewTimestampLogicalType(false, TimeUnitNanos), physical: parquet.Types.Int64, len: -1,
336			expectConverted: false, converted: ConvertedTypes.NA, expectLogical: true, checkLogical: checkTimestamp,
337		},
338	}
339
340	for _, tt := range timeTests {
341		s.Run(tt.name, func() {
342			t := s.reconstructTemporal(tt, getTimeUnit)
343			s.Equal(t.adjusted, t.element.LogicalType.TIME.IsAdjustedToUTC)
344			s.inspectTemporal(t)
345		})
346	}
347	for _, tt := range timeStampTests {
348		s.Run(tt.name, func() {
349			t := s.reconstructTemporal(tt, getTimestampUnit)
350			s.Equal(t.adjusted, t.element.LogicalType.TIMESTAMP.IsAdjustedToUTC)
351			s.inspectTemporal(t)
352		})
353	}
354}
355
356func (s *SchemaElementConstructionSuite) reconstructInteger(c schemaElementConstructArgs) *intSchemaElementConstruction {
357	base := s.reconstruct(c)
358	l := c.logical.(*IntLogicalType)
359	return &intSchemaElementConstruction{
360		*base,
361		l.BitWidth(),
362		l.IsSigned(),
363	}
364}
365
366func (s *SchemaElementConstructionSuite) inspectInt(i *intSchemaElementConstruction) {
367	s.inspect(&i.schemaElementConstruction)
368	s.Equal(i.width, i.element.LogicalType.INTEGER.BitWidth)
369	s.Equal(i.signed, i.element.LogicalType.INTEGER.IsSigned)
370}
371
372func (s *SchemaElementConstructionSuite) TestIntegerCases() {
373	checkInt := func(p *format.SchemaElement) bool { return p.LogicalType.IsSetINTEGER() }
374
375	tests := []schemaElementConstructArgs{
376		{
377			name: "uint8", logical: NewIntLogicalType(8, false), physical: parquet.Types.Int32, len: -1,
378			expectConverted: true, converted: ConvertedTypes.Uint8, expectLogical: true, checkLogical: checkInt,
379		},
380		{
381			name: "uint16", logical: NewIntLogicalType(16, false), physical: parquet.Types.Int32, len: -1,
382			expectConverted: true, converted: ConvertedTypes.Uint16, expectLogical: true, checkLogical: checkInt,
383		},
384		{
385			name: "uint32", logical: NewIntLogicalType(32, false), physical: parquet.Types.Int32, len: -1,
386			expectConverted: true, converted: ConvertedTypes.Uint32, expectLogical: true, checkLogical: checkInt,
387		},
388		{
389			name: "uint64", logical: NewIntLogicalType(64, false), physical: parquet.Types.Int64, len: -1,
390			expectConverted: true, converted: ConvertedTypes.Uint64, expectLogical: true, checkLogical: checkInt,
391		},
392		{
393			name: "int8", logical: NewIntLogicalType(8, true), physical: parquet.Types.Int32, len: -1,
394			expectConverted: true, converted: ConvertedTypes.Int8, expectLogical: true, checkLogical: checkInt,
395		},
396		{
397			name: "int16", logical: NewIntLogicalType(16, true), physical: parquet.Types.Int32, len: -1,
398			expectConverted: true, converted: ConvertedTypes.Int16, expectLogical: true, checkLogical: checkInt,
399		},
400		{
401			name: "int32", logical: NewIntLogicalType(32, true), physical: parquet.Types.Int32, len: -1,
402			expectConverted: true, converted: ConvertedTypes.Int32, expectLogical: true, checkLogical: checkInt,
403		},
404		{
405			name: "int64", logical: NewIntLogicalType(64, true), physical: parquet.Types.Int64, len: -1,
406			expectConverted: true, converted: ConvertedTypes.Int64, expectLogical: true, checkLogical: checkInt,
407		},
408	}
409	for _, tt := range tests {
410		s.Run(tt.name, func() {
411			t := s.reconstructInteger(tt)
412			s.inspectInt(t)
413		})
414	}
415}
416
417func TestSchemaElementNestedSerialization(t *testing.T) {
418	// confirm that the intermediate thrift objects created during node serialization
419	// contain correct ConvertedType and ConvertedType information
420
421	strNode := MustPrimitive(NewPrimitiveNodeLogical("string" /*name */, parquet.Repetitions.Required, StringLogicalType{}, parquet.Types.ByteArray, -1 /* type len */, -1 /* fieldID */))
422	dateNode := MustPrimitive(NewPrimitiveNodeLogical("date" /*name */, parquet.Repetitions.Required, DateLogicalType{}, parquet.Types.Int32, -1 /* type len */, -1 /* fieldID */))
423	jsonNode := MustPrimitive(NewPrimitiveNodeLogical("json" /*name */, parquet.Repetitions.Required, JSONLogicalType{}, parquet.Types.ByteArray, -1 /* type len */, -1 /* fieldID */))
424	uuidNode := MustPrimitive(NewPrimitiveNodeLogical("uuid" /*name */, parquet.Repetitions.Required, UUIDLogicalType{}, parquet.Types.FixedLenByteArray, 16 /* type len */, - /* fieldID */ 1))
425	timestampNode := MustPrimitive(NewPrimitiveNodeLogical("timestamp" /*name */, parquet.Repetitions.Required, NewTimestampLogicalType(false /* adjustedToUTC */, TimeUnitNanos), parquet.Types.Int64, -1 /* type len */, -1 /* fieldID */))
426	intNode := MustPrimitive(NewPrimitiveNodeLogical("int" /*name */, parquet.Repetitions.Required, NewIntLogicalType(64 /* bitWidth */, false /* signed */), parquet.Types.Int64, -1 /* type len */, -1 /* fieldID */))
427	decimalNode := MustPrimitive(NewPrimitiveNodeLogical("decimal" /*name */, parquet.Repetitions.Required, NewDecimalLogicalType(16 /* precision */, 6 /* scale */), parquet.Types.Int64, -1 /* type len */, -1 /* fieldID */))
428	listNode := MustGroup(NewGroupNodeLogical("list" /*name */, parquet.Repetitions.Repeated, []Node{strNode, dateNode, jsonNode, uuidNode, timestampNode, intNode, decimalNode}, NewListLogicalType(), -1 /* fieldID */))
429
430	listElems := ToThrift(listNode)
431	assert.Equal(t, "list", listElems[0].Name)
432	assert.True(t, listElems[0].IsSetConvertedType())
433	assert.True(t, listElems[0].IsSetLogicalType())
434	assert.Equal(t, format.ConvertedType(ConvertedTypes.List), listElems[0].GetConvertedType())
435	assert.True(t, listElems[0].LogicalType.IsSetLIST())
436	assert.True(t, listElems[1].LogicalType.IsSetSTRING())
437	assert.True(t, listElems[2].LogicalType.IsSetDATE())
438	assert.True(t, listElems[3].LogicalType.IsSetJSON())
439	assert.True(t, listElems[4].LogicalType.IsSetUUID())
440	assert.True(t, listElems[5].LogicalType.IsSetTIMESTAMP())
441	assert.True(t, listElems[6].LogicalType.IsSetINTEGER())
442	assert.True(t, listElems[7].LogicalType.IsSetDECIMAL())
443
444	mapNode := MustGroup(NewGroupNodeLogical("map" /* name */, parquet.Repetitions.Required, []Node{}, MapLogicalType{}, -1 /* fieldID */))
445	mapElems := ToThrift(mapNode)
446	assert.Equal(t, "map", mapElems[0].Name)
447	assert.True(t, mapElems[0].IsSetConvertedType())
448	assert.True(t, mapElems[0].IsSetLogicalType())
449	assert.Equal(t, format.ConvertedType(ConvertedTypes.Map), mapElems[0].GetConvertedType())
450	assert.True(t, mapElems[0].LogicalType.IsSetMAP())
451}
452
453func TestLogicalTypeSerializationRoundTrip(t *testing.T) {
454	tests := []struct {
455		name     string
456		logical  LogicalType
457		physical parquet.Type
458		len      int
459	}{
460		{"string", StringLogicalType{}, parquet.Types.ByteArray, -1},
461		{"enum", EnumLogicalType{}, parquet.Types.ByteArray, -1},
462		{"decimal", NewDecimalLogicalType(16, 6), parquet.Types.Int64, -1},
463		{"date", DateLogicalType{}, parquet.Types.Int32, -1},
464		{"time_T_ms", NewTimeLogicalType(true, TimeUnitMillis), parquet.Types.Int32, -1},
465		{"time_T_us", NewTimeLogicalType(true, TimeUnitMicros), parquet.Types.Int64, -1},
466		{"time_T_ns", NewTimeLogicalType(true, TimeUnitNanos), parquet.Types.Int64, -1},
467		{"time_F_ms", NewTimeLogicalType(false, TimeUnitMillis), parquet.Types.Int32, -1},
468		{"time_F_us", NewTimeLogicalType(false, TimeUnitMicros), parquet.Types.Int64, -1},
469		{"time_F_ns", NewTimeLogicalType(false, TimeUnitNanos), parquet.Types.Int64, -1},
470		{"timestamp_T_ms", NewTimestampLogicalType(true, TimeUnitMillis), parquet.Types.Int64, -1},
471		{"timestamp_T_us", NewTimestampLogicalType(true, TimeUnitMicros), parquet.Types.Int64, -1},
472		{"timestamp_T_ns", NewTimestampLogicalType(true, TimeUnitNanos), parquet.Types.Int64, -1},
473		{"timestamp_F_ms", NewTimestampLogicalType(false, TimeUnitMillis), parquet.Types.Int64, -1},
474		{"timestamp_F_us", NewTimestampLogicalType(false, TimeUnitMicros), parquet.Types.Int64, -1},
475		{"timestamp_F_ns", NewTimestampLogicalType(false, TimeUnitNanos), parquet.Types.Int64, -1},
476		{"interval", IntervalLogicalType{}, parquet.Types.FixedLenByteArray, 12},
477		{"uint8", NewIntLogicalType(8, false), parquet.Types.Int32, -1},
478		{"uint16", NewIntLogicalType(16, false), parquet.Types.Int32, -1},
479		{"uint32", NewIntLogicalType(32, false), parquet.Types.Int32, -1},
480		{"uint64", NewIntLogicalType(64, false), parquet.Types.Int64, -1},
481		{"int8", NewIntLogicalType(8, true), parquet.Types.Int32, -1},
482		{"int16", NewIntLogicalType(16, true), parquet.Types.Int32, -1},
483		{"int32", NewIntLogicalType(32, true), parquet.Types.Int32, -1},
484		{"int64", NewIntLogicalType(64, true), parquet.Types.Int64, -1},
485		{"null", NullLogicalType{}, parquet.Types.Boolean, -1},
486		{"json", JSONLogicalType{}, parquet.Types.ByteArray, -1},
487		{"bson", BSONLogicalType{}, parquet.Types.ByteArray, -1},
488		{"uuid", UUIDLogicalType{}, parquet.Types.FixedLenByteArray, 16},
489		{"none", NoLogicalType{}, parquet.Types.Boolean, -1},
490	}
491
492	for _, tt := range tests {
493		t.Run(tt.name, func(t *testing.T) {
494			n := MustPrimitive(NewPrimitiveNodeLogical("something" /* name */, parquet.Repetitions.Required, tt.logical, tt.physical, tt.len, -1 /* fieldID */))
495			elem := n.toThrift()
496			recover := MustPrimitive(PrimitiveNodeFromThrift(elem))
497			assert.True(t, n.Equals(recover))
498		})
499	}
500
501	n := MustGroup(NewGroupNodeLogical("map" /* name */, parquet.Repetitions.Required, []Node{}, MapLogicalType{}, -1 /* fieldID */))
502	elem := n.toThrift()
503	recover := MustGroup(GroupNodeFromThrift(elem, []Node{}))
504	assert.True(t, recover.Equals(n))
505
506	n = MustGroup(NewGroupNodeLogical("list" /* name */, parquet.Repetitions.Required, []Node{}, ListLogicalType{}, -1 /* fieldID */))
507	elem = n.toThrift()
508	recover = MustGroup(GroupNodeFromThrift(elem, []Node{}))
509	assert.True(t, recover.Equals(n))
510}
511
512func TestSchemaElementConstruction(t *testing.T) {
513	suite.Run(t, new(SchemaElementConstructionSuite))
514}
515