github.com/dgraph-io/dgraph@v1.2.8/graphql/schema/gqlschema.go (about) 1 /* 2 * Copyright 2019 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 "fmt" 21 "sort" 22 "strings" 23 24 "github.com/dgraph-io/dgraph/x" 25 "github.com/vektah/gqlparser/ast" 26 "github.com/vektah/gqlparser/gqlerror" 27 "github.com/vektah/gqlparser/parser" 28 ) 29 30 const ( 31 inverseDirective = "hasInverse" 32 inverseArg = "field" 33 34 searchDirective = "search" 35 searchArgs = "by" 36 37 dgraphDirective = "dgraph" 38 dgraphTypeArg = "type" 39 dgraphPredArg = "pred" 40 idDirective = "id" 41 42 Typename = "__typename" 43 44 // schemaExtras is everything that gets added to an input schema to make it 45 // GraphQL valid and for the completion algorithm to use to build in search 46 // capability into the schema. 47 schemaExtras = ` 48 scalar DateTime 49 50 enum DgraphIndex { 51 int 52 float 53 bool 54 hash 55 exact 56 term 57 fulltext 58 trigram 59 regexp 60 year 61 month 62 day 63 hour 64 } 65 66 directive @hasInverse(field: String!) on FIELD_DEFINITION 67 directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION 68 directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION 69 directive @id on FIELD_DEFINITION 70 71 input IntFilter { 72 eq: Int 73 le: Int 74 lt: Int 75 ge: Int 76 gt: Int 77 } 78 79 input FloatFilter { 80 eq: Float 81 le: Float 82 lt: Float 83 ge: Float 84 gt: Float 85 } 86 87 input DateTimeFilter { 88 eq: DateTime 89 le: DateTime 90 lt: DateTime 91 ge: DateTime 92 gt: DateTime 93 } 94 95 input StringTermFilter { 96 allofterms: String 97 anyofterms: String 98 } 99 100 input StringRegExpFilter { 101 regexp: String 102 } 103 104 input StringFullTextFilter { 105 alloftext: String 106 anyoftext: String 107 } 108 109 input StringExactFilter { 110 eq: String 111 le: String 112 lt: String 113 ge: String 114 gt: String 115 } 116 117 input StringHashFilter { 118 eq: String 119 } 120 ` 121 ) 122 123 // Filters for Boolean and enum aren't needed in here schemaExtras because they are 124 // generated directly for the bool field / enum. E.g. if 125 // `type T { b: Boolean @search }`, 126 // then the filter allows `filter: {b: true}`. That's better than having 127 // `input BooleanFilter { eq: Boolean }`, which would require writing 128 // `filter: {b: {eq: true}}`. 129 // 130 // It'd be nice to be able to just write `filter: isPublished` for say a Post 131 // with a Boolean isPublished field, but there's no way to get that in GraphQL 132 // because input union types aren't yet sorted out in GraphQL. So it's gotta 133 // be `filter: {isPublished: true}` 134 135 type directiveValidator func( 136 sch *ast.Schema, 137 typ *ast.Definition, 138 field *ast.FieldDefinition, 139 dir *ast.Directive) *gqlerror.Error 140 141 type searchTypeIndex struct { 142 gqlType string 143 dgIndex string 144 } 145 146 // search arg -> supported GraphQL type 147 // == supported Dgraph index -> GraphQL type it applies to 148 var supportedSearches = map[string]searchTypeIndex{ 149 "int": {"Int", "int"}, 150 "float": {"Float", "float"}, 151 "bool": {"Boolean", "bool"}, 152 "hash": {"String", "hash"}, 153 "exact": {"String", "exact"}, 154 "term": {"String", "term"}, 155 "fulltext": {"String", "fulltext"}, 156 "trigram": {"String", "trigram"}, 157 "regexp": {"String", "trigram"}, 158 "year": {"DateTime", "year"}, 159 "month": {"DateTime", "month"}, 160 "day": {"DateTime", "day"}, 161 "hour": {"DateTime", "hour"}, 162 } 163 164 // GraphQL scalar type -> default Dgraph index (/search) 165 // used if the schema specifies @search without an arg 166 var defaultSearches = map[string]string{ 167 "Boolean": "bool", 168 "Int": "int", 169 "Float": "float", 170 "String": "term", 171 "DateTime": "year", 172 } 173 174 // Dgraph index filters that have contains intersecting filter 175 // directive. 176 var filtersCollisions = map[string][]string{ 177 "StringHashFilter": {"StringExactFilter"}, 178 "StringExactFilter": {"StringHashFilter"}, 179 } 180 181 // GraphQL types that can be used for ordering in orderasc and orderdesc. 182 var orderable = map[string]bool{ 183 "Int": true, 184 "Float": true, 185 "String": true, 186 "DateTime": true, 187 } 188 189 var enumDirectives = map[string]bool{ 190 "trigram": true, 191 "hash": true, 192 "exact": true, 193 "regexp": true, 194 } 195 196 // index name -> GraphQL input filter for that index 197 var builtInFilters = map[string]string{ 198 "bool": "Boolean", 199 "int": "IntFilter", 200 "float": "FloatFilter", 201 "year": "DateTimeFilter", 202 "month": "DateTimeFilter", 203 "day": "DateTimeFilter", 204 "hour": "DateTimeFilter", 205 "term": "StringTermFilter", 206 "trigram": "StringRegExpFilter", 207 "regexp": "StringRegExpFilter", 208 "fulltext": "StringFullTextFilter", 209 "exact": "StringExactFilter", 210 "hash": "StringHashFilter", 211 } 212 213 // GraphQL scalar -> Dgraph scalar 214 var scalarToDgraph = map[string]string{ 215 "ID": "uid", 216 "Boolean": "bool", 217 "Int": "int", 218 "Float": "float", 219 "String": "string", 220 "DateTime": "dateTime", 221 } 222 223 var directiveValidators = map[string]directiveValidator{ 224 inverseDirective: hasInverseValidation, 225 searchDirective: searchValidation, 226 dgraphDirective: dgraphDirectiveValidation, 227 idDirective: idValidation, 228 } 229 230 var defnValidations, typeValidations []func(defn *ast.Definition) *gqlerror.Error 231 var fieldValidations []func(typ *ast.Definition, field *ast.FieldDefinition) *gqlerror.Error 232 233 func copyAstFieldDef(src *ast.FieldDefinition) *ast.FieldDefinition { 234 var dirs ast.DirectiveList 235 for _, d := range src.Directives { 236 if d.Name != inverseDirective { 237 dirs = append(dirs, d) 238 } 239 } 240 241 // Lets leave out copying the arguments as types in input schemas are not supposed to contain 242 // them. We add arguments for filters and order statements later. 243 dst := &ast.FieldDefinition{ 244 Name: src.Name, 245 DefaultValue: src.DefaultValue, 246 Type: src.Type, 247 Directives: dirs, 248 Position: src.Position, 249 } 250 return dst 251 } 252 253 func expandSchema(doc *ast.SchemaDocument) { 254 docExtras, gqlErr := parser.ParseSchema(&ast.Source{Input: schemaExtras}) 255 if gqlErr != nil { 256 panic(gqlErr) 257 } 258 259 // Cache the interface definitions in a map. They could also be defined after types which 260 // implement them. 261 interfaces := make(map[string]*ast.Definition) 262 for _, defn := range doc.Definitions { 263 if defn.Kind == ast.Interface { 264 interfaces[defn.Name] = defn 265 } 266 } 267 268 // Walk through type definitions which implement an interface and fill in the fields from the 269 // interface. 270 for _, defn := range doc.Definitions { 271 if defn.Kind == ast.Object && len(defn.Interfaces) > 0 { 272 for _, implements := range defn.Interfaces { 273 i, ok := interfaces[implements] 274 if !ok { 275 // This would fail schema validation later. 276 continue 277 } 278 fields := make([]*ast.FieldDefinition, 0, len(i.Fields)) 279 for _, field := range i.Fields { 280 // Creating a copy here is important, otherwise arguments like filter, order 281 // etc. are added multiple times if the pointer is shared. 282 fields = append(fields, copyAstFieldDef(field)) 283 } 284 defn.Fields = append(fields, defn.Fields...) 285 } 286 } 287 } 288 289 doc.Definitions = append(doc.Definitions, docExtras.Definitions...) 290 doc.Directives = append(doc.Directives, docExtras.Directives...) 291 } 292 293 // preGQLValidation validates schema before GraphQL validation. Validation 294 // before GraphQL validation means the schema only has allowed structures, and 295 // means we can give better errors than GrqphQL validation would give if their 296 // schema contains something that will fail because of the extras we inject into 297 // the schema. 298 func preGQLValidation(schema *ast.SchemaDocument) gqlerror.List { 299 var errs []*gqlerror.Error 300 301 for _, defn := range schema.Definitions { 302 if defn.BuiltIn { 303 // prelude definitions are built in and we don't want to validate them. 304 continue 305 } 306 errs = append(errs, applyDefnValidations(defn, defnValidations)...) 307 } 308 309 return errs 310 } 311 312 // postGQLValidation validates schema after gql validation. Some validations 313 // are easier to run once we know that the schema is GraphQL valid and that validation 314 // has fleshed out the schema structure; we just need to check if it also satisfies 315 // the extra rules. 316 func postGQLValidation(schema *ast.Schema, definitions []string) gqlerror.List { 317 var errs []*gqlerror.Error 318 319 for _, defn := range definitions { 320 typ := schema.Types[defn] 321 322 errs = append(errs, applyDefnValidations(typ, typeValidations)...) 323 324 for _, field := range typ.Fields { 325 errs = append(errs, applyFieldValidations(typ, field)...) 326 327 for _, dir := range field.Directives { 328 errs = appendIfNotNull(errs, 329 directiveValidators[dir.Name](schema, typ, field, dir)) 330 } 331 } 332 } 333 334 return errs 335 } 336 337 func applyDefnValidations(defn *ast.Definition, 338 rules []func(defn *ast.Definition) *gqlerror.Error) gqlerror.List { 339 var errs []*gqlerror.Error 340 341 for _, rule := range rules { 342 errs = appendIfNotNull(errs, rule(defn)) 343 } 344 345 return errs 346 } 347 348 func applyFieldValidations(typ *ast.Definition, field *ast.FieldDefinition) gqlerror.List { 349 var errs []*gqlerror.Error 350 351 for _, rule := range fieldValidations { 352 errs = appendIfNotNull(errs, rule(typ, field)) 353 } 354 355 return errs 356 } 357 358 // completeSchema generates all the required types and fields for 359 // query/mutation/update for all the types mentioned the the schema. 360 func completeSchema(sch *ast.Schema, definitions []string) { 361 362 sch.Query = &ast.Definition{ 363 Kind: ast.Object, 364 Name: "Query", 365 Fields: make([]*ast.FieldDefinition, 0), 366 } 367 368 sch.Mutation = &ast.Definition{ 369 Kind: ast.Object, 370 Name: "Mutation", 371 Fields: make([]*ast.FieldDefinition, 0), 372 } 373 374 for _, key := range definitions { 375 defn := sch.Types[key] 376 377 if defn.Kind != ast.Interface && defn.Kind != ast.Object { 378 continue 379 } 380 381 // Common types to both Interface and Object. 382 addReferenceType(sch, defn) 383 addPatchType(sch, defn) 384 addUpdateType(sch, defn) 385 addUpdatePayloadType(sch, defn) 386 addDeletePayloadType(sch, defn) 387 388 switch defn.Kind { 389 case ast.Interface: 390 // addInputType doesn't make sense as interface is like an abstract class and we can't 391 // create objects of its type. 392 addUpdateMutation(sch, defn) 393 addDeleteMutation(sch, defn) 394 395 case ast.Object: 396 // types and inputs needed for mutations 397 addInputType(sch, defn) 398 addAddPayloadType(sch, defn) 399 addMutations(sch, defn) 400 } 401 402 // types and inputs needed for query and search 403 addFilterType(sch, defn) 404 addTypeOrderable(sch, defn) 405 addFieldFilters(sch, defn) 406 addQueries(sch, defn) 407 } 408 } 409 410 func addInputType(schema *ast.Schema, defn *ast.Definition) { 411 schema.Types["Add"+defn.Name+"Input"] = &ast.Definition{ 412 Kind: ast.InputObject, 413 Name: "Add" + defn.Name + "Input", 414 Fields: getFieldsWithoutIDType(schema, defn), 415 } 416 } 417 418 func addReferenceType(schema *ast.Schema, defn *ast.Definition) { 419 var flds ast.FieldList 420 if defn.Kind == ast.Interface { 421 if !hasID(defn) && !hasXID(defn) { 422 return 423 } 424 flds = append(getIDField(defn), getXIDField(defn)...) 425 } else { 426 flds = append(getIDField(defn), getFieldsWithoutIDType(schema, defn)...) 427 } 428 429 if len(flds) == 1 && (hasID(defn) || hasXID(defn)) { 430 flds[0].Type.NonNull = true 431 } else { 432 for _, fld := range flds { 433 fld.Type.NonNull = false 434 } 435 } 436 437 schema.Types[defn.Name+"Ref"] = &ast.Definition{ 438 Kind: ast.InputObject, 439 Name: defn.Name + "Ref", 440 Fields: flds, 441 } 442 } 443 444 func addUpdateType(schema *ast.Schema, defn *ast.Definition) { 445 if !hasFilterable(defn) { 446 return 447 } 448 if _, ok := schema.Types[defn.Name+"Patch"]; !ok { 449 return 450 } 451 452 updType := &ast.Definition{ 453 Kind: ast.InputObject, 454 Name: "Update" + defn.Name + "Input", 455 Fields: append( 456 ast.FieldList{&ast.FieldDefinition{ 457 Name: "filter", 458 Type: &ast.Type{ 459 NamedType: defn.Name + "Filter", 460 NonNull: true, 461 }, 462 }}, 463 &ast.FieldDefinition{ 464 Name: "set", 465 Type: &ast.Type{ 466 NamedType: defn.Name + "Patch", 467 }, 468 }, 469 &ast.FieldDefinition{ 470 Name: "remove", 471 Type: &ast.Type{ 472 NamedType: defn.Name + "Patch", 473 }, 474 }), 475 } 476 schema.Types["Update"+defn.Name+"Input"] = updType 477 } 478 479 func addPatchType(schema *ast.Schema, defn *ast.Definition) { 480 if !hasFilterable(defn) { 481 return 482 } 483 484 nonIDFields := getNonIDFields(schema, defn) 485 if len(nonIDFields) == 0 { 486 // The user might just have an external id field and nothing else. We don't generate patch 487 // type in that case. 488 return 489 } 490 491 patchDefn := &ast.Definition{ 492 Kind: ast.InputObject, 493 Name: defn.Name + "Patch", 494 Fields: nonIDFields, 495 } 496 schema.Types[defn.Name+"Patch"] = patchDefn 497 498 for _, fld := range patchDefn.Fields { 499 fld.Type.NonNull = false 500 } 501 } 502 503 // addFieldFilters adds field arguments that allow filtering to all fields of 504 // defn that can be searched. For example, if there's another type 505 // `type R { ... f: String @search(by: [term]) ... }` 506 // and defn has a field of type R, e.g. if defn is like 507 // `type T { ... g: R ... }` 508 // then a query should be able to filter on g by term search on f, like 509 // query { 510 // getT(id: 0x123) { 511 // ... 512 // g(filter: { f: { anyofterms: "something" } }, first: 10) { ... } 513 // ... 514 // } 515 // } 516 func addFieldFilters(schema *ast.Schema, defn *ast.Definition) { 517 for _, fld := range defn.Fields { 518 // Filtering makes sense both for lists (= return only items that match 519 // this filter) and for singletons (= only have this value in the result 520 // if it satisfies this filter) 521 addFilterArgument(schema, fld) 522 523 // Ordering and pagination, however, only makes sense for fields of 524 // list types (not scalar lists). 525 if _, scalar := scalarToDgraph[fld.Type.Name()]; !scalar && fld.Type.Elem != nil { 526 addOrderArgument(schema, fld) 527 528 // Pagination even makes sense when there's no orderables because 529 // Dgraph will do UID order by default. 530 addPaginationArguments(fld) 531 } 532 } 533 } 534 535 func addFilterArgument(schema *ast.Schema, fld *ast.FieldDefinition) { 536 fldType := fld.Type.Name() 537 if hasFilterable(schema.Types[fldType]) { 538 fld.Arguments = append(fld.Arguments, 539 &ast.ArgumentDefinition{ 540 Name: "filter", 541 Type: &ast.Type{NamedType: fldType + "Filter"}, 542 }) 543 } 544 } 545 546 func addOrderArgument(schema *ast.Schema, fld *ast.FieldDefinition) { 547 fldType := fld.Type.Name() 548 if hasOrderables(schema.Types[fldType]) { 549 fld.Arguments = append(fld.Arguments, 550 &ast.ArgumentDefinition{ 551 Name: "order", 552 Type: &ast.Type{NamedType: fldType + "Order"}, 553 }) 554 } 555 } 556 557 func addPaginationArguments(fld *ast.FieldDefinition) { 558 fld.Arguments = append(fld.Arguments, 559 &ast.ArgumentDefinition{Name: "first", Type: &ast.Type{NamedType: "Int"}}, 560 &ast.ArgumentDefinition{Name: "offset", Type: &ast.Type{NamedType: "Int"}}, 561 ) 562 } 563 564 // getFilterTypes converts search arguments of a field to graphql filter types. 565 func getFilterTypes(schema *ast.Schema, fld *ast.FieldDefinition, filterName string) []string { 566 searchArgs := getSearchArgs(fld) 567 filterNames := make([]string, len(searchArgs)) 568 569 for i, search := range searchArgs { 570 filterNames[i] = builtInFilters[search] 571 572 if (search == "hash" || search == "exact") && schema.Types[fld.Type.Name()].Kind == ast.Enum { 573 stringFilterName := fmt.Sprintf("String%sFilter", strings.Title(search)) 574 var l ast.FieldList 575 576 for _, i := range schema.Types[stringFilterName].Fields { 577 l = append(l, &ast.FieldDefinition{ 578 Name: i.Name, 579 Type: fld.Type, 580 Description: i.Description, 581 DefaultValue: i.DefaultValue, 582 }) 583 } 584 585 filterNames[i] = fld.Type.Name() + "_" + search 586 schema.Types[filterNames[i]] = &ast.Definition{ 587 Kind: ast.InputObject, 588 Name: filterNames[i], 589 Fields: l, 590 } 591 } 592 } 593 594 return filterNames 595 } 596 597 // mergeAndAddFilters merges multiple filterTypes into one and adds it to the schema. 598 func mergeAndAddFilters(filterTypes []string, schema *ast.Schema, filterName string) { 599 if len(filterTypes) <= 1 { 600 // Filters only require to be merged if there are alteast 2 601 return 602 } 603 604 var fieldList ast.FieldList 605 for _, typeName := range filterTypes { 606 fieldList = append(fieldList, schema.Types[typeName].Fields...) 607 } 608 609 schema.Types[filterName] = &ast.Definition{ 610 Kind: ast.InputObject, 611 Name: filterName, 612 Fields: fieldList, 613 } 614 } 615 616 // addFilterType add a `input TFilter { ... }` type to the schema, if defn 617 // is a type that has fields that can be filtered on. This type filter is used 618 // in constructing the corresponding query 619 // queryT(filter: TFilter, ... ) 620 // and in adding search to any fields of this type, like: 621 // type R { 622 // f(filter: TFilter, ... ): T 623 // ... 624 // } 625 func addFilterType(schema *ast.Schema, defn *ast.Definition) { 626 if !hasFilterable(defn) { 627 return 628 } 629 630 filterName := defn.Name + "Filter" 631 filter := &ast.Definition{ 632 Kind: ast.InputObject, 633 Name: filterName, 634 } 635 636 for _, fld := range defn.Fields { 637 if isID(fld) { 638 filter.Fields = append(filter.Fields, 639 &ast.FieldDefinition{ 640 Name: fld.Name, 641 Type: ast.ListType(&ast.Type{ 642 NamedType: IDType, 643 NonNull: true, 644 }, nil), 645 }) 646 continue 647 } 648 649 filterTypes := getFilterTypes(schema, fld, filterName) 650 if len(filterTypes) > 0 { 651 filterName := strings.Join(filterTypes, "_") 652 filter.Fields = append(filter.Fields, 653 &ast.FieldDefinition{ 654 Name: fld.Name, 655 Type: &ast.Type{ 656 NamedType: filterName, 657 }, 658 }) 659 660 mergeAndAddFilters(filterTypes, schema, filterName) 661 } 662 } 663 664 // Not filter makes sense even if the filter has only one field. And/Or would only make sense 665 // if the filter has more than one field or if it has one non-id field. 666 if (len(filter.Fields) == 1 && !isID(filter.Fields[0])) || len(filter.Fields) > 1 { 667 filter.Fields = append(filter.Fields, 668 &ast.FieldDefinition{Name: "and", Type: &ast.Type{NamedType: filterName}}, 669 &ast.FieldDefinition{Name: "or", Type: &ast.Type{NamedType: filterName}}, 670 ) 671 } 672 673 filter.Fields = append(filter.Fields, 674 &ast.FieldDefinition{Name: "not", Type: &ast.Type{NamedType: filterName}}) 675 schema.Types[filterName] = filter 676 } 677 678 func hasFilterable(defn *ast.Definition) bool { 679 return fieldAny(defn.Fields, 680 func(fld *ast.FieldDefinition) bool { 681 return len(getSearchArgs(fld)) != 0 || isID(fld) 682 }) 683 } 684 685 func hasOrderables(defn *ast.Definition) bool { 686 return fieldAny(defn.Fields, 687 func(fld *ast.FieldDefinition) bool { return orderable[fld.Type.Name()] }) 688 } 689 690 func hasID(defn *ast.Definition) bool { 691 return fieldAny(defn.Fields, 692 func(fld *ast.FieldDefinition) bool { return isID(fld) }) 693 } 694 695 func hasXID(defn *ast.Definition) bool { 696 return fieldAny(defn.Fields, 697 func(fld *ast.FieldDefinition) bool { return hasIDDirective(fld) }) 698 } 699 700 // fieldAny returns true if any field in fields satisfies pred 701 func fieldAny(fields ast.FieldList, pred func(*ast.FieldDefinition) bool) bool { 702 for _, fld := range fields { 703 if pred(fld) { 704 return true 705 } 706 } 707 return false 708 } 709 710 func addHashIfRequired(fld *ast.FieldDefinition, indexes []string) []string { 711 id := fld.Directives.ForName(idDirective) 712 if id != nil { 713 // If @id directive is applied along with @search, we check if the search has hash as an 714 // arg. If it doesn't, then we add it. 715 containsHash := false 716 for _, index := range indexes { 717 if index == "hash" { 718 containsHash = true 719 } 720 } 721 if !containsHash { 722 indexes = append(indexes, "hash") 723 } 724 } 725 return indexes 726 } 727 728 func getDefaultSearchIndex(fldName string) string { 729 if search, ok := defaultSearches[fldName]; ok { 730 return search 731 } 732 // it's an enum - always has hash index 733 return "hash" 734 735 } 736 737 // getSearchArgs returns the name of the search applied to fld, or "" 738 // if fld doesn't have a search directive. 739 func getSearchArgs(fld *ast.FieldDefinition) []string { 740 search := fld.Directives.ForName(searchDirective) 741 id := fld.Directives.ForName(idDirective) 742 if search == nil { 743 if id == nil { 744 return nil 745 } 746 // If search directive wasn't supplied but id was, then hash is the only index 747 // that we apply. 748 return []string{"hash"} 749 } 750 if len(search.Arguments) == 0 || 751 len(search.Arguments.ForName(searchArgs).Value.Children) == 0 { 752 return []string{getDefaultSearchIndex(fld.Type.Name())} 753 } 754 val := search.Arguments.ForName(searchArgs).Value 755 res := make([]string, len(val.Children)) 756 757 for i, child := range val.Children { 758 res[i] = child.Value.Raw 759 } 760 761 res = addHashIfRequired(fld, res) 762 sort.Strings(res) 763 return res 764 } 765 766 // addTypeOrderable adds an input type that allows ordering in query. 767 // Two things are added: an enum with the names of all the orderable fields, 768 // for a type T that's called TOrderable; and an input type that allows saying 769 // order asc or desc, for type T that's called TOrder. 770 // TOrder's fields are TOrderable's. So you 771 // might get: 772 // enum PostOrderable { datePublished, numLikes, ... }, and 773 // input PostOrder { asc : PostOrderable, desc: PostOrderable ...} 774 // Together they allow things like 775 // order: { asc: datePublished } 776 // and 777 // order: { asc: datePublished, then: { desc: title } } 778 // 779 // Dgraph allows multiple orderings `orderasc: datePublished, orderasc: title` 780 // to order by datePublished and then by title when dataPublished is the same. 781 // GraphQL doesn't allow the same field to be repeated, so 782 // `orderasc: datePublished, orderasc: title` wouldn't be valid. Instead, our 783 // GraphQL orderings are given by the structure 784 // `order: { asc: datePublished, then: { asc: title } }`. 785 // a further `then` would be a third ordering, etc. 786 func addTypeOrderable(schema *ast.Schema, defn *ast.Definition) { 787 if !hasOrderables(defn) { 788 return 789 } 790 791 orderName := defn.Name + "Order" 792 orderableName := defn.Name + "Orderable" 793 794 schema.Types[orderName] = &ast.Definition{ 795 Kind: ast.InputObject, 796 Name: orderName, 797 Fields: ast.FieldList{ 798 &ast.FieldDefinition{Name: "asc", Type: &ast.Type{NamedType: orderableName}}, 799 &ast.FieldDefinition{Name: "desc", Type: &ast.Type{NamedType: orderableName}}, 800 &ast.FieldDefinition{Name: "then", Type: &ast.Type{NamedType: orderName}}, 801 }, 802 } 803 804 order := &ast.Definition{ 805 Kind: ast.Enum, 806 Name: orderableName, 807 } 808 809 for _, fld := range defn.Fields { 810 if orderable[fld.Type.Name()] { 811 order.EnumValues = append(order.EnumValues, 812 &ast.EnumValueDefinition{Name: fld.Name}) 813 } 814 } 815 816 schema.Types[orderableName] = order 817 } 818 819 func addAddPayloadType(schema *ast.Schema, defn *ast.Definition) { 820 qry := &ast.FieldDefinition{ 821 Name: strings.ToLower(defn.Name), 822 Type: ast.ListType(&ast.Type{ 823 NamedType: defn.Name, 824 }, nil), 825 } 826 827 addFilterArgument(schema, qry) 828 addOrderArgument(schema, qry) 829 addPaginationArguments(qry) 830 831 schema.Types["Add"+defn.Name+"Payload"] = &ast.Definition{ 832 Kind: ast.Object, 833 Name: "Add" + defn.Name + "Payload", 834 Fields: []*ast.FieldDefinition{qry}, 835 } 836 } 837 838 func addUpdatePayloadType(schema *ast.Schema, defn *ast.Definition) { 839 if !hasFilterable(defn) { 840 return 841 } 842 843 // This covers the case where the Type only had one field (which had @id directive). 844 // Since we don't allow updating the field with @id directive we don't need to generate any 845 // update payload. 846 if _, ok := schema.Types[defn.Name+"Patch"]; !ok { 847 return 848 } 849 850 qry := &ast.FieldDefinition{ 851 Name: strings.ToLower(defn.Name), 852 Type: &ast.Type{ 853 Elem: &ast.Type{ 854 NamedType: defn.Name, 855 }, 856 }, 857 } 858 859 addFilterArgument(schema, qry) 860 addOrderArgument(schema, qry) 861 addPaginationArguments(qry) 862 863 schema.Types["Update"+defn.Name+"Payload"] = &ast.Definition{ 864 Kind: ast.Object, 865 Name: "Update" + defn.Name + "Payload", 866 Fields: []*ast.FieldDefinition{ 867 qry, 868 }, 869 } 870 } 871 872 func addDeletePayloadType(schema *ast.Schema, defn *ast.Definition) { 873 if !hasFilterable(defn) { 874 return 875 } 876 877 schema.Types["Delete"+defn.Name+"Payload"] = &ast.Definition{ 878 Kind: ast.Object, 879 Name: "Delete" + defn.Name + "Payload", 880 Fields: []*ast.FieldDefinition{ 881 { 882 Name: "msg", 883 Type: &ast.Type{ 884 NamedType: "String", 885 }, 886 }, 887 }, 888 } 889 } 890 891 func addGetQuery(schema *ast.Schema, defn *ast.Definition) { 892 hasIDField := hasID(defn) 893 hasXIDField := hasXID(defn) 894 if !hasIDField && !hasXIDField { 895 return 896 } 897 898 qry := &ast.FieldDefinition{ 899 Name: "get" + defn.Name, 900 Type: &ast.Type{ 901 NamedType: defn.Name, 902 }, 903 } 904 905 // If the defn, only specified one of ID/XID field, they they are mandatory. If it specified 906 // both, then they are optional. 907 if hasIDField { 908 fields := getIDField(defn) 909 qry.Arguments = append(qry.Arguments, &ast.ArgumentDefinition{ 910 Name: fields[0].Name, 911 Type: &ast.Type{ 912 NamedType: idTypeFor(defn), 913 NonNull: !hasXIDField, 914 }, 915 }) 916 } 917 if hasXIDField { 918 name := xidTypeFor(defn) 919 qry.Arguments = append(qry.Arguments, &ast.ArgumentDefinition{ 920 Name: name, 921 Type: &ast.Type{ 922 NamedType: "String", 923 NonNull: !hasIDField, 924 }, 925 }) 926 } 927 schema.Query.Fields = append(schema.Query.Fields, qry) 928 } 929 930 func addFilterQuery(schema *ast.Schema, defn *ast.Definition) { 931 qry := &ast.FieldDefinition{ 932 Name: "query" + defn.Name, 933 Type: &ast.Type{ 934 Elem: &ast.Type{ 935 NamedType: defn.Name, 936 }, 937 }, 938 } 939 addFilterArgument(schema, qry) 940 addOrderArgument(schema, qry) 941 addPaginationArguments(qry) 942 943 schema.Query.Fields = append(schema.Query.Fields, qry) 944 } 945 946 func addQueries(schema *ast.Schema, defn *ast.Definition) { 947 addGetQuery(schema, defn) 948 addFilterQuery(schema, defn) 949 } 950 951 func addAddMutation(schema *ast.Schema, defn *ast.Definition) { 952 add := &ast.FieldDefinition{ 953 Name: "add" + defn.Name, 954 Type: &ast.Type{ 955 NamedType: "Add" + defn.Name + "Payload", 956 }, 957 Arguments: []*ast.ArgumentDefinition{ 958 { 959 Name: "input", 960 Type: &ast.Type{ 961 NamedType: "[Add" + defn.Name + "Input!]", 962 NonNull: true, 963 }, 964 }, 965 }, 966 } 967 schema.Mutation.Fields = append(schema.Mutation.Fields, add) 968 } 969 970 func addUpdateMutation(schema *ast.Schema, defn *ast.Definition) { 971 if !hasFilterable(defn) { 972 return 973 } 974 975 if _, ok := schema.Types[defn.Name+"Patch"]; !ok { 976 return 977 } 978 979 upd := &ast.FieldDefinition{ 980 Name: "update" + defn.Name, 981 Type: &ast.Type{ 982 NamedType: "Update" + defn.Name + "Payload", 983 }, 984 Arguments: []*ast.ArgumentDefinition{ 985 { 986 Name: "input", 987 Type: &ast.Type{ 988 NamedType: "Update" + defn.Name + "Input", 989 NonNull: true, 990 }, 991 }, 992 }, 993 } 994 schema.Mutation.Fields = append(schema.Mutation.Fields, upd) 995 } 996 997 func addDeleteMutation(schema *ast.Schema, defn *ast.Definition) { 998 if !hasFilterable(defn) { 999 return 1000 } 1001 1002 del := &ast.FieldDefinition{ 1003 Name: "delete" + defn.Name, 1004 Type: &ast.Type{ 1005 NamedType: "Delete" + defn.Name + "Payload", 1006 }, 1007 Arguments: []*ast.ArgumentDefinition{ 1008 { 1009 Name: "filter", 1010 Type: &ast.Type{NamedType: defn.Name + "Filter", NonNull: true}, 1011 }, 1012 }, 1013 } 1014 schema.Mutation.Fields = append(schema.Mutation.Fields, del) 1015 } 1016 1017 func addMutations(schema *ast.Schema, defn *ast.Definition) { 1018 addAddMutation(schema, defn) 1019 addUpdateMutation(schema, defn) 1020 addDeleteMutation(schema, defn) 1021 } 1022 1023 func createField(schema *ast.Schema, fld *ast.FieldDefinition) *ast.FieldDefinition { 1024 if schema.Types[fld.Type.Name()].Kind == ast.Object || 1025 schema.Types[fld.Type.Name()].Kind == ast.Interface { 1026 newDefn := &ast.FieldDefinition{ 1027 Name: fld.Name, 1028 } 1029 1030 newDefn.Type = &ast.Type{} 1031 newDefn.Type.NonNull = fld.Type.NonNull 1032 if fld.Type.NamedType != "" { 1033 newDefn.Type.NamedType = fld.Type.Name() + "Ref" 1034 } else { 1035 newDefn.Type.Elem = &ast.Type{ 1036 NamedType: fld.Type.Name() + "Ref", 1037 NonNull: fld.Type.Elem.NonNull, 1038 } 1039 } 1040 1041 return newDefn 1042 } 1043 1044 newFld := *fld 1045 newFldType := *fld.Type 1046 newFld.Type = &newFldType 1047 newFld.Directives = nil 1048 return &newFld 1049 } 1050 1051 func getNonIDFields(schema *ast.Schema, defn *ast.Definition) ast.FieldList { 1052 fldList := make([]*ast.FieldDefinition, 0) 1053 for _, fld := range defn.Fields { 1054 if isIDField(defn, fld) || hasIDDirective(fld) { 1055 continue 1056 } 1057 1058 // Even if a field isn't referenceable with an ID or XID, it can still go into an 1059 // input/update type because it can be created (but not linked by reference) as 1060 // part of the mutation. 1061 // 1062 // But if it's an interface, that can't happen because you can't directly create 1063 // interfaces - only the types that implement them 1064 if schema.Types[fld.Type.Name()].Kind == ast.Interface && 1065 (!hasID(schema.Types[fld.Type.Name()]) && !hasXID(schema.Types[fld.Type.Name()])) { 1066 continue 1067 } 1068 1069 fldList = append(fldList, createField(schema, fld)) 1070 } 1071 return fldList 1072 } 1073 1074 func getFieldsWithoutIDType(schema *ast.Schema, defn *ast.Definition) ast.FieldList { 1075 fldList := make([]*ast.FieldDefinition, 0) 1076 for _, fld := range defn.Fields { 1077 if isIDField(defn, fld) { 1078 continue 1079 } 1080 1081 // see also comment in getNonIDFields 1082 if schema.Types[fld.Type.Name()].Kind == ast.Interface && 1083 (!hasID(schema.Types[fld.Type.Name()]) && !hasXID(schema.Types[fld.Type.Name()])) { 1084 continue 1085 } 1086 1087 fldList = append(fldList, createField(schema, fld)) 1088 } 1089 return fldList 1090 } 1091 1092 func getIDField(defn *ast.Definition) ast.FieldList { 1093 fldList := make([]*ast.FieldDefinition, 0) 1094 for _, fld := range defn.Fields { 1095 if isIDField(defn, fld) { 1096 newFld := *fld 1097 newFldType := *fld.Type 1098 newFld.Type = &newFldType 1099 fldList = append(fldList, &newFld) 1100 break 1101 } 1102 } 1103 return fldList 1104 } 1105 1106 func getXIDField(defn *ast.Definition) ast.FieldList { 1107 fldList := make([]*ast.FieldDefinition, 0) 1108 for _, fld := range defn.Fields { 1109 if hasIDDirective(fld) { 1110 newFld := *fld 1111 newFldType := *fld.Type 1112 newFld.Type = &newFldType 1113 fldList = append(fldList, &newFld) 1114 break 1115 } 1116 } 1117 return fldList 1118 } 1119 1120 func genArgumentsDefnString(args ast.ArgumentDefinitionList) string { 1121 if len(args) == 0 { 1122 return "" 1123 } 1124 1125 argStrs := make([]string, len(args)) 1126 for i, arg := range args { 1127 argStrs[i] = genArgumentDefnString(arg) 1128 } 1129 1130 return fmt.Sprintf("(%s)", strings.Join(argStrs, ", ")) 1131 } 1132 1133 func genArgumentsString(args ast.ArgumentList) string { 1134 if len(args) == 0 { 1135 return "" 1136 } 1137 1138 argStrs := make([]string, len(args)) 1139 for i, arg := range args { 1140 argStrs[i] = genArgumentString(arg) 1141 } 1142 1143 return fmt.Sprintf("(%s)", strings.Join(argStrs, ", ")) 1144 } 1145 1146 func genDirectivesString(direcs ast.DirectiveList) string { 1147 if len(direcs) == 0 { 1148 return "" 1149 } 1150 1151 direcArgs := make([]string, len(direcs)) 1152 1153 for idx, dir := range direcs { 1154 direcArgs[idx] = fmt.Sprintf("@%s%s", dir.Name, genArgumentsString(dir.Arguments)) 1155 } 1156 1157 return " " + strings.Join(direcArgs, " ") 1158 } 1159 1160 func genFieldsString(flds ast.FieldList) string { 1161 if flds == nil { 1162 return "" 1163 } 1164 1165 var sch strings.Builder 1166 1167 for _, fld := range flds { 1168 // Some extra types are generated by gqlparser for internal purpose. 1169 if !strings.HasPrefix(fld.Name, "__") { 1170 if d := generateDescription(fld.Description); d != "" { 1171 x.Check2(sch.WriteString(fmt.Sprintf("\t%s", d))) 1172 } 1173 x.Check2(sch.WriteString(genFieldString(fld))) 1174 } 1175 } 1176 1177 return sch.String() 1178 } 1179 1180 func genFieldString(fld *ast.FieldDefinition) string { 1181 return fmt.Sprintf( 1182 "\t%s%s: %s%s\n", fld.Name, genArgumentsDefnString(fld.Arguments), 1183 fld.Type.String(), genDirectivesString(fld.Directives)) 1184 } 1185 1186 func genArgumentDefnString(arg *ast.ArgumentDefinition) string { 1187 return fmt.Sprintf("%s: %s", arg.Name, arg.Type.String()) 1188 } 1189 1190 func genArgumentString(arg *ast.Argument) string { 1191 return fmt.Sprintf("%s: %s", arg.Name, arg.Value.String()) 1192 } 1193 1194 func generateInputString(typ *ast.Definition) string { 1195 return fmt.Sprintf("input %s {\n%s}\n", typ.Name, genFieldsString(typ.Fields)) 1196 } 1197 1198 func generateEnumString(typ *ast.Definition) string { 1199 var sch strings.Builder 1200 1201 x.Check2(sch.WriteString(fmt.Sprintf("%senum %s {\n", generateDescription(typ.Description), 1202 typ.Name))) 1203 for _, val := range typ.EnumValues { 1204 if !strings.HasPrefix(val.Name, "__") { 1205 if d := generateDescription(val.Description); d != "" { 1206 x.Check2(sch.WriteString(fmt.Sprintf("\t%s", d))) 1207 } 1208 x.Check2(sch.WriteString(fmt.Sprintf("\t%s\n", val.Name))) 1209 } 1210 } 1211 x.Check2(sch.WriteString("}\n")) 1212 1213 return sch.String() 1214 } 1215 1216 func generateDescription(description string) string { 1217 if description == "" { 1218 return "" 1219 } 1220 1221 return fmt.Sprintf("\"\"\"%s\"\"\"\n", description) 1222 } 1223 1224 func generateInterfaceString(typ *ast.Definition) string { 1225 return fmt.Sprintf("%sinterface %s%s {\n%s}\n", 1226 generateDescription(typ.Description), typ.Name, genDirectivesString(typ.Directives), 1227 genFieldsString(typ.Fields)) 1228 } 1229 1230 func generateObjectString(typ *ast.Definition) string { 1231 if len(typ.Interfaces) > 0 { 1232 interfaces := strings.Join(typ.Interfaces, " & ") 1233 return fmt.Sprintf("%stype %s implements %s%s {\n%s}\n", 1234 generateDescription(typ.Description), typ.Name, interfaces, 1235 genDirectivesString(typ.Directives), genFieldsString(typ.Fields)) 1236 } 1237 return fmt.Sprintf("%stype %s%s {\n%s}\n", 1238 generateDescription(typ.Description), typ.Name, genDirectivesString(typ.Directives), 1239 genFieldsString(typ.Fields)) 1240 } 1241 1242 // Stringify the schema as a GraphQL SDL string. It's assumed that the schema was 1243 // built by completeSchema, and so contains an original set of definitions, the 1244 // definitions from schemaExtras and generated types, queries and mutations. 1245 // 1246 // Any types in originalTypes are printed first, followed by the schemaExtras, 1247 // and then all generated types, scalars, enums, directives, query and 1248 // mutations all in alphabetical order. 1249 func Stringify(schema *ast.Schema, originalTypes []string) string { 1250 var sch, original, object, input, enum strings.Builder 1251 1252 if schema.Types == nil { 1253 return "" 1254 } 1255 1256 printed := make(map[string]bool) 1257 1258 // original defs can only be types and enums, print those in the same order 1259 // as the original schema. 1260 for _, typName := range originalTypes { 1261 typ := schema.Types[typName] 1262 switch typ.Kind { 1263 case ast.Interface: 1264 x.Check2(original.WriteString(generateInterfaceString(typ) + "\n")) 1265 case ast.Object: 1266 x.Check2(original.WriteString(generateObjectString(typ) + "\n")) 1267 case ast.Enum: 1268 x.Check2(original.WriteString(generateEnumString(typ) + "\n")) 1269 } 1270 printed[typName] = true 1271 } 1272 1273 // schemaExtras gets added to the result as a string, but we need to mark 1274 // off all it's contents as printed, so nothing in there gets printed with 1275 // the generated definitions. 1276 docExtras, gqlErr := parser.ParseSchema(&ast.Source{Input: schemaExtras}) 1277 if gqlErr != nil { 1278 panic(gqlErr) 1279 } 1280 for _, defn := range docExtras.Definitions { 1281 printed[defn.Name] = true 1282 } 1283 1284 // schema.Types is all type names (types, inputs, enums, etc.). 1285 // The original schema defs have already been printed, and everything in 1286 // schemaExtras is marked as printed. So build typeNames as anything 1287 // left to be printed. 1288 typeNames := make([]string, 0, len(schema.Types)-len(printed)) 1289 for typName, typDef := range schema.Types { 1290 if typDef.BuiltIn { 1291 // These are the types that are coming from ast.Prelude 1292 continue 1293 } 1294 if !printed[typName] { 1295 typeNames = append(typeNames, typName) 1296 } 1297 } 1298 sort.Strings(typeNames) 1299 1300 // Now consider the types generated by completeSchema, which can only be 1301 // types, inputs and enums 1302 for _, typName := range typeNames { 1303 typ := schema.Types[typName] 1304 switch typ.Kind { 1305 case ast.Object: 1306 x.Check2(object.WriteString(generateObjectString(typ) + "\n")) 1307 case ast.InputObject: 1308 x.Check2(input.WriteString(generateInputString(typ) + "\n")) 1309 case ast.Enum: 1310 x.Check2(enum.WriteString(generateEnumString(typ) + "\n")) 1311 } 1312 } 1313 1314 x.Check2(sch.WriteString( 1315 "#######################\n# Input Schema\n#######################\n\n")) 1316 x.Check2(sch.WriteString(original.String())) 1317 x.Check2(sch.WriteString( 1318 "#######################\n# Extended Definitions\n#######################\n")) 1319 x.Check2(sch.WriteString(schemaExtras)) 1320 x.Check2(sch.WriteString("\n")) 1321 x.Check2(sch.WriteString( 1322 "#######################\n# Generated Types\n#######################\n\n")) 1323 x.Check2(sch.WriteString(object.String())) 1324 x.Check2(sch.WriteString( 1325 "#######################\n# Generated Enums\n#######################\n\n")) 1326 x.Check2(sch.WriteString(enum.String())) 1327 x.Check2(sch.WriteString( 1328 "#######################\n# Generated Inputs\n#######################\n\n")) 1329 x.Check2(sch.WriteString(input.String())) 1330 x.Check2(sch.WriteString( 1331 "#######################\n# Generated Query\n#######################\n\n")) 1332 x.Check2(sch.WriteString(generateObjectString(schema.Query) + "\n")) 1333 x.Check2(sch.WriteString( 1334 "#######################\n# Generated Mutations\n#######################\n\n")) 1335 x.Check2(sch.WriteString(generateObjectString(schema.Mutation))) 1336 1337 return sch.String() 1338 } 1339 1340 func isIDField(defn *ast.Definition, fld *ast.FieldDefinition) bool { 1341 return fld.Type.Name() == idTypeFor(defn) 1342 } 1343 1344 func idTypeFor(defn *ast.Definition) string { 1345 return "ID" 1346 } 1347 1348 func xidTypeFor(defn *ast.Definition) string { 1349 for _, fld := range defn.Fields { 1350 if hasIDDirective(fld) { 1351 return fld.Name 1352 } 1353 } 1354 return "" 1355 } 1356 1357 func appendIfNotNull(errs []*gqlerror.Error, err *gqlerror.Error) gqlerror.List { 1358 if err != nil { 1359 errs = append(errs, err) 1360 } 1361 1362 return errs 1363 }