github.com/apache/beam/sdks/v2@v2.48.2/go/examples/snippets/06schemas_test.go (about)

     1  // Licensed to the Apache Software Foundation (ASF) under one or more
     2  // contributor license agreements.  See the NOTICE file distributed with
     3  // this work for additional information regarding copyright ownership.
     4  // The ASF licenses this file to You under the Apache License, Version 2.0
     5  // (the "License"); you may not use this file except in compliance with
     6  // the License.  You may obtain a copy of the License at
     7  //
     8  //    http://www.apache.org/licenses/LICENSE-2.0
     9  //
    10  // Unless required by applicable law or agreed to in writing, software
    11  // distributed under the License is distributed on an "AS IS" BASIS,
    12  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  // See the License for the specific language governing permissions and
    14  // limitations under the License.
    15  
    16  package snippets
    17  
    18  import (
    19  	"fmt"
    20  	"reflect"
    21  	"testing"
    22  	"time"
    23  
    24  	"github.com/apache/beam/sdks/v2/go/pkg/beam"
    25  	"github.com/apache/beam/sdks/v2/go/pkg/beam/core/graph/coder/testutil"
    26  	"github.com/apache/beam/sdks/v2/go/pkg/beam/core/runtime/graphx/schema"
    27  	pipepb "github.com/apache/beam/sdks/v2/go/pkg/beam/model/pipeline_v1"
    28  	"github.com/google/go-cmp/cmp"
    29  	"google.golang.org/protobuf/proto"
    30  	"google.golang.org/protobuf/testing/protocmp"
    31  )
    32  
    33  func atomicSchemaField(name string, typ pipepb.AtomicType) *pipepb.Field {
    34  	return &pipepb.Field{
    35  		Name: name,
    36  		Type: &pipepb.FieldType{
    37  			TypeInfo: &pipepb.FieldType_AtomicType{
    38  				AtomicType: typ,
    39  			},
    40  		},
    41  	}
    42  }
    43  
    44  func rowSchemaField(name string, typ *pipepb.Schema) *pipepb.Field {
    45  	return &pipepb.Field{
    46  		Name: name,
    47  		Type: &pipepb.FieldType{
    48  			TypeInfo: &pipepb.FieldType_RowType{
    49  				RowType: &pipepb.RowType{
    50  					Schema: typ,
    51  				},
    52  			},
    53  		},
    54  	}
    55  }
    56  
    57  func listSchemaField(name string, typ *pipepb.Field) *pipepb.Field {
    58  	return &pipepb.Field{
    59  		Name: name,
    60  		Type: &pipepb.FieldType{
    61  			TypeInfo: &pipepb.FieldType_ArrayType{
    62  				ArrayType: &pipepb.ArrayType{
    63  					ElementType: typ.GetType(),
    64  				},
    65  			},
    66  		},
    67  	}
    68  }
    69  
    70  func nillable(f *pipepb.Field) *pipepb.Field {
    71  	f.Type.Nullable = true
    72  	return f
    73  }
    74  
    75  func TestSchemaTypes(t *testing.T) {
    76  	transactionSchema := &pipepb.Schema{
    77  		Fields: []*pipepb.Field{
    78  			atomicSchemaField("bank", pipepb.AtomicType_STRING),
    79  			atomicSchemaField("purchaseAmount", pipepb.AtomicType_DOUBLE),
    80  		},
    81  	}
    82  	shippingAddressSchema := &pipepb.Schema{
    83  		Fields: []*pipepb.Field{
    84  			atomicSchemaField("streetAddress", pipepb.AtomicType_STRING),
    85  			atomicSchemaField("city", pipepb.AtomicType_STRING),
    86  			nillable(atomicSchemaField("state", pipepb.AtomicType_STRING)),
    87  			atomicSchemaField("country", pipepb.AtomicType_STRING),
    88  			atomicSchemaField("postCode", pipepb.AtomicType_STRING),
    89  		},
    90  	}
    91  
    92  	tests := []struct {
    93  		rt     reflect.Type
    94  		st     *pipepb.Schema
    95  		preReg func(reg *schema.Registry)
    96  	}{{
    97  		rt: reflect.TypeOf(Transaction{}),
    98  		st: transactionSchema,
    99  	}, {
   100  		rt: reflect.TypeOf(ShippingAddress{}),
   101  		st: shippingAddressSchema,
   102  	}, {
   103  		rt: reflect.TypeOf(Purchase{}),
   104  		st: &pipepb.Schema{
   105  			Fields: []*pipepb.Field{
   106  				atomicSchemaField("userId", pipepb.AtomicType_STRING),
   107  				atomicSchemaField("itemId", pipepb.AtomicType_INT64),
   108  				rowSchemaField("shippingAddress", shippingAddressSchema),
   109  				atomicSchemaField("cost", pipepb.AtomicType_INT64),
   110  				listSchemaField("transactions",
   111  					rowSchemaField("n/a", transactionSchema)),
   112  			},
   113  		},
   114  	}, {
   115  		rt: tnType,
   116  		st: &pipepb.Schema{
   117  			Fields: []*pipepb.Field{
   118  				atomicSchemaField("seconds", pipepb.AtomicType_INT64),
   119  				atomicSchemaField("nanos", pipepb.AtomicType_INT32),
   120  			},
   121  		},
   122  		preReg: func(reg *schema.Registry) {
   123  			reg.RegisterLogicalType(schema.ToLogicalType(tnType.Name(), tnType, tnStorageType))
   124  		},
   125  	}}
   126  	for _, test := range tests {
   127  		t.Run(fmt.Sprintf("%v", test.rt), func(t *testing.T) {
   128  			reg := schema.NewRegistry()
   129  			if test.preReg != nil {
   130  				test.preReg(reg)
   131  			}
   132  			{
   133  				got, err := reg.FromType(test.rt)
   134  				if err != nil {
   135  					t.Fatalf("error FromType(%v) = %v", test.rt, err)
   136  				}
   137  				if d := cmp.Diff(test.st, got,
   138  					protocmp.Transform(),
   139  					protocmp.IgnoreFields(proto.Message(&pipepb.Schema{}), "id", "options"),
   140  				); d != "" {
   141  					t.Errorf("diff (-want, +got): %v", d)
   142  				}
   143  			}
   144  		})
   145  	}
   146  }
   147  
   148  func TestSchema_validate(t *testing.T) {
   149  	tests := []struct {
   150  		rt               reflect.Type
   151  		p                beam.SchemaProvider
   152  		logical, storage any
   153  	}{
   154  		{
   155  			rt:      tnType,
   156  			p:       &TimestampNanosProvider{},
   157  			logical: TimestampNanos(time.Unix(2300003, 456789)),
   158  			storage: tnStorage{},
   159  		},
   160  	}
   161  	for _, test := range tests {
   162  		sc := &testutil.SchemaCoder{
   163  			CmpOptions: cmp.Options{
   164  				cmp.Comparer(func(a, b TimestampNanos) bool {
   165  					return a.Seconds() == b.Seconds() && a.Nanos() == b.Nanos()
   166  				})},
   167  		}
   168  		sc.Validate(t, test.rt, test.p.BuildEncoder, test.p.BuildDecoder, test.storage, test.logical)
   169  	}
   170  }