Skip to content

Commit

Permalink
Add support for multiple relationships between same collections
Browse files Browse the repository at this point in the history
  • Loading branch information
AndrewSisley committed Jan 10, 2022
1 parent 914d386 commit 6932fd2
Show file tree
Hide file tree
Showing 8 changed files with 684 additions and 24 deletions.
456 changes: 456 additions & 0 deletions db/tests/query/one_to_two_many/simple_test.go

Large diffs are not rendered by default.

44 changes: 44 additions & 0 deletions db/tests/query/one_to_two_many/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright 2020 Source Inc.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
package one_to_two_many

import (
"testing"

testUtils "github.com/sourcenetwork/defradb/db/tests"
)

var bookAuthorGQLSchema = (`
type book {
name: String
rating: Float
price: price
author: author @relation(name: "written_books")
reviewedBy: author @relation(name: "reviewed_books")
}
type author {
name: String
age: Int
verified: Boolean
written: [book] @relation(name: "written_books")
reviewed: [book] @relation(name: "reviewed_books")
}
type price {
currency: String
value: Float
books: [book]
}
`)

func executeTestCase(t *testing.T, test testUtils.QueryTestCase) {
testUtils.ExecuteQueryTestCase(t, bookAuthorGQLSchema, []string{"book", "author", "price"}, test)
}
115 changes: 115 additions & 0 deletions db/tests/query/one_to_two_many/with_sort_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// Copyright 2020 Source Inc.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
package one_to_two_many

import (
"testing"

testUtils "github.com/sourcenetwork/defradb/db/tests"
)

func TestQueryOneToTwoManyWithSort(t *testing.T) {
tests := []testUtils.QueryTestCase{
{
Description: "One-to-many relation query from one side, sort in opposite directions on children",
Query: `query {
author {
name
written (order: {rating: ASC}) {
name
}
reviewed (order: {rating: DESC}){
name
rating
}
}
}`,
Docs: map[int][]string{
//books
0: {
(`{
"name": "Painted House",
"rating": 4.9,
"author_id": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3",
"reviewedBy_id": "bae-b769708d-f552-5c3d-a402-ccfd7ac7fb04"
}`),
(`{
"name": "A Time for Mercy",
"rating": 4.5,
"author_id": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3",
"reviewedBy_id": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3"
}`),
(`{
"name": "Theif Lord",
"rating": 4.8,
"author_id": "bae-b769708d-f552-5c3d-a402-ccfd7ac7fb04",
"reviewedBy_id": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3"
}`),
},
//authors
1: {
// bae-41598f0c-19bc-5da6-813b-e80f14a10df3
(`{
"name": "John Grisham",
"age": 65,
"verified": true
}`),
// bae-b769708d-f552-5c3d-a402-ccfd7ac7fb04
(`{
"name": "Cornelia Funke",
"age": 62,
"verified": false
}`),
},
},
Results: []map[string]interface{}{
{
"name": "John Grisham",
"reviewed": []map[string]interface{}{
{
"name": "Theif Lord",
"rating": 4.8,
},
{
"name": "A Time for Mercy",
"rating": 4.5,
},
},
"written": []map[string]interface{}{
{
"name": "A Time for Mercy",
},
{
"name": "Painted House",
},
},
},
{
"name": "Cornelia Funke",
"reviewed": []map[string]interface{}{
{
"name": "Painted House",
"rating": 4.9,
},
},
"written": []map[string]interface{}{
{
"name": "Theif Lord",
},
},
},
},
},
}

for _, test := range tests {
executeTestCase(t, test)
}
}
22 changes: 20 additions & 2 deletions query/graphql/planner/type_join.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ func (p *Planner) makeTypeJoinOne(parent *selectNode, source planNode, subType *

typeJoin.subTypeName = subTypeFieldDesc.Name
typeJoin.subTypeFieldName = subtypefieldname
typeJoin.rootName = desc.Name // @todo: Correctly handle getting sub/root names
typeJoin.rootName = getOtherRelationshipFieldName(rel, subType.Name)

// split filter
if scan, ok := source.(*scanNode); ok {
Expand Down Expand Up @@ -357,7 +357,13 @@ func (p *Planner) makeTypeJoinMany(parent *selectNode, source planNode, subType
}
typeJoin.subType = selectPlan
typeJoin.subTypeName = subTypeFieldDesc.Name
typeJoin.rootName = desc.Name
rm := p.db.SchemaManager().Relations
rel, err := rm.GetRelation(subTypeFieldDesc.RelationName)
if err != nil {
return nil, err
}

typeJoin.rootName = getOtherRelationshipFieldName(rel, subType.Name)

// split filter
if scan, ok := source.(*scanNode); ok {
Expand All @@ -367,6 +373,18 @@ func (p *Planner) makeTypeJoinMany(parent *selectNode, source planNode, subType
return typeJoin, nil
}

// Returns the name of the field on the other side of the relationship
func getOtherRelationshipFieldName(relationship *schema.Relation, knownFieldName string) string {
for _, relationshipField := range relationship.GetFields() {
if relationshipField != knownFieldName {
return relationshipField
}
}

// if it makes it to here, both sides of the relationship must share the same name
return knownFieldName
}

func (n *typeJoinMany) Init() error {
if err := n.subType.Init(); err != nil {
return err
Expand Down
1 change: 1 addition & 0 deletions query/graphql/schema/descriptions.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ func (g *Generator) CreateDescriptions(types []*gql.Object) ([]base.CollectionDe
field.Type.Name(),
t.Name())
}
fd.RelationName = rel.name

_, fieldRelationType, ok := rel.GetField(fname)
if !ok {
Expand Down
44 changes: 24 additions & 20 deletions query/graphql/schema/descriptions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,11 +173,12 @@ func TestSingleSimpleType(t *testing.T) {
Typ: core.NONE_CRDT,
},
{
Name: "author",
Kind: base.FieldKind_FOREIGN_OBJECT,
Typ: core.NONE_CRDT,
Schema: "author",
Meta: base.Meta_Relation_ONE | base.Meta_Relation_ONEONE,
Name: "author",
RelationName: "author_book",
Kind: base.FieldKind_FOREIGN_OBJECT,
Typ: core.NONE_CRDT,
Schema: "author",
Meta: base.Meta_Relation_ONE | base.Meta_Relation_ONEONE,
},
{
Name: "author_id",
Expand Down Expand Up @@ -219,11 +220,12 @@ func TestSingleSimpleType(t *testing.T) {
Typ: core.LWW_REGISTER,
},
{
Name: "published",
Kind: base.FieldKind_FOREIGN_OBJECT,
Typ: core.NONE_CRDT,
Schema: "book",
Meta: base.Meta_Relation_ONE | base.Meta_Relation_ONEONE | base.Meta_Relation_Primary,
Name: "published",
RelationName: "author_book",
Kind: base.FieldKind_FOREIGN_OBJECT,
Typ: core.NONE_CRDT,
Schema: "book",
Meta: base.Meta_Relation_ONE | base.Meta_Relation_ONEONE | base.Meta_Relation_Primary,
},
{
Name: "published_id",
Expand Down Expand Up @@ -263,11 +265,12 @@ func TestSingleSimpleType(t *testing.T) {
Typ: core.NONE_CRDT,
},
{
Name: "author",
Kind: base.FieldKind_FOREIGN_OBJECT,
Typ: core.NONE_CRDT,
Schema: "author",
Meta: base.Meta_Relation_ONE | base.Meta_Relation_ONEMANY | base.Meta_Relation_Primary,
Name: "author",
RelationName: "author_book",
Kind: base.FieldKind_FOREIGN_OBJECT,
Typ: core.NONE_CRDT,
Schema: "author",
Meta: base.Meta_Relation_ONE | base.Meta_Relation_ONEMANY | base.Meta_Relation_Primary,
},
{
Name: "author_id",
Expand Down Expand Up @@ -309,11 +312,12 @@ func TestSingleSimpleType(t *testing.T) {
Typ: core.LWW_REGISTER,
},
{
Name: "published",
Kind: base.FieldKind_FOREIGN_OBJECT_ARRAY,
Typ: core.NONE_CRDT,
Schema: "book",
Meta: base.Meta_Relation_MANY | base.Meta_Relation_ONEMANY,
Name: "published",
RelationName: "author_book",
Kind: base.FieldKind_FOREIGN_OBJECT_ARRAY,
Typ: core.NONE_CRDT,
Schema: "book",
Meta: base.Meta_Relation_MANY | base.Meta_Relation_ONEMANY,
},
},
},
Expand Down
22 changes: 20 additions & 2 deletions query/graphql/schema/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -299,15 +299,15 @@ func (g *Generator) buildTypesFromAST(document *ast.Document) ([]*gql.Object, er
fields[fType.Name+"_id"] = &gql.Field{Type: gql.ID}

// register the relation
relName, err := genRelationName(objconf.Name, ttype.Name())
relName, err := getRelationshipName(field, objconf, ttype)
if err != nil {
return nil, err
}
g.manager.Relations.RegisterSingle(relName, ttype.Name(), fType.Name, base.Meta_Relation_ONE)
case *gql.List:
ltype := subobj.OfType
// register the relation
relName, err := genRelationName(objconf.Name, ltype.Name())
relName, err := getRelationshipName(field, objconf, ltype)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -352,6 +352,24 @@ func (g *Generator) buildTypesFromAST(document *ast.Document) ([]*gql.Object, er
return objs, nil
}

func getRelationshipName(field *ast.FieldDefinition, hostName gql.ObjectConfig, targetName gql.Type) (string, error) {
for _, directive := range field.Directives {
if directive.Name.Value == "relation" {
for _, arguement := range directive.Arguments {
if arguement.Name.Value == "name" {
name, isString := arguement.Value.GetValue().(string)
if !isString {
return "", fmt.Errorf("Relationship name must be of type string, but was: %v", arguement.Value.GetKind())
}
return name, nil
}
}
}
}

return genRelationName(hostName.Name, targetName.Name())
}

// Given a parsed ast.Node object, lookup the type in the TypeMap and return if its there
// otherwise return an error
// ast.Node, can either be a ast.Named type, a ast.List, or a ast.NonNull.
Expand Down
4 changes: 4 additions & 0 deletions query/graphql/schema/relations.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,10 @@ func (r *Relation) finalize() error {
return nil
}

func (r Relation) GetFields() []string {
return r.fields
}

// Type returns what kind of relation it is
func (r Relation) Kind() uint8 {
return r.relType
Expand Down

0 comments on commit 6932fd2

Please sign in to comment.