Skip to content

Commit

Permalink
feat: Add primary directive for schema definitions (@primary) (#650)
Browse files Browse the repository at this point in the history
* Added primary directive support for schemas

* Tests for primary directive
  • Loading branch information
jsimnz authored Jul 22, 2022
1 parent dd28534 commit a0332b7
Show file tree
Hide file tree
Showing 3 changed files with 313 additions and 5 deletions.
264 changes: 264 additions & 0 deletions query/graphql/schema/descriptions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -241,9 +241,273 @@ func TestSingleSimpleType(t *testing.T) {
},
},
},
{
description: "Multiple simple types",
sdl: `
type user {
name: String
age: Int
verified: Boolean
}
type author {
name: String
publisher: String
rating: Float
}
`,
targetDescs: []client.CollectionDescription{
{
Name: "user",
Schema: client.SchemaDescription{
Name: "user",
Fields: []client.FieldDescription{
{
Name: "_key",
Kind: client.FieldKind_DocKey,
Typ: client.NONE_CRDT,
},
{
Name: "age",
Kind: client.FieldKind_INT,
Typ: client.LWW_REGISTER,
},
{
Name: "name",
Kind: client.FieldKind_STRING,
Typ: client.LWW_REGISTER,
},
{
Name: "verified",
Kind: client.FieldKind_BOOL,
Typ: client.LWW_REGISTER,
},
},
},
Indexes: testDefaultIndex,
},
{
Name: "author",
Schema: client.SchemaDescription{
Name: "author",
Fields: []client.FieldDescription{
{
Name: "_key",
Kind: client.FieldKind_DocKey,
Typ: client.NONE_CRDT,
},
{
Name: "name",
Kind: client.FieldKind_STRING,
Typ: client.LWW_REGISTER,
},
{
Name: "publisher",
Kind: client.FieldKind_STRING,
Typ: client.LWW_REGISTER,
},
{
Name: "rating",
Kind: client.FieldKind_FLOAT,
Typ: client.LWW_REGISTER,
},
},
},
Indexes: testDefaultIndex,
},
},
},
{
description: "Multiple types with relations (one-to-one)",
sdl: `
type book {
name: String
rating: Float
author: author @relation(name:"book_authors")
}
type author {
name: String
age: Int
published: book @relation(name:"book_authors")
}
`,
targetDescs: []client.CollectionDescription{
{
Name: "book",
Schema: client.SchemaDescription{
Name: "book",
Fields: []client.FieldDescription{
{
Name: "_key",
Kind: client.FieldKind_DocKey,
Typ: client.NONE_CRDT,
},
{
Name: "author",
RelationName: "book_authors",
Kind: client.FieldKind_FOREIGN_OBJECT,
Typ: client.NONE_CRDT,
Schema: "author",
RelationType: client.Relation_Type_ONE | client.Relation_Type_ONEONE,
},
{
Name: "author_id",
Kind: client.FieldKind_DocKey,
Typ: client.LWW_REGISTER,
RelationType: client.Relation_Type_INTERNAL_ID,
},
{
Name: "name",
Kind: client.FieldKind_STRING,
Typ: client.LWW_REGISTER,
},
{
Name: "rating",
Kind: client.FieldKind_FLOAT,
Typ: client.LWW_REGISTER,
},
},
},
Indexes: testDefaultIndex,
},
{
Name: "author",
Schema: client.SchemaDescription{
Name: "author",
Fields: []client.FieldDescription{
{
Name: "_key",
Kind: client.FieldKind_DocKey,
Typ: client.NONE_CRDT,
},
{
Name: "age",
Kind: client.FieldKind_INT,
Typ: client.LWW_REGISTER,
},
{
Name: "name",
Kind: client.FieldKind_STRING,
Typ: client.LWW_REGISTER,
},
{
Name: "published",
RelationName: "book_authors",
Kind: client.FieldKind_FOREIGN_OBJECT,
Typ: client.NONE_CRDT,
Schema: "book",
RelationType: client.Relation_Type_ONE | client.Relation_Type_ONEONE | client.Relation_Type_Primary,
},
{
Name: "published_id",
Kind: client.FieldKind_DocKey,
Typ: client.LWW_REGISTER,
RelationType: client.Relation_Type_INTERNAL_ID,
},
},
},
Indexes: testDefaultIndex,
},
},
},
{
description: "Multiple types with relations (one-to-one) with directive",
sdl: `
type book {
name: String
rating: Float
author: author @primary
}
type author {
name: String
age: Int
published: book
}
`,
targetDescs: []client.CollectionDescription{
{
Name: "book",
Schema: client.SchemaDescription{
Name: "book",
Fields: []client.FieldDescription{
{
Name: "_key",
Kind: client.FieldKind_DocKey,
Typ: client.NONE_CRDT,
},
{
Name: "author",
RelationName: "author_book",
Kind: client.FieldKind_FOREIGN_OBJECT,
Typ: client.NONE_CRDT,
Schema: "author",
RelationType: client.Relation_Type_ONE | client.Relation_Type_ONEONE | client.Relation_Type_Primary,
},
{
Name: "author_id",
Kind: client.FieldKind_DocKey,
Typ: client.LWW_REGISTER,
RelationType: client.Relation_Type_INTERNAL_ID,
},
{
Name: "name",
Kind: client.FieldKind_STRING,
Typ: client.LWW_REGISTER,
},
{
Name: "rating",
Kind: client.FieldKind_FLOAT,
Typ: client.LWW_REGISTER,
},
},
},
Indexes: testDefaultIndex,
},
{
Name: "author",
Schema: client.SchemaDescription{
Name: "author",
Fields: []client.FieldDescription{
{
Name: "_key",
Kind: client.FieldKind_DocKey,
Typ: client.NONE_CRDT,
},
{
Name: "age",
Kind: client.FieldKind_INT,
Typ: client.LWW_REGISTER,
},
{
Name: "name",
Kind: client.FieldKind_STRING,
Typ: client.LWW_REGISTER,
},
{
Name: "published",
RelationName: "author_book",
Kind: client.FieldKind_FOREIGN_OBJECT,
Typ: client.NONE_CRDT,
Schema: "book",
RelationType: client.Relation_Type_ONE | client.Relation_Type_ONEONE,
},
{
Name: "published_id",
Kind: client.FieldKind_DocKey,
Typ: client.LWW_REGISTER,
RelationType: client.Relation_Type_INTERNAL_ID,
},
},
},
Indexes: testDefaultIndex,
},
},
},
{
description: "Multiple types with relations (one-to-many)",
sdl: `
type book {
name: String
rating: Float
Expand Down
21 changes: 19 additions & 2 deletions query/graphql/schema/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -431,11 +431,18 @@ func (g *Generator) buildTypesFromAST(
return nil, err
}

// set the primary relation bit on the relation type if the directive exists on the
// field
relType := client.Relation_Type_ONE
if _, exists := findDirective(field, "primary"); exists {
relType |= client.Relation_Type_Primary
}

_, err = g.manager.Relations.RegisterSingle(
relName,
ttype.Name(),
fType.Name,
client.Relation_Type_ONE,
relType,
)
if err != nil {
log.ErrorE(ctx, "Error while registering single relation", err)
Expand Down Expand Up @@ -509,7 +516,7 @@ func getRelationshipName(
hostName gql.ObjectConfig,
targetName gql.Type,
) (string, error) {
// search for a user-defined name, and return it if found
// search for a @relation directive name, and return it if found
for _, directive := range field.Directives {
if directive.Name.Value == "relation" {
for _, argument := range directive.Arguments {
Expand Down Expand Up @@ -1300,6 +1307,16 @@ func isNumericArray(list *gql.List) bool {
list.OfType.Name() == gql.NewNonNull(gql.Int).Name()
}

// find a given directive
func findDirective(field *ast.FieldDefinition, directiveName string) (*ast.Directive, bool) {
for _, directive := range field.Directives {
if directive.Name.Value == directiveName {
return directive, true
}
}
return nil, false
}

/* Example
typeDefs := ` ... `
Expand Down
33 changes: 30 additions & 3 deletions query/graphql/schema/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ import (
gql "github.com/graphql-go/graphql"
)

const ExplainLabel string = "explain"
const (
ExplainLabel string = "explain"
PrimaryLabel string = "primary"
RelationLabel string = "relation"
)

var (

Expand Down Expand Up @@ -117,9 +121,7 @@ var (
}

ExplainDirective *gql.Directive = gql.NewDirective(gql.DirectiveConfig{

Name: ExplainLabel,

Args: gql.FieldConfigArgument{
"simple": &gql.ArgumentConfig{
Type: gql.Boolean,
Expand All @@ -142,6 +144,31 @@ var (
gql.DirectiveLocationMutation,
},
})

// PrimaryDirective @primary is used to indicate the primary
// side of a one-to-one relationship.
PrimaryDirective = gql.NewDirective(gql.DirectiveConfig{
Name: PrimaryLabel,
Locations: []string{
gql.DirectiveLocationFieldDefinition,
},
})

// RelationDirective @relation is used to explicitly define
// the attributes of a relationship, specifically, the name
// if you don't want to use the default generated relationship
// name.
RelationDirective = gql.NewDirective(gql.DirectiveConfig{
Name: RelationLabel,
Args: gql.FieldConfigArgument{
"name": &gql.ArgumentConfig{
Type: gql.String,
},
},
Locations: []string{
gql.DirectiveLocationFieldDefinition,
},
})
)

func NewArgConfig(t gql.Type) *gql.ArgumentConfig {
Expand Down

0 comments on commit a0332b7

Please sign in to comment.