github.com/hasnat/dolt/go@v0.0.0-20210628190320-9eb5d843fbb7/libraries/doltcore/schema/encoding/schema_marshaling_test.go (about) 1 // Copyright 2019 Dolthub, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package encoding 16 17 import ( 18 "context" 19 "errors" 20 "fmt" 21 "reflect" 22 "testing" 23 24 "github.com/dolthub/go-mysql-server/sql" 25 "github.com/dolthub/vitess/go/sqltypes" 26 "github.com/stretchr/testify/assert" 27 "github.com/stretchr/testify/require" 28 29 "github.com/dolthub/dolt/go/libraries/doltcore/dbfactory" 30 "github.com/dolthub/dolt/go/libraries/doltcore/schema" 31 "github.com/dolthub/dolt/go/libraries/doltcore/schema/typeinfo" 32 "github.com/dolthub/dolt/go/store/constants" 33 "github.com/dolthub/dolt/go/store/marshal" 34 "github.com/dolthub/dolt/go/store/types" 35 ) 36 37 func createTestSchema() schema.Schema { 38 columns := []schema.Column{ 39 schema.NewColumn("id", 4, types.UUIDKind, true, schema.NotNullConstraint{}), 40 schema.NewColumn("first", 1, types.StringKind, false), 41 schema.NewColumn("last", 2, types.StringKind, false, schema.NotNullConstraint{}), 42 schema.NewColumn("age", 3, types.UintKind, false), 43 } 44 45 colColl := schema.NewColCollection(columns...) 46 sch := schema.MustSchemaFromCols(colColl) 47 _, _ = sch.Indexes().AddIndexByColTags("idx_age", []uint64{3}, schema.IndexProperties{IsUnique: false, Comment: ""}) 48 return sch 49 } 50 51 func TestNomsMarshalling(t *testing.T) { 52 tSchema := createTestSchema() 53 db, err := dbfactory.MemFactory{}.CreateDB(context.Background(), types.Format_Default, nil, nil) 54 55 if err != nil { 56 t.Fatal("Could not create in mem noms db.") 57 } 58 59 val, err := MarshalSchemaAsNomsValue(context.Background(), db, tSchema) 60 61 if err != nil { 62 t.Fatal("Failed to marshal Schema as a types.Value.") 63 } 64 65 unMarshalled, err := UnmarshalSchemaNomsValue(context.Background(), types.Format_Default, val) 66 67 if err != nil { 68 t.Fatal("Failed to unmarshal types.Value as Schema") 69 } 70 71 if !reflect.DeepEqual(tSchema, unMarshalled) { 72 t.Error("Value different after marshalling and unmarshalling.") 73 } 74 75 validated, err := validateUnmarshaledNomsValue(context.Background(), types.Format_Default, val) 76 77 if err != nil { 78 t.Fatal(fmt.Sprintf("Failed compatibility test. Schema could not be unmarshalled with mirror type, error: %s", err.Error())) 79 } 80 81 if !reflect.DeepEqual(tSchema, validated) { 82 t.Error("Value different after marshalling and unmarshalling.") 83 } 84 85 tSuperSchema, err := schema.NewSuperSchema(tSchema) 86 require.NoError(t, err) 87 88 ssVal, err := MarshalSuperSchemaAsNomsValue(context.Background(), db, tSuperSchema) 89 require.NoError(t, err) 90 91 unMarshalledSS, err := UnmarshalSuperSchemaNomsValue(context.Background(), types.Format_Default, ssVal) 92 require.NoError(t, err) 93 94 if !reflect.DeepEqual(tSuperSchema, unMarshalledSS) { 95 t.Error("Value different after marshalling and unmarshalling.") 96 } 97 98 } 99 100 func TestTypeInfoMarshalling(t *testing.T) { 101 //TODO: determine the storage format for BINARY 102 //TODO: determine the storage format for BLOB 103 //TODO: determine the storage format for LONGBLOB 104 //TODO: determine the storage format for MEDIUMBLOB 105 //TODO: determine the storage format for TINYBLOB 106 //TODO: determine the storage format for VARBINARY 107 sqlTypes := []sql.Type{ 108 sql.Int64, //BIGINT 109 sql.Uint64, //BIGINT UNSIGNED 110 //sql.MustCreateBinary(sqltypes.Binary, 10), //BINARY(10) 111 sql.MustCreateBitType(10), //BIT(10) 112 //sql.Blob, //BLOB 113 sql.Boolean, //BOOLEAN 114 sql.MustCreateStringWithDefaults(sqltypes.Char, 10), //CHAR(10) 115 sql.Date, //DATE 116 sql.Datetime, //DATETIME 117 sql.MustCreateDecimalType(9, 5), //DECIMAL(9, 5) 118 sql.Float64, //DOUBLE 119 sql.MustCreateEnumType([]string{"a", "b", "c"}, sql.Collation_Default), //ENUM('a','b','c') 120 sql.Float32, //FLOAT 121 sql.Int32, //INT 122 sql.Uint32, //INT UNSIGNED 123 //sql.LongBlob, //LONGBLOB 124 sql.LongText, //LONGTEXT 125 //sql.MediumBlob, //MEDIUMBLOB 126 sql.Int24, //MEDIUMINT 127 sql.Uint24, //MEDIUMINT UNSIGNED 128 sql.MediumText, //MEDIUMTEXT 129 sql.MustCreateSetType([]string{"a", "b", "c"}, sql.Collation_Default), //SET('a','b','c') 130 sql.Int16, //SMALLINT 131 sql.Uint16, //SMALLINT UNSIGNED 132 sql.Text, //TEXT 133 sql.Time, //TIME 134 sql.Timestamp, //TIMESTAMP 135 //sql.TinyBlob, //TINYBLOB 136 sql.Int8, //TINYINT 137 sql.Uint8, //TINYINT UNSIGNED 138 sql.TinyText, //TINYTEXT 139 //sql.MustCreateBinary(sqltypes.VarBinary, 10), //VARBINARY(10) 140 sql.MustCreateStringWithDefaults(sqltypes.VarChar, 10), //VARCHAR(10) 141 sql.MustCreateString(sqltypes.VarChar, 10, sql.Collation_utf8mb3_bin), //VARCHAR(10) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin 142 sql.Year, //YEAR 143 } 144 145 for _, sqlType := range sqlTypes { 146 t.Run(sqlType.String(), func(t *testing.T) { 147 ti, err := typeinfo.FromSqlType(sqlType) 148 require.NoError(t, err) 149 col, err := schema.NewColumnWithTypeInfo("pk", 1, ti, true, "", false, "") 150 require.NoError(t, err) 151 colColl := schema.NewColCollection(col) 152 originalSch, err := schema.SchemaFromCols(colColl) 153 require.NoError(t, err) 154 155 nbf, err := types.GetFormatForVersionString(constants.FormatDefaultString) 156 require.NoError(t, err) 157 db, err := dbfactory.MemFactory{}.CreateDB(context.Background(), nbf, nil, nil) 158 require.NoError(t, err) 159 val, err := MarshalSchemaAsNomsValue(context.Background(), db, originalSch) 160 require.NoError(t, err) 161 unmarshalledSch, err := UnmarshalSchemaNomsValue(context.Background(), nbf, val) 162 require.NoError(t, err) 163 ok := schema.SchemasAreEqual(originalSch, unmarshalledSch) 164 assert.True(t, ok) 165 }) 166 } 167 } 168 169 func validateUnmarshaledNomsValue(ctx context.Context, nbf *types.NomsBinFormat, schemaVal types.Value) (schema.Schema, error) { 170 var sd testSchemaData 171 err := marshal.Unmarshal(ctx, nbf, schemaVal, &sd) 172 173 if err != nil { 174 return nil, err 175 } 176 177 return sd.decodeSchema() 178 } 179 180 func TestMirroredTypes(t *testing.T) { 181 realType := reflect.ValueOf(&encodedColumn{}).Elem() 182 mirrorType := reflect.ValueOf(&testEncodedColumn{}).Elem() 183 require.Equal(t, mirrorType.NumField(), realType.NumField()) 184 185 // TODO: create reflection tests to ensure that: 186 // - no fields in testEncodeColumn have the 'omitempty' annotation 187 // - no legacy fields in encodeColumn have the 'omitempty' annotation (with whitelist) 188 // - all new fields in encodeColumn have the 'omitempty' annotation 189 } 190 191 // testEncodedColumn is a mirror type of encodedColumn that helps ensure compatibility between Dolt versions 192 // 193 // If a field in encodedColumn is not optional, it should be added WITHOUT the "omitempty" annotation here 194 // in order to guarantee that all fields in encodeColumn are always being written when encodedColumn is serialized. 195 // See the comment above type encodeColumn. 196 type testEncodedColumn struct { 197 Tag uint64 `noms:"tag" json:"tag"` 198 199 Name string `noms:"name" json:"name"` 200 201 Kind string `noms:"kind" json:"kind"` 202 203 IsPartOfPK bool `noms:"is_part_of_pk" json:"is_part_of_pk"` 204 205 TypeInfo encodedTypeInfo `noms:"typeinfo" json:"typeinfo"` 206 207 Default string `noms:"default,omitempty" json:"default,omitempty"` 208 209 AutoIncrement bool `noms:"auto_increment,omitempty" json:"auto_increment,omitempty"` 210 211 Comment string `noms:"comment,omitempty" json:"comment,omitempty"` 212 213 Constraints []encodedConstraint `noms:"col_constraints" json:"col_constraints"` 214 } 215 216 type testEncodedIndex struct { 217 Name string `noms:"name" json:"name"` 218 Tags []uint64 `noms:"tags" json:"tags"` 219 Comment string `noms:"comment" json:"comment"` 220 Unique bool `noms:"unique" json:"unique"` 221 Hidden bool `noms:"hidden,omitempty" json:"hidden,omitempty"` 222 } 223 224 type testSchemaData struct { 225 Columns []testEncodedColumn `noms:"columns" json:"columns"` 226 IndexCollection []testEncodedIndex `noms:"idxColl,omitempty" json:"idxColl,omitempty"` 227 } 228 229 func (tec testEncodedColumn) decodeColumn() (schema.Column, error) { 230 var typeInfo typeinfo.TypeInfo 231 var err error 232 if tec.TypeInfo.Type != "" { 233 typeInfo, err = tec.TypeInfo.decodeTypeInfo() 234 if err != nil { 235 return schema.Column{}, err 236 } 237 } else if tec.Kind != "" { 238 typeInfo = typeinfo.FromKind(schema.LwrStrToKind[tec.Kind]) 239 } else { 240 return schema.Column{}, errors.New("cannot decode column due to unknown schema format") 241 } 242 colConstraints := decodeAllColConstraint(tec.Constraints) 243 return schema.NewColumnWithTypeInfo(tec.Name, tec.Tag, typeInfo, tec.IsPartOfPK, tec.Default, tec.AutoIncrement, tec.Comment, colConstraints...) 244 } 245 246 func (tsd testSchemaData) decodeSchema() (schema.Schema, error) { 247 numCols := len(tsd.Columns) 248 cols := make([]schema.Column, numCols) 249 250 var err error 251 for i, col := range tsd.Columns { 252 cols[i], err = col.decodeColumn() 253 if err != nil { 254 return nil, err 255 } 256 } 257 258 colColl := schema.NewColCollection(cols...) 259 260 sch, err := schema.SchemaFromCols(colColl) 261 if err != nil { 262 return nil, err 263 } 264 265 for _, encodedIndex := range tsd.IndexCollection { 266 _, err = sch.Indexes().AddIndexByColTags(encodedIndex.Name, encodedIndex.Tags, schema.IndexProperties{IsUnique: encodedIndex.Unique, Comment: encodedIndex.Comment}) 267 if err != nil { 268 return nil, err 269 } 270 } 271 272 return sch, nil 273 }