github.com/unigraph-dev/dgraph@v1.1.1-0.20200923154953-8b52b426f765/schema/parse_test.go (about) 1 /* 2 * Copyright 2016-2018 Dgraph Labs, Inc. and Contributors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * 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 17 package schema 18 19 import ( 20 "io/ioutil" 21 "os" 22 "testing" 23 24 "github.com/dgraph-io/badger" 25 "github.com/stretchr/testify/require" 26 27 "github.com/dgraph-io/dgraph/protos/pb" 28 "github.com/dgraph-io/dgraph/types" 29 "github.com/dgraph-io/dgraph/x" 30 ) 31 32 type nameType struct { 33 name string 34 typ *pb.SchemaUpdate 35 } 36 37 func checkSchema(t *testing.T, h map[string]*pb.SchemaUpdate, expected []nameType) { 38 require.Len(t, h, len(expected)) 39 for _, nt := range expected { 40 typ, found := h[nt.name] 41 require.True(t, found, nt) 42 require.EqualValues(t, *nt.typ, *typ) 43 } 44 } 45 46 var schemaVal = ` 47 age:int . 48 49 name: string . 50 address: string . 51 <http://scalar.com/helloworld/> : string . 52 ` 53 54 func TestSchema(t *testing.T) { 55 require.NoError(t, ParseBytes([]byte(schemaVal), 1)) 56 checkSchema(t, State().predicate, []nameType{ 57 {"name", &pb.SchemaUpdate{ 58 Predicate: "name", 59 ValueType: pb.Posting_STRING, 60 }}, 61 {"address", &pb.SchemaUpdate{ 62 Predicate: "address", 63 ValueType: pb.Posting_STRING, 64 }}, 65 {"http://scalar.com/helloworld/", &pb.SchemaUpdate{ 66 Predicate: "http://scalar.com/helloworld/", 67 ValueType: pb.Posting_STRING, 68 }}, 69 {"age", &pb.SchemaUpdate{ 70 Predicate: "age", 71 ValueType: pb.Posting_INT, 72 }}, 73 }) 74 75 typ, err := State().TypeOf("age") 76 require.NoError(t, err) 77 require.Equal(t, types.IntID, typ) 78 79 _, err = State().TypeOf("agea") 80 require.Error(t, err) 81 } 82 83 var schemaVal1 = ` 84 age:int . 85 86 name: string . 87 address: string . 88 89 ) 90 ` 91 92 func TestSchema1_Error(t *testing.T) { 93 require.Error(t, ParseBytes([]byte(schemaVal1), 1)) 94 } 95 96 var schemaVal2 = ` 97 name: ( string 98 ` 99 100 func TestSchema2_Error(t *testing.T) { 101 require.Error(t, ParseBytes([]byte(schemaVal2), 1)) 102 } 103 104 var schemaVal3 = ` 105 test test: int 106 ` 107 108 func TestSchema3_Error(t *testing.T) { 109 require.Error(t, ParseBytes([]byte(schemaVal3), 1)) 110 } 111 112 var schemaIndexVal1 = ` 113 age:int @index(int) . 114 115 name: string . 116 address: string @index(term) .` 117 118 func TestSchemaIndex(t *testing.T) { 119 require.NoError(t, ParseBytes([]byte(schemaIndexVal1), 1)) 120 require.Equal(t, 2, len(State().IndexedFields())) 121 } 122 123 var schemaIndexVal2 = ` 124 name: string @index(exact, exact) . 125 address: string @index(term) . 126 id: id @index(exact, term, exact) . 127 ` 128 129 // Duplicate tokenizers 130 func TestSchemaIndex_Error1(t *testing.T) { 131 require.Error(t, ParseBytes([]byte(schemaIndexVal2), 1)) 132 } 133 134 var schemaIndexVal3Uid = ` 135 person: uid @index . 136 ` 137 138 var schemaIndexVal3Default = ` 139 value: default @index . 140 ` 141 142 var schemaIndexVal3Password = ` 143 pass: password @index . 144 ` 145 146 // Object types cant be indexed. 147 func TestSchemaIndex_Error2(t *testing.T) { 148 require.Error(t, ParseBytes([]byte(schemaIndexVal3Uid), 1)) 149 require.Error(t, ParseBytes([]byte(schemaIndexVal3Default), 1)) 150 require.Error(t, ParseBytes([]byte(schemaIndexVal3Password), 1)) 151 } 152 153 var schemaIndexVal4 = ` 154 name:string @index(exact term) . 155 ` 156 157 // Missing comma. 158 func TestSchemaIndex_Error3(t *testing.T) { 159 require.Error(t, ParseBytes([]byte(schemaIndexVal4), 1)) 160 } 161 162 var schemaIndexVal5 = ` 163 age : int @index(int) . 164 name : string @index(exact) @count . 165 address : string @index(term) . 166 friend : [uid] @reverse @count . 167 ` 168 169 func TestSchemaIndexCustom(t *testing.T) { 170 require.NoError(t, ParseBytes([]byte(schemaIndexVal5), 1)) 171 checkSchema(t, State().predicate, []nameType{ 172 {"name", &pb.SchemaUpdate{ 173 Predicate: "name", 174 ValueType: pb.Posting_STRING, 175 Tokenizer: []string{"exact"}, 176 Directive: pb.SchemaUpdate_INDEX, 177 Count: true, 178 }}, 179 {"address", &pb.SchemaUpdate{ 180 Predicate: "address", 181 ValueType: pb.Posting_STRING, 182 Tokenizer: []string{"term"}, 183 Directive: pb.SchemaUpdate_INDEX, 184 }}, 185 {"age", &pb.SchemaUpdate{ 186 Predicate: "age", 187 ValueType: pb.Posting_INT, 188 Tokenizer: []string{"int"}, 189 Directive: pb.SchemaUpdate_INDEX, 190 }}, 191 {"friend", &pb.SchemaUpdate{ 192 ValueType: pb.Posting_UID, 193 Predicate: "friend", 194 Directive: pb.SchemaUpdate_REVERSE, 195 Count: true, 196 List: true, 197 }}, 198 }) 199 require.True(t, State().IsIndexed("name")) 200 require.False(t, State().IsReversed("name")) 201 require.Equal(t, "int", State().Tokenizer("age")[0].Name()) 202 require.Equal(t, 3, len(State().IndexedFields())) 203 } 204 205 func TestParse(t *testing.T) { 206 reset() 207 _, err := Parse("age:int @index . name:string") 208 require.Error(t, err) 209 } 210 211 func TestParse2(t *testing.T) { 212 reset() 213 result, err := Parse("") 214 require.NoError(t, err) 215 require.Nil(t, result.Preds) 216 } 217 218 func TestParse3_Error(t *testing.T) { 219 reset() 220 result, err := Parse("age:uid @index .") 221 require.Error(t, err) 222 require.Nil(t, result) 223 } 224 225 func TestParse4_Error(t *testing.T) { 226 reset() 227 result, err := Parse("alive:bool @index(geo) .") 228 require.Contains(t, err.Error(), 229 "Tokenizer: geo isn't valid for predicate: alive of type: bool") 230 require.Nil(t, result) 231 } 232 233 func TestParse4_NoError(t *testing.T) { 234 reset() 235 result, err := Parse("name:string @index(fulltext) .") 236 require.NotNil(t, result) 237 require.Nil(t, err) 238 } 239 240 func TestParse5_Error(t *testing.T) { 241 reset() 242 result, err := Parse("value:default @index .") 243 require.Error(t, err) 244 require.Nil(t, result) 245 } 246 247 func TestParse6_Error(t *testing.T) { 248 reset() 249 result, err := Parse("pass:password @index .") 250 require.Error(t, err) 251 require.Nil(t, result) 252 } 253 254 func TestParse7_Error(t *testing.T) { 255 reset() 256 result, err := Parse("name:string @index .") 257 require.Error(t, err) 258 require.Nil(t, result) 259 } 260 261 func TestParse8_Error(t *testing.T) { 262 reset() 263 result, err := Parse("dob:dateTime @index .") 264 require.Error(t, err) 265 require.Nil(t, result) 266 } 267 268 func TestParseScalarList(t *testing.T) { 269 reset() 270 result, err := Parse(` 271 jobs: [string] @index(term) . 272 occupations: [string] . 273 graduation: [dateTime] . 274 `) 275 require.NoError(t, err) 276 require.Equal(t, 3, len(result.Preds)) 277 require.EqualValues(t, &pb.SchemaUpdate{ 278 Predicate: "jobs", 279 ValueType: 9, 280 Directive: pb.SchemaUpdate_INDEX, 281 Tokenizer: []string{"term"}, 282 List: true, 283 }, result.Preds[0]) 284 285 require.EqualValues(t, &pb.SchemaUpdate{ 286 Predicate: "occupations", 287 ValueType: 9, 288 List: true, 289 }, result.Preds[1]) 290 291 require.EqualValues(t, &pb.SchemaUpdate{ 292 Predicate: "graduation", 293 ValueType: 5, 294 List: true, 295 }, result.Preds[2]) 296 } 297 298 func TestParseScalarListError1(t *testing.T) { 299 reset() 300 result, err := Parse(` 301 friend: [string . 302 `) 303 require.Error(t, err) 304 require.Contains(t, err.Error(), "Unclosed [ while parsing schema for: friend") 305 require.Nil(t, result) 306 } 307 308 func TestParseScalarListError2(t *testing.T) { 309 reset() 310 result, err := Parse(` 311 friend: string] . 312 `) 313 require.Error(t, err) 314 require.Contains(t, err.Error(), "Invalid ending") 315 require.Nil(t, result) 316 } 317 318 func TestParseScalarListError3(t *testing.T) { 319 reset() 320 _, err := Parse(` 321 friend: [bool] . 322 `) 323 require.Error(t, err) 324 require.Contains(t, err.Error(), "Unsupported type for list: [bool]") 325 } 326 327 func TestParseUidList(t *testing.T) { 328 reset() 329 result, err := Parse(` 330 friend: [uid] . 331 `) 332 require.NoError(t, err) 333 require.Equal(t, 1, len(result.Preds)) 334 require.EqualValues(t, &pb.SchemaUpdate{ 335 Predicate: "friend", 336 ValueType: 7, 337 List: true, 338 }, result.Preds[0]) 339 } 340 341 func TestParseUidSingleValue(t *testing.T) { 342 reset() 343 result, err := Parse(` 344 friend: uid . 345 `) 346 require.NoError(t, err) 347 require.Equal(t, 1, len(result.Preds)) 348 require.EqualValues(t, &pb.SchemaUpdate{ 349 Predicate: "friend", 350 ValueType: 7, 351 List: false, 352 }, result.Preds[0]) 353 } 354 func TestParseUnderscore(t *testing.T) { 355 reset() 356 _, err := Parse("_share_:string @index(term) .") 357 require.NoError(t, err) 358 } 359 360 func TestParseUpsert(t *testing.T) { 361 reset() 362 _, err := Parse(` 363 jobs : string @index(exact) @upsert . 364 age : int @index(int) @upsert . 365 `) 366 require.NoError(t, err) 367 } 368 369 func TestParseEmptyType(t *testing.T) { 370 reset() 371 result, err := Parse(` 372 type Person { 373 374 } 375 `) 376 require.NoError(t, err) 377 require.Equal(t, 1, len(result.Types)) 378 require.Equal(t, &pb.TypeUpdate{ 379 TypeName: "Person", 380 }, result.Types[0]) 381 382 } 383 384 func TestParseSingleType(t *testing.T) { 385 reset() 386 result, err := Parse(` 387 type Person { 388 Name: string 389 } 390 `) 391 require.NoError(t, err) 392 require.Equal(t, 1, len(result.Types)) 393 require.Equal(t, &pb.TypeUpdate{ 394 TypeName: "Person", 395 Fields: []*pb.SchemaUpdate{ 396 { 397 Predicate: "Name", 398 ValueType: pb.Posting_STRING, 399 }, 400 }, 401 }, result.Types[0]) 402 } 403 404 func TestParseBaseTypesCaseInsensitive(t *testing.T) { 405 reset() 406 result, err := Parse(` 407 type Person { 408 Name: string 409 LastName: String 410 } 411 `) 412 require.NoError(t, err) 413 require.Equal(t, 1, len(result.Types)) 414 require.Equal(t, &pb.TypeUpdate{ 415 TypeName: "Person", 416 Fields: []*pb.SchemaUpdate{ 417 { 418 Predicate: "Name", 419 ValueType: pb.Posting_STRING, 420 }, 421 { 422 Predicate: "LastName", 423 ValueType: pb.Posting_STRING, 424 }, 425 }, 426 }, result.Types[0]) 427 } 428 429 func TestParseCombinedSchemasAndTypes(t *testing.T) { 430 reset() 431 result, err := Parse(` 432 type Person { 433 434 } 435 name: string . 436 `) 437 require.NoError(t, err) 438 require.Equal(t, 1, len(result.Preds)) 439 require.Equal(t, &pb.SchemaUpdate{ 440 Predicate: "name", 441 ValueType: 9, 442 }, result.Preds[0]) 443 require.Equal(t, 1, len(result.Types)) 444 require.Equal(t, &pb.TypeUpdate{ 445 TypeName: "Person", 446 }, result.Types[0]) 447 } 448 449 func TestParseMultipleTypes(t *testing.T) { 450 reset() 451 result, err := Parse(` 452 type Person { 453 Name: string 454 } 455 type Animal { 456 Name: string 457 } 458 `) 459 require.NoError(t, err) 460 require.Equal(t, 2, len(result.Types)) 461 require.Equal(t, &pb.TypeUpdate{ 462 TypeName: "Person", 463 Fields: []*pb.SchemaUpdate{ 464 { 465 Predicate: "Name", 466 ValueType: pb.Posting_STRING, 467 }, 468 }, 469 }, result.Types[0]) 470 require.Equal(t, &pb.TypeUpdate{ 471 TypeName: "Animal", 472 Fields: []*pb.SchemaUpdate{ 473 { 474 Predicate: "Name", 475 ValueType: pb.Posting_STRING, 476 }, 477 }, 478 }, result.Types[1]) 479 } 480 481 func TestParseObjectType(t *testing.T) { 482 reset() 483 result, err := Parse(` 484 type Person { 485 Father: Person 486 Mother: Person 487 Children: [Person] 488 } 489 `) 490 require.NoError(t, err) 491 require.Equal(t, 1, len(result.Types)) 492 require.Equal(t, &pb.TypeUpdate{ 493 TypeName: "Person", 494 Fields: []*pb.SchemaUpdate{ 495 { 496 Predicate: "Father", 497 ValueType: pb.Posting_OBJECT, 498 ObjectTypeName: "Person", 499 }, 500 { 501 Predicate: "Mother", 502 ValueType: pb.Posting_OBJECT, 503 ObjectTypeName: "Person", 504 }, 505 { 506 Predicate: "Children", 507 ValueType: pb.Posting_OBJECT, 508 ObjectTypeName: "Person", 509 List: true, 510 }, 511 }, 512 }, result.Types[0]) 513 } 514 515 func TestParseScalarType(t *testing.T) { 516 reset() 517 result, err := Parse(` 518 type Person { 519 Name: string 520 Nickname: [String] 521 Alive: Bool 522 } 523 `) 524 require.NoError(t, err) 525 require.Equal(t, 1, len(result.Types)) 526 require.Equal(t, &pb.TypeUpdate{ 527 TypeName: "Person", 528 Fields: []*pb.SchemaUpdate{ 529 { 530 Predicate: "Name", 531 ValueType: pb.Posting_STRING, 532 }, 533 { 534 Predicate: "Nickname", 535 ValueType: pb.Posting_STRING, 536 List: true, 537 }, 538 { 539 Predicate: "Alive", 540 ValueType: pb.Posting_BOOL, 541 }, 542 }, 543 }, result.Types[0]) 544 } 545 546 func TestParseCombinedTypes(t *testing.T) { 547 reset() 548 result, err := Parse(` 549 type Person { 550 Name: string 551 Nickname: [string] 552 Parents: [Person] 553 } 554 `) 555 require.NoError(t, err) 556 require.Equal(t, 1, len(result.Types)) 557 require.Equal(t, &pb.TypeUpdate{ 558 TypeName: "Person", 559 Fields: []*pb.SchemaUpdate{ 560 { 561 Predicate: "Name", 562 ValueType: pb.Posting_STRING, 563 }, 564 { 565 Predicate: "Nickname", 566 ValueType: pb.Posting_STRING, 567 List: true, 568 }, 569 { 570 Predicate: "Parents", 571 ValueType: pb.Posting_OBJECT, 572 ObjectTypeName: "Person", 573 List: true, 574 }, 575 }, 576 }, result.Types[0]) 577 } 578 579 func TestParseNonNullableScalar(t *testing.T) { 580 reset() 581 result, err := Parse(` 582 type Person { 583 Name: string! 584 Nickname: [string] 585 } 586 `) 587 require.NoError(t, err) 588 require.Equal(t, 1, len(result.Types)) 589 require.Equal(t, &pb.TypeUpdate{ 590 TypeName: "Person", 591 Fields: []*pb.SchemaUpdate{ 592 { 593 Predicate: "Name", 594 ValueType: pb.Posting_STRING, 595 NonNullable: true, 596 }, 597 { 598 Predicate: "Nickname", 599 ValueType: pb.Posting_STRING, 600 List: true, 601 }, 602 }, 603 }, result.Types[0]) 604 } 605 606 func TestParseNonNullableList(t *testing.T) { 607 reset() 608 result, err := Parse(` 609 type Person { 610 Name: string 611 Nickname: [string]! 612 } 613 `) 614 require.NoError(t, err) 615 require.Equal(t, 1, len(result.Types)) 616 require.Equal(t, &pb.TypeUpdate{ 617 TypeName: "Person", 618 Fields: []*pb.SchemaUpdate{ 619 { 620 Predicate: "Name", 621 ValueType: pb.Posting_STRING, 622 }, 623 { 624 Predicate: "Nickname", 625 ValueType: pb.Posting_STRING, 626 List: true, 627 NonNullableList: true, 628 }, 629 }, 630 }, result.Types[0]) 631 } 632 633 func TestParseNonNullableScalarAndList(t *testing.T) { 634 reset() 635 result, err := Parse(` 636 type Person { 637 Name: string! 638 Nickname: [string!]! 639 } 640 `) 641 require.NoError(t, err) 642 require.Equal(t, 1, len(result.Types)) 643 require.Equal(t, &pb.TypeUpdate{ 644 TypeName: "Person", 645 Fields: []*pb.SchemaUpdate{ 646 { 647 Predicate: "Name", 648 ValueType: pb.Posting_STRING, 649 NonNullable: true, 650 }, 651 { 652 Predicate: "Nickname", 653 ValueType: pb.Posting_STRING, 654 List: true, 655 NonNullable: true, 656 NonNullableList: true, 657 }, 658 }, 659 }, result.Types[0]) 660 } 661 662 func TestParseTypeErrMissingNewLine(t *testing.T) { 663 reset() 664 _, err := Parse(` 665 type Person { 666 }type Animal {} 667 `) 668 require.Error(t, err) 669 require.Contains(t, err.Error(), "Expected new line after type declaration") 670 } 671 672 func TestParseTypeErrMissingColon(t *testing.T) { 673 reset() 674 _, err := Parse(` 675 type Person { 676 Name string 677 } 678 `) 679 require.Error(t, err) 680 require.Contains(t, err.Error(), "Missing colon in type declaration") 681 } 682 683 func TestParseTypeErrMultipleTypes(t *testing.T) { 684 reset() 685 _, err := Parse(` 686 type Person { 687 Name: bool string 688 } 689 `) 690 require.Error(t, err) 691 require.Contains(t, err.Error(), "Expected new line after field declaration") 692 } 693 694 func TestParseTypeErrMultipleExclamationMarks(t *testing.T) { 695 reset() 696 _, err := Parse(` 697 type Person { 698 Name: bool!! 699 } 700 `) 701 require.Error(t, err) 702 require.Contains(t, err.Error(), "Expected new line after field declaration") 703 } 704 705 func TestParseTypeErrMissingSquareBracket(t *testing.T) { 706 reset() 707 _, err := Parse(` 708 type Person { 709 Name: [string 710 } 711 `) 712 require.Error(t, err) 713 require.Contains(t, err.Error(), "Expected matching square bracket") 714 } 715 716 func TestParseTypeErrMultipleSquareBrackets(t *testing.T) { 717 reset() 718 _, err := Parse(` 719 type Person { 720 Name: [[string]] 721 } 722 `) 723 require.Error(t, err) 724 require.Contains(t, err.Error(), "Missing field type in type declaration") 725 } 726 727 func TestParseTypeErrMissingType(t *testing.T) { 728 reset() 729 _, err := Parse(` 730 type Person { 731 Name: 732 } 733 `) 734 require.Error(t, err) 735 require.Contains(t, err.Error(), "Missing field type in type declaration") 736 } 737 738 func TestParseComments(t *testing.T) { 739 reset() 740 _, err := Parse(` 741 # 742 # This is a test 743 # 744 user: bool . 745 user.name: string @index(exact) . # this should be unique 746 user.email: string @index(exact) . # this should be unique and lower-cased 747 user.password: password . 748 user.code: string . # for password recovery (can be null) 749 node: bool . 750 node.hashid: string @index(exact) . # @username/hashid 751 node.owner: uid @reverse . # (can be null) 752 node.parent: uid . # [uid] (use facet) 753 node.xdata: string . # store custom json data 754 # 755 # End of test 756 # 757 `) 758 require.NoError(t, err) 759 } 760 761 func TestParseCommentsNoop(t *testing.T) { 762 reset() 763 _, err := Parse(` 764 # Spicy jalapeno bacon ipsum dolor amet t-bone kevin spare ribs sausage jowl cow pastrami short. 765 # Leberkas alcatra kielbasa chicken pastrami swine bresaola. Spare ribs landjaeger meatloaf. 766 # Chicken biltong boudin porchetta jowl swine burgdoggen cow kevin ground round landjaeger ham. 767 # Tongue buffalo cow filet mignon boudin sirloin pancetta pork belly beef ribs. Cow landjaeger. 768 `) 769 require.NoError(t, err) 770 } 771 772 func TestParseCommentsErrMissingType(t *testing.T) { 773 reset() 774 _, err := Parse(` 775 # The definition below should trigger an error 776 # because we commented out its type. 777 node: # bool . 778 `) 779 require.Error(t, err) 780 require.Contains(t, err.Error(), "Missing Type") 781 } 782 783 func TestParseTypeComments(t *testing.T) { 784 reset() 785 _, err := Parse(` 786 # User is a service user 787 type User { 788 # TODO: add more fields 789 Name: string # e.g., srfrog 790 # expanded comment 791 # embedded # comments # here 792 } 793 # /User 794 `) 795 require.NoError(t, err) 796 } 797 798 var ps *badger.DB 799 800 func TestMain(m *testing.M) { 801 x.Init() 802 803 dir, err := ioutil.TempDir("", "storetest_") 804 x.Check(err) 805 kvOpt := badger.DefaultOptions(dir) 806 ps, err = badger.OpenManaged(kvOpt) 807 x.Check(err) 808 Init(ps) 809 810 r := m.Run() 811 812 ps.Close() 813 os.RemoveAll(dir) 814 os.Exit(r) 815 }