Commit 6163ec7d authored by Tim Holzheim's avatar Tim Holzheim
Browse files

Updated Documentation

parent 0402bc14
......@@ -22,7 +22,7 @@ If multiple service are defined in the configuration the schema is summarized on
### Queries
For each object type of the provided or extracted schema a query field is generated.
To query for specific IRIs (IDs in UGQL) the argument **_id** can be used to filter for the provided IRIs.
A detailed description of all possible query modifiers is provided [here](./docs/query_modifiers.md).
A detailed description of the query translation and is provided [here](./docs/translation_phase.md) and an overview of all possible query modifiers is provided [here](./docs/query_modifiers.md).
To test the different features of UGQL the provided [examples](examples/README.md) can be used to write and test the query features.
> Note: The service MUST not be defined in the query. Based on the UGQL schema (UGQLS) the different services will be queried accordingly.
......@@ -30,7 +30,7 @@ To test the different features of UGQL the provided [examples](examples/README.m
<img src="./docs/figures/ugql_query_schematic.svg">
### Mutations
Insert and delete mutations are generated for each object type of the UGQLS which corresponds to the output type of the mutation allowing to directly query the modified data.
Insert and delete mutations are [generated](./docs/translation_phase.md#mutation-translation) for each object type of the UGQLS which corresponds to the output type of the mutation allowing to directly query the modified data.
The mutation actions are only performed on one service of the services defined in the configuration.
As shown in the examples the mutations allow to insert and delete object data.
......@@ -83,11 +83,11 @@ For example UGQL service setups look into the [examples](examples/README.md) and
## Differences to HyperGraphQL
- Automatic bootstrapping phase (through schema summarization)
- Configurable summarization querying
- Configurable mapping vocabulary
- Configurable summarization querying
- Configurable mapping vocabulary
- Mutation support
- Insert and delete mutation fields are generated for all objects in the schema
- Mutation action is limited to one service (MUST be LocalModelSPARQLService or SPARQLEndpointService)
- Insert and delete mutation fields are generated for all objects in the schema
- Mutation action is limited to one service (MUST be LocalModelSPARQLService or SPARQLEndpointService)
- Support for [multiple services](./docs/multiple_service_feature.md) per schema entity
- Support of equivalence relations in the schema and during querying
- [Interafaces](./docs/interface.md) and [Unions](./docs/union.md) supported in the schema
......
This diff is collapsed.
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
# UGQL Mutations
# TODO: Explain Mutations
UltraGraphQL generates for all object types defined in the UGQLS a **insert** and **delete** mutation field.
Through the arguments of these fields all fields of the corresponding object can be defined to write insertions or deletions based on the input.
The insertion action always applies the same translation and the performed delete action depends on the defined input.
The mutation actions are only performed against one service and the selection set of the mutation is executed against all defined services.
## Enable Mutations
In order to enable the mutations the mutation setting in the UGQL [configuration](./config.md) must be set to true and a mutation service must be defined.
The following example shows a possible configuration.
```json
{
"name": "hgql-example-with-sparql",
"schema": "gql/schema.graphql",
"mutations": true,
"mutationService": "dbpedia-sparql",
"server": {
"port": 8080,
"graphql": "/graphql",
"graphiql": "/graphiql"
},
"services": [
{
"id": "dbpedia-sparql",
"type": "SPARQLEndpointService",
"filepath": "data/dataset_8000.ttl",
"fieltype": "TTL",
"graph": "http://dbpedia.org"
}
]
}
```
> The *mutationService* MUST be the id of a defined service in the *services* list.
## Insertions
Insertions are translated straight forward to SPARQL updates since the *_id* is mandatory the input is translated into triples and inserted into the service.
The following example shows a possible insert mutation.
```graphql
mutation{
insert_ex_Person(_id: "https://example.org/Bob", ex_name: "Bob", ex_age: "42", ex_relatedWith: {_id: "https://example.org/Alice"}){
......@@ -13,6 +47,47 @@ mutation{
}
}
```
The example insert mutation results in the following SPARQL update.
```sparql
PREFIX ex: <https://example.org/>
INSERT DATA{
GRAPH <...>{
ex:Bob a ex:Person;
ex:name "Bob";
ex:age "42";
ex:relstedWith ex:Aliceb .
}
}
```
> If no graph is defined for the service the *GRAPH* clause is left out
The selection set of the mutation is internally treated as the following GraphQL query and executed by the query handler.
```graphql
query{
ex_Person{
_id
ex_name
ex_relatedWith{
ex_name
}
}
}
```
The final result of the mutation is than the result of the mutation concatenated with the result of the query.
## Deletions
Depending on the provided input the mutation action of the delete mutation alters.
In the following all possible delete actions are explained.
>NOTE: Deleting data may not result in the actual deletion of the information.
> In RDF information can be implied through inference rules meaning that the deletion of the actual data may not delete the inference of the same data through other information in the triple store.
### Delete by ID
If only the ID (IRI) of an resource is given than all information about this ID related to the object is deleted from the mutation service.
This means that only the information that are defined as fields in the object are deleted.
Data that is linked through another object is left out.
The following example therefore deletes all person data that includes the defined ID.
```graphql
mutation{
delete_ex_Person(_id: "https://example.org/Bob"){
......@@ -20,6 +95,29 @@ mutation{
}
}
```
Resulting in the following SPARQL update.
```sparql
PREFIX ex: <https://example.org/>
WITH <...>
DELETE{
?perosn ?p1 ?o .
?s ?p2 ?person .
}
WHERE{
?person a ex:Person .
OPTIONAL{
?perosn ?p1 ?o .
}
OPTIONAL{
?s ?p2 ?person .
}
}
```
>The *WITH* clause contains the graph information from the mutation service. If the graph is not defined than the clause is left out.
### Delete by Matching Data
Is the **_id** not defined but other input data is provided, than all data of this object type that matches the provided input is deleted.
The following example deletes all persons that have the name *Bob*, are *42* jears old and are related to *Alice*.
```graphql
mutation{
delete_ex_Person(ex_name: "Bob", ex_age: "42", ex_relatedWith: {_id: "https://example.org/Alice"}){
......@@ -31,6 +129,34 @@ mutation{
}
}
```
The example delete mutation results in the following SPARQL update.
```sparql
PREFIX ex: <https://example.org/>
WITH <...>
DELETE{
?perosn ?p1 ?o .
?s ?p2 ?person .
}
WHERE{
?person a ex:Person;
ex:name "Bob";
ex:age "42";
ex:relatedWith ex:Alice .
OPTIONAL{
?perosn ?p1 ?o .
}
OPTIONAL{
?s ?p2 ?person .
}
}
```
>The *WITH* clause contains the graph information from the mutation service. If the graph is not defined than the clause is left out.
### Delete Data
If the **_id** and other input information are provided than only the provided input data is deleted, except the _id.
The triple that defines the _id as type of the object is not deleted since other data of this _id related to the object could still exist.
The following delete mutation deletes the provided name age and related with connection to Alice.
Deleting the relation to the object Alice does not delete the object Alice only the connection to the object form Bobs object.
```graphql
mutation{
delete_ex_Person(_id: "https://example.org/Bob", ex_name: "Bob", ex_age: "42", ex_relatedWith: {_id: "https://example.org/Alice"}){
......@@ -41,4 +167,16 @@ mutation{
}
}
}
```
Resulting in the following SPARQL update.
```sparql
PREFIX ex: <https://example.org/>
DELETE DATA{
GRAPH <...>{
ex:Bob a ex:Person;
ex:name "Bob";
ex:age "42";
ex:relstedWith ex:Aliceb .
}
}
```
\ No newline at end of file
# Translation Phase
In the translation phase, received queries and mutations are translated
to corresponding SPARQL queries and updates and executed against the
services defined in the configuration. If a query requests data from
multiple services the query is broken down to subqueries depending on
the services defined in the UGQL schema (UGQLS). Therefore a SPARQL
query for a service only queries information that are assigned to the
service in the UGQLS. A detailed description of the SPARQL query translation
is provided in the Section [Query Translation](#query-translation).
As shown in Figure 1 the translation phase begins with converting the UGQLS
into a GraphQL schema by adding the query and mutation fields with assigned
data fetchers and additionally field arguments and input types are added
that are needed for the query and mutation writing.
After the GraphQL schema is generated it is exposed at the UGQL endpoint
and serves the user or tools like GraphiQL for simplified query writing.
Internally the schema is used to validate incoming GraphQL requests, as
depicted in step 6 in Figure 1 and analogously for the mutations.
>Figure 1: Abstract component overview of the translation phase
>
>![Abstract overview of the components of the translation phase](figures/translation_phase.svg)
## Query Translation
The query translation depends on the underlying schema. For example if
only one service is configured the whole GQL query is translated into one
SPARQL query. If multiple services are defined the query might be translated
into multiple queries depending on the queried entities. In the following
the query translation is explained based on a example which is based on the following simplified UGQLS.
```graphql
type ex_Person @service(id:"service_1"){
ex_name: [String] @service(id:"service_1")
ex_age: [String] @service(id:"service_2")
ex_address: [ex_Address] @service(id:"service_1")
ex_owns: [ex_Car] @service(id:"service_4")
}
type ex_Address @service(id:"service_3"){
ex_street: [String] @service(id:"service_3")
}
type ex_Car @service(id:"service_4"){
ex_model: [String] @service(id:"service_4")
ex_color: [String] @service(id:["service_1", "service_4"])
}
```
### Querying One Service
By querying the name of all persons only data from **service 1** is
queried and as explained above is only translated into one SPARQL query.
```graphql
ex_Person{
ex_name
}
```
The Query is converted to the following SPARQL query:
```sparql
PREFIX ex: <http://example.org/>
SELECT *
WHERE {
{
SELECT *
WHERE {
?x_1 a ex:Person.
}
}
OPTIONAL{
?x_1 ex:name ?x_1_1.
}
}
```
> Note: The actual SPARQL query does not use prefixes. They were used in
> the examples to increase the readability. Also the formatting may vary
The root query is always wrapped into a separate select clause and fields
are queried in the optional clause. If the field has as output type a
object type of the schema a type check is added to the query.
If the output type is a interface or union then the translation is altered.
In the case that no specific type is queried virtual fields are added
during the translation for all possible output types and if specific types
are queried only for those types virtual fields are added.
These virtual fields query information separately and the resulty of these
fields are transformed according to the GraphQL schema therefore they are
only used internally. More detailed information about the query translation
in these cases can be found in the [union test](evaluation/union_test.md)
and [interface test](evaluation/interface_test.md).
### Querying Multiple Services
Querying schema entities that have different services assigned results
in multiple queries. The following query queries information from four
different services.
```graphql
ex_Person{
ex_name
ex_age
ex_address{
ex_street
}
ex_owns{
ex_model
}
}
```
Similar to the case with only one service the first query extracts the
information about all persons there IRI, name and IRI of there addresses
since this information is also in **service 1**.
Resulting in a similar query to the example with one service:
```sparql
PREFIX ex: <http://example.org/>
SELECT *
WHERE {
{
SELECT *
WHERE {
?x_1 a ex:Person.
}
}
OPTIONAL{
?x_1 ex:name ?x_1_1.
}
}
```
With the IRIs of the persons and the address the next queries are generated.
The information about the age of a person is located at **service 2** and
needs the IRIs of the persons to link the infomation together resulting in the following query.
```sparql
PREFIX ex: <http://example.org/>
SELECT *
WHERE {
VALUES ?x_1 {
<http://example.org/bob>
...
}
OPTIONAL{
?x_1 ex:age ?x_1_2.
}
}
```
The subquery for the car model a person owns is translated similarly with
an additional type check and fields resulting in the following query.
```sparql
PREFIX ex: <http://example.org/>
SELECT *
WHERE {
VALUES ?x_1 {
<http://example.org/bob>
...
}
OPTIONAL{
?x_1 ex:owns ?x_1_4.
OPTIONAL{
?x_1_4 ex:model ?x_1_4_1.
}
}
}
```
For the subquery querying the street name the IRI of the person is not needed
since the IRI of the address is unique. If multiple persons share the same
address (IRI) the street of the address is only queried once.
Resulting in the following query where the queried address IRIs of the first query are included as values.
```sparql
PREFIX ex: <http://example.org/>
SELECT *
WHERE {
VALUES ?x_1_3 {
<http://example.org/addr_a>
...
}
OPTIONAL{
?x_1_3 ex:street ?x_1_3_1.
}
}
```
#### Distributed Data
In the case of distributed data a queried schema entity has multiple
services in this case the all assigned services must be queried and the
results are merged. The merging of the results is done before a subquery
relying on this schema entity is translated and executed because as explained
above the subqueries depend on the results of there root field. Detailed
information about the implementation of this feature can be found
[here](./multiple_service_feature.md) and detailed information about the
query translation [here](./evaluation/test_multiple_service.md).
## Mutation Translation
Mutations in GraphQL consist of the mutation action and a defined selection set.
The mutation action is either an insertion or a deletion based on the input given in the mutation.
The selection set is a query after the mutation action is executed.
Currently UltraGraphQL only supports mutation actions against on service but the query is executed against all defined services.
The service is defined in the [configuration](./config.md).
UGQL generates for all object types in the UGQLS a *insert* and *delete* mutation field.
For input data the arguments of the fields contain use the same or equivalent names to the fields of the objects.
Furthermore, the output types of the fields stay the same for scalar values and in case of objects they are translated to equivalent input types that were generated during the generation of the GQLS (Figure 1).
In the case the output type is a union or interface the input type field or argument is split up to all possible output types meaning that for each possible output type a new field or argument is generated.
This step of breaking up the interface and union structure is needed because GraphQL does not allow interfaces or unions as output types of input types and arguments.
The example in the following demonstrates how the UGQLS is translated to the corresponding GQLS.
>UGQLS shown in this example is not valid since the context type and implementation of the own interface is missing. It is a shortent abstract of a valid UGQLS.
```graphql
type ex_Person @service(id:"service_1"){
ex_name: [String] @service(id:"service_1")
ex_age: [String] @service(id:"service_2")
ex_address: [ex_Address] @service(id:"service_1")
ex_owns: [ex_owns_OutputType] @service(id:"service_4")
}
type ex_Address @service(id:"service_3"){
ex_street @service(id:"service_3")
type ex_Bike @service(id:"service_5"){
ex_frame: [String] @service(id:"service_5")
ex_model: [String] @service(id:"service_5")
ex_color: [String] @service(id:"service_5")
}
type ex_Car @service(id:"service_4"){
ex_model: [String] @service(id:"service_4")
ex_color: [String] @service(id:["service_1", "service_4"])
}
interface ex_owns_OutputType{
ex_model: [String]
ex_color: [String]
}
```
Resulting GQLS:
>The GQLS in this example is simplified and misses field and object arguments for the query filtering.
```graphql
mutation{
insert_ex_Person(
_id: ID!
ex_name: [String],
ex_age: [String],
ex_address: [input_ex_Address],
ex_owns_as_ex_Bike: [input_ex_Bike],
ex_owns_as_ex_Car: [input_ex_Car])
delete_ex_Person(
_id: ID
ex_name: [String],
ex_age: [String],
ex_address: [input_ex_Address],
ex_owns_as_ex_Bike: [input_ex_Bike],
ex_owns_as_ex_Car: [input_ex_Car])
insert_ex_Address(
_id: ID!
ex_street: [String])
delete_ex_Address(
_id: ID
ex_street: [String])
insert_ex_Bike(
_id: ID!
ex_frame: [String],
ex_model: [String],
ex_color: [String])
delete_ex_Bike(
_id: ID
ex_frame: [String],
ex_model: [String],
ex_color: [String])
insert_ex_Car(
_id: ID!
ex_model: [String],
ex_color: [String])
delete_ex_Car(
_id: ID
ex_model: [String],
ex_color: [String])
}
input input_ex_Person{
_id: ID!
ex_name: [String]
ex_age: [String]
ex_address: [ex_Address]
ex_owns: [ex_owns_OutputType]
}
input input_ex_Address{
_id: ID!
ex_street
}
input input_ex_Bike{
_id: ID!
ex_frame: [String]
ex_model: [String]
ex_color: [String]
}
input input_ex_Car{
_id: ID!
ex_model: [String]
ex_color: [String]
}
interface ex_owns_OutputType{
_id: ID
ex_model: [String]
ex_color: [String]
}
type ex_Person @service(id:"service_1"){
_id: ID
ex_name: [String]
ex_age: [String]
ex_address: [ex_Address]
ex_owns: [ex_owns_OutputType]
}
type ex_Address @service(id:"service_3"){
_id: ID
ex_street
}
type ex_Bike implements ex_owns_OutputType{
_id: ID
ex_frame: [String]
ex_model: [String]
ex_color: [String]
}
type ex_Car implements ex_owns_OutputType{
_id: ID
ex_model: [String]
ex_color: [String]
}
```
With the generation of the input types of all object types complex mutations are possible.
For example the insertion of multiple nested objects with a single mutation.
In the following example the a person is inserted with a nested object containing information about the person.
If the address would already exist only the _id would be required.
```graphql
mutation{
insert_ex_Person(
_id:"https://example.org/bob",
ex_name:"Bob",
ex_address{
_id:"https://example.org/addr_a",
ex_street: "Evergreen Terrace 742"
})
}
```
As shown in **Figure 2** a received mutation is translated into an SPARQL update and executed against the defined service.
The result of the mutation execution and the selection set of the mutation are then forwarded to the query handler which executes the selection set as normal query.
The final result is than the result of the selection set combined with the result of the mutation.
Detailed information about the mutation translation can be found [here](./mutations.md)
>Currently the mutation fields only contatin the fields of there corresponding object type as arguments.
> The normal filtering features like limit, offset, and by _id are therefore, not possible for the root element of the selection set but for the rest of the selection set.
> A way to include is the normal filtering features into the root of the selection set is currently in construction.
>**Figure 2:**
>
>![Abstract process flow of the mutation handler](figures/mutation_abstract_processflow.svg)
## Internal Processing of an Query and Results
Any GraphQL query that the UGQL endpoint receives is validated against the GraphQL schema of the endpoint.
This validation is done by the java-graphql library.
If the query is invalid an error message is returned to the user.
The validated query is then transformed into JSON representations of the query which contains the SPARQL variable names for the queried entities and also splits the query into subqueries for the different services as explained in section [Query Translation](#query-translation).
As shown in **Figure 3** the set of generated JSON queries is then transformed into the actual SPARQL queries. The order of the translation and execution of the queries depends on the order of the GQL query since nested queries depend on the results of their root query.
The SPARQL results that are returned by the triple store are then enriched with information from the schema and inserted as triples into the **Result Pool**.
Furthermore, if the query has nested queries the results are forwarded to them and they start with the query translation and execution.
The **Result Pool** contains the results of all queries that were generated for the GQL query and represents a superset of the results for the query.
Since the query filter features like **limit** are applied in cases of multiple services multiple times the number of results in the result pool can be greater than the defined limit.
Therefore, the final result that is returned to the user is only a subset of the result pool.
>If the *offset* term is defined in a query then the generated SPARQL queries
> do not contain the offset because if multiple services are defined the
> offset in the SPARQL queries could lead to an incorrect query response.
> Therefore, the offset is only applied to the final result during the
> extraction form the result pool.
The extraction of the final result form the result pool is performed by java-graphql library and the data fetchers that are assigned to the GQL schema.
As pictured in **Figure 3** depending on the query the generation the final result can result in many SPARQL queries against the result pool.
As shown list results are queried with one query but if the entities in the list are not scalar values then each entity of the list results in another SPARQL query against the result pool.
>Evaluation has shown that more than half of the execution time is taken
> by the final result build up. A result build-up without the insertion
> and querying of the result pool would drastically improve the execution
> overhead of UltraGraphQL.
The final result that is returned to the user is then the result of the data fetchers appended with the context information.
For a detailed view of the process flow of a query execution consider
consider the diagram in section [Detailed Process Flow of a Query Execution](#detailed-process-flow-of-a-query-execution).
>**Figure 3:**
>
>![Process flow of the query translation and result transformation](figures/query_processflow.svg)
### Detailed Process Flow of a Query execution
![](figures/hgql_query.svg)
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment