From 8c416fe2b931a497b0071bcb7622b19e5d608a89 Mon Sep 17 00:00:00 2001 From: rderbier Date: Mon, 13 Feb 2023 15:17:09 -0800 Subject: [PATCH 1/4] group mutation info --- content/dql-overview/_index.md | 8 - content/dql-syntax/_index.md | 12 ++ content/dql-syntax/dql-mutation.md | 182 ++++++++++++++++++ .../{dql-overview => dql-syntax}/dql-query.md | 4 +- content/dql-syntax/dql-rdf.md | 79 ++++++++ .../to-sort.md => dql-syntax/to-sort.md.txt} | 0 content/dql/_index.md | 10 +- 7 files changed, 278 insertions(+), 17 deletions(-) delete mode 100644 content/dql-overview/_index.md create mode 100644 content/dql-syntax/_index.md create mode 100644 content/dql-syntax/dql-mutation.md rename content/{dql-overview => dql-syntax}/dql-query.md (97%) create mode 100644 content/dql-syntax/dql-rdf.md rename content/{dql-overview/to-sort.md => dql-syntax/to-sort.md.txt} (100%) diff --git a/content/dql-overview/_index.md b/content/dql-overview/_index.md deleted file mode 100644 index 07e48a8d..00000000 --- a/content/dql-overview/_index.md +++ /dev/null @@ -1,8 +0,0 @@ -+++ -title = "DQL syntax" -[menu.main] - name = "DQL Syntax" - identifier = "dql-syntax" - parent = "dql" - weight = 2 -+++ diff --git a/content/dql-syntax/_index.md b/content/dql-syntax/_index.md new file mode 100644 index 00000000..5d7616ea --- /dev/null +++ b/content/dql-syntax/_index.md @@ -0,0 +1,12 @@ ++++ +title = "DQL syntax" +[menu.main] + name = "DQL Syntax" + identifier = "dql-syntax" + parent = "dql" + weight = 2 ++++ + +Dgraph Query Language (DQL) is Dgraph’s proprietary language to add, modify, delete and fetch data. + +Fetching data is done through [DQL Queries]({{< relref "dql-query.md" >}}). Adding, modifying or deleting data is done through [DQL Mutations]({{< relref "dql-mutation.md" >}}). diff --git a/content/dql-syntax/dql-mutation.md b/content/dql-syntax/dql-mutation.md new file mode 100644 index 00000000..5ed0b4f5 --- /dev/null +++ b/content/dql-syntax/dql-mutation.md @@ -0,0 +1,182 @@ ++++ +title = "DQL mutation" +[menu.main] + name = "DQL mutation" + identifier = "dql-mutation" + parent = "dql-syntax" + weight = 2 ++++ + +Dgraph Query Language (DQL) is Dgraph's proprietary language to add, modify, delete and fetch data. + +Fetching data is done through [DQL Queries]({{< relref "dql-query.md" >}}). Adding, modifying or deleting data is done through ***DQL Mutations***. + +This overview explains the structure of DQL Mutations and provides links to the appropriate DQL reference documentation. + + +DQL mutations support JSON or [RDF]({{< relref "dql-rdf.md" >}}) format. + +### Add data +In DQL, you add data using a set mutation, identified by the `set` keyword. +{{% tabs %}} {{< tab "JSON" >}} +```dql + { + "set": [ + { + "name":"Star Wars: Episode IV - A New Hope", + "release_date": "1977-05-25", + "director": { + "name": "George Lucas", + "dgraph.type": "Person" + }, + "starring" : [ + { + "name": "Luke Skywalker" + }, + { + "name": "Princess Leia" + }, + { + "name": "Han Solo" + } + ] + }, + { + "name":"Star Trek: The Motion Picture", + "release_date": "1979-12-07" + } + ] + } +``` +{{% /tab %}} +{{< tab "RDF" >}} +``` +{ + set { + # triples in here + _:n1 "Star Wars: Episode IV - A New Hope" . + _:n1 "1977-05-25" . + _:n1 _:n2 . + _:n2 "George Lucas" . + + } +} +``` + +triples are in [RDF]({{< relref "dql-rdf.md" >}}) format. + +#### Node reference +A mutation can include a blank nodes as an identifier for the subject or object, or a known UID. +``` +{ + set { + # triples in here + <0x632ea2> "1977-05-25" . + } +} +``` +will add the `release_date` information to the node identified by UID `0x632ea2`. + +#### language support +``` +{ + set { + # triples in here + <0x632ea2> "Star Wars, épisode IV : Un nouvel espoir"@fr . + } +} +``` + +{{% /tab %}} +{{% /tabs %}} + + +### delete data +A delete mutation, identified by the `delete` keyword, removes +[triples](/mutations/triples) from the store. + +For example, if the store contained the following: +```RDF +<0xf11168064b01135b> "Lewis Carrol" +<0xf11168064b01135b> "1998" +<0xf11168064b01135b> "Person" . +``` + +Then, the following delete mutation deletes the specified erroneous data, and +removes it from any indexes: + +```sh +{ + delete { + <0xf11168064b01135b> "1998" . + } +} +``` + +#### Wildcard delete + +In many cases you will need to delete multiple types of data for a predicate. +For a particular node `N`, all data for predicate `P` (and all corresponding +indexing) is removed with the pattern `S P *`. + +```sh +{ + delete { + <0xf11168064b01135b> * . + } +} +``` + +The pattern `S * *` deletes all the known edges out of a node, any reverse edges +corresponding to the removed edges, and any indexing for the removed data. + +{{% notice "note" %}} For mutations that fit the `S * *` pattern, only +predicates that are among the types associated with a given node (using +`dgraph.type`) are deleted. Any predicates that don't match one of the +node's types will remain after an `S * *` delete mutation.{{% /notice %}} + +```sh +{ + delete { + <0xf11168064b01135b> * * . + } +} +``` + +If the node `S` in the delete pattern `S * *` has only a few predicates with a +type defined by `dgraph.type`, then only those triples with typed predicates are +deleted. A node that contains untyped predicates will still exist after a +`S * *` delete mutation. + +{{% notice "note" %}} The patterns `* P O` and `* * O` are not supported because +it's inefficient to store and find all the incoming edges. {{% /notice %}} + +#### Deletion of non-list predicates + +Deleting the value of a non-list predicate (i.e a 1-to-1 relationship) can be +done in two ways. + +* Using the [wildcard delete](#wildcard-delete) (star notation) + mentioned in the last section. +* Setting the object to a specific value. If the value passed is not the +current value, the mutation will succeed but will have no effect. If the value +passed is the current value, the mutation will succeed and will delete the +non-list predicate. + +For language-tagged values, the following special syntax is supported: + +``` +{ + delete { + <0x12345> * . + } +} +``` + +In this example, the value of the `name` field that is tagged with the language +tag `es` is deleted. Other tagged values are left untouched. + + + +### Comments +Anything on a line following a `#` is a comment diff --git a/content/dql-overview/dql-query.md b/content/dql-syntax/dql-query.md similarity index 97% rename from content/dql-overview/dql-query.md rename to content/dql-syntax/dql-query.md index 41dffdca..63e03fd1 100644 --- a/content/dql-overview/dql-query.md +++ b/content/dql-syntax/dql-query.md @@ -7,9 +7,7 @@ title = "DQL query" weight = 1 +++ -Dgraph Query Language (DQL) is Dgraph's proprietary language to add, modify, delete and fetch data. - -Fetching data is done through **DQL Queries**. Adding, modifying or deleting data is done through DQL Mutations. +Fetching data with Dgraph Query Language (DQL), is done through **DQL Queries**. Adding, modifying or deleting data is done through [DQL Mutations]({{< relref "dql-mutation.md" >}}). This overview explains the structure of DQL Queries and provides links to the appropriate DQL reference documentation. diff --git a/content/dql-syntax/dql-rdf.md b/content/dql-syntax/dql-rdf.md new file mode 100644 index 00000000..c8fd31f0 --- /dev/null +++ b/content/dql-syntax/dql-rdf.md @@ -0,0 +1,79 @@ ++++ +title = "RDF" +[menu.main] + name = "RDF" + identifier = "dql-rdf" + parent = "dql-syntax" + weight = 3 ++++ +Along with JSON, Dgraph supports RDF format to create, delete, import and export data. + +RDF 1.1 is a Semantic Web Standards for data interchange. It expresses statements about resources. The format of these statements is simple and in the form of triples. + + +A triple has the form +``` + . +``` +Meaning that the graph node identified by `subject` is linked to `object` with directed edge `predicate`. Each triple ends with a period. The subject of a triple is always a node in the graph, while the object may be a node or a value (a literal). + +For example, the triple +``` +<0x01> "Alice" . +<0x01> "Person" . +``` +Represents that graph node with ID `0x01` has a `name` with string value `"Alice"`. While triple +``` +<0x01> <0x02> . +``` +Represents that graph node with ID `0x01` is linked with the `friend` relationship to node `0x02`. + +### Blank nodes in mutations +When creating nodes in Dgraph, you should let Dgraph assign a [UID]({{< relref "dgraph-glossary.md#uid" >}}). + +However you need to reference the node in the mutation. + +Blank nodes in mutations, written `_:identifier`, identify nodes within a mutation. Dgraph creates a UID identifying each blank node. +### Language for string values +Languages are written using `@lang`. For example +``` +<0x01> "Adelaide"@en . +<0x01> "Аделаида"@ru . +<0x01> "Adélaïde"@fr . +<0x01> "Person" . +``` +See also [how language strings are handled in queries]({{< relref "query-language/graphql-fundamentals.md#language-support" >}}). + +### Types +You can specify literals type with the standard `^^` separator. For example +``` +<0x01> "32"^^ . +<0x01> "1985-06-08"^^ . +``` + +The supported [RDF datatypes](https://www.w3.org/TR/rdf11-concepts/#section-Datatypes) and the corresponding internal type in which the data is stored are as follows. + +| Storage Type | Dgraph type | +| ------------- | :------------: | +| <xs:string> | `string` | +| <xs:dateTime> | `dateTime` | +| <xs:date> | `datetime` | +| <xs:int> | `int` | +| <xs:integer> | `int` | +| <xs:boolean> | `bool` | +| <xs:double> | `float` | +| <xs:float> | `float` | +| <geo:geojson> | `geo` | +| <xs:password> | `password` | +| <http://www.w3.org/2001/XMLSchema#string> | `string` | +| <http://www.w3.org/2001/XMLSchema#dateTime> | `dateTime` | +| <http://www.w3.org/2001/XMLSchema#date> | `dateTime` | +| <http://www.w3.org/2001/XMLSchema#int> | `int` | +| <http://www.w3.org/2001/XMLSchema#positiveInteger> | `int` | +| <http://www.w3.org/2001/XMLSchema#integer> | `int` | +| <http://www.w3.org/2001/XMLSchema#boolean> | `bool` | +| <http://www.w3.org/2001/XMLSchema#double> | `float` | +| <http://www.w3.org/2001/XMLSchema#float> | `float` | + + +See the section on [RDF schema types]({{< relref "query-language/schema.md#rdf-types" >}}) to understand how RDF types affect mutations and storage. diff --git a/content/dql-overview/to-sort.md b/content/dql-syntax/to-sort.md.txt similarity index 100% rename from content/dql-overview/to-sort.md rename to content/dql-syntax/to-sort.md.txt diff --git a/content/dql/_index.md b/content/dql/_index.md index e5b560bd..53230ec8 100644 --- a/content/dql/_index.md +++ b/content/dql/_index.md @@ -1,5 +1,5 @@ +++ -title = "DQL" +title = "Dgraph Query Language" [menu.main] identifier = "dql" weight = 4 @@ -8,18 +8,16 @@ title = "DQL"

- Dgraph Query Language (DQL, formerly GraphQL+-) extends and modifies - the GraphQL standard created by Facebook to support deep queries for - modern apps. + Dgraph Query Language (DQL) is Dgraph’s proprietary language to add, modify, delete and fetch data.

From e523d9c784877a6b93fea4a4b35425d7b538f2f8 Mon Sep 17 00:00:00 2001 From: rderbier Date: Mon, 13 Feb 2023 15:41:46 -0800 Subject: [PATCH 2/4] add upsert --- .hugo_build.lock | 0 content/dql-syntax/dql-mutation.md | 288 ++++++++++++++++++++++++++++- 2 files changed, 287 insertions(+), 1 deletion(-) create mode 100644 .hugo_build.lock diff --git a/.hugo_build.lock b/.hugo_build.lock new file mode 100644 index 00000000..e69de29b diff --git a/content/dql-syntax/dql-mutation.md b/content/dql-syntax/dql-mutation.md index 5ed0b4f5..24acfee2 100644 --- a/content/dql-syntax/dql-mutation.md +++ b/content/dql-syntax/dql-mutation.md @@ -91,7 +91,7 @@ will add the `release_date` information to the node identified by UID `0x632ea2` {{% /tabs %}} -### delete data +### Delete data A delete mutation, identified by the `delete` keyword, removes [triples](/mutations/triples) from the store. @@ -176,7 +176,293 @@ For language-tagged values, the following special syntax is supported: In this example, the value of the `name` field that is tagged with the language tag `es` is deleted. Other tagged values are left untouched. +### Update data +The upsert block allows performing queries and mutations in a single request. The upsert +block contains one query block and one or more than one mutation blocks. Variables defined +in the query block can be used in the mutation blocks using the `uid` and `val` function. +In general, the structure of the upsert block is as follows: + +``` +upsert { + query + [fragment ] + mutation + [mutation ] + ... +} +``` + +Execution of an upsert block also returns the response of the query executed on the state +of the database *before mutation was executed*. To get the latest result, we should commit +the mutation and execute another query. + +#### `uid` Function + +The `uid` function allows extracting UIDs from variables defined in the query block. +There are two possible outcomes based on the results of executing the query block: + +* If the variable is empty i.e. no node matched the query, the `uid` function returns a new UID in case of a `set` operation and is thus treated similar to a blank node. On the other hand, for `delete/del` operation, it returns no UID, and thus the operation becomes a no-op and is silently ignored. A blank node gets the same UID across all the mutation blocks. +* If the variable stores one or more than one UIDs, the `uid` function returns all the UIDs stored in the variable. In this case, the operation is performed on all the UIDs returned, one at a time. + +#### `val` Function + +The `val` function allows extracting values from value variables. Value variables store +a mapping from UIDs to their corresponding values. Hence, `val(v)` is replaced by the value +stored in the mapping for the UID (Subject) in the N-Quad. If the variable `v` has no value +for a given UID, the mutation is silently ignored. The `val` function can be used with the +result of aggregate variables as well, in which case, all the UIDs in the mutation would +be updated with the aggregate value. + +#### Example of `uid` Function + +Consider an example with the following schema: + +```sh +curl localhost:8080/alter -X POST -d $' + name: string @index(term) . + email: string @index(exact, trigram) @upsert . + age: int @index(int) .' | jq +``` + +Now, let's say we want to create a new user with `email` and `name` information. +We also want to make sure that one email has exactly one corresponding user in +the database. To achieve this, we need to first query whether a user exists +in the database with the given email. If a user exists, we use its UID +to update the `name` information. If the user doesn't exist, we create +a new user and update the `email` and `name` information. + +We can do this using the upsert block as follows: + +```sh +curl -H "Content-Type: application/rdf" -X POST localhost:8080/mutate?commitNow=true -d $' +upsert { + query { + q(func: eq(email, "user@company1.io")) { + v as uid + name + } + } + + mutation { + set { + uid(v) "first last" . + uid(v) "user@company1.io" . + } + } +}' | jq +``` + +Result: + +```json +{ + "data": { + "q": [], + "code": "Success", + "message": "Done", + "uids": { + "uid(v)": "0x1" + } + }, + "extensions": {...} +} +``` + +The query part of the upsert block stores the UID of the user with the provided email +in the variable `v`. The mutation part then extracts the UID from variable `v`, and +stores the `name` and `email` information in the database. If the user exists, +the information is updated. If the user doesn't exist, `uid(v)` is treated +as a blank node and a new user is created as explained above. + +If we run the same mutation again, the data would just be overwritten, and no new uid is +created. Note that the `uids` map is empty in the result when the mutation is executed +again and the `data` map (key `q`) contains the uid that was created in the previous upsert. + +```json +{ + "data": { + "q": [ + { + "uid": "0x1", + "name": "first last" + } + ], + "code": "Success", + "message": "Done", + "uids": {} + }, + "extensions": {...} +} +``` + +We can achieve the same result using `json` dataset as follows: + +```sh +curl -H "Content-Type: application/json" -X POST localhost:8080/mutate?commitNow=true -d ' +{ + "query": "{ q(func: eq(email, \"user@company1.io\")) {v as uid, name} }", + "set": { + "uid": "uid(v)", + "name": "first last", + "email": "user@company1.io" + } +}' | jq +``` + +Now, we want to add the `age` information for the same user having the same email +`user@company1.io`. We can use the upsert block to do the same as follows: + +```sh +curl -H "Content-Type: application/rdf" -X POST localhost:8080/mutate?commitNow=true -d $' +upsert { + query { + q(func: eq(email, "user@company1.io")) { + v as uid + } + } + + mutation { + set { + uid(v) "28" . + } + } +}' | jq +``` + +Result: + +```json +{ + "data": { + "q": [ + { + "uid": "0x1" + } + ], + "code": "Success", + "message": "Done", + "uids": {} + }, + "extensions": {...} +} +``` + +Here, the query block queries for a user with `email` as `user@company1.io`. It stores +the `uid` of the user in variable `v`. The mutation block then updates the `age` of the +user by extracting the uid from the variable `v` using `uid` function. + +We can achieve the same result using `json` dataset as follows: + +```sh +curl -H "Content-Type: application/json" -X POST localhost:8080/mutate?commitNow=true -d $' +{ + "query": "{ q(func: eq(email, \\"user@company1.io\\")) {v as uid} }", + "set":{ + "uid": "uid(v)", + "age": "28" + } +}' | jq +``` + +If we want to execute the mutation only when the user exists, we could use +[Conditional Upsert]({{< relref "mutations/conditional-upsert.md" >}}). + +#### Example of `val` Function + +Let's say we want to migrate the predicate `age` to `other`. We can do this using the +following mutation: + +```sh +curl -H "Content-Type: application/rdf" -X POST localhost:8080/mutate?commitNow=true -d $' +upsert { + query { + v as var(func: has(age)) { + a as age + } + } + + mutation { + # we copy the values from the old predicate + set { + uid(v) val(a) . + } + + # and we delete the old predicate + delete { + uid(v) * . + } + } +}' | jq +``` + +Result: + +```json +{ + "data": { + "code": "Success", + "message": "Done", + "uids": {} + }, + "extensions": {...} +} +``` + +Here, variable `a` will store a mapping from all the UIDs to their `age`. The mutation +block then stores the corresponding value of `age` for each UID in the `other` predicate +and deletes the `age` predicate. + +We can achieve the same result using `json` dataset as follows: + +```sh +curl -H "Content-Type: application/json" -X POST localhost:8080/mutate?commitNow=true -d $'{ + "query": "{ v as var(func: regexp(email, /.*@company1.io$/)) }", + "delete": { + "uid": "uid(v)", + "age": null + }, + "set": { + "uid": "uid(v)", + "other": "val(a)" + } +}' | jq +``` + +### Bulk Delete + +Let's say we want to delete all the users of `company1` from the database. This can be +achieved in just one query using the upsert block as follows: + +```sh +curl -H "Content-Type: application/rdf" -X POST localhost:8080/mutate?commitNow=true -d $' +upsert { + query { + v as var(func: regexp(email, /.*@company1.io$/)) + } + + mutation { + delete { + uid(v) * . + uid(v) * . + uid(v) * . + } + } +}' | jq +``` + +We can achieve the same result using `json` dataset as follows: + +```sh +curl -H "Content-Type: application/json" -X POST localhost:8080/mutate?commitNow=true -d '{ + "query": "{ v as var(func: regexp(email, /.*@company1.io$/)) }", + "delete": { + "uid": "uid(v)", + "name": null, + "email": null, + "age": null + } +}' | jq ### Comments Anything on a line following a `#` is a comment From 7547bd2dd32afd9b8aeb15a4b91b07dd687a946a Mon Sep 17 00:00:00 2001 From: rderbier Date: Mon, 13 Feb 2023 18:21:50 -0800 Subject: [PATCH 3/4] clean mutation section --- content/clients/csharp.md | 1 - content/clients/java.md | 4 +- content/clients/javascript/grpc.md | 3 +- content/clients/python.md | 2 +- content/clients/raw-http.md | 3 +- content/deploy/dgraph-administration.md | 2 - content/dgraph-overview.md | 20 +- content/dql-syntax/dql-mutation.md | 285 ++-------- content/dql-syntax/dql-rdf.md | 63 ++- .../json-mutation-format.md | 5 +- content/dql-syntax/to-sort.md.txt | 487 ++++++++++++++++++ content/dql/predicate-types.md | 2 +- content/howto/importdata/bulk-loader.md | 3 +- content/howto/upserts.md | 2 +- content/migration/loading-csv-data.md | 4 +- content/mutations/blank-nodes.md | 86 ---- content/mutations/conditional-upsert.md | 285 ---------- content/mutations/delete.md | 91 ---- content/mutations/external-ids.md | 66 --- .../mutations/facets-in-list-type-with-rdf.md | 58 --- content/mutations/mutations-using-curl.md | 39 -- content/mutations/reverse-edges.md | 227 -------- content/mutations/triples.md | 37 -- .../{upsert-block.md => uid-upsert.md} | 94 +--- content/mutations/val-upsert.md | 81 +++ 25 files changed, 689 insertions(+), 1261 deletions(-) rename content/{mutations => dql-syntax}/json-mutation-format.md (99%) delete mode 100644 content/mutations/blank-nodes.md delete mode 100644 content/mutations/conditional-upsert.md delete mode 100644 content/mutations/delete.md delete mode 100644 content/mutations/external-ids.md delete mode 100644 content/mutations/facets-in-list-type-with-rdf.md delete mode 100644 content/mutations/mutations-using-curl.md delete mode 100644 content/mutations/reverse-edges.md delete mode 100644 content/mutations/triples.md rename content/mutations/{upsert-block.md => uid-upsert.md} (68%) create mode 100644 content/mutations/val-upsert.md diff --git a/content/clients/csharp.md b/content/clients/csharp.md index 35a2a883..8adb4279 100644 --- a/content/clients/csharp.md +++ b/content/clients/csharp.md @@ -153,7 +153,6 @@ Console.Write(res.Value.Json); The `Transaction.Mutate` function allows you to run upserts consisting of one query and one mutation. -To know more about upsert, we highly recommend going through the [upsert block documentation]({{< relref "upsert-block.md" >}}). ```c# var query = @" diff --git a/content/clients/java.md b/content/clients/java.md index 20cdf20a..0eb5296c 100644 --- a/content/clients/java.md +++ b/content/clients/java.md @@ -385,8 +385,6 @@ The `txn.doRequest` function allows you to run upserts consisting of one query a one mutation. Variables can be defined in the query and used in the mutation. You could also use `txn.doRequest` to perform a query followed by a mutation. -To know more about upsert, we highly recommend going through the docs at -[upsert-block]({{< relref "mutations/upsert-block.md" >}}). ```java String query = "query {\n" + @@ -408,7 +406,7 @@ txn.doRequest(request); The upsert block also allows specifying a conditional mutation block using an `@if` directive. The mutation is executed only when the specified condition is true. If the condition is false, the mutation is silently ignored. -See more about Conditional Upsert [Here]({{< relref "mutations/conditional-upsert.md" >}}). +See more about Conditional Upsert [Here]({{< relref "dql-mutation.md#conditional-upsert" >}}). ```java String query = "query {\n" + diff --git a/content/clients/javascript/grpc.md b/content/clients/javascript/grpc.md index 458f613c..e211e660 100644 --- a/content/clients/javascript/grpc.md +++ b/content/clients/javascript/grpc.md @@ -291,7 +291,6 @@ The `txn.doRequest` function allows you to run upserts consisting of one query a Query variables could be defined and can then be used in the mutation. You can also use the `txn.doRequest` function to perform just a query or a mutation. -To know more about upsert, we highly recommend going through the docs at [upsert-block]({{< relref "mutations/upsert-block.md" >}}). ```js const query = ` @@ -317,7 +316,7 @@ await dgraphClient.newTxn().doRequest(req); The upsert block allows specifying a conditional mutation block using an `@if` directive. The mutation is executed only when the specified condition is true. If the condition is false, the mutation is silently ignored. -See more about Conditional Upsert [Here]({{< relref "mutations/conditional-upsert.md" >}}). +See more about Conditional Upsert [Here]({{< relref "dql-mutation.md#conditional-upsert" >}}). ```js const query = ` diff --git a/content/clients/python.md b/content/clients/python.md index 7cddc295..4fd4e51a 100644 --- a/content/clients/python.md +++ b/content/clients/python.md @@ -313,7 +313,7 @@ txn.do_request(request) The upsert block also allows specifying a conditional mutation block using an `@if` directive. The mutation is executed only when the specified condition is true. If the condition is false, the mutation is silently ignored. -See more about Conditional Upserts [here]({{< relref "mutations/conditional-upsert.md" >}}). +See more about Conditional Upserts [here]({{< relref "dql-mutation.md#conditional-upsert" >}}). ```python query = """ diff --git a/content/clients/raw-http.md b/content/clients/raw-http.md index c143d940..05f57fcb 100644 --- a/content/clients/raw-http.md +++ b/content/clients/raw-http.md @@ -132,7 +132,7 @@ be used in all subsequent interactions with Dgraph for this transaction, and so should become part of the transaction state. ## Run a Mutation - +Mutations can be done over HTTP by making a `POST` request to an Alpha's `/mutate` endpoint. Now that we have the current balances, we need to send a mutation to Dgraph with the updated balances. If Bob transfers $10 to Alice, then the RDFs to send are: @@ -197,6 +197,7 @@ We get some `keys`. These should be added to the set of `keys` stored in the transaction state. We also get some `preds`, which should be added to the set of `preds` stored in the transaction state. + ## Committing the transaction {{% notice "note" %}} diff --git a/content/deploy/dgraph-administration.md b/content/deploy/dgraph-administration.md index a75f9078..1caee782 100644 --- a/content/deploy/dgraph-administration.md +++ b/content/deploy/dgraph-administration.md @@ -171,8 +171,6 @@ This stops the Alpha on which the command is executed and not the entire cluster ## Delete database -Individual triples, patterns of triples and predicates can be deleted as described in the [DQL docs]({{< relref "mutations/delete.md" >}}). - To drop all data, you could send a `DropAll` request via `/alter` endpoint. Alternatively, you could: diff --git a/content/dgraph-overview.md b/content/dgraph-overview.md index 1e3a53e4..ad7ac4ee 100644 --- a/content/dgraph-overview.md +++ b/content/dgraph-overview.md @@ -29,15 +29,15 @@ The difference with Dgraph is that no resolvers or custom queries are needed. Si For complex queries that the GraphQL specification does not support, Dgraph provides a query language called "DQL" which is inspired by GraphQL, but includes more features. With GraphQL simple use cases remain simple, and with DQL more complex cases become possible. - ## The Graph Model - Nodes, Relationships and Values @@ -63,11 +63,11 @@ This JSON structure succinctly represents rich data: - **Facet** metadata: the Address relation is qualified with a property specifying the Address relationship started on February 20, 2022. --> -Dgraph supports [JSON]({{< relref "mutations/json-mutation-format" >}}) data as both a return structure and an insert/update format. In Dgraph JSON nesting represents relations among nodes, so `{ "name":"Bob", "homeAddress": { "Street":"123 Main st" } }` efficiently and intuitively represents a Person node, an Address node, and a relation (called "homeAddress") between them. +Dgraph supports [JSON]({{< relref "json-mutation-format.md" >}}) data as both a return structure and an insert/update format. In Dgraph JSON nesting represents relations among nodes, so `{ "name":"Bob", "homeAddress": { "Street":"123 Main st" } }` efficiently and intuitively represents a Person node, an Address node, and a relation (called "homeAddress") between them. -In addition, Dgraph supports [RDF triples]({{< relref "mutations/triples/" >}}) as an input and outpup format. +In addition, Dgraph supports [RDF triples]({{< relref "dql-rdf.md" >}}) as an input and outpup format. -Dgraph relationships are directed links between nodes, allowing optimized traversal from node to node. Dgraph allows a bidirectional relation via directed relationships in both directions if desired. +Dgraph relationships are directed links between nodes, allowing optimized traversal from node to node. Dgraph allows a bidirectional relation via directed relationships in both directions if desired. ## Application developers and data engineers work together seamlessly Dgraph allows a particularly smoooth interaction among data teams or experts and data consumers. GraphQL's flexibility empowers data consumers to get exactly the data they want, in the format they want it, at the speed they need, without writing custom REST APIs or understanding a new graph query language. @@ -78,11 +78,11 @@ Database experts can focus on the data, schema and indexes, without maintaining - **Shared Instance**: Dgraph Cloud with [shared instances](https://dgraph.io/graphql) is a fully-managed graph database service that lets you focus on building apps, not -managing infrastructure. This is a fast and easy way to get started with GraphQL, and does not require any graph database knowledge to start and run. +managing infrastructure. This is a fast and easy way to get started with GraphQL, and does not require any graph database knowledge to start and run. Shared instances run in a common database using Dgraph multi-tenancy. Your data is protected but you share resources and will have limited scale. - Try the [Introduction to GraphQL](https://dgraph.io/tour/graphqlintro/2/) tutorial to define a GraphQL schema, insert and query data in just a few minutes. + Try the [Introduction to GraphQL](https://dgraph.io/tour/graphqlintro/2/) tutorial to define a GraphQL schema, insert and query data in just a few minutes. - **Dedicated instances** run on their own dedicated hardware to ensure consistent performance. This option extends the capabilities of the lower-cost shared instances to support enterprise, production workloads, and includes a high availability option. @@ -101,7 +101,7 @@ In addition, people use common tools to define schemas, load data, and query the - **Ratel** Ratel is a GUI app from Dgraph that runs DQL queries and mutations, and allows schema viewing and editing (as well as some cluster management operations). -- **Dgraph lambdas**: A Dgraph lambda is a data function written in JavaScript that can augment results of a query. Lambdas implement database triggers and custom GraphQL resolvers, and run in an optional node.js server (included in any cloud deployment). +- **Dgraph lambdas**: A Dgraph lambda is a data function written in JavaScript that can augment results of a query. Lambdas implement database triggers and custom GraphQL resolvers, and run in an optional node.js server (included in any cloud deployment). ### Scale, replication and sharding Every cluster will have at least one Dgraph Zero node and one Dgraph Alpha node. Then databases are expanded in two ways. @@ -114,7 +114,7 @@ Every cluster will have at least one Dgraph Zero node and one Dgraph Alpha node. ## What's Next -- **Get Started** with a [free database instance](https://cloud.dgraph.io) +- **Get Started** with a [free database instance](https://cloud.dgraph.io) - get familiar with some terms in our [Glossary](/dgraph-glossary) - Take the [Dgraph tour](https://dgraph.io/tour/) - Go through some [tutorials]({{< relref "tutorials" >}}) diff --git a/content/dql-syntax/dql-mutation.md b/content/dql-syntax/dql-mutation.md index 24acfee2..1970b771 100644 --- a/content/dql-syntax/dql-mutation.md +++ b/content/dql-syntax/dql-mutation.md @@ -16,7 +16,7 @@ This overview explains the structure of DQL Mutations and provides links to the DQL mutations support JSON or [RDF]({{< relref "dql-rdf.md" >}}) format. -### Add data +## set block In DQL, you add data using a set mutation, identified by the `set` keyword. {{% tabs %}} {{< tab "JSON" >}} ```dql @@ -65,7 +65,7 @@ In DQL, you add data using a set mutation, identified by the `set` keyword. triples are in [RDF]({{< relref "dql-rdf.md" >}}) format. -#### Node reference +### Node reference A mutation can include a blank nodes as an identifier for the subject or object, or a known UID. ``` { @@ -77,7 +77,7 @@ A mutation can include a blank nodes as an identifier for the subject or object, ``` will add the `release_date` information to the node identified by UID `0x632ea2`. -#### language support +### language support ``` { set { @@ -91,7 +91,7 @@ will add the `release_date` information to the node identified by UID `0x632ea2` {{% /tabs %}} -### Delete data +## delete block A delete mutation, identified by the `delete` keyword, removes [triples](/mutations/triples) from the store. @@ -113,7 +113,7 @@ removes it from any indexes: } ``` -#### Wildcard delete +### Wildcard delete In many cases you will need to delete multiple types of data for a predicate. For a particular node `N`, all data for predicate `P` (and all corresponding @@ -151,7 +151,7 @@ deleted. A node that contains untyped predicates will still exist after a {{% notice "note" %}} The patterns `* P O` and `* * O` are not supported because it's inefficient to store and find all the incoming edges. {{% /notice %}} -#### Deletion of non-list predicates +### Deletion of non-list predicates Deleting the value of a non-list predicate (i.e a 1-to-1 relationship) can be done in two ways. @@ -176,17 +176,22 @@ For language-tagged values, the following special syntax is supported: In this example, the value of the `name` field that is tagged with the language tag `es` is deleted. Other tagged values are left untouched. -### Update data +## upsert block +Upsert is an operation where: + +1. A node is searched for, and then +2. Depending on if it is found or not, either: + - Updating some of its attributes, or + - Creating a new node with those attributes. + The upsert block allows performing queries and mutations in a single request. The upsert -block contains one query block and one or more than one mutation blocks. Variables defined -in the query block can be used in the mutation blocks using the `uid` and `val` function. +block contains one query block and mutation blocks. -In general, the structure of the upsert block is as follows: +The structure of the upsert block is as follows: ``` upsert { query - [fragment ] mutation [mutation ] ... @@ -194,254 +199,43 @@ upsert { ``` Execution of an upsert block also returns the response of the query executed on the state -of the database *before mutation was executed*. To get the latest result, we should commit -the mutation and execute another query. - -#### `uid` Function - -The `uid` function allows extracting UIDs from variables defined in the query block. -There are two possible outcomes based on the results of executing the query block: - -* If the variable is empty i.e. no node matched the query, the `uid` function returns a new UID in case of a `set` operation and is thus treated similar to a blank node. On the other hand, for `delete/del` operation, it returns no UID, and thus the operation becomes a no-op and is silently ignored. A blank node gets the same UID across all the mutation blocks. -* If the variable stores one or more than one UIDs, the `uid` function returns all the UIDs stored in the variable. In this case, the operation is performed on all the UIDs returned, one at a time. - -#### `val` Function - -The `val` function allows extracting values from value variables. Value variables store -a mapping from UIDs to their corresponding values. Hence, `val(v)` is replaced by the value -stored in the mapping for the UID (Subject) in the N-Quad. If the variable `v` has no value -for a given UID, the mutation is silently ignored. The `val` function can be used with the -result of aggregate variables as well, in which case, all the UIDs in the mutation would -be updated with the aggregate value. - -#### Example of `uid` Function - -Consider an example with the following schema: - -```sh -curl localhost:8080/alter -X POST -d $' - name: string @index(term) . - email: string @index(exact, trigram) @upsert . - age: int @index(int) .' | jq -``` - -Now, let's say we want to create a new user with `email` and `name` information. -We also want to make sure that one email has exactly one corresponding user in -the database. To achieve this, we need to first query whether a user exists -in the database with the given email. If a user exists, we use its UID -to update the `name` information. If the user doesn't exist, we create -a new user and update the `email` and `name` information. - -We can do this using the upsert block as follows: - -```sh -curl -H "Content-Type: application/rdf" -X POST localhost:8080/mutate?commitNow=true -d $' -upsert { - query { - q(func: eq(email, "user@company1.io")) { - v as uid - name - } - } - - mutation { - set { - uid(v) "first last" . - uid(v) "user@company1.io" . - } - } -}' | jq -``` - -Result: - -```json -{ - "data": { - "q": [], - "code": "Success", - "message": "Done", - "uids": { - "uid(v)": "0x1" - } - }, - "extensions": {...} -} -``` - -The query part of the upsert block stores the UID of the user with the provided email -in the variable `v`. The mutation part then extracts the UID from variable `v`, and -stores the `name` and `email` information in the database. If the user exists, -the information is updated. If the user doesn't exist, `uid(v)` is treated -as a blank node and a new user is created as explained above. - -If we run the same mutation again, the data would just be overwritten, and no new uid is -created. Note that the `uids` map is empty in the result when the mutation is executed -again and the `data` map (key `q`) contains the uid that was created in the previous upsert. - -```json -{ - "data": { - "q": [ - { - "uid": "0x1", - "name": "first last" - } - ], - "code": "Success", - "message": "Done", - "uids": {} - }, - "extensions": {...} -} -``` - -We can achieve the same result using `json` dataset as follows: - -```sh -curl -H "Content-Type: application/json" -X POST localhost:8080/mutate?commitNow=true -d ' -{ - "query": "{ q(func: eq(email, \"user@company1.io\")) {v as uid, name} }", - "set": { - "uid": "uid(v)", - "name": "first last", - "email": "user@company1.io" - } -}' | jq -``` - -Now, we want to add the `age` information for the same user having the same email -`user@company1.io`. We can use the upsert block to do the same as follows: - -```sh -curl -H "Content-Type: application/rdf" -X POST localhost:8080/mutate?commitNow=true -d $' -upsert { - query { - q(func: eq(email, "user@company1.io")) { - v as uid - } - } +of the database *before mutation was executed*. +To get the latest result, you have to execute another query after the transaction is commited. - mutation { - set { - uid(v) "28" . - } - } -}' | jq -``` +Variables defined in the query block can be used in the mutation blocks using the [uid]({{< relref "uid-upsert.md" >}}) and [val]({{< relref "val-upsert.md" >}}) functions. -Result: +## conditional upsert +The upsert block also allows specifying conditional mutation blocks using an `@if` +directive. The mutation is executed only when the specified condition is true. If the +condition is false, the mutation is silently ignored. The general structure of +Conditional Upsert looks like as follows: -```json -{ - "data": { - "q": [ - { - "uid": "0x1" - } - ], - "code": "Success", - "message": "Done", - "uids": {} - }, - "extensions": {...} -} ``` - -Here, the query block queries for a user with `email` as `user@company1.io`. It stores -the `uid` of the user in variable `v`. The mutation block then updates the `age` of the -user by extracting the uid from the variable `v` using `uid` function. - -We can achieve the same result using `json` dataset as follows: - -```sh -curl -H "Content-Type: application/json" -X POST localhost:8080/mutate?commitNow=true -d $' -{ - "query": "{ q(func: eq(email, \\"user@company1.io\\")) {v as uid} }", - "set":{ - "uid": "uid(v)", - "age": "28" - } -}' | jq -``` - -If we want to execute the mutation only when the user exists, we could use -[Conditional Upsert]({{< relref "mutations/conditional-upsert.md" >}}). - -#### Example of `val` Function - -Let's say we want to migrate the predicate `age` to `other`. We can do this using the -following mutation: - -```sh -curl -H "Content-Type: application/rdf" -X POST localhost:8080/mutate?commitNow=true -d $' upsert { - query { - v as var(func: has(age)) { - a as age - } - } - - mutation { - # we copy the values from the old predicate - set { - uid(v) val(a) . - } - - # and we delete the old predicate - delete { - uid(v) * . - } - } -}' | jq -``` - -Result: - -```json -{ - "data": { - "code": "Success", - "message": "Done", - "uids": {} - }, - "extensions": {...} + query + [fragment ] + mutation [@if()] + [mutation [@if()] ] + ... } ``` +The `@if` directive accepts a condition on variables defined in the query block and can be +connected using `AND`, `OR` and `NOT`. -Here, variable `a` will store a mapping from all the UIDs to their `age`. The mutation -block then stores the corresponding value of `age` for each UID in the `other` predicate -and deletes the `age` predicate. +## Example of Conditional Upsert -We can achieve the same result using `json` dataset as follows: +Let's say in our previous example, we know the `company1` has less than 100 employees. +For safety, we want the mutation to execute only when the variable `v` stores less than +100 but greater than 50 UIDs in it. This can be achieved as follows: ```sh -curl -H "Content-Type: application/json" -X POST localhost:8080/mutate?commitNow=true -d $'{ - "query": "{ v as var(func: regexp(email, /.*@company1.io$/)) }", - "delete": { - "uid": "uid(v)", - "age": null - }, - "set": { - "uid": "uid(v)", - "other": "val(a)" - } -}' | jq -``` - -### Bulk Delete - -Let's say we want to delete all the users of `company1` from the database. This can be -achieved in just one query using the upsert block as follows: - -```sh -curl -H "Content-Type: application/rdf" -X POST localhost:8080/mutate?commitNow=true -d $' +curl -H "Content-Type: application/rdf" -X POST localhost:8080/mutate?commitNow=true -d $' upsert { query { v as var(func: regexp(email, /.*@company1.io$/)) } - mutation { + mutation @if(lt(len(v), 100) AND gt(len(v), 50)) { delete { uid(v) * . uid(v) * . @@ -456,6 +250,7 @@ We can achieve the same result using `json` dataset as follows: ```sh curl -H "Content-Type: application/json" -X POST localhost:8080/mutate?commitNow=true -d '{ "query": "{ v as var(func: regexp(email, /.*@company1.io$/)) }", + "cond": "@if(lt(len(v), 100) AND gt(len(v), 50))", "delete": { "uid": "uid(v)", "name": null, @@ -463,6 +258,4 @@ curl -H "Content-Type: application/json" -X POST localhost:8080/mutate?commitNow "age": null } }' | jq - -### Comments -Anything on a line following a `#` is a comment +``` diff --git a/content/dql-syntax/dql-rdf.md b/content/dql-syntax/dql-rdf.md index c8fd31f0..6d1d17c0 100644 --- a/content/dql-syntax/dql-rdf.md +++ b/content/dql-syntax/dql-rdf.md @@ -15,18 +15,20 @@ A triple has the form ``` . ``` -Meaning that the graph node identified by `subject` is linked to `object` with directed edge `predicate`. Each triple ends with a period. The subject of a triple is always a node in the graph, while the object may be a node or a value (a literal). -For example, the triple +In RDF terminology, a predicate is the smallest piece of information about an object. A predicate can hold a literal value or can describe a relation to another entity : + ``` <0x01> "Alice" . -<0x01> "Person" . -``` -Represents that graph node with ID `0x01` has a `name` with string value `"Alice"`. While triple -``` -<0x01> <0x02> . +<0x01> <0x02> . ``` -Represents that graph node with ID `0x01` is linked with the `friend` relationship to node `0x02`. +when we store that an entity name is “Alice”. The predicate is `name` and predicate value is the string `"Alice"`. It becomes a node property. +when we store that Alice knows Bob, we may use a predicate `knows` with the node representing Alice. The value of this predicate would be the uid of the node representing Bob. In that case, knows is a relationship. + +Each triple ends with a period. + +The subject of a triple is always a node in the graph, while the object may be a node or a value (a literal). + ### Blank nodes in mutations When creating nodes in Dgraph, you should let Dgraph assign a [UID]({{< relref "dgraph-glossary.md#uid" >}}). @@ -77,3 +79,48 @@ The supported [RDF datatypes](https://www.w3.org/TR/rdf11-concepts/#section-Data See the section on [RDF schema types]({{< relref "query-language/schema.md#rdf-types" >}}) to understand how RDF types affect mutations and storage. + +### Facets +#### Creating a list with facets + +```sh +{ + set { + _:Julian "Julian" . + _:Julian "Jay-Jay" (kind="first") . + _:Julian "Jules" (kind="official") . + _:Julian "JB" (kind="CS-GO") . + } +} +``` + +```graphql +{ + q(func: eq(name,"Julian")){ + name + nickname @facets + } +} +``` +Result: +```JSON +{ + "data": { + "q": [ + { + "name": "Julian", + "nickname|kind": { + "0": "first", + "1": "official", + "2": "CS-GO" + }, + "nickname": [ + "Jay-Jay", + "Jules", + "JB" + ] + } + ] + } +} +``` diff --git a/content/mutations/json-mutation-format.md b/content/dql-syntax/json-mutation-format.md similarity index 99% rename from content/mutations/json-mutation-format.md rename to content/dql-syntax/json-mutation-format.md index 222a3175..457a2df5 100644 --- a/content/mutations/json-mutation-format.md +++ b/content/dql-syntax/json-mutation-format.md @@ -3,7 +3,8 @@ date = "2017-03-20T22:25:17+11:00" title = "JSON Mutation Format" weight = 10 [menu.main] - parent = "mutations" + parent = "dql-syntax" + weight = 4 +++ Mutations can also be specified using JSON objects. This can allow mutations to @@ -255,7 +256,7 @@ All edges for a predicate emanating from a single node can be deleted at once If no predicates are specified, then all of the node's known outbound edges (to other nodes and to literal values) are deleted (corresponding to deleting `S * *`). The predicates to delete are derived using the type system. Refer to the -[RDF format]({{< relref "mutations/delete.md" >}}) documentation and the section on the +[RDF format]({{< relref "dql-rdf.md" >}}) documentation and the section on the [type system]({{< relref "type-system.md" >}}) for more information: diff --git a/content/dql-syntax/to-sort.md.txt b/content/dql-syntax/to-sort.md.txt index 188070db..93226ecf 100644 --- a/content/dql-syntax/to-sort.md.txt +++ b/content/dql-syntax/to-sort.md.txt @@ -142,3 +142,490 @@ query test($a: int = 2, $b: int!, $name: string) { } {{< /runnable >}} ### Clients + + + +### conditional upsert +## Example of Conditional Upsert + +Let's say in our previous example, we know the `company1` has less than 100 employees. +For safety, we want the mutation to execute only when the variable `v` stores less than +100 but greater than 50 UIDs in it. This can be achieved as follows: + +```sh +curl -H "Content-Type: application/rdf" -X POST localhost:8080/mutate?commitNow=true -d $' +upsert { + query { + v as var(func: regexp(email, /.*@company1.io$/)) + } + + mutation @if(lt(len(v), 100) AND gt(len(v), 50)) { + delete { + uid(v) * . + uid(v) * . + uid(v) * . + } + } +}' | jq +``` + +We can achieve the same result using `json` dataset as follows: + +```sh +curl -H "Content-Type: application/json" -X POST localhost:8080/mutate?commitNow=true -d '{ + "query": "{ v as var(func: regexp(email, /.*@company1.io$/)) }", + "cond": "@if(lt(len(v), 100) AND gt(len(v), 50))", + "delete": { + "uid": "uid(v)", + "name": null, + "email": null, + "age": null + } +}' | jq +``` + +## Example of Multiple Mutation Blocks + +Consider an example with the following schema: + +```sh +curl localhost:8080/alter -X POST -d $' + name: string @index(term) . + email: [string] @index(exact) @upsert .' | jq +``` + +Let's say, we have many users stored in our database each having one or more than +one email Addresses. Now, we get two email Addresses that belong to the same user. +If the email Addresses belong to the different nodes in the database, we want to delete +the existing nodes and create a new node with both the emails attached to this new node. +Otherwise, we create/update the new/existing node with both the emails. + +```sh +curl -H "Content-Type: application/rdf" -X POST localhost:8080/mutate?commitNow=true -d $' +upsert { + query { + # filter is needed to ensure that we do not get same UIDs in u1 and u2 + q1(func: eq(email, "user_email1@company1.io")) @filter(not(eq(email, "user_email2@company1.io"))) { + u1 as uid + } + + q2(func: eq(email, "user_email2@company1.io")) @filter(not(eq(email, "user_email1@company1.io"))) { + u2 as uid + } + + q3(func: eq(email, "user_email1@company1.io")) @filter(eq(email, "user_email2@company1.io")) { + u3 as uid + } + } + + # case when both emails do not exist + mutation @if(eq(len(u1), 0) AND eq(len(u2), 0) AND eq(len(u3), 0)) { + set { + _:user "user" . + _:user "Person" . + _:user "user_email1@company1.io" . + _:user "user_email2@company1.io" . + } + } + + # case when email1 exists but email2 does not + mutation @if(eq(len(u1), 1) AND eq(len(u2), 0) AND eq(len(u3), 0)) { + set { + uid(u1) "user_email2@company1.io" . + } + } + + # case when email1 does not exist but email2 exists + mutation @if(eq(len(u1), 0) AND eq(len(u2), 1) AND eq(len(u3), 0)) { + set { + uid(u2) "user_email1@company1.io" . + } + } + + # case when both emails exist and needs merging + mutation @if(eq(len(u1), 1) AND eq(len(u2), 1) AND eq(len(u3), 0)) { + set { + _:user "user" . + _:user "Person" . + _:user "user_email1@company1.io" . + _:user "user_email2@company1.io" . + } + + delete { + uid(u1) * . + uid(u1) * . + uid(u2) * . + uid(u2) * . + } + } +}' | jq +``` + +Result (when database is empty): + +```json +{ + "data": { + "q1": [], + "q2": [], + "q3": [], + "code": "Success", + "message": "Done", + "uids": { + "user": "0x1" + } + }, + "extensions": {...} +} +``` + +Result (both emails exist and are attached to different nodes): +```json +{ + "data": { + "q1": [ + { + "uid": "0x2" + } + ], + "q2": [ + { + "uid": "0x3" + } + ], + "q3": [], + "code": "Success", + "message": "Done", + "uids": { + "user": "0x4" + } + }, + "extensions": {...} +} +``` + +Result (when both emails exist and are already attached to the same node): + +```json +{ + "data": { + "q1": [], + "q2": [], + "q3": [ + { + "uid": "0x4" + } + ], + "code": "Success", + "message": "Done", + "uids": {} + }, + "extensions": {...} +} +``` + +We can achieve the same result using `json` dataset as follows: + +```sh +curl -H "Content-Type: application/json" -X POST localhost:8080/mutate?commitNow=true -d '{ + "query": "{q1(func: eq(email, \"user_email1@company1.io\")) @filter(not(eq(email, \"user_email2@company1.io\"))) {u1 as uid} \n q2(func: eq(email, \"user_email2@company1.io\")) @filter(not(eq(email, \"user_email1@company1.io\"))) {u2 as uid} \n q3(func: eq(email, \"user_email1@company1.io\")) @filter(eq(email, \"user_email2@company1.io\")) {u3 as uid}}", + "mutations": [ + { + "cond": "@if(eq(len(u1), 0) AND eq(len(u2), 0) AND eq(len(u3), 0))", + "set": [ + { + "uid": "_:user", + "name": "user", + "dgraph.type": "Person" + }, + { + "uid": "_:user", + "email": "user_email1@company1.io", + "dgraph.type": "Person" + }, + { + "uid": "_:user", + "email": "user_email2@company1.io", + "dgraph.type": "Person" + } + ] + }, + { + "cond": "@if(eq(len(u1), 1) AND eq(len(u2), 0) AND eq(len(u3), 0))", + "set": [ + { + "uid": "uid(u1)", + "email": "user_email2@company1.io", + "dgraph.type": "Person" + } + ] + }, + { + "cond": "@if(eq(len(u1), 1) AND eq(len(u2), 0) AND eq(len(u3), 0))", + "set": [ + { + "uid": "uid(u2)", + "email": "user_email1@company1.io", + "dgraph.type": "Person" + } + ] + }, + { + "cond": "@if(eq(len(u1), 1) AND eq(len(u2), 1) AND eq(len(u3), 0))", + "set": [ + { + "uid": "_:user", + "name": "user", + "dgraph.type": "Person" + }, + { + "uid": "_:user", + "email": "user_email1@company1.io", + "dgraph.type": "Person" + }, + { + "uid": "_:user", + "email": "user_email2@company1.io", + "dgraph.type": "Person" + } + ], + "delete": [ + { + "uid": "uid(u1)", + "name": null, + "email": null + }, + { + "uid": "uid(u2)", + "name": null, + "email": null + } + ] + } + ] +}' | jq +``` + +## reverse edge ?? +Any outgoing edge in Dgraph can be reversed using the `@reverse` directive in the schema and be queried using tilde as the prefix of the edge name. e.g. `<~myEdge>`. + +Dgraph serializes directed graphs. This means that all properties always point from an entity to another entity or value in a single direction. `S P -> O`. + +Reverse edges are automatically generated edges and are not part of your dataset. This means that you cannot run mutations directly on the reverse edges. Mutating the forward edge will automatically update the reverse edge. + +**Using Reverse Edges correctly** + +In RDF the arrangement of the triples already defines what can be reversed. + +```RDF +_:MyObject _:BlankNode . #That's the right syntax of a reverse edge. +_:BlankNode "Person" . +``` + +The easiest way to correct and apply reverse edges is using JSON. It is simply having the directive on schema at the desired edge. When building your mutations remember that there is no reverse syntax in JSON. So what you should do is similar to RDF: change the arrangement of the JSON objects. + +Since `MyObject` is above the `Person` entity, `MyObject` must come before when formatting the mutation. + +```JSON +{ + "set": [ + { + "uid": "_:MyObject", + "dgraph.type": "Object", + "myEdge": { + "uid": "_:BlankNode", + "dgraph.type": "Person" + } + } + ] +} +``` + +Another way to do this is to separate into small chunks/batches and use blank nodes as references. This facilitates the organization and reuse of references. + +```JSON +{ + "set": [ + { + "uid": "_:MyObject", + "dgraph.type": "Object", + "myEdge": [{"uid": "_:BlankNode"}] + }, + { + "uid": "_:BlankNode", + "dgraph.type": "Person" + } + ] +} +``` + +### More reverse examples + +In RDF the correct way to apply reverse edges is very straight-forward. + +```RDF +name: String . +husband: uid @reverse . +wife: uid @reverse . +parent: [uid] @reverse . +``` + +```RDF +{ + set { + _:Megalosaurus "Earl Sneed Sinclair" . + _:Megalosaurus "Dinosaur" . + _:Megalosaurus _:Allosaurus . + _:Allosaurus "Francis Johanna Phillips Sinclair" (short="Fran") . + _:Allosaurus "Dinosaur" . + _:Allosaurus _:Megalosaurus . + _:Hypsilophodon "Robert Mark Sinclair" (short="Robbie") . + _:Hypsilophodon "Dinosaur" . + _:Hypsilophodon _:Allosaurus (role="son") . + _:Hypsilophodon _:Megalosaurus (role="son") . + _:Protoceratops "Charlene Fiona Sinclair" . + _:Protoceratops "Dinosaur" . + _:Protoceratops _:Allosaurus (role="daughter") . + _:Protoceratops _:Megalosaurus (role="daughter") . + _:MegalosaurusBaby "Baby Sinclair" (short="Baby") . + _:MegalosaurusBaby "Dinosaur" . + _:MegalosaurusBaby _:Allosaurus (role="son") . + _:MegalosaurusBaby _:Megalosaurus (role="son") . + } +} +``` + +The directions are like: + +```rdf +Exchanged hierarchy: + Object -> Parent; + Object <~ Parent; #Reverse + Children to parents via "parent" edge. + wife and husband bidirectional using reverse. +Normal hierarchy: + Parent -> Object; + Parent <~ Object; #Reverse + This hierarchy is not part of the example, but is generally used in all graph models. + To make this hierarchy we need to bring the hierarchical relationship starting from the parents and not from the children. Instead of using the edges "wife" and "husband" we switch to single edge called "married" to simplify the model. + _:Megalosaurus "Earl Sneed Sinclair" . + _:Megalosaurus "Dinosaur" . + _:Megalosaurus _:Allosaurus . + _:Megalosaurus _:Hypsilophodon (role="son") . + _:Megalosaurus _:Protoceratops (role="daughter") . + _:Megalosaurus _:MegalosaurusBaby (role="son") . + _:Allosaurus "Francis Johanna Phillips Sinclair" (short="Fran") . + _:Allosaurus "Dinosaur" . + _:Allosaurus _:Megalosaurus . + _:Allosaurus _:Hypsilophodon (role="son") . + _:Allosaurus _:Protoceratops (role="daughter") . + _:Allosaurus _:MegalosaurusBaby (role="son") . +``` + +### Queries + +1. `wife_husband` is the reversed `wife` edge. +2. `husband` is an actual edge. + +```graphql +{ + q(func: has(wife)) { + name + WF as wife { + name + } + } + reverseIt(func: uid(WF)) { + name + wife_husband : ~wife { + name + } + husband { + name + } + } +} +``` + +1. `Children` is the reversed `parent` edge. + +```graphql +{ + q(func: has(name)) @filter(eq(name, "Earl Sneed Sinclair")){ + name + Children : ~parent @facets { + name + } + } +} +``` + +### Reverse Edges and Facets + +Facets on reverse edges are the same as the forward edge. That is, if you set or update a facet on an edge, its reverse will have the same facets. + +```rdf +{ + set { + _:Megalosaurus "Earl Sneed Sinclair" . + _:Megalosaurus "Dinosaur" . + _:Megalosaurus _:Allosaurus . + _:Megalosaurus _:MegalosaurusBaby (role="parent -> child") . + _:MegalosaurusBaby "Baby Sinclair" (short="Baby -> parent") . + _:MegalosaurusBaby "Dinosaur" . + _:MegalosaurusBaby _:Megalosaurus (role="child -> parent") . + } +} +``` + +Using a similar query from the previous example: + +```graphql +{ + Parent(func: has(name)) @filter(eq(name, "Earl Sneed Sinclair")){ + name + C as Children : parent @facets { + name + } + } + Child(func: uid(C)) { + name + parent @facets { + name + } + } +} +``` + +```json +{ + "data": { + "Parent": [ + { + "name": "Earl Sneed Sinclair", + "Children": [ + { + "name": "Baby Sinclair", + "Children|role": "parent -> child" + } + ] + } + ], + "Child": [ + { + "name": "Baby Sinclair", + "parent": [ + { + "name": "Earl Sneed Sinclair", + "parent|role": "child -> parent" + } + ] + } + ] + } +} + ``` +## bulk delete to put in drop data + +Drop data in admin ? +Individual triples, patterns of triples and predicates can be deleted as described in the [DQL docs]({{< relref "mutations/delete.md" >}}). diff --git a/content/dql/predicate-types.md b/content/dql/predicate-types.md index bf6ee07a..0c8788ec 100644 --- a/content/dql/predicate-types.md +++ b/content/dql/predicate-types.md @@ -256,7 +256,7 @@ email: string @index(exact) @noconflict . ## RDF Types -Dgraph supports a number of [RDF types in mutations]({{< relref "mutations/language-rdf-types.md" >}}). +Dgraph supports a number of [RDF]({{< relref "dql-rdf.md" >}}) types. As well as implying a schema type for a first mutation, an RDF type can override a schema type for storage. diff --git a/content/howto/importdata/bulk-loader.md b/content/howto/importdata/bulk-loader.md index 96f0c30d..35d70418 100644 --- a/content/howto/importdata/bulk-loader.md +++ b/content/howto/importdata/bulk-loader.md @@ -178,8 +178,7 @@ ending in `.rdf`, `.rdf.gz`, `.json`, and `.json.gz` will be loaded. - `--format` (optional): Specify file format (`rdf` or `json`) instead of getting it from filenames. This is useful if you need to define a strict format manually. -- `--store_xids`: Generate a xid edge for each node. It will store the XIDs (The identifier / Blank-nodes) in an attribute named `xid` in the entity itself. It is useful if you gonna -use [External IDs]({{< relref "mutations/external-ids.md" >}}). +- `--store_xids`: Generate a xid edge for each node. It will store the XIDs (The identifier / Blank-nodes) in an attribute named `xid` in the entity itself. - `--xidmap` (default: `disabled`. Need a path): Store xid to uid mapping to a directory. Dgraph will save all identifiers used in the load for later use in other data ingest operations. The mapping will be saved in the path you provide and you must indicate that same path in the next load. It is recommended to use this flag if you have full control over your identifiers (Blank-nodes). Because the identifier will be mapped to a specific UID. diff --git a/content/howto/upserts.md b/content/howto/upserts.md index 171ea662..dcfa44ba 100644 --- a/content/howto/upserts.md +++ b/content/howto/upserts.md @@ -50,7 +50,7 @@ whichever way you wish. You can also use the `Upsert Block` in DQL to achieve the upsert procedure in a single mutation. The request will contain both the query and the mutation as explained -[here]({{< relref "mutations/upsert-block.md" >}}). +[here]({{< relref "dql-mutation.md#Update data with upsert block" >}}). In GraphQL, you can use the `upsert` input variable in an `add` mutation, as explained [here]({{< relref "graphql/mutations/upsert.md">}}). diff --git a/content/migration/loading-csv-data.md b/content/migration/loading-csv-data.md index 2e15394a..881d75d4 100644 --- a/content/migration/loading-csv-data.md +++ b/content/migration/loading-csv-data.md @@ -51,10 +51,10 @@ There are many tools available to convert CSV to JSON. You can import large data ``` This JSON file follows - the [JSON Mutation Format]({{< relref "mutations/json-mutation-format.md" >}}), it can be loaded into Dgraph using [Dgraph Live Loader]({{< relref "live-loader.md" >}}) , [Dgraph Bulk Loader]({{< relref "bulk-loader.md" >}}) or the programmatic clients. + the [JSON Mutation Format]({{< relref "json-mutation-format.md" >}}), it can be loaded into Dgraph using [Dgraph Live Loader]({{< relref "live-loader.md" >}}) , [Dgraph Bulk Loader]({{< relref "bulk-loader.md" >}}) or the programmatic clients. 4. To load the data to Ratel and HTTP clients. The JSON data has to be stored within the `"set"` -[key]({{< relref "mutations/json-mutation-format.md#json-syntax-using-raw-http-or-ratel-ui" +[key]({{< relref "json-mutation-format.md#json-syntax-using-raw-http-or-ratel-ui" >}}). You can use `jq` to transform the JSON into the correct format: ```sh diff --git a/content/mutations/blank-nodes.md b/content/mutations/blank-nodes.md deleted file mode 100644 index d46507a1..00000000 --- a/content/mutations/blank-nodes.md +++ /dev/null @@ -1,86 +0,0 @@ -+++ -date = "2017-03-20T22:25:17+11:00" -title = "Blank Nodes and UID" -weight = 2 -[menu.main] - parent = "mutations" -+++ - -Blank nodes in mutations, written `_:identifier`, identify nodes within a mutation. Dgraph creates a UID identifying each blank node and returns the created UIDs as the mutation result. For example, mutation: - -``` -{ - set { - _:class _:x . - _:class _:y . - _:class "awesome class" . - _:class "Class" . - _:x "Alice" . - _:x "Person" . - _:x "Student" . - _:x "Mars" . - _:x _:y . - _:y "Bob" . - _:y "Person" . - _:y "Student" . - } -} -``` -results in output (the actual UIDs will be different on any run of this mutation) -``` -{ - "data": { - "code": "Success", - "message": "Done", - "uids": { - "class": "0x2712", - "x": "0x2713", - "y": "0x2714" - } - } -} -``` -The graph has thus been updated as if it had stored the triples -``` -<0x6bc818dc89e78754> <0xc3bcc578868b719d> . -<0x6bc818dc89e78754> <0xb294fb8464357b0a> . -<0x6bc818dc89e78754> "awesome class" . -<0x6bc818dc89e78754> "Class" . -<0xc3bcc578868b719d> "Alice" . -<0xc3bcc578868b719d> "Person" . -<0xc3bcc578868b719d> "Student" . -<0xc3bcc578868b719d> "Mars" . -<0xc3bcc578868b719d> <0xb294fb8464357b0a> . -<0xb294fb8464357b0a> "Bob" . -<0xb294fb8464357b0a> "Person" . -<0xb294fb8464357b0a> "Student" . -``` -The blank node labels `_:class`, `_:x` and `_:y` do not identify the nodes after the mutation, and can be safely reused to identify new nodes in later mutations. - -A later mutation can update the data for existing UIDs. For example, the following to add a new student to the class. -``` -{ - set { - <0x6bc818dc89e78754> _:x . - _:x "Chris" . - _:x "Person" . - _:x "Student" . - } -} -``` - -A query can also directly use UID. -``` -{ - class(func: uid(0x6bc818dc89e78754)) { - name - student { - name - planet - friend { - name - } - } - } -} -``` \ No newline at end of file diff --git a/content/mutations/conditional-upsert.md b/content/mutations/conditional-upsert.md deleted file mode 100644 index 5a5c0e38..00000000 --- a/content/mutations/conditional-upsert.md +++ /dev/null @@ -1,285 +0,0 @@ -+++ -date = "2017-03-20T22:25:17+11:00" -title = "Conditional Upsert" -weight = 12 -[menu.main] - parent = "mutations" -+++ - -The upsert block also allows specifying conditional mutation blocks using an `@if` -directive. The mutation is executed only when the specified condition is true. If the -condition is false, the mutation is silently ignored. The general structure of -Conditional Upsert looks like as follows: - -``` -upsert { - query - [fragment ] - mutation [@if()] - [mutation [@if()] ] - ... -} -``` - -The `@if` directive accepts a condition on variables defined in the query block and can be -connected using `AND`, `OR` and `NOT`. - -## Example of Conditional Upsert - -Let's say in our previous example, we know the `company1` has less than 100 employees. -For safety, we want the mutation to execute only when the variable `v` stores less than -100 but greater than 50 UIDs in it. This can be achieved as follows: - -```sh -curl -H "Content-Type: application/rdf" -X POST localhost:8080/mutate?commitNow=true -d $' -upsert { - query { - v as var(func: regexp(email, /.*@company1.io$/)) - } - - mutation @if(lt(len(v), 100) AND gt(len(v), 50)) { - delete { - uid(v) * . - uid(v) * . - uid(v) * . - } - } -}' | jq -``` - -We can achieve the same result using `json` dataset as follows: - -```sh -curl -H "Content-Type: application/json" -X POST localhost:8080/mutate?commitNow=true -d '{ - "query": "{ v as var(func: regexp(email, /.*@company1.io$/)) }", - "cond": "@if(lt(len(v), 100) AND gt(len(v), 50))", - "delete": { - "uid": "uid(v)", - "name": null, - "email": null, - "age": null - } -}' | jq -``` - -## Example of Multiple Mutation Blocks - -Consider an example with the following schema: - -```sh -curl localhost:8080/alter -X POST -d $' - name: string @index(term) . - email: [string] @index(exact) @upsert .' | jq -``` - -Let's say, we have many users stored in our database each having one or more than -one email Addresses. Now, we get two email Addresses that belong to the same user. -If the email Addresses belong to the different nodes in the database, we want to delete -the existing nodes and create a new node with both the emails attached to this new node. -Otherwise, we create/update the new/existing node with both the emails. - -```sh -curl -H "Content-Type: application/rdf" -X POST localhost:8080/mutate?commitNow=true -d $' -upsert { - query { - # filter is needed to ensure that we do not get same UIDs in u1 and u2 - q1(func: eq(email, "user_email1@company1.io")) @filter(not(eq(email, "user_email2@company1.io"))) { - u1 as uid - } - - q2(func: eq(email, "user_email2@company1.io")) @filter(not(eq(email, "user_email1@company1.io"))) { - u2 as uid - } - - q3(func: eq(email, "user_email1@company1.io")) @filter(eq(email, "user_email2@company1.io")) { - u3 as uid - } - } - - # case when both emails do not exist - mutation @if(eq(len(u1), 0) AND eq(len(u2), 0) AND eq(len(u3), 0)) { - set { - _:user "user" . - _:user "Person" . - _:user "user_email1@company1.io" . - _:user "user_email2@company1.io" . - } - } - - # case when email1 exists but email2 does not - mutation @if(eq(len(u1), 1) AND eq(len(u2), 0) AND eq(len(u3), 0)) { - set { - uid(u1) "user_email2@company1.io" . - } - } - - # case when email1 does not exist but email2 exists - mutation @if(eq(len(u1), 0) AND eq(len(u2), 1) AND eq(len(u3), 0)) { - set { - uid(u2) "user_email1@company1.io" . - } - } - - # case when both emails exist and needs merging - mutation @if(eq(len(u1), 1) AND eq(len(u2), 1) AND eq(len(u3), 0)) { - set { - _:user "user" . - _:user "Person" . - _:user "user_email1@company1.io" . - _:user "user_email2@company1.io" . - } - - delete { - uid(u1) * . - uid(u1) * . - uid(u2) * . - uid(u2) * . - } - } -}' | jq -``` - -Result (when database is empty): - -```json -{ - "data": { - "q1": [], - "q2": [], - "q3": [], - "code": "Success", - "message": "Done", - "uids": { - "user": "0x1" - } - }, - "extensions": {...} -} -``` - -Result (both emails exist and are attached to different nodes): -```json -{ - "data": { - "q1": [ - { - "uid": "0x2" - } - ], - "q2": [ - { - "uid": "0x3" - } - ], - "q3": [], - "code": "Success", - "message": "Done", - "uids": { - "user": "0x4" - } - }, - "extensions": {...} -} -``` - -Result (when both emails exist and are already attached to the same node): - -```json -{ - "data": { - "q1": [], - "q2": [], - "q3": [ - { - "uid": "0x4" - } - ], - "code": "Success", - "message": "Done", - "uids": {} - }, - "extensions": {...} -} -``` - -We can achieve the same result using `json` dataset as follows: - -```sh -curl -H "Content-Type: application/json" -X POST localhost:8080/mutate?commitNow=true -d '{ - "query": "{q1(func: eq(email, \"user_email1@company1.io\")) @filter(not(eq(email, \"user_email2@company1.io\"))) {u1 as uid} \n q2(func: eq(email, \"user_email2@company1.io\")) @filter(not(eq(email, \"user_email1@company1.io\"))) {u2 as uid} \n q3(func: eq(email, \"user_email1@company1.io\")) @filter(eq(email, \"user_email2@company1.io\")) {u3 as uid}}", - "mutations": [ - { - "cond": "@if(eq(len(u1), 0) AND eq(len(u2), 0) AND eq(len(u3), 0))", - "set": [ - { - "uid": "_:user", - "name": "user", - "dgraph.type": "Person" - }, - { - "uid": "_:user", - "email": "user_email1@company1.io", - "dgraph.type": "Person" - }, - { - "uid": "_:user", - "email": "user_email2@company1.io", - "dgraph.type": "Person" - } - ] - }, - { - "cond": "@if(eq(len(u1), 1) AND eq(len(u2), 0) AND eq(len(u3), 0))", - "set": [ - { - "uid": "uid(u1)", - "email": "user_email2@company1.io", - "dgraph.type": "Person" - } - ] - }, - { - "cond": "@if(eq(len(u1), 1) AND eq(len(u2), 0) AND eq(len(u3), 0))", - "set": [ - { - "uid": "uid(u2)", - "email": "user_email1@company1.io", - "dgraph.type": "Person" - } - ] - }, - { - "cond": "@if(eq(len(u1), 1) AND eq(len(u2), 1) AND eq(len(u3), 0))", - "set": [ - { - "uid": "_:user", - "name": "user", - "dgraph.type": "Person" - }, - { - "uid": "_:user", - "email": "user_email1@company1.io", - "dgraph.type": "Person" - }, - { - "uid": "_:user", - "email": "user_email2@company1.io", - "dgraph.type": "Person" - } - ], - "delete": [ - { - "uid": "uid(u1)", - "name": null, - "email": null - }, - { - "uid": "uid(u2)", - "name": null, - "email": null - } - ] - } - ] -}' | jq -``` diff --git a/content/mutations/delete.md b/content/mutations/delete.md deleted file mode 100644 index 6c8cbb06..00000000 --- a/content/mutations/delete.md +++ /dev/null @@ -1,91 +0,0 @@ -+++ -date = "2017-03-20T22:25:17+11:00" -title = "Delete" -weight = 7 -[menu.main] - parent = "mutations" -+++ - -A delete mutation, identified by the `delete` keyword, removes -[triples](/mutations/triples) from the store. - -For example, if the store contained the following: -```RDF -<0xf11168064b01135b> "Lewis Carrol" -<0xf11168064b01135b> "1998" -<0xf11168064b01135b> "Person" . -``` - -Then, the following delete mutation deletes the specified erroneous data, and -removes it from any indexes: - -```sh -{ - delete { - <0xf11168064b01135b> "1998" . - } -} -``` - -## Wildcard delete - -In many cases you will need to delete multiple types of data for a predicate. -For a particular node `N`, all data for predicate `P` (and all corresponding -indexing) is removed with the pattern `S P *`. - -```sh -{ - delete { - <0xf11168064b01135b> * . - } -} -``` - -The pattern `S * *` deletes all the known edges out of a node, any reverse edges -corresponding to the removed edges, and any indexing for the removed data. - -{{% notice "note" %}} For mutations that fit the `S * *` pattern, only -predicates that are among the types associated with a given node (using -`dgraph.type`) are deleted. Any predicates that don't match one of the -node's types will remain after an `S * *` delete mutation.{{% /notice %}} - -```sh -{ - delete { - <0xf11168064b01135b> * * . - } -} -``` - -If the node `S` in the delete pattern `S * *` has only a few predicates with a -type defined by `dgraph.type`, then only those triples with typed predicates are -deleted. A node that contains untyped predicates will still exist after a -`S * *` delete mutation. - -{{% notice "note" %}} The patterns `* P O` and `* * O` are not supported because -it's inefficient to store and find all the incoming edges. {{% /notice %}} - -## Deletion of non-list predicates - -Deleting the value of a non-list predicate (i.e a 1-to-1 relationship) can be -done in two ways. - -* Using the [wildcard delete](#wildcard-delete) (star notation) - mentioned in the last section. -* Setting the object to a specific value. If the value passed is not the -current value, the mutation will succeed but will have no effect. If the value -passed is the current value, the mutation will succeed and will delete the -non-list predicate. - -For language-tagged values, the following special syntax is supported: - -``` -{ - delete { - <0x12345> * . - } -} -``` - -In this example, the value of the `name` field that is tagged with the language -tag `es` is deleted. Other tagged values are left untouched. diff --git a/content/mutations/external-ids.md b/content/mutations/external-ids.md deleted file mode 100644 index a2b42d6d..00000000 --- a/content/mutations/external-ids.md +++ /dev/null @@ -1,66 +0,0 @@ -+++ -date = "2017-03-20T22:25:17+11:00" -title = "External IDs" -weight = 3 -[menu.main] - parent = "mutations" -+++ - -Dgraph's input language, RDF, also supports triples of the form ` literal/node` and variants on this, where the label `a_fixed_identifier` is intended as a unique identifier for a node. For example, mixing [schema.org](http://schema.org) identifiers, [the movie database](https://www.themoviedb.org/) identifiers and blank nodes: - -``` -_:userA . -_:userA "Person" . -_:userA "FirstName LastName" . - . - "Robin Wright" . -``` - -As Dgraph doesn't natively support such external IDs as node identifiers. Instead, external IDs can be stored as properties of a node with an `xid` edge. For example, from the above, the predicate names are valid in Dgraph, but the node identified with `` could be identified in Dgraph with a UID, say `0x123`, and an edge - -``` -<0x123> "http://schema.org/Person" . -<0x123> "ExternalType" . -``` - -While Robin Wright might get UID `0x321` and triples - -``` -<0x321> "https://www.themoviedb.org/person/32-robin-wright" . -<0x321> <0x123> . -<0x321> "Robin Wright" . -<0x321> "Person" . -``` - -An appropriate schema might be as follows. -``` -xid: string @index(exact) . -: [uid] @reverse . -``` - -Query Example: All people. - -``` -{ - var(func: eq(xid, "http://schema.org/Person")) { - allPeople as <~http://schema.org/type> - } - - q(func: uid(allPeople)) { - - } -} -``` - -Query Example: Robin Wright by external ID. - -``` -{ - robin(func: eq(xid, "https://www.themoviedb.org/person/32-robin-wright")) { - expand(_all_) { expand(_all_) } - } -} - -``` - -{{% notice "note" %}} `xid` edges are not added automatically in mutations. In general it is a user's responsibility to check for existing `xid`'s and add nodes and `xid` edges if necessary. Dgraph leaves all checking of uniqueness of such `xid`'s to external processes. {{% /notice %}} diff --git a/content/mutations/facets-in-list-type-with-rdf.md b/content/mutations/facets-in-list-type-with-rdf.md deleted file mode 100644 index 7009a16d..00000000 --- a/content/mutations/facets-in-list-type-with-rdf.md +++ /dev/null @@ -1,58 +0,0 @@ -+++ -date = "2017-03-20T22:25:17+11:00" -title = "Facet Lists in RDF" -weight = 8 -[menu.main] - parent = "mutations" -+++ - -Schema: - -```sh -: string @index(exact). -: [string] . -``` - -Creating a list with facets in RDF is straightforward. - -```sh -{ - set { - _:Julian "Julian" . - _:Julian "Jay-Jay" (kind="first") . - _:Julian "Jules" (kind="official") . - _:Julian "JB" (kind="CS-GO") . - } -} -``` - -```graphql -{ - q(func: eq(name,"Julian")){ - name - nickname @facets - } -} -``` -Result: -```JSON -{ - "data": { - "q": [ - { - "name": "Julian", - "nickname|kind": { - "0": "first", - "1": "official", - "2": "CS-GO" - }, - "nickname": [ - "Jay-Jay", - "Jules", - "JB" - ] - } - ] - } -} -``` \ No newline at end of file diff --git a/content/mutations/mutations-using-curl.md b/content/mutations/mutations-using-curl.md deleted file mode 100644 index d0b4f7bd..00000000 --- a/content/mutations/mutations-using-curl.md +++ /dev/null @@ -1,39 +0,0 @@ -+++ -date = "2017-03-20T22:25:17+11:00" -title = "Mutations Using cURL" -weight = 9 -[menu.main] - parent = "mutations" -+++ - -Mutations can be done over HTTP by making a `POST` request to an Alpha's `/mutate` endpoint. On the command line this can be done with curl. To commit the mutation, pass the parameter `commitNow=true` in the URL. - -To run a `set` mutation: - -```sh -curl -H "Content-Type: application/rdf" -X POST localhost:8080/mutate?commitNow=true -d $' -{ - set { - _:alice "Alice" . - _:alice "Person" . - } -}' -``` - -To run a `delete` mutation: - -```sh -curl -H "Content-Type: application/rdf" -X POST localhost:8080/mutate?commitNow=true -d $' -{ - delete { - # Example: The UID of Alice is 0x56f33 - <0x56f33> * . - } -}' -``` - -To run an RDF mutation stored in a file, use curl's `--data-binary` option so that, unlike the `-d` option, the data is not URL encoded. - -```sh -curl -H "Content-Type: application/rdf" -X POST localhost:8080/mutate?commitNow=true --data-binary @mutation.txt -``` \ No newline at end of file diff --git a/content/mutations/reverse-edges.md b/content/mutations/reverse-edges.md deleted file mode 100644 index 8a49aa1b..00000000 --- a/content/mutations/reverse-edges.md +++ /dev/null @@ -1,227 +0,0 @@ -+++ -date = "2017-03-20T22:25:17+11:00" -title = "Reverse Edges" -weight = 13 -[menu.main] - parent = "mutations" -+++ - -Any outgoing edge in Dgraph can be reversed using the `@reverse` directive in the schema and be queried using tilde as the prefix of the edge name. e.g. `<~myEdge>`. - -Dgraph serializes directed graphs. This means that all properties always point from an entity to another entity or value in a single direction. `S P -> O`. - -Reverse edges are automatically generated edges and are not part of your dataset. This means that you cannot run mutations directly on the reverse edges. Mutating the forward edge will automatically update the reverse edge. - -**Using Reverse Edges correctly** - -In RDF the arrangement of the triples already defines what can be reversed. - -```RDF -_:MyObject _:BlankNode . #That's the right syntax of a reverse edge. -_:BlankNode "Person" . -``` - -The easiest way to correct and apply reverse edges is using JSON. It is simply having the directive on schema at the desired edge. When building your mutations remember that there is no reverse syntax in JSON. So what you should do is similar to RDF: change the arrangement of the JSON objects. - -Since `MyObject` is above the `Person` entity, `MyObject` must come before when formatting the mutation. - -```JSON -{ - "set": [ - { - "uid": "_:MyObject", - "dgraph.type": "Object", - "myEdge": { - "uid": "_:BlankNode", - "dgraph.type": "Person" - } - } - ] -} -``` - -Another way to do this is to separate into small chunks/batches and use blank nodes as references. This facilitates the organization and reuse of references. - -```JSON -{ - "set": [ - { - "uid": "_:MyObject", - "dgraph.type": "Object", - "myEdge": [{"uid": "_:BlankNode"}] - }, - { - "uid": "_:BlankNode", - "dgraph.type": "Person" - } - ] -} -``` - -### More reverse examples - -In RDF the correct way to apply reverse edges is very straight-forward. - -```RDF -name: String . -husband: uid @reverse . -wife: uid @reverse . -parent: [uid] @reverse . -``` - -```RDF -{ - set { - _:Megalosaurus "Earl Sneed Sinclair" . - _:Megalosaurus "Dinosaur" . - _:Megalosaurus _:Allosaurus . - _:Allosaurus "Francis Johanna Phillips Sinclair" (short="Fran") . - _:Allosaurus "Dinosaur" . - _:Allosaurus _:Megalosaurus . - _:Hypsilophodon "Robert Mark Sinclair" (short="Robbie") . - _:Hypsilophodon "Dinosaur" . - _:Hypsilophodon _:Allosaurus (role="son") . - _:Hypsilophodon _:Megalosaurus (role="son") . - _:Protoceratops "Charlene Fiona Sinclair" . - _:Protoceratops "Dinosaur" . - _:Protoceratops _:Allosaurus (role="daughter") . - _:Protoceratops _:Megalosaurus (role="daughter") . - _:MegalosaurusBaby "Baby Sinclair" (short="Baby") . - _:MegalosaurusBaby "Dinosaur" . - _:MegalosaurusBaby _:Allosaurus (role="son") . - _:MegalosaurusBaby _:Megalosaurus (role="son") . - } -} -``` - -The directions are like: - -```rdf -Exchanged hierarchy: - Object -> Parent; - Object <~ Parent; #Reverse - Children to parents via "parent" edge. - wife and husband bidirectional using reverse. -Normal hierarchy: - Parent -> Object; - Parent <~ Object; #Reverse - This hierarchy is not part of the example, but is generally used in all graph models. - To make this hierarchy we need to bring the hierarchical relationship starting from the parents and not from the children. Instead of using the edges "wife" and "husband" we switch to single edge called "married" to simplify the model. - _:Megalosaurus "Earl Sneed Sinclair" . - _:Megalosaurus "Dinosaur" . - _:Megalosaurus _:Allosaurus . - _:Megalosaurus _:Hypsilophodon (role="son") . - _:Megalosaurus _:Protoceratops (role="daughter") . - _:Megalosaurus _:MegalosaurusBaby (role="son") . - _:Allosaurus "Francis Johanna Phillips Sinclair" (short="Fran") . - _:Allosaurus "Dinosaur" . - _:Allosaurus _:Megalosaurus . - _:Allosaurus _:Hypsilophodon (role="son") . - _:Allosaurus _:Protoceratops (role="daughter") . - _:Allosaurus _:MegalosaurusBaby (role="son") . -``` - -### Queries - -1. `wife_husband` is the reversed `wife` edge. -2. `husband` is an actual edge. - -```graphql -{ - q(func: has(wife)) { - name - WF as wife { - name - } - } - reverseIt(func: uid(WF)) { - name - wife_husband : ~wife { - name - } - husband { - name - } - } -} -``` - -1. `Children` is the reversed `parent` edge. - -```graphql -{ - q(func: has(name)) @filter(eq(name, "Earl Sneed Sinclair")){ - name - Children : ~parent @facets { - name - } - } -} -``` - -### Reverse Edges and Facets - -Facets on reverse edges are the same as the forward edge. That is, if you set or update a facet on an edge, its reverse will have the same facets. - -```rdf -{ - set { - _:Megalosaurus "Earl Sneed Sinclair" . - _:Megalosaurus "Dinosaur" . - _:Megalosaurus _:Allosaurus . - _:Megalosaurus _:MegalosaurusBaby (role="parent -> child") . - _:MegalosaurusBaby "Baby Sinclair" (short="Baby -> parent") . - _:MegalosaurusBaby "Dinosaur" . - _:MegalosaurusBaby _:Megalosaurus (role="child -> parent") . - } -} -``` - -Using a similar query from the previous example: - -```graphql -{ - Parent(func: has(name)) @filter(eq(name, "Earl Sneed Sinclair")){ - name - C as Children : parent @facets { - name - } - } - Child(func: uid(C)) { - name - parent @facets { - name - } - } -} -``` - -```json -{ - "data": { - "Parent": [ - { - "name": "Earl Sneed Sinclair", - "Children": [ - { - "name": "Baby Sinclair", - "Children|role": "parent -> child" - } - ] - } - ], - "Child": [ - { - "name": "Baby Sinclair", - "parent": [ - { - "name": "Earl Sneed Sinclair", - "parent|role": "child -> parent" - } - ] - } - ] - } -} - ``` - \ No newline at end of file diff --git a/content/mutations/triples.md b/content/mutations/triples.md deleted file mode 100644 index 929a9b3e..00000000 --- a/content/mutations/triples.md +++ /dev/null @@ -1,37 +0,0 @@ -+++ -date = "2017-03-20T22:25:17+11:00" -title = "Triples" -weight = 1 -[menu.main] - parent = "mutations" -+++ - -A mutation that adds triples is done with the `set` keyword. -``` -{ - set { - # triples in here - } -} -``` - -The input language is triples in the W3C standard [RDF N-Quad format](https://www.w3.org/TR/n-quads/). - -Each triple has the form -``` - . -``` -Meaning that the graph node identified by `subject` is linked to `object` with directed edge `predicate`. Each triple ends with a period. The subject of a triple is always a node in the graph, while the object may be a node or a value (a literal). - -For example, the triple -``` -<0x01> "Alice" . -<0x01> "Person" . -``` -Represents that graph node with ID `0x01` has a `name` with string value `"Alice"`. While triple -``` -<0x01> <0x02> . -``` -Represents that graph node with ID `0x01` is linked with the `friend` edge to node `0x02`. - -Dgraph creates a unique 64 bit identifier for every blank node in the mutation - the node's UID. A mutation can include a blank node as an identifier for the subject or object, or a known UID from a previous mutation. \ No newline at end of file diff --git a/content/mutations/upsert-block.md b/content/mutations/uid-upsert.md similarity index 68% rename from content/mutations/upsert-block.md rename to content/mutations/uid-upsert.md index fc14f754..78d8ec22 100644 --- a/content/mutations/upsert-block.md +++ b/content/mutations/uid-upsert.md @@ -1,47 +1,20 @@ +++ date = "2017-03-20T22:25:17+11:00" -title = "Upsert Block" -weight = 11 +title = "uid function in upsert" +weight = 1 [menu.main] parent = "mutations" +++ -The upsert block allows performing queries and mutations in a single request. The upsert -block contains one query block and one or more than one mutation blocks. Variables defined +The upsert block contains one query block and mutation blocks. Variables defined in the query block can be used in the mutation blocks using the `uid` and `val` function. -In general, the structure of the upsert block is as follows: - -``` -upsert { - query - [fragment ] - mutation - [mutation ] - ... -} -``` - -Execution of an upsert block also returns the response of the query executed on the state -of the database *before mutation was executed*. To get the latest result, we should commit -the mutation and execute another query. - -## `uid` Function - The `uid` function allows extracting UIDs from variables defined in the query block. There are two possible outcomes based on the results of executing the query block: * If the variable is empty i.e. no node matched the query, the `uid` function returns a new UID in case of a `set` operation and is thus treated similar to a blank node. On the other hand, for `delete/del` operation, it returns no UID, and thus the operation becomes a no-op and is silently ignored. A blank node gets the same UID across all the mutation blocks. * If the variable stores one or more than one UIDs, the `uid` function returns all the UIDs stored in the variable. In this case, the operation is performed on all the UIDs returned, one at a time. -## `val` Function - -The `val` function allows extracting values from value variables. Value variables store -a mapping from UIDs to their corresponding values. Hence, `val(v)` is replaced by the value -stored in the mapping for the UID (Subject) in the N-Quad. If the variable `v` has no value -for a given UID, the mutation is silently ignored. The `val` function can be used with the -result of aggregate variables as well, in which case, all the UIDs in the mutation would -be updated with the aggregate value. ## Example of `uid` Function @@ -195,68 +168,9 @@ curl -H "Content-Type: application/json" -X POST localhost:8080/mutate?commitNow ``` If we want to execute the mutation only when the user exists, we could use -[Conditional Upsert]({{< relref "mutations/conditional-upsert.md" >}}). - -## Example of `val` Function - -Let's say we want to migrate the predicate `age` to `other`. We can do this using the -following mutation: - -```sh -curl -H "Content-Type: application/rdf" -X POST localhost:8080/mutate?commitNow=true -d $' -upsert { - query { - v as var(func: has(age)) { - a as age - } - } - - mutation { - # we copy the values from the old predicate - set { - uid(v) val(a) . - } +[Conditional Upsert]({{< relref "dql-mutation.md#conditional-upsert" >}}). - # and we delete the old predicate - delete { - uid(v) * . - } - } -}' | jq -``` -Result: - -```json -{ - "data": { - "code": "Success", - "message": "Done", - "uids": {} - }, - "extensions": {...} -} -``` - -Here, variable `a` will store a mapping from all the UIDs to their `age`. The mutation -block then stores the corresponding value of `age` for each UID in the `other` predicate -and deletes the `age` predicate. - -We can achieve the same result using `json` dataset as follows: - -```sh -curl -H "Content-Type: application/json" -X POST localhost:8080/mutate?commitNow=true -d $'{ - "query": "{ v as var(func: regexp(email, /.*@company1.io$/)) }", - "delete": { - "uid": "uid(v)", - "age": null - }, - "set": { - "uid": "uid(v)", - "other": "val(a)" - } -}' | jq -``` ## Bulk Delete Example diff --git a/content/mutations/val-upsert.md b/content/mutations/val-upsert.md new file mode 100644 index 00000000..70446b46 --- /dev/null +++ b/content/mutations/val-upsert.md @@ -0,0 +1,81 @@ ++++ +date = "2017-03-20T22:25:17+11:00" +title = "val function in upsert" +weight = 2 +[menu.main] + parent = "mutations" ++++ + +The upsert block allows performing queries and mutations in a single request. The upsert +block contains one query block and one or more than one mutation blocks. Variables defined +in the query block can be used in the mutation blocks using the `uid` and `val` function. + + +The `val` function allows extracting values from value variables. Value variables store +a mapping from UIDs to their corresponding values. Hence, `val(v)` is replaced by the value +stored in the mapping for the UID (Subject) in the N-Quad. If the variable `v` has no value +for a given UID, the mutation is silently ignored. The `val` function can be used with the +result of aggregate variables as well, in which case, all the UIDs in the mutation would +be updated with the aggregate value. + + +## Example of `val` Function + +Let's say we want to migrate the predicate `age` to `other`. We can do this using the +following mutation: + +```sh +curl -H "Content-Type: application/rdf" -X POST localhost:8080/mutate?commitNow=true -d $' +upsert { + query { + v as var(func: has(age)) { + a as age + } + } + + mutation { + # we copy the values from the old predicate + set { + uid(v) val(a) . + } + + # and we delete the old predicate + delete { + uid(v) * . + } + } +}' | jq +``` + +Result: + +```json +{ + "data": { + "code": "Success", + "message": "Done", + "uids": {} + }, + "extensions": {...} +} +``` + +Here, variable `a` will store a mapping from all the UIDs to their `age`. The mutation +block then stores the corresponding value of `age` for each UID in the `other` predicate +and deletes the `age` predicate. + +We can achieve the same result using `json` dataset as follows: + +```sh +curl -H "Content-Type: application/json" -X POST localhost:8080/mutate?commitNow=true -d $'{ + "query": "{ v as var(func: regexp(email, /.*@company1.io$/)) }", + "delete": { + "uid": "uid(v)", + "age": null + }, + "set": { + "uid": "uid(v)", + "other": "val(a)" + } +}' | jq +``` From e3a70e8e23af0b77f53d1f86246af692a36c7a63 Mon Sep 17 00:00:00 2001 From: rderbier Date: Tue, 14 Feb 2023 10:43:43 -0800 Subject: [PATCH 4/4] Update dql-rdf.md --- content/dql-syntax/dql-rdf.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/content/dql-syntax/dql-rdf.md b/content/dql-syntax/dql-rdf.md index 6d1d17c0..1b0b4a5f 100644 --- a/content/dql-syntax/dql-rdf.md +++ b/content/dql-syntax/dql-rdf.md @@ -78,10 +78,10 @@ The supported [RDF datatypes](https://www.w3.org/TR/rdf11-concepts/#section-Data | <http://www.w3.org/2001/XMLSchema#float> | `float` | -See the section on [RDF schema types]({{< relref "query-language/schema.md#rdf-types" >}}) to understand how RDF types affect mutations and storage. + ### Facets -#### Creating a list with facets +#### Creating a list with facets ```sh {