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  }