Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(graphql): adds @default directive to graphql (#8017) #8837

Merged
merged 3 commits into from
Aug 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions graphql/e2e/schema/apollo_service_response.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,10 @@ input CustomHTTP {
skipIntrospection: Boolean
}

input DgraphDefault {
value: String
}

type Point {
longitude: Float!
latitude: Float!
Expand Down Expand Up @@ -201,6 +205,7 @@ directive @search(by: [String!]) on FIELD_DEFINITION
directive @embedding on FIELD_DEFINITION
directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION
directive @id(interface: Boolean) on FIELD_DEFINITION
directive @default(add: DgraphDefault, update: DgraphDefault) on FIELD_DEFINITION
directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION
directive @secret(field: String!, pred: String) on OBJECT | INTERFACE
directive @remote on OBJECT | INTERFACE | UNION | INPUT_OBJECT | ENUM
Expand Down
5 changes: 5 additions & 0 deletions graphql/e2e/schema/generatedSchema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@ input CustomHTTP {
skipIntrospection: Boolean
}

input DgraphDefault {
value: String
}

type Point {
longitude: Float!
latitude: Float!
Expand Down Expand Up @@ -182,6 +186,7 @@ directive @search(by: [String!]) on FIELD_DEFINITION
directive @embedding on FIELD_DEFINITION
directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION
directive @id(interface: Boolean) on FIELD_DEFINITION
directive @default(add: DgraphDefault, update: DgraphDefault) on FIELD_DEFINITION
directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION
directive @secret(field: String!, pred: String) on OBJECT | INTERFACE
directive @auth(
Expand Down
161 changes: 160 additions & 1 deletion graphql/resolve/add_mutation_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5499,4 +5499,163 @@
error2:
{
"message": "failed to rewrite mutation payload because field id cannot be empty"
}
}

-
name: "Add mutation with @default directive"
gqlmutation: |
mutation($input: [AddBookingInput!]!) {
addBooking(input: $input) {
booking {
name
created
updated
}
}
}
gqlvariables: |
{
"input": [
{
"name": "Holiday to Bermuda"
}
]
}
explanation: "As booking has @default fields and is being added, these should be set to the default add value"
dgmutations:
- setjson: |
{
"Booking.created": "2000-01-01T00:00:00.00Z",
"Booking.active": "false",
"Booking.count": "1",
"Booking.hotel": "add",
"Booking.length": "1.1",
"Booking.status": "ACTIVE",
"Booking.name": "Holiday to Bermuda",
"Booking.updated": "2000-01-01T00:00:00.00Z",
"dgraph.type": [
"Booking"
],
"uid":"_:Booking_1"
}
-
name: "Add mutation with @default directive uses provided values"
gqlmutation: |
mutation($input: [AddBookingInput!]!) {
addBooking(input: $input) {
booking {
name
created
updated
}
}
}
gqlvariables: |
{
"input": [
{
"name": "Holiday to Bermuda",
"created": "2022-10-12T07:20:50.52Z",
"updated": "2023-10-12T07:20:50.52Z",
"active": false,
"length": 12.3,
"status": "INACTIVE",
"hotel": "provided"
}
]
}
explanation: "Fields with @default(add) should use input values if provided (note that count is still using default)"
dgmutations:
- setjson: |
{
"Booking.name": "Holiday to Bermuda",
"Booking.created": "2022-10-12T07:20:50.52Z",
"Booking.updated": "2023-10-12T07:20:50.52Z",
"Booking.active": false,
"Booking.count": "1",
"Booking.hotel": "provided",
"Booking.length": 12.3,
"Booking.status": "INACTIVE",
"dgraph.type": [
"Booking"
],
"uid":"_:Booking_1"
}
-
name: "Upsert mutation with @default directives where only one of the nodes exists"
explanation: "Booking1 should only have updated timestamp as it exists, Booking2 should have created and updated timestamps"
gqlmutation: |
mutation addBookingXID($input: [AddBookingXIDInput!]!) {
addBookingXID(input: $input, upsert: true) {
bookingXID {
name
}
}
}
gqlvariables: |
{ "input":
[
{
"id": "Booking1",
"name": "Trip to Bermuda"
},
{
"id": "Booking2",
"name": "Trip to Antigua"
}
]
}
dgquery: |-
query {
BookingXID_1(func: eq(BookingXID.id, "Booking1")) {
uid
dgraph.type
}
BookingXID_2(func: eq(BookingXID.id, "Booking2")) {
uid
dgraph.type
}
}
qnametouid: |-
{
"BookingXID_1": "0x11"
}
dgquerysec: |-
query {
BookingXID_1 as BookingXID_1(func: uid(0x11)) @filter(type(BookingXID)) {
uid
}
}
dgmutations:
- setjson: |
{
"uid" : "uid(BookingXID_1)",
"BookingXID.id": "Booking1",
"BookingXID.name": "Trip to Bermuda",
"BookingXID.updated": "2000-01-01T00:00:00.00Z",
"BookingXID.active": "true",
"BookingXID.count": "2",
"BookingXID.length": "1.2",
"BookingXID.status": "INACTIVE",
"BookingXID.hotel": "update"
}
cond: "@if(gt(len(BookingXID_1), 0))"
- setjson: |
{
"uid": "_:BookingXID_2",
"BookingXID.id": "Booking2",
"BookingXID.name": "Trip to Antigua",
"BookingXID.created": "2000-01-01T00:00:00.00Z",
"BookingXID.updated": "2000-01-01T00:00:00.00Z",
"BookingXID.active": "false",
"BookingXID.count": "1",
"BookingXID.length": "1.1",
"BookingXID.status": "ACTIVE",
"BookingXID.hotel": "add",
"dgraph.type": [
"BookingXID"
]
}
23 changes: 20 additions & 3 deletions graphql/resolve/mutation_rewriter.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,11 @@ import (
)

const (
MutationQueryVar = "x"
MutationQueryVarUID = "uid(x)"
updateMutationCondition = `gt(len(x), 0)`
MutationQueryVar = "x"
MutationQueryVarUID = "uid(x)"
updateMutationCondition = `gt(len(x), 0)`
defaultDirectiveUpdateAct = "update"
defaultDirectiveAddAct = "add"
)

// Enum passed on to rewriteObject function.
Expand Down Expand Up @@ -1587,6 +1589,8 @@ func rewriteObject(
}
}

action := defaultDirectiveUpdateAct

// This is not an XID reference. This is also not a UID reference.
// This is definitely a new node.
// Create new node
Expand Down Expand Up @@ -1635,6 +1639,19 @@ func rewriteObject(
// "_:Project2" . myUID will store the variable generated to reference this node.
newObj["dgraph.type"] = dgraphTypes
newObj["uid"] = myUID
action = defaultDirectiveAddAct
}

// Now we know whether this is a new node or not, we can set @default(add/update) fields
for _, field := range typ.Fields() {
var pred = field.DgraphPredicate()
if newObj[pred] != nil {
continue
}
var value = field.GetDefaultValue(action)
if value != nil {
newObj[pred] = value
}
}

// Add Inverse Link if necessary
Expand Down
26 changes: 26 additions & 0 deletions graphql/resolve/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,32 @@ type Student implements People {
taughtBy: [Teacher] @hasInverse(field: "teaches")
}

# For testing default values
type Booking {
id: ID!
name: String!
created: DateTime! @default(add: {value: "$now"})
updated: DateTime! @default(add: {value: "$now"}, update: {value: "$now"})
count: Int! @default(add: {value: "1"}, update: {value: "2"})
length: Float! @default(add: {value: "1.1"}, update: {value: "1.2"})
hotel: String! @default(add: {value: "add"}, update: {value: "update"})
active: Boolean! @default(add: {value: "false"}, update: {value: "true"})
status: Status! @default(add: {value: "ACTIVE"}, update: {value: "INACTIVE"})
}

# For testing default values with upserts
type BookingXID {
id: String! @id
name: String!
created: DateTime! @default(add: {value: "$now"})
updated: DateTime! @default(add: {value: "$now"}, update: {value: "$now"})
count: Int! @default(add: {value: "1"}, update: {value: "2"})
length: Float! @default(add: {value: "1.1"}, update: {value: "1.2"})
hotel: String! @default(add: {value: "add"}, update: {value: "update"})
active: Boolean! @default(add: {value: "false"}, update: {value: "true"})
status: Status! @default(add: {value: "ACTIVE"}, update: {value: "INACTIVE"})
}

type Comment {
id: ID!
author: String!
Expand Down
87 changes: 86 additions & 1 deletion graphql/resolve/update_mutation_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2331,7 +2331,7 @@
explaination: "If nested object have inherited @id field which have interface argument set, and that
field already exist in some other implementing type than we returns error.In below mutation manages
is of type LibraryMember but node with given refID already exist in some other
type than LibraryMember"
type than than LibraryMember"
gqlmutation: |
mutation update($patch: UpdateLibraryManagerInput!) {
updateLibraryManager(input: $patch) {
Expand Down Expand Up @@ -2447,3 +2447,88 @@
"uid": "uid(x)"
}
cond: "@if(gt(len(x), 0))"
-
name: "Update with @default directive"
gqlmutation: |
mutation updateBooking($patch: UpdateBookingInput!) {
updateBooking(input: $patch) {
booking {
id
}
}
}
gqlvariables: |
{ "patch":
{ "filter": {
"id": ["0x123", "0x124"]
},
"set": {
"name": "Flight to Antigua"
}
}
}
explanation: "The update patch should include default values on the fields with the @default(update:) directive"
dgquerysec: |-
query {
x as updateBooking(func: uid(0x123, 0x124)) @filter(type(Booking)) {
uid
}
}
dgmutations:
- setjson: |
{ "uid" : "uid(x)",
"Booking.name": "Flight to Antigua",
"Booking.updated": "2000-01-01T00:00:00.00Z",
"Booking.active": "true",
"Booking.count": "2",
"Booking.length": "1.2",
"Booking.status": "INACTIVE",
"Booking.hotel": "update"
}
cond: "@if(gt(len(x), 0))"
-
name: "Update with @default directive uses provided values"
gqlmutation: |
mutation updateBooking($patch: UpdateBookingInput!) {
updateBooking(input: $patch) {
booking {
id
}
}
}
gqlvariables: |
{ "patch":
{ "filter": {
"id": ["0x123", "0x124"]
},
"set": {
"name": "Flight to Antigua",
"updated": "2022-10-12T07:20:50.52Z",
"active": false,
"length": 12.3,
"status": "ACTIVE",
"hotel": "provided"
}
}
}
explanation: "Fields with @default(update) should use input values if provided (note that count is still using default)"
dgquerysec: |-
query {
x as updateBooking(func: uid(0x123, 0x124)) @filter(type(Booking)) {
uid
}
}
dgmutations:
- setjson: |
{ "uid" : "uid(x)",
"Booking.name": "Flight to Antigua",
"Booking.updated": "2022-10-12T07:20:50.52Z",
"Booking.active": false,
"Booking.count": "2",
"Booking.length": 12.3,
"Booking.status": "ACTIVE",
"Booking.hotel": "provided"
}
cond: "@if(gt(len(x), 0))"
Loading
Loading