Repository: polarsignals/frostdb
Pull request #632 (2023-12-15 02:14:31)

Define dynamic column as map[string]T
 No special tag is needed any field of type `map[string]T` where `T` is
`int64|bool|float64|string` is a dynamic column.

Example

```go
type Sample struct {
	Labels      map[string]string `frostdb:"labels,rle_dict,asc(1),null_first"`
}
```


Patch:
diff --git a/dynparquet/example.go b/dynparquet/example.go
index 55335d1..4fa4675 100644
--- a/dynparquet/example.go
+++ b/dynparquet/example.go
@@ -16,17 +16,12 @@ import (
 	schemav2pb "github.com/polarsignals/frostdb/gen/proto/go/frostdb/schema/v1alpha2"
 )
 
-type Label struct {
-	Name  string
-	Value string
-}
-
 type Sample struct {
-	ExampleType string      `frostdb:"example_type,rle_dict,asc(0)"`
-	Labels      []Label     `frostdb:"labels,rle_dict,null,dyn,asc(1),null_first"`
-	Stacktrace  []uuid.UUID `frostdb:"stacktrace,rle_dict,asc(3),null_first"`
-	Timestamp   int64       `frostdb:"timestamp,asc(2)"`
-	Value       int64       `frostdb:"value"`
+	ExampleType string            `frostdb:"example_type,rle_dict,asc(0)"`
+	Labels      map[string]string `frostdb:"labels,rle_dict,asc(1),null_first"`
+	Stacktrace  []uuid.UUID       `frostdb:"stacktrace,rle_dict,asc(3),null_first"`
+	Timestamp   int64             `frostdb:"timestamp,asc(2)"`
+	Value       int64             `frostdb:"value"`
 }
 
 type Samples []Sample
@@ -66,14 +61,14 @@ func (s Samples) ToRecord() (arrow.Record, error) {
 	}
 	seen := map[string]struct{}{}
 	for _, smpl := range s {
-		for _, lbl := range smpl.Labels {
-			if _, ok := seen[lbl.Name]; ok {
+		for lbl := range smpl.Labels {
+			if _, ok := seen[lbl]; ok {
 				continue
 			}
-			seen[lbl.Name] = struct{}{}
+			seen[lbl] = struct{}{}
 			fields = append(fields,
 				arrow.Field{
-					Name:     "labels." + lbl.Name,
+					Name:     "labels." + lbl,
 					Nullable: true,
 					Type: &arrow.DictionaryType{
 						IndexType: &arrow.Int32Type{},
@@ -106,9 +101,9 @@ func (s Samples) ToRecord() (arrow.Record, error) {
 		}
 		for i := 0; i < numLabels; i++ {
 			found := false
-			for _, lbl := range sample.Labels {
-				if "labels."+lbl.Name == schema.Field(i+1).Name {
-					if err := bld.Field(i + 1).(*array.BinaryDictionaryBuilder).Append([]byte(lbl.Value)); err != nil {
+			for lbl, value := range sample.Labels {
+				if "labels."+lbl == schema.Field(i+1).Name {
+					if err := bld.Field(i + 1).(*array.BinaryDictionaryBuilder).Append([]byte(value)); err != nil {
 						return nil, fmt.Errorf("failed to append value: %v", err)
 					}
 					found = true
@@ -135,10 +130,10 @@ func (s Samples) SampleLabelNames() []string {
 	seen := map[string]struct{}{}
 
 	for _, sample := range s {
-		for _, label := range sample.Labels {
-			if _, ok := seen[label.Name]; !ok {
-				names = append(names, label.Name)
-				seen[label.Name] = struct{}{}
+		for label := range sample.Labels {
+			if _, ok := seen[label]; !ok {
+				names = append(names, label)
+				seen[label] = struct{}{}
 			}
 		}
 	}
@@ -159,11 +154,10 @@ func (s Sample) ToParquetRow(labelNames []string) parquet.Row {
 
 	i, j := 0, 0
 	for i < nameNumber {
-		if labelNames[i] == s.Labels[j].Name {
-			row = append(row, parquet.ValueOf(s.Labels[j].Value).Level(0, 1, i+1))
+		if value, ok := s.Labels[labelNames[i]]; ok {
+			row = append(row, parquet.ValueOf(value).Level(0, 1, i+1))
 			i++
 			j++
-
 			if j >= labelLen {
 				for ; i < nameNumber; i++ {
 					row = append(row, parquet.ValueOf(nil).Level(0, 0, i+1))
@@ -333,10 +327,9 @@ func NewTestSamples() Samples {
 	return Samples{
 		{
 			ExampleType: "cpu",
-			Labels: []Label{{
-				Name:  "node",
-				Value: "test3",
-			}},
+			Labels: map[string]string{
+				"node": "test3",
+			},
 			Stacktrace: []uuid.UUID{
 				{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
 				{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2},
@@ -345,13 +338,10 @@ func NewTestSamples() Samples {
 			Value:     5,
 		}, {
 			ExampleType: "cpu",
-			Labels: []Label{{
-				Name:  "namespace",
-				Value: "default",
-			}, {
-				Name:  "pod",
-				Value: "test1",
-			}},
+			Labels: map[string]string{
+				"namespace": "default",
+				"pod":       "test1",
+			},
 			Stacktrace: []uuid.UUID{
 				{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
 				{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2},
@@ -360,13 +350,10 @@ func NewTestSamples() Samples {
 			Value:     3,
 		}, {
 			ExampleType: "cpu",
-			Labels: []Label{{
-				Name:  "container",
-				Value: "test2",
-			}, {
-				Name:  "namespace",
-				Value: "default",
-			}},
+			Labels: map[string]string{
+				"container": "test2",
+				"namespace": "default",
+			},
 			Stacktrace: []uuid.UUID{
 				{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
 				{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2},
@@ -383,10 +370,9 @@ func GenerateTestSamples(n int) Samples {
 		s = append(s,
 			Sample{
 				ExampleType: "cpu",
-				Labels: []Label{{
-					Name:  "node",
-					Value: "test3",
-				}},
+				Labels: map[string]string{
+					"node": "test3",
+				},
 				Stacktrace: []uuid.UUID{
 					{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
 					{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2},
diff --git a/dynparquet/record_builder.go b/dynparquet/record_builder.go
index f60ba51..017e1d8 100644
--- a/dynparquet/record_builder.go
+++ b/dynparquet/record_builder.go
@@ -27,8 +27,6 @@ const (
 //
 // This api is opinionated.
 //
-//   - Dynamic columns are supported but only for  maps and struct slices
-//
 //   - Nested Columns are not supported
 //
 // # Tags
@@ -52,7 +50,6 @@ const (
 //	                   desc | Sorts in descending order.Use desc(n) where n is an integer for sorting order
 //	                lz4_raw | LZ4_RAW compression.
 //	               pre_hash | Prehash the column before storing it.
-//	                    dyn | Whether the column can dynamically expand.
 //	                   null | Nullable column.
 //	             null_first | When used wit asc nulls are smallest and with des nulls are largest.
 //	                   zstd | ZSTD compression.
@@ -71,19 +68,11 @@ const (
 //
 // # Dynamic columns
 //
-// Any field of type map<string, T> is a dynamic column by default. You can  also use
-// a list of structs with two fields , the first field of string type will be
-// used as column nave and the second field will be used as column value.
+// Field of type map<string, T> is a dynamic column by default.
 //
 //	type Example struct {
-//		// Adding dyn tag is optional for map fields. Map keys must be string any other
-//		// type will result in a panic.
-//		// Use supported tags to customize the column value
-//		Dyn00 map[string]string `frostdb:"dyn_00"`
-//
-//		// It is required to set dyn tag. Omitting the dyn tag will result in a panic.
 //		// Use supported tags to customize the column value
-//		Labels []Label `frostdb:"labels,dyn"`
+//		Labels map[string]string `frostdb:"labels"`
 //	}
 //
 // # Repeated columns
@@ -112,7 +101,6 @@ func NewBuild[T any](mem memory.Allocator) *Build[T] {
 	for i := 0; i < r.NumField(); i++ {
 		f := r.Field(i)
 		var (
-			dynamic    bool
 			typ        arrow.DataType
 			dictionary bool
 			preHash    bool
@@ -130,8 +118,6 @@ func NewBuild[T any](mem memory.Allocator) *Build[T] {
 		if tag != "" {
 			walkTag(tag, func(key, value string) {
 				switch key {
-				case "dyn":
-					dynamic = true
 				case "null":
 					null = true
 				case "null_first":
@@ -172,7 +158,6 @@ func NewBuild[T any](mem memory.Allocator) *Build[T] {
 		}
 		fr := &fieldRecord{
 			name:        name,
-			dynamic:     dynamic,
 			preHash:     preHash,
 			nullable:    null,
 			sort:        sortColumn,
@@ -198,22 +183,6 @@ func NewBuild[T any](mem memory.Allocator) *Build[T] {
 			case isUUIDSlice(fty):
 				fr.typ = schemapb.StorageLayout_TYPE_STRING
 				fr.build = newUUIDSliceField(mem, name)
-			case dynamic:
-				elem := fty.Elem()
-				for elem.Kind() == reflect.Ptr {
-					elem = elem.Elem()
-				}
-				if elem.Kind() != reflect.Struct {
-					panic("frostdb/dynschema: only structs are supported for struct list ")
-				}
-				if elem.NumField() != 2 {
-					panic("frostdb/dynschema: mismatch number of fields for slices columns ")
-				}
-				typ, fr.typ = baseType(elem.Field(1).Type, dictionary)
-				fr.dynamic = true
-				// Dynamic columns are nullable
-				fr.nullable = true
-				fr.build = newMapFieldBuilder(newFieldFunc(typ, mem, name))
 			default:
 				typ, styp = baseType(fty.Elem(), dictionary)
 				fr.typ = styp


Test patch:
diff --git a/aggregate_test.go b/aggregate_test.go
index f067510..d4b6588 100644
--- a/aggregate_test.go
+++ b/aggregate_test.go
@@ -38,8 +38,8 @@ func TestAggregateInconsistentSchema(t *testing.T) {
 	require.NoError(t, err)
 
 	samples := dynparquet.Samples{{
-		Labels: []dynparquet.Label{
-			{Name: "label1", Value: "value1"},
+		Labels: map[string]string{
+			"label1": "value1",
 		},
 		Stacktrace: []uuid.UUID{
 			{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -48,8 +48,8 @@ func TestAggregateInconsistentSchema(t *testing.T) {
 		Timestamp: 1,
 		Value:     1,
 	}, {
-		Labels: []dynparquet.Label{
-			{Name: "label2", Value: "value2"},
+		Labels: map[string]string{
+			"label2": "value2",
 		},
 		Stacktrace: []uuid.UUID{
 			{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -58,8 +58,8 @@ func TestAggregateInconsistentSchema(t *testing.T) {
 		Timestamp: 2,
 		Value:     2,
 	}, {
-		Labels: []dynparquet.Label{
-			{Name: "label2", Value: "value2"},
+		Labels: map[string]string{
+			"label2": "value2",
 		},
 		Stacktrace: []uuid.UUID{
 			{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -161,9 +161,9 @@ func TestAggregationProjection(t *testing.T) {
 	require.NoError(t, err)
 
 	samples := dynparquet.Samples{{
-		Labels: []dynparquet.Label{
-			{Name: "label1", Value: "value1"},
-			{Name: "label2", Value: "value2"},
+		Labels: map[string]string{
+			"label1": "value1",
+			"label2": "value2",
 		},
 		Stacktrace: []uuid.UUID{
 			{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -172,10 +172,10 @@ func TestAggregationProjection(t *testing.T) {
 		Timestamp: 1,
 		Value:     1,
 	}, {
-		Labels: []dynparquet.Label{
-			{Name: "label1", Value: "value2"},
-			{Name: "label2", Value: "value2"},
-			{Name: "label3", Value: "value3"},
+		Labels: map[string]string{
+			"label1": "value2",
+			"label2": "value2",
+			"label3": "value3",
 		},
 		Stacktrace: []uuid.UUID{
 			{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -184,10 +184,10 @@ func TestAggregationProjection(t *testing.T) {
 		Timestamp: 2,
 		Value:     2,
 	}, {
-		Labels: []dynparquet.Label{
-			{Name: "label1", Value: "value3"},
-			{Name: "label2", Value: "value2"},
-			{Name: "label4", Value: "value4"},
+		Labels: map[string]string{
+			"label1": "value3",
+			"label2": "value2",
+			"label4": "value4",
 		},
 		Stacktrace: []uuid.UUID{
 			{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -354,9 +354,9 @@ func BenchmarkAggregation(b *testing.B) {
 		samples := make(dynparquet.Samples, 0, 10_000)
 		for i := 0; i < cap(samples); i++ {
 			samples = append(samples, dynparquet.Sample{
-				Labels: []dynparquet.Label{
-					{Name: "label1", Value: "value1"},
-					{Name: "label2", Value: "value" + strconv.Itoa(i%3)},
+				Labels: map[string]string{
+					"label1": "value1",
+					"label2": "value" + strconv.Itoa(i%3),
 				},
 				Stacktrace: []uuid.UUID{
 					{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
diff --git a/bench_test.go b/bench_test.go
index a10f6c3..c2082fd 100644
--- a/bench_test.go
+++ b/bench_test.go
@@ -421,10 +421,9 @@ func NewTestSamples(num int) dynparquet.Samples {
 		samples = append(samples,
 			dynparquet.Sample{
 				ExampleType: "cpu",
-				Labels: []dynparquet.Label{{
-					Name:  "node",
-					Value: "test3",
-				}},
+				Labels: map[string]string{
+					"node": "test3",
+				},
 				Stacktrace: []uuid.UUID{
 					{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
 					{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2},
diff --git a/db_test.go b/db_test.go
index 2940905..ad31d03 100644
--- a/db_test.go
+++ b/db_test.go
@@ -125,9 +125,9 @@ func TestDBWithWAL(t *testing.T) {
 
 	samples := dynparquet.Samples{{
 		ExampleType: "test",
-		Labels: []dynparquet.Label{
-			{Name: "label1", Value: "value1"},
-			{Name: "label2", Value: "value2"},
+		Labels: map[string]string{
+			"label1": "value1",
+			"label2": "value2",
 		},
 		Stacktrace: []uuid.UUID{
 			{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -137,10 +137,10 @@ func TestDBWithWAL(t *testing.T) {
 		Value:     1,
 	}, {
 		ExampleType: "test",
-		Labels: []dynparquet.Label{
-			{Name: "label1", Value: "value2"},
-			{Name: "label2", Value: "value2"},
-			{Name: "label3", Value: "value3"},
+		Labels: map[string]string{
+			"label1": "value2",
+			"label2": "value2",
+			"label3": "value3",
 		},
 		Stacktrace: []uuid.UUID{
 			{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -150,10 +150,10 @@ func TestDBWithWAL(t *testing.T) {
 		Value:     2,
 	}, {
 		ExampleType: "test",
-		Labels: []dynparquet.Label{
-			{Name: "label1", Value: "value3"},
-			{Name: "label2", Value: "value2"},
-			{Name: "label4", Value: "value4"},
+		Labels: map[string]string{
+			"label1": "value3",
+			"label2": "value2",
+			"label4": "value4",
 		},
 		Stacktrace: []uuid.UUID{
 			{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -171,9 +171,9 @@ func TestDBWithWAL(t *testing.T) {
 
 	samples = dynparquet.Samples{{
 		ExampleType: "test",
-		Labels: []dynparquet.Label{
-			{Name: "label1", Value: "value1"},
-			{Name: "label2", Value: "value2"},
+		Labels: map[string]string{
+			"label1": "value1",
+			"label2": "value2",
 		},
 		Stacktrace: []uuid.UUID{
 			{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -191,10 +191,10 @@ func TestDBWithWAL(t *testing.T) {
 
 	samples = dynparquet.Samples{{
 		ExampleType: "test",
-		Labels: []dynparquet.Label{
-			{Name: "label1", Value: "value1"},
-			{Name: "label2", Value: "value2"},
-			{Name: "label3", Value: "value3"},
+		Labels: map[string]string{
+			"label1": "value1",
+			"label2": "value2",
+			"label3": "value3",
 		},
 		Stacktrace: []uuid.UUID{
 			{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -285,9 +285,9 @@ func Test_DB_WithStorage(t *testing.T) {
 
 	samples := dynparquet.Samples{{
 		ExampleType: "test",
-		Labels: []dynparquet.Label{
-			{Name: "label1", Value: "value1"},
-			{Name: "label2", Value: "value2"},
+		Labels: map[string]string{
+			"label1": "value1",
+			"label2": "value2",
 		},
 		Stacktrace: []uuid.UUID{
 			{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -297,10 +297,10 @@ func Test_DB_WithStorage(t *testing.T) {
 		Value:     1,
 	}, {
 		ExampleType: "test",
-		Labels: []dynparquet.Label{
-			{Name: "label1", Value: "value2"},
-			{Name: "label2", Value: "value2"},
-			{Name: "label3", Value: "value3"},
+		Labels: map[string]string{
+			"label1": "value2",
+			"label2": "value2",
+			"label3": "value3",
 		},
 		Stacktrace: []uuid.UUID{
 			{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -310,10 +310,10 @@ func Test_DB_WithStorage(t *testing.T) {
 		Value:     2,
 	}, {
 		ExampleType: "test",
-		Labels: []dynparquet.Label{
-			{Name: "label1", Value: "value3"},
-			{Name: "label2", Value: "value2"},
-			{Name: "label4", Value: "value4"},
+		Labels: map[string]string{
+			"label1": "value3",
+			"label2": "value2",
+			"label4": "value4",
 		},
 		Stacktrace: []uuid.UUID{
 			{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -424,9 +424,9 @@ func Test_DB_ColdStart(t *testing.T) {
 			samples := dynparquet.Samples{
 				{
 					ExampleType: "test",
-					Labels: []dynparquet.Label{
-						{Name: "label1", Value: "value1"},
-						{Name: "label2", Value: "value2"},
+					Labels: map[string]string{
+						"label1": "value1",
+						"label2": "value2",
 					},
 					Stacktrace: []uuid.UUID{
 						{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -437,9 +437,9 @@ func Test_DB_ColdStart(t *testing.T) {
 				},
 				{
 					ExampleType: "test",
-					Labels: []dynparquet.Label{
-						{Name: "label1", Value: "value1"},
-						{Name: "label2", Value: "value2"},
+					Labels: map[string]string{
+						"label1": "value1",
+						"label2": "value2",
 					},
 					Stacktrace: []uuid.UUID{
 						{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -450,9 +450,9 @@ func Test_DB_ColdStart(t *testing.T) {
 				},
 				{
 					ExampleType: "test",
-					Labels: []dynparquet.Label{
-						{Name: "label1", Value: "value1"},
-						{Name: "label2", Value: "value2"},
+					Labels: map[string]string{
+						"label1": "value1",
+						"label2": "value2",
 					},
 					Stacktrace: []uuid.UUID{
 						{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -685,9 +685,9 @@ func Test_DB_Filter_Block(t *testing.T) {
 			samples := dynparquet.Samples{
 				{
 					ExampleType: "test",
-					Labels: []dynparquet.Label{
-						{Name: "label1", Value: "value1"},
-						{Name: "label2", Value: "value2"},
+					Labels: map[string]string{
+						"label1": "value1",
+						"label2": "value2",
 					},
 					Stacktrace: []uuid.UUID{
 						{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -698,9 +698,9 @@ func Test_DB_Filter_Block(t *testing.T) {
 				},
 				{
 					ExampleType: "test",
-					Labels: []dynparquet.Label{
-						{Name: "label1", Value: "value1"},
-						{Name: "label2", Value: "value2"},
+					Labels: map[string]string{
+						"label1": "value1",
+						"label2": "value2",
 					},
 					Stacktrace: []uuid.UUID{
 						{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -711,9 +711,9 @@ func Test_DB_Filter_Block(t *testing.T) {
 				},
 				{
 					ExampleType: "test",
-					Labels: []dynparquet.Label{
-						{Name: "label1", Value: "value1"},
-						{Name: "label2", Value: "value2"},
+					Labels: map[string]string{
+						"label1": "value1",
+						"label2": "value2",
 					},
 					Stacktrace: []uuid.UUID{
 						{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -970,9 +970,9 @@ func Test_DB_Block_Optimization(t *testing.T) {
 			samples := dynparquet.Samples{
 				{
 					ExampleType: "test",
-					Labels: []dynparquet.Label{
-						{Name: "label1", Value: "value1"},
-						{Name: "label2", Value: "value2"},
+					Labels: map[string]string{
+						"label1": "value1",
+						"label2": "value2",
 					},
 					Stacktrace: []uuid.UUID{
 						{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -983,9 +983,9 @@ func Test_DB_Block_Optimization(t *testing.T) {
 				},
 				{
 					ExampleType: "test",
-					Labels: []dynparquet.Label{
-						{Name: "label1", Value: "value1"},
-						{Name: "label2", Value: "value2"},
+					Labels: map[string]string{
+						"label1": "value1",
+						"label2": "value2",
 					},
 					Stacktrace: []uuid.UUID{
 						{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -996,9 +996,9 @@ func Test_DB_Block_Optimization(t *testing.T) {
 				},
 				{
 					ExampleType: "test",
-					Labels: []dynparquet.Label{
-						{Name: "label1", Value: "value1"},
-						{Name: "label2", Value: "value2"},
+					Labels: map[string]string{
+						"label1": "value1",
+						"label2": "value2",
 					},
 					Stacktrace: []uuid.UUID{
 						{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -1119,9 +1119,9 @@ func Test_DB_TableWrite_DynamicSchema(t *testing.T) {
 	samples := dynparquet.Samples{
 		{
 			ExampleType: "test",
-			Labels: []dynparquet.Label{
-				{Name: "label1", Value: "value1"},
-				{Name: "label2", Value: "value2"},
+			Labels: map[string]string{
+				"label1": "value1",
+				"label2": "value2",
 			},
 			Stacktrace: []uuid.UUID{
 				{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -1132,10 +1132,10 @@ func Test_DB_TableWrite_DynamicSchema(t *testing.T) {
 		},
 		{
 			ExampleType: "test",
-			Labels: []dynparquet.Label{
-				{Name: "label1", Value: "value1"},
-				{Name: "label2", Value: "value2"},
-				{Name: "label3", Value: "value3"},
+			Labels: map[string]string{
+				"label1": "value1",
+				"label2": "value2",
+				"label3": "value3",
 			},
 			Stacktrace: []uuid.UUID{
 				{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -1146,9 +1146,9 @@ func Test_DB_TableWrite_DynamicSchema(t *testing.T) {
 		},
 		{
 			ExampleType: "test",
-			Labels: []dynparquet.Label{
-				{Name: "label1", Value: "value1"},
-				{Name: "label2", Value: "value2"},
+			Labels: map[string]string{
+				"label1": "value1",
+				"label2": "value2",
 			},
 			Stacktrace: []uuid.UUID{
 				{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -1219,9 +1219,9 @@ func Test_DB_TableWrite_ArrowRecord(t *testing.T) {
 		samples := dynparquet.Samples{
 			{
 				ExampleType: "test",
-				Labels: []dynparquet.Label{
-					{Name: "label1", Value: "value1"},
-					{Name: "label2", Value: "value2"},
+				Labels: map[string]string{
+					"label1": "value1",
+					"label2": "value2",
 				},
 				Stacktrace: []uuid.UUID{
 					{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -1232,10 +1232,10 @@ func Test_DB_TableWrite_ArrowRecord(t *testing.T) {
 			},
 			{
 				ExampleType: "test",
-				Labels: []dynparquet.Label{
-					{Name: "label1", Value: "value1"},
-					{Name: "label2", Value: "value2"},
-					{Name: "label3", Value: "value3"},
+				Labels: map[string]string{
+					"label1": "value1",
+					"label2": "value2",
+					"label3": "value3",
 				},
 				Stacktrace: []uuid.UUID{
 					{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -1246,9 +1246,9 @@ func Test_DB_TableWrite_ArrowRecord(t *testing.T) {
 			},
 			{
 				ExampleType: "test",
-				Labels: []dynparquet.Label{
-					{Name: "label1", Value: "value1"},
-					{Name: "label2", Value: "value2"},
+				Labels: map[string]string{
+					"label1": "value1",
+					"label2": "value2",
 				},
 				Stacktrace: []uuid.UUID{
 					{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -2026,9 +2026,9 @@ func Test_DB_All(t *testing.T) {
 
 	samples := dynparquet.Samples{{
 		ExampleType: "test",
-		Labels: []dynparquet.Label{
-			{Name: "label1", Value: "value1"},
-			{Name: "label2", Value: "value2"},
+		Labels: map[string]string{
+			"label1": "value1",
+			"label2": "value2",
 		},
 		Stacktrace: []uuid.UUID{
 			{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -2038,10 +2038,10 @@ func Test_DB_All(t *testing.T) {
 		Value:     1,
 	}, {
 		ExampleType: "test",
-		Labels: []dynparquet.Label{
-			{Name: "label1", Value: "value2"},
-			{Name: "label2", Value: "value2"},
-			{Name: "label3", Value: "value3"},
+		Labels: map[string]string{
+			"label1": "value2",
+			"label2": "value2",
+			"label3": "value3",
 		},
 		Stacktrace: []uuid.UUID{
 			{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -2051,10 +2051,10 @@ func Test_DB_All(t *testing.T) {
 		Value:     2,
 	}, {
 		ExampleType: "test",
-		Labels: []dynparquet.Label{
-			{Name: "label1", Value: "value3"},
-			{Name: "label2", Value: "value2"},
-			{Name: "label4", Value: "value4"},
+		Labels: map[string]string{
+			"label1": "value3",
+			"label2": "value2",
+			"label4": "value4",
 		},
 		Stacktrace: []uuid.UUID{
 			{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -2107,9 +2107,9 @@ func Test_DB_PrehashedStorage(t *testing.T) {
 
 	samples := dynparquet.Samples{{
 		ExampleType: "test",
-		Labels: []dynparquet.Label{
-			{Name: "label1", Value: "value1"},
-			{Name: "label2", Value: "value2"},
+		Labels: map[string]string{
+			"label1": "value1",
+			"label2": "value2",
 		},
 		Stacktrace: []uuid.UUID{
 			{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -2119,10 +2119,10 @@ func Test_DB_PrehashedStorage(t *testing.T) {
 		Value:     1,
 	}, {
 		ExampleType: "test",
-		Labels: []dynparquet.Label{
-			{Name: "label1", Value: "value2"},
-			{Name: "label2", Value: "value2"},
-			{Name: "label3", Value: "value3"},
+		Labels: map[string]string{
+			"label1": "value2",
+			"label2": "value2",
+			"label3": "value3",
 		},
 		Stacktrace: []uuid.UUID{
 			{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -2132,10 +2132,10 @@ func Test_DB_PrehashedStorage(t *testing.T) {
 		Value:     2,
 	}, {
 		ExampleType: "test",
-		Labels: []dynparquet.Label{
-			{Name: "label1", Value: "value3"},
-			{Name: "label2", Value: "value2"},
-			{Name: "label4", Value: "value4"},
+		Labels: map[string]string{
+			"label1": "value3",
+			"label2": "value2",
+			"label4": "value4",
 		},
 		Stacktrace: []uuid.UUID{
 			{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
diff --git a/dynparquet/row_test.go b/dynparquet/row_test.go
index 1baec65..31f2e6c 100644
--- a/dynparquet/row_test.go
+++ b/dynparquet/row_test.go
@@ -187,10 +187,7 @@ func TestLess(t *testing.T) {
 		schema := NewSampleSchema()
 		sample := Sample{
 			ExampleType: "cpu",
-			Labels: []Label{{
-				Name:  "node",
-				Value: "test3",
-			}},
+			Labels:      map[string]string{"node": "test3"},
 			Stacktrace: []uuid.UUID{
 				{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
 				{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2},
@@ -231,8 +228,8 @@ func TestLess(t *testing.T) {
 func TestLessWithDynamicSchemas(t *testing.T) {
 	schema := NewSampleSchema()
 	samples := Samples{{
-		Labels: []Label{
-			{Name: "label12", Value: "value12"},
+		Labels: map[string]string{
+			"label12": "value12",
 		},
 		Stacktrace: []uuid.UUID{
 			{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -241,8 +238,8 @@ func TestLessWithDynamicSchemas(t *testing.T) {
 		Timestamp: 2,
 		Value:     2,
 	}, {
-		Labels: []Label{
-			{Name: "label14", Value: "value14"},
+		Labels: map[string]string{
+			"label14": "value14",
 		},
 		Stacktrace: []uuid.UUID{
 			{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
diff --git a/dynparquet/schema_test.go b/dynparquet/schema_test.go
index b81f71a..7c2a1f8 100644
--- a/dynparquet/schema_test.go
+++ b/dynparquet/schema_test.go
@@ -162,9 +162,9 @@ func TestMultipleIterations(t *testing.T) {
 	schema := NewSampleSchema()
 
 	samples := Samples{{
-		Labels: []Label{
-			{Name: "label1", Value: "value1"},
-			{Name: "label2", Value: "value2"},
+		Labels: map[string]string{
+			"label1": "value1",
+			"label2": "value2",
 		},
 		Stacktrace: []uuid.UUID{
 			{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -173,10 +173,10 @@ func TestMultipleIterations(t *testing.T) {
 		Timestamp: 1,
 		Value:     1,
 	}, {
-		Labels: []Label{
-			{Name: "label1", Value: "value2"},
-			{Name: "label2", Value: "value2"},
-			{Name: "label3", Value: "value3"},
+		Labels: map[string]string{
+			"label1": "value2",
+			"label2": "value2",
+			"label3": "value3",
 		},
 		Stacktrace: []uuid.UUID{
 			{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -185,10 +185,10 @@ func TestMultipleIterations(t *testing.T) {
 		Timestamp: 2,
 		Value:     2,
 	}, {
-		Labels: []Label{
-			{Name: "label1", Value: "value3"},
-			{Name: "label2", Value: "value2"},
-			{Name: "label4", Value: "value4"},
+		Labels: map[string]string{
+			"label1": "value3",
+			"label2": "value2",
+			"label4": "value4",
 		},
 		Stacktrace: []uuid.UUID{
 			{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -235,9 +235,9 @@ func Test_SchemaFromParquetFile(t *testing.T) {
 	schema := NewSampleSchema()
 
 	samples := Samples{{
-		Labels: []Label{
-			{Name: "label1", Value: "value1"},
-			{Name: "label2", Value: "value2"},
+		Labels: map[string]string{
+			"label1": "value1",
+			"label2": "value2",
 		},
 		Stacktrace: []uuid.UUID{
 			{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -246,10 +246,10 @@ func Test_SchemaFromParquetFile(t *testing.T) {
 		Timestamp: 1,
 		Value:     1,
 	}, {
-		Labels: []Label{
-			{Name: "label1", Value: "value2"},
-			{Name: "label2", Value: "value2"},
-			{Name: "label3", Value: "value3"},
+		Labels: map[string]string{
+			"label1": "value2",
+			"label2": "value2",
+			"label3": "value3",
 		},
 		Stacktrace: []uuid.UUID{
 			{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -258,10 +258,10 @@ func Test_SchemaFromParquetFile(t *testing.T) {
 		Timestamp: 2,
 		Value:     2,
 	}, {
-		Labels: []Label{
-			{Name: "label1", Value: "value3"},
-			{Name: "label2", Value: "value2"},
-			{Name: "label4", Value: "value4"},
+		Labels: map[string]string{
+			"label1": "value3",
+			"label2": "value2",
+			"label4": "value4",
 		},
 		Stacktrace: []uuid.UUID{
 			{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
diff --git a/pqarrow/arrow_test.go b/pqarrow/arrow_test.go
index d39a88d..783c12e 100644
--- a/pqarrow/arrow_test.go
+++ b/pqarrow/arrow_test.go
@@ -20,9 +20,9 @@ func TestDifferentSchemasToArrow(t *testing.T) {
 	dynSchema := dynparquet.NewSampleSchema()
 
 	samples := dynparquet.Samples{{
-		Labels: []dynparquet.Label{
-			{Name: "label1", Value: "value1"},
-			{Name: "label2", Value: "value2"},
+		Labels: map[string]string{
+			"label1": "value1",
+			"label2": "value2",
 		},
 		Stacktrace: []uuid.UUID{
 			{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -36,10 +36,10 @@ func TestDifferentSchemasToArrow(t *testing.T) {
 	require.NoError(t, err)
 
 	samples = dynparquet.Samples{{
-		Labels: []dynparquet.Label{
-			{Name: "label1", Value: "value2"},
-			{Name: "label2", Value: "value2"},
-			{Name: "label3", Value: "value3"},
+		Labels: map[string]string{
+			"label1": "value2",
+			"label2": "value2",
+			"label3": "value3",
 		},
 		Stacktrace: []uuid.UUID{
 			{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -53,10 +53,10 @@ func TestDifferentSchemasToArrow(t *testing.T) {
 	require.NoError(t, err)
 
 	samples = dynparquet.Samples{{
-		Labels: []dynparquet.Label{
-			{Name: "label1", Value: "value3"},
-			{Name: "label2", Value: "value2"},
-			{Name: "label4", Value: "value4"},
+		Labels: map[string]string{
+			"label1": "value3",
+			"label2": "value2",
+			"label4": "value4",
 		},
 		Stacktrace: []uuid.UUID{
 			{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -70,9 +70,9 @@ func TestDifferentSchemasToArrow(t *testing.T) {
 	require.NoError(t, err)
 
 	samples = dynparquet.Samples{{
-		Labels: []dynparquet.Label{
-			{Name: "label1", Value: "value1"},
-			{Name: "label2", Value: "value2"},
+		Labels: map[string]string{
+			"label1": "value1",
+			"label2": "value2",
 		},
 		Stacktrace: []uuid.UUID{
 			{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -86,10 +86,10 @@ func TestDifferentSchemasToArrow(t *testing.T) {
 	require.NoError(t, err)
 
 	samples = dynparquet.Samples{{
-		Labels: []dynparquet.Label{
-			{Name: "label1", Value: "value1"},
-			{Name: "label2", Value: "value2"},
-			{Name: "label3", Value: "value3"},
+		Labels: map[string]string{
+			"label1": "value1",
+			"label2": "value2",
+			"label3": "value3",
 		},
 		Stacktrace: []uuid.UUID{
 			{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -148,9 +148,9 @@ func TestMergeToArrow(t *testing.T) {
 	dynSchema := dynparquet.NewSampleSchema()
 
 	samples := dynparquet.Samples{{
-		Labels: []dynparquet.Label{
-			{Name: "label1", Value: "value1"},
-			{Name: "label2", Value: "value2"},
+		Labels: map[string]string{
+			"label1": "value1",
+			"label2": "value2",
 		},
 		Stacktrace: []uuid.UUID{
 			{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -159,10 +159,10 @@ func TestMergeToArrow(t *testing.T) {
 		Timestamp: 1,
 		Value:     1,
 	}, {
-		Labels: []dynparquet.Label{
-			{Name: "label1", Value: "value2"},
-			{Name: "label2", Value: "value2"},
-			{Name: "label3", Value: "value3"},
+		Labels: map[string]string{
+			"label1": "value2",
+			"label2": "value2",
+			"label3": "value3",
 		},
 		Stacktrace: []uuid.UUID{
 			{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -171,10 +171,10 @@ func TestMergeToArrow(t *testing.T) {
 		Timestamp: 2,
 		Value:     2,
 	}, {
-		Labels: []dynparquet.Label{
-			{Name: "label1", Value: "value3"},
-			{Name: "label2", Value: "value2"},
-			{Name: "label4", Value: "value4"},
+		Labels: map[string]string{
+			"label1": "value3",
+			"label2": "value2",
+			"label4": "value4",
 		},
 		Stacktrace: []uuid.UUID{
 			{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -188,9 +188,9 @@ func TestMergeToArrow(t *testing.T) {
 	require.NoError(t, err)
 
 	samples = dynparquet.Samples{{
-		Labels: []dynparquet.Label{
-			{Name: "label1", Value: "value1"},
-			{Name: "label2", Value: "value2"},
+		Labels: map[string]string{
+			"label1": "value1",
+			"label2": "value2",
 		},
 		Stacktrace: []uuid.UUID{
 			{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -204,10 +204,10 @@ func TestMergeToArrow(t *testing.T) {
 	require.NoError(t, err)
 
 	samples = dynparquet.Samples{{
-		Labels: []dynparquet.Label{
-			{Name: "label1", Value: "value1"},
-			{Name: "label2", Value: "value2"},
-			{Name: "label3", Value: "value3"},
+		Labels: map[string]string{
+			"label1": "value1",
+			"label2": "value2",
+			"label3": "value3",
 		},
 		Stacktrace: []uuid.UUID{
 			{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -304,9 +304,9 @@ func BenchmarkParquetToArrow(b *testing.B) {
 	samples := make(dynparquet.Samples, 0, 1000)
 	for i := 0; i < 1000; i++ {
 		samples = append(samples, dynparquet.Sample{
-			Labels: []dynparquet.Label{
-				{Name: "label1", Value: "value1"},
-				{Name: "label2", Value: "value2"},
+			Labels: map[string]string{
+				"label1": "value1",
+				"label2": "value2",
 			},
 			Stacktrace: []uuid.UUID{
 				{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -411,9 +411,9 @@ func TestDistinctBinaryExprOptimization(t *testing.T) {
 	dynSchema := dynparquet.NewSampleSchema()
 
 	samples := dynparquet.Samples{{
-		Labels: []dynparquet.Label{
-			{Name: "label1", Value: "value1"},
-			{Name: "label2", Value: "value2"},
+		Labels: map[string]string{
+			"label1": "value1",
+			"label2": "value2",
 		},
 		Stacktrace: []uuid.UUID{
 			{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -422,9 +422,9 @@ func TestDistinctBinaryExprOptimization(t *testing.T) {
 		Timestamp: 1,
 		Value:     1,
 	}, {
-		Labels: []dynparquet.Label{
-			{Name: "label1", Value: "value1"},
-			{Name: "label2", Value: "value2"},
+		Labels: map[string]string{
+			"label1": "value1",
+			"label2": "value2",
 		},
 		Stacktrace: []uuid.UUID{
 			{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -485,9 +485,9 @@ func TestDistinctBinaryExprOptimizationMixed(t *testing.T) {
 	dynSchema := dynparquet.NewSampleSchema()
 
 	samples := dynparquet.Samples{{
-		Labels: []dynparquet.Label{
-			{Name: "label1", Value: "value1"},
-			{Name: "label2", Value: "value2"},
+		Labels: map[string]string{
+			"label1": "value1",
+			"label2": "value2",
 		},
 		Stacktrace: []uuid.UUID{
 			{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -496,9 +496,9 @@ func TestDistinctBinaryExprOptimizationMixed(t *testing.T) {
 		Timestamp: 1,
 		Value:     1,
 	}, {
-		Labels: []dynparquet.Label{
-			{Name: "label1", Value: "value1"},
-			{Name: "label2", Value: "value2"},
+		Labels: map[string]string{
+			"label1": "value1",
+			"label2": "value2",
 		},
 		Stacktrace: []uuid.UUID{
 			{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -507,9 +507,9 @@ func TestDistinctBinaryExprOptimizationMixed(t *testing.T) {
 		Timestamp: 2,
 		Value:     2,
 	}, {
-		Labels: []dynparquet.Label{
-			{Name: "label1", Value: "value1"},
-			{Name: "label2", Value: "value2"},
+		Labels: map[string]string{
+			"label1": "value1",
+			"label2": "value2",
 		},
 		Stacktrace: []uuid.UUID{
 			{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -518,9 +518,9 @@ func TestDistinctBinaryExprOptimizationMixed(t *testing.T) {
 		Timestamp: 3,
 		Value:     0,
 	}, {
-		Labels: []dynparquet.Label{
-			{Name: "label1", Value: "value1"},
-			{Name: "label2", Value: "value2"},
+		Labels: map[string]string{
+			"label1": "value1",
+			"label2": "value2",
 		},
 		Stacktrace: []uuid.UUID{
 			{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -817,9 +817,9 @@ func Test_ParquetToArrow(t *testing.T) {
 	samples := make(dynparquet.Samples, 0, 1000)
 	for i := 0; i < 1000; i++ {
 		samples = append(samples, dynparquet.Sample{
-			Labels: []dynparquet.Label{
-				{Name: "label1", Value: "value1"},
-				{Name: "label2", Value: "value2"},
+			Labels: map[string]string{
+				"label1": "value1",
+				"label2": "value2",
 			},
 			Stacktrace: []uuid.UUID{
 				{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
diff --git a/snapshot_test.go b/snapshot_test.go
index 465c92c..3f8bf54 100644
--- a/snapshot_test.go
+++ b/snapshot_test.go
@@ -28,8 +28,8 @@ func insertSampleRecords(ctx context.Context, t *testing.T, table *Table, timest
 	for _, ts := range timestamps {
 		samples = append(samples, dynparquet.Sample{
 			ExampleType: "ex",
-			Labels: []dynparquet.Label{
-				{Name: "label1", Value: "value1"},
+			Labels: map[string]string{
+				"label1": "value1",
 			},
 			Stacktrace: []uuid.UUID{
 				{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
diff --git a/table_test.go b/table_test.go
index cce1dc9..c8ca3f9 100644
--- a/table_test.go
+++ b/table_test.go
@@ -92,9 +92,9 @@ func Test_Table_Concurrency(t *testing.T) {
 				rows := make(dynparquet.Samples, 0, n)
 				for i := 0; i < n; i++ {
 					rows = append(rows, dynparquet.Sample{
-						Labels: []dynparquet.Label{ // TODO would be nice to not have all the same column
-							{Name: "label1", Value: "value1"},
-							{Name: "label2", Value: "value2"},
+						Labels: map[string]string{ // TODO would be nice to not have all the same column
+							"label1": "value1",
+							"label2": "value2",
 						},
 						Stacktrace: []uuid.UUID{
 							{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -199,9 +199,9 @@ func benchmarkTableInserts(b *testing.B, rows, iterations, writers int) {
 		rows := make(dynparquet.Samples, 0, n)
 		for i := 0; i < n; i++ {
 			rows = append(rows, dynparquet.Sample{
-				Labels: []dynparquet.Label{ // TODO would be nice to not have all the same column
-					{Name: "label1", Value: id},
-					{Name: "label2", Value: "value2"},
+				Labels: map[string]string{ // TODO would be nice to not have all the same column
+					"label1": id,
+					"label2": "value2",
 				},
 				Stacktrace: []uuid.UUID{
 					{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -286,9 +286,9 @@ func Test_Table_ReadIsolation(t *testing.T) {
 	defer c.Close()
 
 	samples := dynparquet.Samples{{
-		Labels: []dynparquet.Label{
-			{Name: "label1", Value: "value1"},
-			{Name: "label2", Value: "value2"},
+		Labels: map[string]string{
+			"label1": "value1",
+			"label2": "value2",
 		},
 		Stacktrace: []uuid.UUID{
 			{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -297,10 +297,10 @@ func Test_Table_ReadIsolation(t *testing.T) {
 		Timestamp: 1,
 		Value:     1,
 	}, {
-		Labels: []dynparquet.Label{
-			{Name: "label1", Value: "value2"},
-			{Name: "label2", Value: "value2"},
-			{Name: "label3", Value: "value3"},
+		Labels: map[string]string{
+			"label1": "value2",
+			"label2": "value2",
+			"label3": "value3",
 		},
 		Stacktrace: []uuid.UUID{
 			{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -309,10 +309,10 @@ func Test_Table_ReadIsolation(t *testing.T) {
 		Timestamp: 2,
 		Value:     2,
 	}, {
-		Labels: []dynparquet.Label{
-			{Name: "label1", Value: "value3"},
-			{Name: "label2", Value: "value2"},
-			{Name: "label4", Value: "value4"},
+		Labels: map[string]string{
+			"label1": "value3",
+			"label2": "value2",
+			"label4": "value4",
 		},
 		Stacktrace: []uuid.UUID{
 			{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -332,9 +332,9 @@ func Test_Table_ReadIsolation(t *testing.T) {
 
 	// Perform a new insert that will have a higher tx id
 	samples = dynparquet.Samples{{
-		Labels: []dynparquet.Label{
-			{Name: "blarg", Value: "blarg"},
-			{Name: "blah", Value: "blah"},
+		Labels: map[string]string{
+			"blarg": "blarg",
+			"blah":  "blah",
 		},
 		Stacktrace: []uuid.UUID{
 			{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -460,9 +460,9 @@ func Test_Table_Bloomfilter(t *testing.T) {
 
 	samples := dynparquet.Samples{{
 		ExampleType: "test",
-		Labels: []dynparquet.Label{
-			{Name: "label1", Value: "value1"},
-			{Name: "label2", Value: "value2"},
+		Labels: map[string]string{
+			"label1": "value1",
+			"label2": "value2",
 		},
 		Stacktrace: []uuid.UUID{
 			{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -472,10 +472,10 @@ func Test_Table_Bloomfilter(t *testing.T) {
 		Value:     1,
 	}, {
 		ExampleType: "test",
-		Labels: []dynparquet.Label{
-			{Name: "label1", Value: "value2"},
-			{Name: "label2", Value: "value2"},
-			{Name: "label3", Value: "value3"},
+		Labels: map[string]string{
+			"label1": "value2",
+			"label2": "value2",
+			"label3": "value3",
 		},
 		Stacktrace: []uuid.UUID{
 			{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -485,10 +485,10 @@ func Test_Table_Bloomfilter(t *testing.T) {
 		Value:     2,
 	}, {
 		ExampleType: "test",
-		Labels: []dynparquet.Label{
-			{Name: "label1", Value: "value3"},
-			{Name: "label2", Value: "value2"},
-			{Name: "label4", Value: "value4"},
+		Labels: map[string]string{
+			"label1": "value3",
+			"label2": "value2",
+			"label4": "value4",
 		},
 		Stacktrace: []uuid.UUID{
 			{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -580,9 +580,9 @@ func Test_L0Query(t *testing.T) {
 
 	samples := dynparquet.Samples{{
 		ExampleType: "test",
-		Labels: []dynparquet.Label{
-			{Name: "label1", Value: "value1"},
-			{Name: "label2", Value: "value2"},
+		Labels: map[string]string{
+			"label1": "value1",
+			"label2": "value2",
 		},
 		Stacktrace: []uuid.UUID{
 			{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -592,10 +592,10 @@ func Test_L0Query(t *testing.T) {
 		Value:     1,
 	}, {
 		ExampleType: "test",
-		Labels: []dynparquet.Label{
-			{Name: "label1", Value: "value2"},
-			{Name: "label2", Value: "value2"},
-			{Name: "label3", Value: "value3"},
+		Labels: map[string]string{
+			"label1": "value2",
+			"label2": "value2",
+			"label3": "value3",
 		},
 		Stacktrace: []uuid.UUID{
 			{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -605,10 +605,10 @@ func Test_L0Query(t *testing.T) {
 		Value:     2,
 	}, {
 		ExampleType: "test",
-		Labels: []dynparquet.Label{
-			{Name: "label1", Value: "value3"},
-			{Name: "label2", Value: "value2"},
-			{Name: "label4", Value: "value4"},
+		Labels: map[string]string{
+			"label1": "value3",
+			"label2": "value2",
+			"label4": "value4",
 		},
 		Stacktrace: []uuid.UUID{
 			{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -653,9 +653,9 @@ func Test_Serialize_DisparateDynamicColumns(t *testing.T) {
 
 	samples := dynparquet.Samples{{
 		ExampleType: "test",
-		Labels: []dynparquet.Label{
-			{Name: "label1", Value: "value1"},
-			{Name: "label2", Value: "value2"},
+		Labels: map[string]string{
+			"label1": "value1",
+			"label2": "value2",
 		},
 		Stacktrace: []uuid.UUID{
 			{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -665,10 +665,10 @@ func Test_Serialize_DisparateDynamicColumns(t *testing.T) {
 		Value:     1,
 	}, {
 		ExampleType: "test",
-		Labels: []dynparquet.Label{
-			{Name: "label1", Value: "value2"},
-			{Name: "label2", Value: "value2"},
-			{Name: "label3", Value: "value3"},
+		Labels: map[string]string{
+			"label1": "value2",
+			"label2": "value2",
+			"label3": "value3",
 		},
 		Stacktrace: []uuid.UUID{
 			{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -678,10 +678,10 @@ func Test_Serialize_DisparateDynamicColumns(t *testing.T) {
 		Value:     2,
 	}, {
 		ExampleType: "test",
-		Labels: []dynparquet.Label{
-			{Name: "label1", Value: "value3"},
-			{Name: "label2", Value: "value2"},
-			{Name: "label4", Value: "value4"},
+		Labels: map[string]string{
+			"label1": "value3",
+			"label2": "value2",
+			"label4": "value4",
 		},
 		Stacktrace: []uuid.UUID{
 			{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},
@@ -700,8 +700,8 @@ func Test_Serialize_DisparateDynamicColumns(t *testing.T) {
 
 	samples = dynparquet.Samples{{
 		ExampleType: "test",
-		Labels: []dynparquet.Label{
-			{Name: "label100", Value: "a"},
+		Labels: map[string]string{
+			"label100": "a",
 		},
 		Stacktrace: []uuid.UUID{
 			{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1},