.. include:: /includes.rst.txt .. _graphql_schema: SHACL and the GraphQL Schema ============================ This section (for maintainers of Ontologies and other RDF/SHACL data models) explains how RDF graphs can be published through the GraphQL services of TopBraid. In a nutshell, one or more GraphQL schemas are automatically generated using data shape definitions in the Shapes Constraint Language (SHACL). These SHACL shapes may be automatically generated using other input GraphQL schemas, enhancing them in the process with numerous features to query data stored in an RDF dataset. SHACL data shapes can also be generated from other input formats supported by TopQuarant's products. The readers of this section are expected to be familiar with GraphQL and have basic RDF skills. Decent knowledge of SHACL is advantageous. This section uses the prefix `dash` which represents the namespace `http://datashapes.org/dash#`. The prefix `graphql` represents the namespace `http://datashapes.org/graphql#`. Both graphs are automatically included into every TopBraid EDG Ontology. Selecting the Shapes -------------------- An RDF graph may contain thousands of classes or data shapes. A GraphQL service that includes all of them at once would quickly become unusable. In order to instruct the processor on which shapes and classes shall be exposed via GraphQL, the starting point is the "Home" asset of a TopBraid EDG ontology. This schema instance must use the following properties to include or exclude shapes: .. figure:: _images/Ontology-GraphQL-Schema.png :alt: Screenshot of TopBraid EDG Ontology editor defining the GraphQL schema properties :align: center :class: edg-figure **Use the GraphQL Schema view of the Ontology's Home asset to edit what gets exposed by GraphQL** .. list-table:: GraphQL Schema generation properties :widths: 20 30 :header-rows: 1 * - Property - Description * - graphql:publicShape - The values are included into the GraphQL schema * - graphql:publicClass - The values and all its subclasses are included * - graphql:publicNamespace - All shapes from the given namespace are included * - graphql:protectedShape - The values are included but not available from the root query * - graphql:protectedClass - The values and all its subclasses are included but not available from the root query * - graphql:privateShape - The values are excluded from the GraphQL schema (even if published by other properties) The algorithm that produces the set of published shapes first collects all shapes or classes defined using the `graphql:publicXY` and `graphql:protectedXY` properties above from the schema and also all its transitive values of `owl:imports` and `rdf:type` properties. Then it removes those that are marked via `graphql:privateShape`. All published shapes can be queried via GraphQL and are automatically exposed by the root query object. Those that are marked protected can not be queried from the root query but can be reached and traversed from other object types. Here is an example: .. literalinclude:: _code/schema/MySchema.ttl :linenos: :language: turtle From these SHACL shapes, the processor will internally generate the following GraphQL schema: .. literalinclude:: _code/schema/typeHumanGenerated.txt :linenos: As shown above, the system automatically produces a root query object that has fields for every public shape, with a name that is basically the plural form of the shape name. These root query fields can take a large number of arguments to select which of the matching objects shall be returned, see :ref:`graphql_querying`. Completing this introductory example, here is an example GraphQL query against this schema, returning all humans where the name starts with L, and all their friends, translating the height from meters to feet. .. literalinclude:: _code/schema/humanQuery.txt :linenos: A possible result JSON would be: .. literalinclude:: _code/schema/humanQueryResult.json :linenos: :language: json Objects and Fields ------------------ For each published node shape in a schema, the processor will create one GraphQL object type as described in the following sections. Object Types for Node Shapes ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The name of this object type will be derived using the following rules (in order): #. Use the value of `graphql:name` of the shape. #. Use the local name (i.e. the part of the shape URI after a separator such as '/' or '#'), replacing '-' with '_', if that is a valid GraphQL name. If there is more than one object type with the same name (e.g. from different namespaces but with the same local name), then preprend the prefix of the namespace and '_'. For example, `ex:Human` would become `ex_Human`. In general, the mapping is rather strict if the underlying shape definitions are invalid. For example if no valid name can be produced for a shape then the schema is rejected and the user encouraged to add suitable `graphql:name` triples. The uri Field """"""""""""" Each generated object type has a built-in field called `uri` that can be used to retrieve the URI of the RDF resource. For blank nodes this is an internal identifier starting with `_:`. In general, these blank node identifiers can be used interchangeably with URIs. The label Field """"""""""""""" Each generated object type has a built-in field called label that can be used to retrieve a human-readable label for an object. This label is typically derived from the `rdfs:label` (or a similar property) and should use the preferred language of the client, if multi-lingual labels exist. The `label` field always returns something, falling back to the local name of the underlying RDF resource, or an internal identifier starting with `_:` for blank nodes. Fields for Property Shapes """""""""""""""""""""""""" The object types produced from a node shape will have one field for each distinct `sh:path` that is defined at any property shape of the node shape. If the node shape is also an `rdfs:Class` then this includes any property shape of the (transitive) superclasses. Furthermore, any property shapes attached to values of `sh:node` of the node shape will (recursively) be included. (As a general pattern, `rdfs:subClassOf` and `sh:node` are treated uniformly, i.e. `sh:node` is an extension and inheritance mechanism similar to subclassing.) The names of these fields are derived using the same rules as for object types, i.e. checking `graphql:name` first, then local names of the `sh:path` (if that's a URI), and prepend a prefix if duplicate names would exist. Note that if a property shape is about a complex SHACL path, then a `graphql:name` is strongly recommended. The type of these generated fields is derived from the `sh:datatype`, `sh:node` or `sh:class`. For example, `sh:datatype xsd:boolean` gets mapped to `Boolean` and `sh:datatype xsd:decimal` to `Float`. To produce `ID`, annotate the property shape with `graphql:isIDField true` combined with `sh:datatype xsd:string`. If the property shape defines an `sh:or` list with at least one member, and all members of that list are node shapes with a URI, then a union type will be generated automatically. If the property shape has an `sh:or` list that is either `xsd:string` or `rdf:langString` or its inverse variation `rdf:langString` or `xsd:string` then the object type `LangString` (with fields lang and string) will be used. For other `sh:or` lists where the first entry is a `sh:datatype` shape, that specified datatype will be used. For object-valued properties for which there is no matching GraphQL object type, the system falls back to a built-in special type `_Resource` that only offers `uri` and `label` fields. This type is for example used for links that typically go outside of the published schema, e.g. `rdf:type` values. Fields are list-typed unless there is a property shape with `sh:maxCount 1`. Fields are marked as non-nullable (with !) if there is a `sh:minCount 1`. Note that, in general, any property shape that is marked as `sh:deactivated true` is ignored by the processor. Defining multiple GraphQL Schemas --------------------------------- A dataset may contain many named graphs and heterogeneous data. It is possible to define multiple GraphQL schemas for the same data, and in the same shapes graph. Each instance of `graphql:Schema` can either be identified by its URI or by its `graphql:name`. This is best explained through an example. .. literalinclude:: _code/schema/MySchema-simple.ttl :linenos: :language: turtle The above schema would be available through the URL schema `[server]/graphql/[dataset]/starwars`. If a schema does not carry a `graphql:name` then it can be accessed via the qname of its URI, replacing : with _: `[server]/graphql/[dataset]/ex_MySchema` would also work.