Skip to content

feat(graphql): adds @default directive for setting default field values at create and update #8017

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

Merged
merged 12 commits into from
Nov 26, 2021
Merged
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 @@ -117,6 +117,10 @@ input CustomHTTP {
skipIntrospection: Boolean
}

input DgraphDefault {
value: String
}

type Point {
longitude: Float!
latitude: Float!
Expand Down Expand Up @@ -199,6 +203,7 @@ directive @hasInverse(field: String!) on FIELD_DEFINITION
directive @search(by: [DgraphIndex!]) 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 @@ -98,6 +98,10 @@ input CustomHTTP {
skipIntrospection: Boolean
}

input DgraphDefault {
value: String
}

type Point {
longitude: Float!
latitude: Float!
Expand Down Expand Up @@ -180,6 +184,7 @@ directive @hasInverse(field: String!) on FIELD_DEFINITION
directive @search(by: [DgraphIndex!]) 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
157 changes: 157 additions & 0 deletions graphql/resolve/add_mutation_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5504,3 +5504,160 @@
implementing types of same interfaces: T1 and T2"
}

-
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"
]
}
15 changes: 15 additions & 0 deletions graphql/resolve/mutation_rewriter.go
Original file line number Diff line number Diff line change
Expand Up @@ -1598,6 +1598,8 @@ func rewriteObject(
}
}

action := "update"

// 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 @@ -1646,6 +1648,19 @@ func rewriteObject(
// "_:Project2" . myUID will store the variable generated to reference this node.
newObj["dgraph.type"] = dgraphTypes
newObj["uid"] = myUID
action = "add"
}

// 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
85 changes: 85 additions & 0 deletions graphql/resolve/update_mutation_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2405,3 +2405,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