admin-shell-io / aas-specs-api

Repository of the Asset Administration Shell Specification DTA-01002 API
https://admin-shell-io.github.io/aas-specs-antora/index/home/index.html
Creative Commons Attribution 4.0 International
12 stars 5 forks source link

[Feat. Req.] AASQL - An interoperable query language for AAS #177

Open mhrimaz opened 1 year ago

mhrimaz commented 1 year ago

What is missing?

Capturing information and making it interoperable is not really the end goal. In the end, the goal is to harness the captured knowledge for better decision-making. Currently, any kind of knowledge extraction and query for information should be done via custom 'hard-coded' logic, even for simple query and filter tasks. This has been mentioned many times: #169, #158, #144, #7.

How should it be fixed?

So clearly the community needs an interoperable way to query various repository, join them, filter and extract information. Similar to DTDL query language, the AAS community must agree on some form of abstract syntax. Then there should also be a tool that can translate the syntax to SQL, SPARQL, MongoDB, ... query.

This can serve as an example:

Select Submodel2.average_temperature, Submodel1.property1, AAS.ID
FROM "URL_TO_AAS_REPOSITORY"
WHERE AAS.SeamnticID MATCH "https://semantic-id-matching.com"
WITH SubmodelRepository ON "URL_TO_SUBMODEL_REPOSITORY"
BIND Submodel1
{
    Submodel.anotherjsonpath AS property1 
    WHERE
    Submodel.jsonpath.value == 12     #filter on element value
    Submodel.SemanticID MATCH "https://another-semantic-id.com" #filter on semantic id
}
BIND Submodel2
{

    AVG(Submodel.jsonpath.value) as average_temperature   # aggregation operator support
    WHERE
    Submodel.SemanticID MATCH "https://yet-another-semantic-id.com"

}
mjacoby commented 1 year ago

I definitely agree that some form of query language for AAS is required.

However I have the feeling that we should not burden ourselves with reinventing the wheel as there are lots of helpful query languages out there. My opinion is that Instead of creating yet query language, which btw is a very complex thing to do and implementing it will be even harder, we should investigate how we could leverage existing solutions. One possible approach would be aligning the REST API with OData which besides supporting complex queries would provide additional benefits.

Regarding your proposal I have the following questions. Where will queries be executed if they are accessing multiple different repositories? What are the benefits of mapping the query language to e.g. SQL or MongoDB if it is totally up to each implementation if and how they are using these databases, meaning implementation A could use SQL and use database schema A while implementation B also uses a relational DB but with schema B, where schema A and B are not the same. How could they benefit from a generic mapping of the AAS query language to SQL?

While it would potentially be possible to create such a mapping, this would require to also create a database schema that goes along with it. I have no doubt that implementers would happily use such a thing if it existed but defining something like this can/should never be part of a specification as it goes beyond the implementation-agnostic part.

mhrimaz commented 1 year ago

The easiest solution is to stick with the RDF representation. Then we can use SPARQL as a query language. We can also have federated queries. The RDF representation is already part of the specification. So all we need is a proper tool to do rdf-json-xml serialization.

The example below is a simple AAS with one Submodel and three properties. You can store it in any triple store and query it with SPARQL.


@prefix aas: <https://admin-shell.io/aas/3/0/> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix xs: <http://www.w3.org/2001/XMLSchema#> .

#$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
#$$$$$$$$$$$$    SHELL     $$$$$$$$$$$$$$$$$$$
#$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$

# This is a simple RDF-based digital twin of Transporter Robot
# We have only one submodel which consists 3 information: Location (site), LoadWeight (in kg), Temperature (in Celsius)

<https://dfki.de/dt/aas/shell-473220a3-8deb-4346-b7df-751bc55d57f3> rdf:type aas:AssetAdministrationShell ;
    <https://admin-shell.io/aas/3/0/Identifiable/id> "https://dfki.de/dt/aas/shell-473220a3-8deb-4346-b7df-751bc55d57f3"^^xs:string ;
    <https://admin-shell.io/aas/3/0/AssetAdministrationShell/assetInformation> [
        rdf:type aas:AssetInformation ;
        <https://admin-shell.io/aas/3/0/AssetInformation/assetKind> <https://admin-shell.io/aas/3/0/AssetKind/Instance> ;
        <https://admin-shell.io/aas/3/0/AssetInformation/globalAssetId> "https://dfki.de/dt/assets/asset-998cc9f2-2df1-44fe-9375-6166fe77d06f"^^xs:string ;
        <https://admin-shell.io/aas/3/0/AssetInformation/assetType> "https://dfki.de/catalouge/dfki_transporter_robot"^^xs:string ;
    ] ;
    <https://admin-shell.io/aas/3/0/AssetAdministrationShell/submodels> [
        rdf:type aas:Reference ;
        <https://admin-shell.io/aas/3/0/Reference/type> <https://admin-shell.io/aas/3/0/ReferenceTypes/ModelReference> ;
        <https://admin-shell.io/aas/3/0/Reference/keys> [
            rdf:type aas:Key ;
            <https://admin-shell.io/aas/3/0/Key/type> <https://admin-shell.io/aas/3/0/KeyTypes/Submodel> ;
            <https://admin-shell.io/aas/3/0/Key/value> "https://dfki.de/dt/aas/shell-473220a3-8deb-4346-b7df-751bc55d57f3/OperationalInformation"^^xs:string ;
        ] ;
    ] ;
.

#$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
#$$$$$$$$$$$$   SUBMODEL   $$$$$$$$$$$$$$$$$$$
#$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$

<https://dfki.de/dt/aas/shell-473220a3-8deb-4346-b7df-751bc55d57f3/OperationalInformation> rdf:type aas:Submodel ;
  <https://admin-shell.io/aas/3/0/Identifiable/id> "https://dfki.de/dt/aas/shell-473220a3-8deb-4346-b7df-751bc55d57f3/OperationalInformation"^^xs:string ;
  <https://admin-shell.io/aas/3/0/HasKind/kind> <https://admin-shell.io/aas/3/0/ModellingKind/Instance> ;
    <https://admin-shell.io/aas/3/0/HasSemantics/semanticId> [
        rdf:type aas:Reference ;
        <https://admin-shell.io/aas/3/0/Reference/type> <https://admin-shell.io/aas/3/0/ReferenceTypes/ExternalReference> ;
        <https://admin-shell.io/aas/3/0/Reference/keys> [
            rdf:type aas:Key ;
            <https://admin-shell.io/aas/3/0/Key/type> <https://admin-shell.io/aas/3/0/KeyTypes/GlobalReference> ;
            <https://admin-shell.io/aas/3/0/Key/value> "https://dfki.de/sm/OperationalInformation"^^xs:string ;
        ] ;
    ] ;
    <https://admin-shell.io/aas/3/0/Submodel/submodelElements> <https://dfki.de/dt/aas/shell-473220a3-8deb-4346-b7df-751bc55d57f3/OperationalInformation/elements/Location> ;
    <https://admin-shell.io/aas/3/0/Submodel/submodelElements> <https://dfki.de/dt/aas/shell-473220a3-8deb-4346-b7df-751bc55d57f3/OperationalInformation/elements/Temperature> ;
    <https://admin-shell.io/aas/3/0/Submodel/submodelElements> <https://dfki.de/dt/aas/shell-473220a3-8deb-4346-b7df-751bc55d57f3/OperationalInformation/elements/LoadWeight>   .

#$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
#$$$$$$$$$$$$  Properties  $$$$$$$$$$$$$$$$$$$
#$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$  

<https://dfki.de/dt/aas/shell-473220a3-8deb-4346-b7df-751bc55d57f3/OperationalInformation/elements/Location> rdf:type aas:Property ;
    <https://admin-shell.io/aas/3/0/Referable/idShort> "https://dfki.de/dt/aas/shell-473220a3-8deb-4346-b7df-751bc55d57f3/OperationalInformation/elements/Location"^^xs:string ;
    <https://admin-shell.io/aas/3/0/Property/valueType> <https://admin-shell.io/aas/3/0/DataTypeDefXsd/String> ;
    <https://admin-shell.io/aas/3/0/Property/value> "DFKI Kaiserslautern"^^xs:string ;
    <https://admin-shell.io/aas/3/0/HasSemantics/semanticId> [
        rdf:type aas:Reference ;
        <https://admin-shell.io/aas/3/0/Reference/type> <https://admin-shell.io/aas/3/0/ReferenceTypes/ExternalReference> ;
        <https://admin-shell.io/aas/3/0/Reference/keys> [
            rdf:type aas:Key ;
            <https://admin-shell.io/aas/3/0/Key/type> <https://admin-shell.io/aas/3/0/KeyTypes/GlobalReference> ;
            <https://admin-shell.io/aas/3/0/Key/value> "https://dfki.de/dt/concepts/Location"^^xs:string ;
        ] ;
    ] ;
.

<https://dfki.de/dt/aas/shell-473220a3-8deb-4346-b7df-751bc55d57f3/OperationalInformation/elements/Temperature> rdf:type aas:Property ;
    <https://admin-shell.io/aas/3/0/Referable/idShort> "https://dfki.de/dt/aas/shell-473220a3-8deb-4346-b7df-751bc55d57f3/OperationalInformation/elements/Temperature"^^xs:string ;
    <https://admin-shell.io/aas/3/0/Property/valueType> <https://admin-shell.io/aas/3/0/DataTypeDefXsd/Double> ;
    <https://admin-shell.io/aas/3/0/Property/value> "81.26346016518073"^^xs:string;
    <https://admin-shell.io/aas/3/0/HasSemantics/semanticId> [
        rdf:type aas:Reference ;
        <https://admin-shell.io/aas/3/0/Reference/type> <https://admin-shell.io/aas/3/0/ReferenceTypes/ModelReference> ;
        <https://admin-shell.io/aas/3/0/Reference/keys> [
            rdf:type aas:Key ;
            <https://admin-shell.io/aas/3/0/Key/type> <https://admin-shell.io/aas/3/0/KeyTypes/ConceptDescription> ;
            <https://admin-shell.io/aas/3/0/Key/value> "https://dfki.de/dt/concepts/CelsiusTemperature"^^xs:string ;
        ] ;
    ] ;
.

<https://dfki.de/dt/aas/shell-473220a3-8deb-4346-b7df-751bc55d57f3/OperationalInformation/elements/LoadWeight> rdf:type aas:Property ;
    <https://admin-shell.io/aas/3/0/Referable/idShort> "https://dfki.de/dt/aas/shell-473220a3-8deb-4346-b7df-751bc55d57f3/OperationalInformation/elements/LoadWeight"^^xs:string ;
    <https://admin-shell.io/aas/3/0/Property/valueType> <https://admin-shell.io/aas/3/0/DataTypeDefXsd/Double> ;
    <https://admin-shell.io/aas/3/0/Property/value> "0.8034224287558729"^^xs:string;
    <https://admin-shell.io/aas/3/0/HasSemantics/semanticId> [
        rdf:type aas:Reference ;
        <https://admin-shell.io/aas/3/0/Reference/type> <https://admin-shell.io/aas/3/0/ReferenceTypes/ModelReference> ;
        <https://admin-shell.io/aas/3/0/Reference/keys> [
            rdf:type aas:Key ;
            <https://admin-shell.io/aas/3/0/Key/type> <https://admin-shell.io/aas/3/0/KeyTypes/ConceptDescription> ;
            <https://admin-shell.io/aas/3/0/Key/value> "https://dfki.de/dt/concepts/KilogramWeight"^^xs:string ;
        ] ;
    ] ;
.

However, this would impose a little technological restriction. So if someone wants to store it somewhere else, like MongoDB, then SPARQL is not going to work. So we need some sort of abstract syntax at the end.

Where will queries be executed if they are accessing multiple different repositories? I really didn't think about the federation, and assumed everything existed in one repository. But that's also an important feature.

What are the benefits of mapping the query language to e.g. SQL or MongoDB

As you mentioned, the schema of a database can be different, so the mapping should not be specific to a single schema or specific technology. Therefore, if anyone has another database or schema, there should be a way to change the code generator. This is probably not easy to implement.

kenwenzel commented 11 months ago

I am working on a SPARQL federated service that directly allows to query the AAS HTTP APIs. If this is finally fully functional then SPARQL queries can be executed independently of the underlying storage mechanism. The logic is currently integrated in LinkedFactory-POD https://github.com/linkedfactory/linkedfactory-pod/tree/aas but a standalone RDF4J-based solution may be provided in the future.

Queries may look like the following:

select * where {
  service <aas:https://v3.admin-shell-io.com> {
    { select ?shell { <aas:endpoint> <aas:shells> ?shell } limit 1 }

    ?shell <r:submodels> ?list . 
    ?list !<:> ?sm . 

    ?sm <r:submodelElements> ?element .
    ?element ?p ?o 
  }
}

The vocabulary prefixed with r: is just ad hoc generated for the JSON serialization. As a next step the real AAS vocabulary should be used. How the vocabulary could be simplified (e.g. by using global property IRIs) is one of the topics of the IDTA Ontologies workstream. Another challenge is the generation of IRIs for AAS elements. For the PoC I took a pragmatic approach that uses IRIs like urn:aas:Submodel:a-submodel-id to align with the AAS model for ModelReferences and keys. Based in these IRIs further data can be fetched automatically. This is used for data about ?sm in the example above.

mhrimaz commented 11 months ago

@kenwenzel That's pretty cool. For URIs for AAS elements I will use the REST API path. In this way, there is a straightforward mapping between elements in RDF Triplestore and REST API. The idea of adhoc terms to shorten and simplify the queries is really cool. However, if it is not part of the standard, and there aren't any appropriate tooling for it, then federation won't work smoothly. For hiding complexity, I tried to do it with a UI and a Query Generator. That also requires a lot of tooling, but the generated query doesn't have anything extra. An example tool can be https://sparnatural.eu/. Wasn't all datatypes as xsd:string any limitation for queries in your opinion?

kenwenzel commented 11 months ago

Hi @mhrimaz , I also think that a query builder would help a lot. Maybe something like Wikidata provides is also enough as a starting point: https://query.wikidata.org/querybuilder/

For supporting simple queries via existing HTTP APIs a good approach is OSLC query (query for LDP): https://docs.oasis-open-projects.org/oslc-op/query/v3.0/oslc-query.html

Unfortunately, neither LDP nor OSLC where considered when designing AAS (APIs). Especially OSLC was designed for similar use cases and LDP is also the base for SOLID.

Regarding identifiers: I would not include the host URLs in identifiers. This complicates use cases where the same shells or submodels are stored in different places. But I have to see some examples and think more about this.

BirgitBoss commented 10 months ago

see also #7 and #204

kenwenzel commented 10 months ago

SPARQL queries like the following

prefix aas: <https://admin-shell.io/aas/3/0/>
select ?sm ?element ?p ?o {
  service <aas-api:https://v3.admin-shell-io.com/> {
    { select ?shell { <aas-api:endpoint> <aas-api:shells> ?shell } limit 2 }

    ?shell aas:submodels ?sm . ?sm aas:semanticId ?semId . ?semId aas:keys [ !<:> ?key ] .
    ?key aas:value "https://admin-shell.io/zvei/nameplate/1/0/Nameplate" .
    ?sm (!<:>)+ ?element .

    { ?element a aas:Property } union { ?element a aas:MultiLanguageProperty }
    ?element ?p ?o .
  }
}

are basically implemented in the aas branch of LinkedFactory POD: https://github.com/linkedfactory/linkedfactory-pod/tree/aas

BirgitBoss commented 6 months ago

See also request for RQL in #204

However, please lets not mix discovery and querying on data