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 }