github.com/willyham/dosa@v2.3.1-0.20171024181418-1e446d37ee71+incompatible/entity_parser_test.go (about)

     1  // Copyright (c) 2017 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package dosa
    22  
    23  import (
    24  	"testing"
    25  
    26  	"time"
    27  
    28  	"io"
    29  
    30  	"github.com/stretchr/testify/assert"
    31  )
    32  
    33  type SinglePrimaryKeyNoParen struct {
    34  	Entity     `dosa:"primaryKey=PrimaryKey"`
    35  	PrimaryKey int64
    36  	Data       string
    37  }
    38  
    39  // happy path: A single primaryKey becomes the partition key
    40  func TestSinglePrimaryKeyNoParen(t *testing.T) {
    41  	dosaTable, err := TableFromInstance(&SinglePrimaryKeyNoParen{})
    42  	assert.Nil(t, err)
    43  	assert.Equal(t, []string{"primarykey"}, dosaTable.Key.PartitionKeys)
    44  	assert.Equal(t, 0, len(dosaTable.Key.ClusteringKeys))
    45  }
    46  
    47  func TestNilPointer(t *testing.T) {
    48  	dosaTable, err := TableFromInstance((*SinglePrimaryKeyNoParen)(nil))
    49  	assert.Nil(t, err)
    50  	assert.Equal(t, []string{"primarykey"}, dosaTable.Key.PartitionKeys)
    51  	assert.Equal(t, 0, len(dosaTable.Key.ClusteringKeys))
    52  }
    53  
    54  type SinglePrimaryKey struct {
    55  	Entity     `dosa:"primaryKey=(PrimaryKey)"`
    56  	PrimaryKey int64
    57  	Data       string
    58  }
    59  
    60  // happy path: A single primaryKey becomes the partition key
    61  func TestSinglePrimaryKey(t *testing.T) {
    62  	dosaTable, err := TableFromInstance(&SinglePrimaryKey{})
    63  	assert.Nil(t, err)
    64  	assert.Equal(t, []string{"primarykey"}, dosaTable.Key.PartitionKeys)
    65  	assert.Equal(t, 0, len(dosaTable.Key.ClusteringKeys))
    66  }
    67  
    68  func BenchmarkSingleKey(b *testing.B) {
    69  	for i := 0; i < b.N; i++ {
    70  		TableFromInstance(&SinglePrimaryKey{})
    71  	}
    72  }
    73  
    74  type SinglePartitionKey struct {
    75  	Entity     `dosa:"primaryKey=PrimaryKey"`
    76  	PrimaryKey int64
    77  	data       string
    78  }
    79  
    80  func TestSinglePartitionKey(t *testing.T) {
    81  	dosaTable, err := TableFromInstance(&SinglePartitionKey{})
    82  	assert.Nil(t, err)
    83  	assert.Equal(t, []string{"primarykey"}, dosaTable.Key.PartitionKeys)
    84  	assert.Equal(t, 0, len(dosaTable.Key.ClusteringKeys))
    85  }
    86  
    87  // unhappy path: this struct doesn't have anything specified for pk
    88  type NoPrimaryKey struct {
    89  	Entity     `dosa:"primaryKey="`
    90  	PrimaryKey int64
    91  	data       string
    92  }
    93  
    94  // unhappy path: If there is no field marked with a primary nor partition key, throw an error
    95  func TestNoPrimaryKey(t *testing.T) {
    96  	dosaTable, err := TableFromInstance(&NoPrimaryKey{})
    97  	assert.Nil(t, dosaTable)
    98  	assert.NotNil(t, err)
    99  }
   100  
   101  // unhappy path: this struct has an empty primary key
   102  type EmptyPrimaryKey struct {
   103  	Entity     `dosa:"primaryKey=()"`
   104  	PrimaryKey int64
   105  	data       string
   106  }
   107  
   108  // unhappy path: If there is no field marked with a primary nor partition key, throw an error
   109  func TestEmptyPrimaryKey(t *testing.T) {
   110  	dosaTable, err := TableFromInstance(&EmptyPrimaryKey{})
   111  	assert.Nil(t, dosaTable)
   112  	assert.NotNil(t, err)
   113  }
   114  
   115  type PrimaryKeyWithSecondaryRange struct {
   116  	Entity         `dosa:"primaryKey=(PartKey,PrimaryKey  )"`
   117  	PartKey        int64
   118  	PrimaryKey     int64
   119  	data, moredata string
   120  }
   121  
   122  func TestPrimaryKeyWithSecondaryRange(t *testing.T) {
   123  	dosaTable, err := TableFromInstance(&PrimaryKeyWithSecondaryRange{})
   124  	assert.Nil(t, err)
   125  	assert.Equal(t, []string{"partkey"}, dosaTable.Key.PartitionKeys)
   126  	assert.Equal(t, []*ClusteringKey{{"primarykey", false}}, dosaTable.Key.ClusteringKeys)
   127  }
   128  
   129  type PrimaryKeyWithDescendingRange struct {
   130  	Entity     `dosa:"primaryKey=(PartKey,PrimaryKey desc )"`
   131  	PartKey    int64
   132  	PrimaryKey int64
   133  	data       string
   134  }
   135  
   136  func TestPrimaryKeyWithDescendingRange(t *testing.T) {
   137  	dosaTable, err := TableFromInstance(&PrimaryKeyWithDescendingRange{})
   138  	assert.Nil(t, err)
   139  	assert.Equal(t, []string{"partkey"}, dosaTable.Key.PartitionKeys)
   140  	assert.Equal(t, []*ClusteringKey{{"primarykey", true}}, dosaTable.Key.ClusteringKeys)
   141  }
   142  
   143  type MultiComponentPrimaryKey struct {
   144  	Entity         `dosa:"primaryKey=((PartKey, AnotherPartKey))"`
   145  	PartKey        int64
   146  	AnotherPartKey int64
   147  	data           string
   148  }
   149  
   150  func TestMultiComponentPrimaryKey(t *testing.T) {
   151  	dosaTable, err := TableFromInstance(&MultiComponentPrimaryKey{})
   152  	assert.Nil(t, err)
   153  	assert.Equal(t, []string{"partkey", "anotherpartkey"}, dosaTable.Key.PartitionKeys)
   154  	assert.Nil(t, dosaTable.Key.ClusteringKeys)
   155  }
   156  
   157  type InvalidDosaAttribute struct {
   158  	Entity `dosa:"oopsie, primaryKey=Oops"`
   159  	Oops   int64
   160  }
   161  
   162  func TestInvalidDosaAttribute(t *testing.T) {
   163  	dosaTable, err := TableFromInstance(&InvalidDosaAttribute{})
   164  	assert.Nil(t, dosaTable)
   165  	assert.NotNil(t, err)
   166  	assert.Contains(t, err.Error(), "oopsie")
   167  }
   168  
   169  func TestStringify(t *testing.T) {
   170  	dosaTable, _ := TableFromInstance(&SinglePrimaryKey{})
   171  	assert.Contains(t, dosaTable.String(), dosaTable.Name)
   172  	assert.Contains(t, dosaTable.String(), "primarykey")
   173  }
   174  
   175  type MissingCloseParen struct {
   176  	Entity            `dosa:"primaryKey=(MissingCloseParen"`
   177  	MissingCloseParen int64
   178  }
   179  
   180  func TestMissingCloseParen(t *testing.T) {
   181  	dosaTable, err := TableFromInstance(&MissingCloseParen{})
   182  	assert.Nil(t, dosaTable)
   183  	assert.NotNil(t, err)
   184  	assert.Contains(t, err.Error(), "MissingCloseParen")
   185  }
   186  
   187  type MissingAnnotation struct {
   188  	Entity
   189  	Oops int64
   190  }
   191  
   192  func TestMissingAnnotation(t *testing.T) {
   193  	dosaTable, err := TableFromInstance(&MissingAnnotation{})
   194  	assert.Nil(t, dosaTable)
   195  	assert.NotNil(t, err)
   196  	assert.Contains(t, err.Error(), "struct")
   197  	assert.Contains(t, err.Error(), "tag")
   198  }
   199  
   200  type AllTypes struct {
   201  	Entity         `dosa:"primaryKey=BoolType"`
   202  	BoolType       bool
   203  	Int32Type      int32
   204  	Int64Type      int64
   205  	DoubleType     float64
   206  	StringType     string
   207  	BlobType       []byte
   208  	TimeType       time.Time
   209  	UUIDType       UUID
   210  	NullBoolType   *bool
   211  	NullInt32Type  *int32
   212  	NullInt64Type  *int64
   213  	NullDoubleType *float64
   214  	NullStringType *string
   215  	NullTimeType   *time.Time
   216  	NullUUIDType   *UUID
   217  }
   218  
   219  func TestAllTypes(t *testing.T) {
   220  	dosaTable, err := TableFromInstance(&AllTypes{})
   221  	assert.NotNil(t, dosaTable)
   222  	assert.NoError(t, err)
   223  	cds := dosaTable.Columns
   224  	assert.Len(t, cds, 15)
   225  	for _, cd := range cds {
   226  		name, err := NormalizeName(cd.Name)
   227  		assert.NoError(t, err)
   228  		switch name {
   229  		case "booltype":
   230  			assert.Equal(t, Bool, cd.Type)
   231  		case "int32type":
   232  			assert.Equal(t, Int32, cd.Type)
   233  		case "int64type":
   234  			assert.Equal(t, Int64, cd.Type)
   235  		case "doubletype":
   236  			assert.Equal(t, Double, cd.Type)
   237  		case "stringtype":
   238  			assert.Equal(t, String, cd.Type)
   239  		case "blobtype":
   240  			assert.Equal(t, Blob, cd.Type)
   241  		case "timetype":
   242  			assert.Equal(t, Timestamp, cd.Type)
   243  		case "uuidtype":
   244  			assert.Equal(t, TUUID, cd.Type)
   245  		case "nullbooltype":
   246  			assert.Equal(t, Bool, cd.Type)
   247  		case "nullint32type":
   248  			assert.Equal(t, Int32, cd.Type)
   249  		case "nullint64type":
   250  			assert.Equal(t, Int64, cd.Type)
   251  		case "nulldoubletype":
   252  			assert.Equal(t, Double, cd.Type)
   253  		case "nullstringtype":
   254  			assert.Equal(t, String, cd.Type)
   255  		case "nulltimetype":
   256  			assert.Equal(t, Timestamp, cd.Type)
   257  		case "nulluuidtype":
   258  			assert.Equal(t, TUUID, cd.Type)
   259  		default:
   260  			assert.Fail(t, "unexpected column name", name)
   261  		}
   262  	}
   263  }
   264  
   265  type NullableType struct {
   266  	Entity         `dosa:"primaryKey=BoolType"`
   267  	BoolType       bool
   268  	NullBoolType   *bool
   269  	NullInt32Type  *int32
   270  	NullInt64Type  *int64
   271  	NullDoubleType *float64
   272  	NullStringType *string
   273  	NullTimeType   *time.Time
   274  	NullUUIDType   *UUID
   275  }
   276  
   277  func TestNullableType(t *testing.T) {
   278  	dosaTable, err := TableFromInstance(&NullableType{})
   279  	assert.NoError(t, err)
   280  	assert.NotNil(t, dosaTable)
   281  	cds := dosaTable.Columns
   282  	assert.Len(t, cds, 8)
   283  	for _, cd := range cds {
   284  		name, err := NormalizeName(cd.Name)
   285  		assert.NoError(t, err)
   286  		switch name {
   287  		case "booltype":
   288  			assert.Equal(t, Bool, cd.Type)
   289  			assert.False(t, cd.IsPointer)
   290  		case "nullbooltype":
   291  			assert.Equal(t, Bool, cd.Type)
   292  			assert.True(t, cd.IsPointer)
   293  		case "nullint32type":
   294  			assert.Equal(t, Int32, cd.Type)
   295  			assert.True(t, cd.IsPointer)
   296  		case "nullint64type":
   297  			assert.Equal(t, Int64, cd.Type)
   298  			assert.True(t, cd.IsPointer)
   299  		case "nulldoubletype":
   300  			assert.Equal(t, Double, cd.Type)
   301  			assert.True(t, cd.IsPointer)
   302  		case "nullstringtype":
   303  			assert.Equal(t, String, cd.Type)
   304  			assert.True(t, cd.IsPointer)
   305  		case "nulltimetype":
   306  			assert.Equal(t, Timestamp, cd.Type)
   307  			assert.True(t, cd.IsPointer)
   308  		case "nulluuidtype":
   309  			assert.Equal(t, TUUID, cd.Type)
   310  			assert.True(t, cd.IsPointer)
   311  		default:
   312  			assert.Fail(t, "unexpected column name", name)
   313  		}
   314  	}
   315  }
   316  
   317  type UnsupportedType struct {
   318  	Entity    `dosa:"primaryKey=BoolType"`
   319  	BoolType  bool
   320  	UnsupType float32
   321  }
   322  
   323  func TestUnsupportedType(t *testing.T) {
   324  	dosaTable, err := TableFromInstance(&UnsupportedType{})
   325  	assert.Nil(t, dosaTable)
   326  	assert.NotNil(t, err)
   327  	assert.Contains(t, err.Error(), "float32")
   328  	assert.Contains(t, err.Error(), "UnsupType")
   329  }
   330  
   331  func TestNullablePrimaryKeyType(t *testing.T) {
   332  	dosaTable, err := TableFromInstance(&NullStringPrimaryKeyType{})
   333  	assert.Nil(t, dosaTable)
   334  	assert.NotNil(t, err)
   335  	assert.Contains(t, err.Error(), "primary key is of nullable type")
   336  
   337  	dosaTable, err = TableFromInstance(&NullBoolPrimaryKeyType{})
   338  	assert.Nil(t, dosaTable)
   339  	assert.NotNil(t, err)
   340  	assert.Contains(t, err.Error(), "primary key is of nullable type")
   341  
   342  	dosaTable, err = TableFromInstance(&NullDoublePrimaryKeyType{})
   343  	assert.Nil(t, dosaTable)
   344  	assert.NotNil(t, err)
   345  	assert.Contains(t, err.Error(), "primary key is of nullable type")
   346  
   347  	dosaTable, err = TableFromInstance(&NullInt32PrimaryKeyType{})
   348  	assert.Nil(t, dosaTable)
   349  	assert.NotNil(t, err)
   350  	assert.Contains(t, err.Error(), "primary key is of nullable type")
   351  
   352  	dosaTable, err = TableFromInstance(&NullInt64PrimaryKeyType{})
   353  	assert.Nil(t, dosaTable)
   354  	assert.NotNil(t, err)
   355  	assert.Contains(t, err.Error(), "primary key is of nullable type")
   356  
   357  	dosaTable, err = TableFromInstance(&NullTimePrimaryKeyType{})
   358  	assert.Nil(t, dosaTable)
   359  	assert.NotNil(t, err)
   360  	assert.Contains(t, err.Error(), "primary key is of nullable type")
   361  
   362  	dosaTable, err = TableFromInstance(&NullUUIDPrimaryKeyType{})
   363  	assert.Nil(t, dosaTable)
   364  	assert.NotNil(t, err)
   365  	assert.Contains(t, err.Error(), "primary key is of nullable type")
   366  }
   367  
   368  type NullStringPrimaryKeyType struct {
   369  	Entity         `dosa:"primaryKey=NullStringType"`
   370  	NullStringType *string
   371  }
   372  
   373  type NullUUIDPrimaryKeyType struct {
   374  	Entity       `dosa:"primaryKey=NullUUIDType"`
   375  	NullUUIDType *UUID
   376  }
   377  
   378  type NullTimePrimaryKeyType struct {
   379  	Entity       `dosa:"primaryKey=NullTimeType"`
   380  	NullTimeType *time.Time
   381  }
   382  
   383  type NullInt64PrimaryKeyType struct {
   384  	Entity        `dosa:"primaryKey=NullInt64Type"`
   385  	NullInt64Type *int64
   386  }
   387  
   388  type NullInt32PrimaryKeyType struct {
   389  	Entity        `dosa:"primaryKey=NullInt32Type"`
   390  	NullInt32Type *int32
   391  }
   392  
   393  type NullDoublePrimaryKeyType struct {
   394  	Entity         `dosa:"primaryKey=NullDoubleType"`
   395  	NullDoubleType *float64
   396  }
   397  
   398  type NullBoolPrimaryKeyType struct {
   399  	Entity       `dosa:"primaryKey=NullBoolType"`
   400  	NullBoolType *bool
   401  }
   402  
   403  type KeyFieldNameTypo struct {
   404  	Entity   `dosa:"primaryKey=BoolHype"`
   405  	BoolType bool
   406  }
   407  
   408  func TestKeyFieldNameTypo(t *testing.T) {
   409  	dosaTable, err := TableFromInstance(&KeyFieldNameTypo{})
   410  	assert.Nil(t, dosaTable)
   411  	assert.NotNil(t, err)
   412  	assert.Contains(t, err.Error(), "BoolHype")
   413  }
   414  
   415  type UnexportedFieldType struct {
   416  	Entity          `dosa:"primaryKey=BoolType"`
   417  	BoolType        bool
   418  	unexported      string
   419  	unsupportedType io.Reader
   420  }
   421  
   422  func TestIgnoreUnexportedFields(t *testing.T) {
   423  	table, err := TableFromInstance(&UnexportedFieldType{})
   424  	assert.NoError(t, err)
   425  	assert.Len(t, table.ColToField, 1)
   426  	assert.NotContains(t, table.ColToField, "unexported")
   427  	assert.NotContains(t, table.ColToField, "unsupportedType")
   428  	assert.Len(t, table.Columns, 1)
   429  }
   430  
   431  type IgnoreTagType struct {
   432  	Entity          `dosa:"primaryKey=BoolType"`
   433  	BoolType        bool
   434  	Exported        string    `dosa:"-"`
   435  	UnsupportedType io.Reader `dosa:"-"`
   436  }
   437  
   438  func TestIgnoreTag(t *testing.T) {
   439  	table, err := TableFromInstance(&IgnoreTagType{})
   440  	assert.NoError(t, err)
   441  	assert.Len(t, table.ColToField, 1)
   442  	// TODO: should be checking normalized names once normalization is done
   443  	assert.NotContains(t, table.ColToField, "Exported")
   444  	assert.NotContains(t, table.ColToField, "UnsupportedType")
   445  	assert.Len(t, table.Columns, 1)
   446  }
   447  
   448  func TestExtraStuffInClusteringKeyDecl(t *testing.T) {
   449  	type BadClusteringKeyDefinition struct {
   450  		Entity     `dosa:"primaryKey=(BoolType,StringType asc asc)"`
   451  		BoolType   bool
   452  		StringType string
   453  	}
   454  
   455  	table, err := TableFromInstance(&BadClusteringKeyDefinition{})
   456  	assert.Nil(t, table)
   457  	assert.Error(t, err)
   458  	assert.Contains(t, err.Error(), "lustering")
   459  }
   460  
   461  func TestDuplicateKey(t *testing.T) {
   462  	type DuplicateKeyDefinition struct {
   463  		Entity     `dosa:"primaryKey=(BoolType,BoolType)"`
   464  		BoolType   bool
   465  		StringType string
   466  	}
   467  
   468  	table, err := TableFromInstance(&DuplicateKeyDefinition{})
   469  	assert.Nil(t, table)
   470  	assert.Error(t, err)
   471  	assert.Contains(t, err.Error(), "uplicate")
   472  }
   473  
   474  func TestInvalidStructName(t *testing.T) {
   475  	type ăBădNăme struct {
   476  		Entity   `dosa:"primaryKey=BoolType"`
   477  		BoolType bool
   478  	}
   479  	table, err := TableFromInstance(&ăBădNăme{})
   480  	assert.Nil(t, table)
   481  	assert.Error(t, err)
   482  	assert.Contains(t, err.Error(), "ormalize")
   483  }
   484  func TestInvalidFieldInTag(t *testing.T) {
   485  	type HasInvalidCharInTag struct {
   486  		Entity   `dosa:"primaryKey=(ABădNăme)"`
   487  		ABădNăme bool
   488  	}
   489  	table, err := TableFromInstance(&HasInvalidCharInTag{})
   490  	assert.Nil(t, table)
   491  	assert.Error(t, err)
   492  	assert.Contains(t, err.Error(), "invalid")
   493  }
   494  
   495  /*
   496   These tests do not currently pass, but I think they should
   497  */
   498  func TestRenameToInvalidName(t *testing.T) {
   499  	type InvalidRename struct {
   500  		Entity    `dosa:"primaryKey=BoolType"`
   501  		BoolType  bool
   502  		AGoodName string `dosa:"name=ABădNăme"`
   503  	}
   504  	table, err := TableFromInstance(&InvalidRename{})
   505  	assert.Nil(t, table)
   506  	assert.Error(t, err)
   507  	assert.Contains(t, err.Error(), "invalid name tag: name=ABădNăme")
   508  }
   509  
   510  type BadColNameButRenamed struct {
   511  	Entity   `dosa:"primaryKey=(ABădNăme)"`
   512  	ABădNăme bool `dosa:"name=goodname"`
   513  }
   514  
   515  func TestRenameColumnToValidName(t *testing.T) {
   516  	table, err := TableFromInstance(&BadColNameButRenamed{})
   517  	assert.NoError(t, err)
   518  	assert.Equal(t, []string{"goodname"}, table.Key.PartitionKeys)
   519  }
   520  
   521  type StructWithUnannotatedEntity struct {
   522  	Entity `notdosa:"covers a rare test case"`
   523  }
   524  
   525  func TestRenameStructToValidName(t *testing.T) {
   526  	type ABădNăme struct {
   527  		Entity `dosa:"name=agoodname,primaryKey=Dummy"`
   528  		Dummy  bool
   529  	}
   530  	table, err := TableFromInstance(&ABădNăme{})
   531  	assert.NotNil(t, table)
   532  	assert.NoError(t, err)
   533  }