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 }