Endpoint : POST - /avatars/find
Payload :
{
"query" : { }
}
or
Endpoint : GET - /avatars/find
The query should be put in a query param q
. It will be a json string as defined per the POST payload.
Content-Type:
-text/turtle
: to receive the data serialized into Turtle
-application/json
: to receive the data serialized in JSON
-application/ziggy-json
: to receive the data serialized in JSON, but first the vertices and in a separate section the edges
The query DSL (Domain Specific Language) is based on a JSON payload respecting the following template (as described above) :
{
"query" : { },
"edges" : true,
"view" : { },
"vars" : { }
}
About the template:
-query
: The actual query to use to filter the avatars, we will define this in the next section
-edges
: true/false if you want to get the edges in the response for the application/ziggy-json result format
-view
: Precise the data you want to be returned (see View section)
-vars
: Define variables used in query to shorten it (see Vars section)
If you are not familiar with the semantic world, we highly suggest you to read the following sections.
Avatars can be filtered as per the following:
Note that you can combine pretty much all of them.
Moreover, inside an operator filter, you can set expressions to enable arithmetical, logical (...) functions that allow you to specify a full range of constraints.
As seen in previous sections, avatars in Thing'in are defined using classes from ontologies. We defined an operator for that purpose : $classes
.
All you need is the IRI of the class(es) you want to use as a filter.
The classes of the avatars can be seen as a set (implemented by an array), and then array operator can be used.
ThingIn supports class Inheritence. In terms of performance, class Inheritence is based on materialization of inherited classes, at the time of creation of an avatar: it is thus very efficient at query time.
Object Property Inheritance triggers when an ontology declares a http://www.w3.org/2000/01/rdf-schema#subPropertyOf axiom
Class Inheritance triggers when an ontology declares a http://www.w3.org/2000/01/rdf-schema#subClassOf axiom
Class inheritance is activated using the $inheritence
operator.
It can be combined with different query operators, with other match operators (see examples below e.g. with $eq
operator).
Starting in ThingIn version 3.3.0, ThingIn supports class Equivalence. In terms of performance, class Equivalence is based on materialization of equivalent classes, at the time of creation of an avatar: it is thus very efficient at query time.
Object Property Equivalence triggers when an ontology declares a http://www.w3.org/2002/07/owl#equivalentProperty or http://www.w3.org/2002/07/owl#sameAs axiom
Class Equivalence triggers when an ontology declares a http://www.w3.org/2002/07/owl#sameAs or http://www.w3.org/2002/07/owl#equivalentClass axiom
Class equivalence is activated using the $equivalence
operator.
It can be combined with different query operators (same set as $inheritence
).
$eq
Ask for avatars having classes that are strictly equal to a provided set of classes. As a set, the order of the IRIs in the array has no impact.
{
"$classes" : {
"$eq" : [
"https://w3id.org/saref#Sensor",
"https://w3id.org/saref#WashingMachine"
]
}
}
The
$eq
operator supports classes ihneritance or equivalance. Ask for avatars having inherited classes strictly equals to a provided set of classes.
{
"$classes" : {
"$eq" : [
"https://w3id.org/saref#Sensor",
"https://w3id.org/saref#WashingMachine"
],
"$inheritance" : true,
"$equivalence" : true
}
}
This operator uses internal indexation and then gives better performance compare with operators that do not use indexation.
$ne
Ask for avatars having their set of classes not equal to a provided set of classes. As a set, the order of the IRIs in the array has no impact.
The
$ne
operator supports classes ihneritance.
This operator uses internal indexation and then gives better performance compare with operators that do not use indexation.
{
"$classes" : {
"$ne" : [
"https://w3id.org/saref#Sensor",
"https://w3id.org/saref#WashingMachine"
]
}
}
$contains_all
Ask for avatars instances of a set of class that include a provided list of classes
The
$contains_all
operator supports classes ihneritance.
This operator uses internal indexation and then gives better performance compare with operators that do not use indexation.
{
"$classes" : {
"$contains_all" : [
"https://w3id.org/saref#Sensor",
"https://w3id.org/saref#WashingMachine"
]
}
}
$any_in
Ask for avatars that have at least one of its classes within a set of classes.
The
$any_in
operator supports classes ihneritance.
This operator uses internal indexation and then gives better performance compare with operators that do not use indexation.
{
"$classes" : {
"$any_in" : [
"https://w3id.org/saref#Sensor",
"https://w3id.org/saref#WashingMachine"
]
}
}
$none_in
Ask for avatars that have none of its classes within a set of classes.
The
$none_in
operator supports classes ihneritance.
This operator uses internal indexation and then gives better performance compare with operators that do not use indexation.
{
"$classes" : {
"$none_in" : [
"https://w3id.org/saref#Sensor",
"https://w3id.org/saref#WashingMachine"
]
}
}
$all_in
Ask for avatars that have all of its classes within a set of classes.
The
$all_in
operator supports classes ihneritance.
This operator does not use internal indexation.
{
"$classes" : {
"$all_in" : [
"https://w3id.org/saref#Sensor",
"https://w3id.org/saref#WashingMachine"
]
}
}
$contains
Ask for avatars that have a given class in its classes.
The
$contains
operator supports classes ihneritance.
This operator uses internal indexation and then gives better performance compare with operators that do not use indexation.
{
"$classes" : {
"$contains" : "https://w3id.org/saref#Sensor"
}
}
To simplify the query, we provide implicit operator
{
"$classes" : { "$eq" : ["https://w3id.org/saref#Sensor"]}
}
can be shortened as :
{
"$classes" : ["https://w3id.org/saref#Sensor"]
}
{
"$classes" : { "$contains" : "https://w3id.org/saref#Sensor"}
}
can be shortened as :
{
"$classes" : "https://w3id.org/saref#Sensor"
}
Each avatar has their specifically assigned IRI, to perform filtering on the IRI we created the $iri
.
With the $iri
operator, you have the same kind of expressivity than with the $class
operator.
Ask for avatars with the given IRI
{
"$iri" : "http://orange-labs.fr/fog/ont/transport.owl#AnIndividual"
}
Ask for avatars within the given set of IRI
{
"$iri" : {
"$in" : [ "http://orange-labs.fr/fog/ont/transport.owl#AnIndividual", "http://orange-labs.fr/fog/ont/transport.owl#AnOtherIndividual" ]
}
}
Ask for avatars which are not within the given set of IRI
{
"$iri" : {
"$nin" : [ "http://orange-labs.fr/fog/ont/transport.owl#AnIndividual", "http://orange-labs.fr/fog/ont/transport.owl#AnOtherIndividual" ]
}
}
The operator $strContains
can also be used on IRI
The following fulltext search operators are also compatible to filter IRIs as well as domain:
$startswith
(see FullText Operators)$phrase
(see FullText Operators)Each avatar owns a lastUpdated property, given the date of the last update in the server. This property is set by Thing in and not by the client.
$lastUpdated
keyword enabling the filtering on the field lastUpdated
of the avatars (possible operators : $eq
, $gt
, $gte
, $lt
,$lte
).
You can use this property to retrieve recent updated avatars since a date (given in epoch format, e.g. : 1623772979243
):
{
"$lastUpdated" : {
"$gt" : 1623772979243
}
}
Datatype property as we explained before are the attributes of our avatars so, basically, i you need to perform a find on avatar where the datatype property, say "weight", is greater or equal to 10, the expected query is the following :
{
"weight" : { "$gte" : 13.5 }
}
Just like in MongoDB where you set the field which you want to filter, you may set the datatype property as a key of the query payload and then assign the requested filter.
{
"http://orange-labs.fr/fog/ont/transport.owl#weight" : { "$gte" : 13.5 }
}
$gt
$gte
$lt
$lte
$eq
$ne
$in
$nin
$exists
$strContains
$match
(use a regex)$geoWithin
(see Geospatial Operators)$geoContains
(see Geospatial Operators)$distance
(see Geospatial Operators)$nearSphere
(see Geospatial Operators)$startswith
(see FullText Operators)$phrase
(see FullText Operators)When you give a query with this form :
{
"http://orange-labs.fr/fog/ont/transport.owl#weight" : 13.5
}
Thing'in will implicitely interpret this as a an $eq clause :
{
"http://orange-labs.fr/fog/ont/transport.owl#weight" : { "$eq" : 13.5 }
}
If you want to precise an operator different from the equality, you may do like in the example above :
{
"http://orange-labs.fr/fog/ont/transport.owl#weight" : { "$lt" : 13.5 }
}
{
"http://orange-labs.fr/fog/ont/transport.owl#weight" : { "$lte" : 13.5 }
}
{
"http://orange-labs.fr/fog/ont/transport.owl#weight" : { "$gt" : 13.5 }
}
{
"http://orange-labs.fr/fog/ont/transport.owl#weight" : { "$gte" : 13.5 }
}
{
"http://orange-labs.fr/fog/ont/transport.owl#weight" : { "$eq" : 13.5 }
}
{
"http://orange-labs.fr/fog/ont/transport.owl#weight" : { "$ne" : 13.5 }
}
{
"http://orange-labs.fr/fog/ont/transport.owl#weight" : { "$in" : [ 13.5, 23.7] }
}
{
"http://orange-labs.fr/fog/ont/transport.owl#weight" : { "$nin" : [ 13.5, 23.7] }
}
Property belongs to avatar:
{
"http://orange-labs.fr/fog/ont/transport.owl#weight" : { "$exists" : true }
}
Property does not belong to avatar:
{
"http://orange-labs.fr/fog/ont/transport.owl#weight" : { "$exists" : false }
}
JSON uses double for decimal number representation, since datatype properties for float and double are both stored as double into OrientDB (easier to handle data that way), there is no lack of precision using JSON.
If you follow strictly the datatypes from JSON to do your research you should not encounter any trouble.
About the query output datatype, nonetheless it will decide if the found avatars will be serialized into turtle or JSON, it also trigger or not datatype checking.
When running the avatar find query with text/turle as output, before running the query against the database each datatype properties, on which filter are performed, will be checked to verify if the given value datatype is sane.
An example :
Assuming that the range of the datatype property is http://random/namespace/ontology.owl#weight xsd:float, the following query :
{
"http://orange-labs.fr/fog/ont/transport.owl#weight" : { "$nin" : [ "AZERTY", true] }
}
Will return to you an error because you are performing a search against a datatype property using the wrong datatype.
It adds a little overhead to execute the query but at least you will be sure you are correctly manipulating your data, OrientDB uses some implicit conversion and may accept to do equality between string, integer, boolean (i do not have the exact list, but there are some possibilities) but in the
end, most of the time it is irrelevant. You may retrieve data which should have never been returned and even worse, data that should have been returned will be kept silent.
When storing such values, simply use the dateTime string as usual. The string must be a valid ISO 8601 dateTime string:
[
{
"_iri": "http://www.example.com/Dates/test1",
"_domain": "http://www.example.com/Dates/",
"_classes": [
"http://purl.oclc.org/NET/ssnx/ssn#Device"
],
"_visibility": 255,
"https://www.bim2twin.eu/ontology/kpi/intervalStartDate": "2022-10-12"
},
{
"_iri": "http://www.example.com/Dates/test2",
"_domain": "http://www.example.com/Dates/",
"_classes": [
"http://purl.oclc.org/NET/ssnx/ssn#Device"
],
"_visibility": 255,
"https://www.bim2twin.eu/ontology/kpi/intervalStartDate": "2022-10-12T10:25:00"
}
]
When querying with dates and datetimes datatypes, e.g. with $gt
, $lt
, $gte
or $lte
operators, implicit mechanisms allow to conveniently query timestamps against dates transparently, and vice-versa.
In the examples below, the https://www.bim2twin.eu/ontology/kpi/intervalStartDate dataproperty expects an xsd:datetime value. Both queries are valid:
{
"query": [
{
"$alias":"someAvatar",
"$domain": "http://www.example.com/Dates/",
"https://www.bim2twin.eu/ontology/kpi/intervalStartDate": {
"$gt": 1665588647109
}
}
],
"return": "someAvatar"
}
{
"query": [
{
"$alias":"someAvatar",
"$domain": "http://www.example.com/Dates/",
"https://www.bim2twin.eu/ontology/kpi/intervalStartDate": {
"$gt": "2022-10-12"
}
}
],
"return": "someAvatar"
}
Alternatively, if a date is stored with a timestamp (using an xsd:long datatype property for instance), it is queryable with a dateTime string as well.
Internally any date/timestamp provided in a query will be converted an ISO 8601 date time string using UTC / Zulu time.
Complementing the previous statements about date, a special operator $currentTime can be used for convenience, replacing a date value in a query:
{
"query": [
{
"$alias": "someAvatar",
"$domain": "http://www.example.com/Dates/",
"https://www.bim2twin.eu/ontology/kpi/intervalStartDate": {
"$gte": "$currentTime"
}
}
],
"return": "someAvatar"
}
The following operators refer to fulltext-search functionnalities on datatype properties:
These operators can be costly and should be preceded by pruning if possible (for instance they can be used within a domain scope).
$startswith
$phrase
The $startswith
operator allows to filter data properties on prefix:
{
"query": {
"$domain": "http://www.example.com/testfulltext",
"$classes": {
"$any_in": [
"http://www.disit.org/km4city/schema#Street_light"
]
},
"http://thingin.orange.com/ontology/smartCity#hasSerialNumber" : {
"$startswith": "006"
}
},
"view": {}
}
The $phrase
operator allows to filter data properties on keywords search:
{
"query": {
"$domain": "http://www.example.com/testfulltext",
"$classes": {
"$any_in": [
"http://www.disit.org/km4city/schema#Street_light"
]
},
"http://thingin.orange.com/ontology/smartCity#hasSerialNumber" : {
"$phrase": "some keyword search"
}
},
"view": {}
}
The $phrase
operator uses a basic english text analyzer to parse literals and look for the keywords provided. More options will be provided for keyword search in the future such as scoring options, ngram search... (work-in-progress)
The available GeoSpatial operators are the following:
$geoWithin
$geoContains
$nearSphere
GeoSpatial operators are special, they need to be backed up by a GeoJson Spatial Aspect. Aspect are behaviors that can be configured above the ontologies concept, their main goal is to optimize the data to enforce their potential. GeoJson Spatial Aspect do exactly that, they build additionnal geo index over the targeted datatype property and enable the use of specific geographic function in the queries. You might want more information about the aspect here : [[Projections - Aspects]]
GeoSpatial Operators works using the GeoJson standard : http://geojson.org/.
We do not make use of feature, we only want the "geometry" part, using the GeoJson standard, if you want to declare a geometry point you will declare something like this :
{
"type":"Feature",
"geometry":{
"type":"Point",
"coordinates":[125.6,10.1]
}
}
We will ask a bit less of data, give us this :
{
"type" : "Point",
"coordinates" : [LONGITUDE*, LATITUDE*]
}
Coordinates order: note the order of longitude, latitude in the coordinates array. This is important because if you take coordinates from google their order is [ LATITUDE, LONGITUDE ]. The engine of the Thing'in database is designed to work with LONGITUDE, LATITUDE and the GeoJSON standard also works that day.
Starting in ThingIn version 3.10, we also comply with the Right-Hand rule of the GeoJson standard: polygons in data and queries should now be expressed by following the Right-hand Rule. A solution/tool is available here to convert legacy polygons that don't respect the right-hand rule. You may also look into this online tool to help you validate your polygons.
Query examples:
Let's say you want all avatars with GeoJson Point located into the given polygon :
{
"type" : "Polygon",
"coordinates" : [[[120.0,8.0], [120.0,12.0], [130.0,12.0], [130.0,8.0]]]
}
Using JSON for the output you will have the following query :
{
"http://www.opengis.net/gml/pos" : {
"$geoWithin" : {
"$geometry" : {
"type" : "Polygon",
"coordinates" : [[[120.0,8.0], [120.0,12.0], [130.0,12.0], [130.0,8.0]]]
}
}
}
}
And for Turtle :
{
"http://www.opengis.net/gml/pos" : {
"$geoWithin" : {
"$geometry" : {
"type" : "Polygon",
"coordinates" : [[[120.0,8.0], [120.0,12.0], [130.0,12.0], [130.0,8.0]]]
}
}
}
}
Since those are filtering operators, you may combine them with other filtering operators:
{
"$and" : [
{
"$classes" : {
"$any_in" : [ "http://random/namespace/ontology.owl#sensor", "http://random/namespace/ontology.owl#device" ],
"$inheritance" : true
}
},
{
"http://orange-labs.fr/fog/ont/transport.owl#weight" : { "$gte" : 13.5 }
},
{
"http://www.opengis.net/gml/pos" : {
"$geoWithin" : {
"$geometry" : {
"type" : "Polygon",
"coordinates" : [[[120.0,8.0], [120.0,12.0], [130.0,12.0], [130.0,8.0]]]
}
}
}
}
]
}
Note the number of nested arrays to describe the polygon, this is not a mistake, (if it does not work that way go back to only 2 level [ [ long1, lat1 ], [long2, lat2] ] and open an issue on the forge please.) The third array is needed to describe "rings", if we take for example
the Pentagon (the building in the USA), this is a large building with a hole in its center. Having multiple "rings" allows you to give a list for the "outer" ring to describe the exterior of the building, and an "inner" ring to describe the interior of the building.
The $geoContains
operator is the opposite of the $geoWithin
operator. It allows to find polygons englobing a point:
{
"http://www.opengis.net/gml/pos/polygon": {
"$geoContains": {
"$geometry": {
"type": "Point",
"coordinates": [5.786278,45.211672]
}
}
}
}
It is also possible instead of using a geometry object inside the query to use an alias previously set. In that case, all geometries attached to the avatar with the alias (in the query below "Tree"), will be used to try to match the condition. The above statement is also true for the $geoWithin
operator.
{
"query": [
{
"$alias":"Tree",
"$iri":"http://www.thingin.com/rennes/polygon/tree_17683"
},
{
"$domain": "http://www.thingin.com/rennes/polygon/",
"http://www.opengis.net/gml/pos/polygon": {
"$geoContains": {
"$alias":"Tree"
}
}
}
],
"view": {}
}
The $distance
operator allows to filter avatars based on the distance, in meters, between two of avatars. The query below finds a first avatar with an $iri
filter. Then finds all avatars for which the distance from the first is below or equals 100 meters.
{
"query": [
{
"$alias":"Tree",
"$iri":"http://www.thingin.com/rennes/polygon/tree_17683"
},
{
"$domain": "http://www.thingin.com/rennes/polygon/",
"http://www.opengis.net/gml/pos": {
"$distance": {
"$alias":"Tree",
"$lte":100
}
}
}
],
"view": {}
}
At last the $nearSphere
operator:
{
"http://www.opengis.net/gml/pos" : {
"$nearSphere" : {
"$geometry" : {
"type" : "Point",
"coordinates" : [125.6,10.1]
},
"$maxDistance" : 5000
}
}
}
The $nearSphere
operator expect a Point into the geometry and will only works on datatype properties storing GeoJson point. You need to set the maximum distance allowed for the research with the $maxDistance
specific inner operator, the value are meters.
Note that there may be a lack of precision using $nearSphere
because it works with a spheric representation of the earth instead of a geoid. It goes much faster but it doesn't take into account the gradual deformation of the earth in high latitude and it leads to potentially location error of several kilometers.
Thing'in core model includes a "hasBlobs" label to count the number of blobs attached to an avatar. This label is also available in queries to help find avatars with a set number of blobs :
{
"query": {
"$alias":"avatarWithBlobs",
"$domain":"http://www.example.com/searchBlobsExample/",
"hasBlobs": 1
}
}
The above query returns all avatars of the domain "http://www.example.com/searchBlobsExample/" with exactly 1 attached blob.
Since this works as a regular label, comparison operators are also available:
{
"query": {
"$alias":"avatarWithBlobs",
"$domain":"http://www.example.com/searchBlobsExample/",
"hasBlobs": {
"$gte": 1
}
}
}
The above query returns all avatars of the domain "http://www.example.com/searchBlobsExample/" with at least 1 attached blob.
A user may not always want all the properties that are attached to an avatar. The view
clause can be added to the POST avatars/find query body to filter out the returned properties as such:
{
"query":[
{
"$domain":"http://www.example.com/"
}
],
"view" : {
"name" : true,
"age" : true
}
}
This field contains property names, only these properties will be present in the response. In the previous example, only the properties name
and age
will be present in the object attributes response. Here the value beside each property is a placeholder, but it may be used for renaming, see section Property renaming
below.
A simpler syntax was introduced in ThingIn v3.10.0, you may use an array of properties:
{
"query":[
{
"$domain":"http://www.example.com/"
}
],
"view":[
"http://orange-labs.fr/fog/ont/object-location.owl#locationPayload",
"creationDate",
"http://www.opengis.net/gml/pos"
]
}
Or a string if only one property is filtered:
{
"query":[
{
"$domain":"http://www.example.com/"
}
],
"view" : "http://www.opengis.net/gml/pos"
}
Note that the following protected metadata fields cannot be removed with subtractive mode and are always set with additive mode : "_labels","_classes","_uuid","_domain","_iri"
.
Alternatively, the user may want to exclude specific properties and keep all others.
The following syntax can be used using the -
prefix:
{
"query":[
{
"$domain":"http://www.example.com/"
}
],
"view" : "-http://www.opengis.net/gml/pos"
}
In the above example, all properties of the returned avatars will be returned, except http://www.opengis.net/gml/pos
.
The array and map/dict syntax is also valid to filter out properties.
To ease the work of the developper in terms of mapping name between the property name inside the database and the property name that he wants, he may use the following rewrite mechanism with the map syntax:
"view" : {
"name" : "nom",
"age" : true
}
With the previous example, the data properties name
will be renamed as nom
in the object's property response. The age property will be present and unmodified.
To shorten the query, local variables can be used. They will be evaluated and replaced before executing the query. Variable could be embedded inside every string of the json payload.
Considering the next query:
{
"query" : [ {
"$classes" : {
"$contains" : "http://elite.polito.it/ontologies/dogont.owl#Room",
"$inheritance" : true
},
"->http://elite.polito.it/ontologies/dogont.owl#isIn" : { "$alias" : "building" }
},
{
"$alias" : "building",
"$classes" : {
"$contains" : "http://elite.polito.it/ontologies/dogont.owl#Building",
"$inheritance" : true
}
} ],
"edge": true,
"view": {}
}
using variable, it could be written :
{
"vars" : { "dogont" : "http://elite.polito.it/ontologies/dogont.owl#"},
"query" : [ {
"$classes" : {
"$contains" : "%dogont%Room",
"$inheritance" : true
},
"->%dogont%isIn" : { "$alias" : "building" }
},
{
"$alias" : "building",
"$classes" : {
"$contains" : "%dogont%Building",
"$inheritance" : true
}
} ],
"edge": true,
"view": {}
}
Object properties are a bit more complex to handle as they introduce sub queries and graph patterns.
In order to search by object properties, you will have to declare a graph pattern.
A graph pattern is a set of vertices and edges. Each vertex will be an avatar defined in a JsonObject while the object property (which will be the edge) will be defined inside the vertex prefixed by ->
.
Consider the following query :
[ {
"$classes" : {
"contains" : "http://elite.polito.it/ontologies/dogont.owl#Room",
"$inheritance" : true
},
"->http://elite.polito.it/ontologies/dogont.owl#isIn" : { "$alias" : "building" }
},
{
"$alias" : "building",
"$classes" : {
"contains" : "http://elite.polito.it/ontologies/dogont.owl#Building",
"$inheritance" : true
}
} ]
It looks like this in a full find avatar payload :
{
"query" : [ {
"$classes" : {
"$contains" : "http://elite.polito.it/ontologies/dogont.owl#Room",
"$inheritance" : true
},
"->http://elite.polito.it/ontologies/dogont.owl#isIn" : { "$alias" : "building" }
},
{
"$alias" : "building",
"$classes" : {
"$contains" : "http://elite.polito.it/ontologies/dogont.owl#Building",
"$inheritance" : true
}
} ],
"edge": true,
"view": {}
}
First of all, the query here is a JSON Array with two elements, both can be seen as sub queries which you would reason about in a classic relational environment:
#Room
or its children#Building
or its childrenWe make use of dereferencing, the first element is targeting the second one with the http://elite.polito.it/ontologies/dogont.owl#isIn
object property using the building
alias. This is our join
if you refer to the relational world. If you think about it, making joins in SQL is similar to declaring graph patterns.
In human language, this query translates to :
Return all the instances of Room and subclasses of Room which are connected with the object property ''http://elite.polito.it/ontologies/dogont.owl#isIn edge'' to instances of Building and subclasses of Building.
Speaking of graph patterns, it looks like this :
Room -http://elite.polito.it/ontologies/dogont.owl#isIn-> Building
The alias system allows you to declare complex graph pattern such as (A -> B && B -> C && A -> C ), you can also make circular patterns such as (A -> B && B -> C && C -> A). About the alias, those must be composed only of string. There is a simple rule to apply, every vertex targeted by an object property must have its own alias.
But there is a contracted form :
[ {
"$classes" : {
"$contains" : "http://elite.polito.it/ontologies/dogont.owl#Room",
"$inheritance" : true
},
"->http://elite.polito.it/ontologies/dogont.owl#isIn" : "building"
},
{
"$alias" : "building",
"$classes" : {
"$contains" : "http://elite.polito.it/ontologies/dogont.owl#Building",
"$inheritance" : true
}
} ]
If you want something of the form ( A -> B && A -> C ) using the same object property, use a JSON Array :
[ {
"->http://xmlns.com/foaf/0.1/knows" : [ "alice", "bob" ]
},
{
"$alias" : "alice"
},
{
"$alias" : "bob"
} ]
It is possible to reverse the arrow notation to declare object properties received from an avatar, instead of the regular outwards direction:
{
"$alias":"someAvatar",
"$domain": "http://www.example.com/",
"<-http://www.disit.org/km4city/schema#hasRouteLink": {
"$alias": "inwardObjectPropertyAvatar"
}
}
Starting in ThingIn version 2.7.0, the arrow notation has been extended to include filtering on cardinality of a given object property. In the example below, a cardinality filter matches any avatars that has exactly 2 object properties outwards of the type http://www.disit.org/km4city/schema#hasRouteLink
:
{
"$alias":"someAvatar",
"$domain": "http://www.example.com/",
"-2>http://www.disit.org/km4city/schema#hasRouteLink": {
"$alias": "exactly2Avatars"
}
}
It is thus possible to filter avatars based on the absence of a property, using a cardinality filter of 0 :
{
"$alias":"someAvatar",
"$domain": "http://www.example.com/",
"-0>http://www.disit.org/km4city/schema#hasRouteLink": {
"$alias": "nonExistent"
}
}
An alternate syntax for this specific case is to use an the -x>
notation:
{
"$alias":"someAvatar",
"$domain": "http://www.example.com/",
"-x>http://www.disit.org/km4city/schema#hasRouteLink": {
"$alias": "nonExistent"
}
}
Starting in ThingIn version 2.7.0, and extending the cardinality filters, it is possible to omit the type of the object property, and match a number of anonymous properties, inwards or outwards from an avatar:
{
"$alias":"someAvatar",
"$domain": "http://www.example.com/",
"-2>": {
"$alias": "exactly2AvatarsFromAnyObjProperty"
}
}
Starting in ThingIn version 3.0.0, and extending the cardinality filters, it is posssible to make filters on edges or tails of an object property/relationship, but also match "head" nodes where the relationship does not appear. This functionnality effectively mimics the OPTIONAL keyword in SPARQL.
{
"query": [
{
"$alias":"head",
"$domain": "http://www.example.com/optional/",
"-?>http://www.disit.org/km4city/schema#hasRouteLink": {
"$alias": "tail"
}
},
{
"$alias": "tail_e",
"http://knoesis.wright.edu/ssw/ont/sensor-observation.owl#distance":300.0
}
],
"return": "head"
}
In the example above, the first subquery matches nodes in the domain http://www.example.com/optional/, that may or may not have an outbound object property of type http://www.disit.org/km4city/schema#hasRouteLink. This is expressed with the "-?>" arrow syntax instead of a cardinality filter.
The second subquery, unlike the traditional case, only applies to the outbound relations that were previsouly matched, and don't reduce the number of nodes matched in the 1st step 1http://www.disit.org/km4city/schema#hasRouteLink. In other words, any node in the domain that doesn't exhibit this outgoing relationship will be matched.
Starting in ThingIn version 3.3.0, and extending the above filters, it is possible to search for relationships without specifying a direction:
{
"$alias":"someAvatar",
"$domain": "http://www.example.com/",
"<->": {
"$alias": "undirectedRelationshipCheck"
}
}
Cardinality as well os Optional patterns are compatible with this new syntax :
{
"$alias":"someAvatar",
"$domain": "http://www.example.com/",
"<2>": {
"$alias": "exactly2UndirectedRelationshipCheck"
}
}
{
"$alias":"someAvatar",
"$domain": "http://www.example.com/",
"<?>": {
"$alias": "optionalUndirectedRelationshipCheck"
}
}
Starting in ThingIn version 2.7.0, a new syntax has been introduced to filter embedded properties on object properties. These embedded properties can be defined using NGSI-LD properties or RDF-star.
To use this syntax, first declare a simple objet property filter. Then, similarly to filtering the target node of the objet property in a subsequent subquery, you can filter the object property and its embdedded properties using the _e
suffix, appended after the previously declared $alias
. The query below shows a complete example:
{
{
"$alias":"someAvatar",
"$domain": "http://www.example.com/",
"->http://www.disit.org/km4city/schema#hasRouteLink": {
"$alias": "hasRouteLinkTarget"
},
{
"$alias": "hasRouteLinkTarget",
"$classes" : {
"$contains" : "http://elite.polito.it/ontologies/dogont.owl#Building",
"$inheritance" : true
}
},
{
"$alias": "hasRouteLinkTarget_e",
"http://www.minesparistech.logistics#hasDistance":300.0
}
}
In the query above:
http://www.example.com/
domain, having an outbound object property of type http://www.disit.org/km4city/schema#hasRouteLink
.$alias
declared in the object property filter in the first subquery.$alias
hasRouteLinkTarget_e
. This filters the object property, on one of its attached datatype properties: the object property must have a http://www.minesparistech.logistics#hasDistance
with a value of 300.0
.Starting in ThingIn version 3.3.0, object properties support inheritance and equivalence.
Inheritance triggers when an ontology declares a http://www.w3.org/2000/01/rdf-schema#subPropertyOf axiom
Equivalence triggers when an ontology declares a http://www.w3.org/2002/07/owl#sameAs or http://www.w3.org/2002/07/owl#equivalentProperty axiom
{
"query": [
{
"$alias": "someSourceNode",
"$domain": "http://www.example.com/inheritance/",
"->http://zajac.orange.pl/feedchainTest#generalObjectProperty": {
"$inheritance": true,
"$alias": "someTargetNode"
}
}
],
"return": "someTargetNode"
}
In this example, the query matches nodes than have an outgoing relationship of type http://zajac.orange.pl/feedchainTest#generalObjectProperty, or a more specialized, i.e. subsumed/child, property. Such a property is e.g. http://zajac.orange.pl/feedchainTest#particularObjectProperty.
Example response:
{
"size": 1,
"items": [
{
"_uuid": "bc1cc297-c0da-5c01-b32a-63a142725b0a",
"_iri": "http://www.example.com/inheritance/test",
"_domain": "http://www.example.com/inheritance/",
"_classes": [
"http://purl.oclc.org/NET/ssnx/ssn#Device"
],
"_static": false,
"_visibility": 255,
"_source": "a1507627-77d3-43e4-9c18-a85c596a10e8",
"_last_updated": 1664288000153,
"_depth": 0,
"_owner": "admin",
"_expirationDate": 1664288300000,
"_creationDate": 1664288000153,
"_outE": [
{
"_id": "aa09fa6c-b8a1-4791-b695-d980a613ed8c",
"_label": "http://zajac.orange.pl/feedchainTest#particularObjectProperty",
"_targetIRI": "http://www.example.com/inheritance/test2",
"_targetUUID": "ffec99d5-a0be-5a2e-ada5-2ba66c6579de",
"source": "a1507627-77d3-43e4-9c18-a85c596a10e8",
"creationDate": 1664288000440,
"expireDate": 1664288300
}
]
}
],
"page_size": 100,
"index": 0,
"hidden": 0,
"classes": [],
"next": "http://localhost:8080/avatars/find?index=100&page_size=100"
}
{
"query": [
{
"$alias": "someSourceNode",
"$domain": "http://www.example.com/inheritance/",
"->http://purl.org/goodrelations/v1#seeks": {
"$equivalence": true,
"$alias": "someTargetNode"
}
}
],
"return": "someTargetNode"
}
In this example, the query matches nodes than have an outgoing relationship of type http://purl.org/goodrelations/v1#seeks, or an equivalent property. Such a property is e.g. https://w3id.org/seas/seeks from the seas ontology.
Example response:
{
"size": 1,
"items": [
{
"_uuid": "bc1cc297-c0da-5c01-b32a-63a142725b0a",
"_iri": "http://www.example.com/inheritance/test",
"_domain": "http://www.example.com/inheritance/",
"_classes": [
"http://purl.oclc.org/NET/ssnx/ssn#Device"
],
"_static": false,
"_visibility": 255,
"_source": "a1507627-77d3-43e4-9c18-a85c596a10e8",
"_last_updated": 1664288000153,
"_depth": 0,
"_owner": "admin",
"_expirationDate": 1664288300000,
"_creationDate": 1664288000153,
"_outE": [
{
"_id": "b906fafe-2f3c-477f-afff-f1cecbc460b9",
"_label": "https://w3id.org/seas/seeks",
"_targetIRI": "http://www.example.com/inheritance/test2",
"_targetUUID": "ffec99d5-a0be-5a2e-ada5-2ba66c6579de",
"source": "a1507627-77d3-43e4-9c18-a85c596a10e8",
"creationDate": 1664288000440,
"expireDate": 1664288300
}
]
}
],
"page_size": 100,
"index": 0,
"hidden": 0,
"classes": [],
"next": "http://localhost:8080/avatars/find?index=100&page_size=100"
}
Inheritance and equivalence filters can be combined:
{
"query": [
{
"$alias": "someSourceNode",
"$domain": "http://www.example.com/inheritance/",
"->http://purl.org/goodrelations/v1#seeks": {
"$inheritance": true,
"$equivalence": true,
"$alias": "someTargetNode"
}
}
],
"return": "someTargetNode"
}
As you can guess from the examples above, the syntax to set an object property can be expressed as follows :
->my_object_property : "alias_as_a_string"
Cool, but what about depth and recursivity ? Deep traversals making use of infinite recursivity are not possible yet (believe it or not, the graph database does not detect graph cycle and runs forever when performing traversals with infinite depth...), but you may perform traversals with reasonnable levels of depths (reasonnable if you want the query to return something before it times out).
->my_object_property : {
"$alias" : "alias_as_a_string",
"$maxDepth" : 3
}
With such a configuration, you tell Thing'in to repeat up to 3 times the edge "my_object_property" to match the pattern. Now that's great but sometimes you don't want the first iteration, you only want to retrieve the data after a certain depth. The classic example about this is getting the friends of the friend. To do so there is the $minDepth
operator which enables you to precise the minimal depth from which you want to retrieve data :
[ {
"$iri" : "http://orange-labs.fr/fog/ont/iot.owl#A",
"->http://xmlns.com/foaf/0.1/knows" : { "$alias" : "people", "$minDepth" : 2, "$maxDepth" : 3 }
},
{
"$alias" : "people"
} ]
If you run the query on a dataset with the following form :
A -> B
A -> C
B -> D
B -> E
C -> F
C -> G
The query will return D, E, F, G as they are the only avatars with the minimal acceptable depth. Note that you can't set $minDepth without setting $maxDepth. $minDepth is not the solution to everything and depending on the structure of your graph it will not be sufficient, let's add a simple relation to the dataset up ahead:
A -> B
A -> C
B -> D
B -> E
C -> F
C -> G
''B -> A''
The same query will now return D, E, F, G ''and A'' because there is an object property which allows the graph traversal to go back to the origin point. Here the depth operator will not help you, you must use $excludeSelf
:
[ {
"$iri" : "http://orange-labs.fr/fog/ont/iot.owl#A",
"->http://xmlns.com/foaf/0.1/knows" : { "$alias" : "people", "$minDepth" : 2, "$maxDepth" : 3, "$excludeSelf" : true }
},
{
"$alias" : "people"
} ]
It adds a constraint on the graph and blocks the starting node from being returned, you will go back to the D, E, F and G result set. Finally, you have the right to mix the contracted form with the complex one :
[ {
"->http://xmlns.com/foaf/0.1/knows" : [ { "$alias" : "alice", "$maxDepth" : 3 }, "bob" ]
},
{
"$alias" : "alice"
},
{
"$alias" : "bob"
} ]
In human language, this query translates to :
"Find a avatar linked to Bob through a direct http://xmlns.com/foaf/0.1/knows edge and linked to alice through at most 3 http://xmlns.com/foaf/0.1/knows edges".
Since every block into the JsonArray is a avatar queryall by itself, described into a JsonObject, you can mix everything you read before in this documentation with the object properties.
[ {
"$classes" : {
"$contains" : "http://elite.polito.it/ontologies/dogont.owl#Room",
"$inheritance" : true
},
"->http://elite.polito.it/ontologies/dogont.owl#isIn" : "building"
},
{
"$alias" : "building",
"$classes" : {
"$contains" : "http://elite.polito.it/ontologies/dogont.owl#Building",
"$inheritance" : true
},
"http://elite.polito.it/ontologies/dogont.owl#name" : { "$in" : ["Orange Gardens - Batiment 3A", "Orange Gardens - Batiment 3B"] }
} ]
In human language, this query translates to :
Return all the instances of Room and subclasses of Room which are connected with the object property ''http://elite.polito.it/ontologies/dogont.owl#isIn edge'' to instances of Building and subclasses of Building which are named ''Orange Gardens - Batiment 3A'' or ''Orange Gardens - Batiment 3B'' ".
The good thing is, we are not in the relationnal world, joins don't scare us, you want to declare a precise pattern ? Go on, Impress me :
[ {
"$classes" : {
"$contains" : "http://elite.polito.it/ontologies/dogont.owl#Room",
"$inheritance" : true
},
"->http://elite.polito.it/ontologies/dogont.owl#adjacent" : "cafet"
"->http://elite.polito.it/ontologies/dogont.owl#isIn" : "og"
}, {
"$alias" : "og",
"$classes" : {
"$contains" : "http://elite.polito.it/ontologies/dogont.owl#Building",
"$inheritance" : true
},
"http://elite.polito.it/ontologies/dogont.owl#name" : { "$in" : ["Orange Gardens - Batiment 3A", "Orange Gardens - Batiment 3B"] }
"->http://elite.polito.it/ontologies/dogont.owl#isIn" : "town"
}, {
"$alias" : "town",
"$classes" : {
"$contains" : "http://orange-labs.fr/fog/ont/flexoffice.owl#Town",
"$inheritance" : true
},
"http://elite.polito.it/ontologies/dogont.owl#name" : "Paris"
}, {
"$alias" : "cafet",
"$classes" : {
"$contains" : "http://elite.polito.it/ontologies/dogont.owl#Cafetaria",
"$inheritance" : true
}
} ]
Using graph pattern, it would look like this :
Room -http://elite.polito.it/ontologies/dogont.owl#isIn-> Building -http://elite.polito.it/ontologies/dogont.owl#isIn-> Town
Room -http://elite.polito.it/ontologies/dogont.owl#adjacent-> Cafetaria
For lisibility's sake, the local clauses are hidden to each node but you have the general idea. Note that even though the ''$classes'' operator is used everywhere, you don't have to do the exact same thing, if you only want an avatar which is connected to another avatar using a specific object property, you can do it !
[ {
"$alias" : "a"
},
{
"->http://elite.polito.it/ontologies/dogont.owl#isIn" : "a"
} ]
This is a very concise and generic query. Here you are asking for avatars which are connected to other avatars using the http://elite.polito.it/ontologies/dogont.owl#isIn
object property. This is correct.
You can configure what you want to retrieve using the aliases. If you take the query right above, and do the following payload :
{
"query" : [ {
"$alias" : "a"
},
{
"->http://elite.polito.it/ontologies/dogont.owl#isIn" : "a"
}],
"return" : "a",
"edge": true,
"view": {}
}
The query will only return the avatars with the a
alias. Unfortunately, this is the limitation we are working on, you can only use one alias. If you try the next query :
{
"query" : [ {
"$alias" : "a"
},
{
"$alias" : "b"
"->http://elite.polito.it/ontologies/dogont.owl#isIn" : "a"
}
],
"return" : [ "a", "b"],
"edge": true,
"view": {}
}
This will ''NOT'' work. And it will be soon. Still, if you want to return all the data (default behavior), you can use the $all
argument:
{
"query" : [ {
"$alias" : "a"
},
{
"$alias" : "b"
"->http://elite.polito.it/ontologies/dogont.owl#isIn" : "a"
}
],
"return" : "$all",
"edge": true,
"view": {}
}
Since the list of avatars and relationships in the response could be large, the result is returned in multiple pages.
In the JSON result, the response body could contain specific fields : index, page_size, size, hidden, count
index
: the index of the first avatar in the whole list of avatars
page_size
: the size of a page
size
: the number of avatars in the returned page
hidden
: the number of avatars of this page that were hidden, then are not in the response
count
: the total number of avatars that can be returned by this query (hidden avatars included). If equals to -1 means, the total was not be computed.
next
: the url of the next page.
sorting_key
: sorting key used to sort the result. Can be set to any label or datatype property. By default will use the internal uuid for sorting. The sort order is ascendent by default. To change the order, prefix the label/property with the symbol -
.
To get all the avatars, the client should get the result page per page by incrementing
index
from0
withpage_size
untilsize+hidden == 0
These information could be retreived in the headers of the http response : Index, Page-Size, Size, Hidden-Items, Count
To ease the pagigination, we use the RFC 8288 to define the header Link
to link to next or previous pages.
example of header :
Link: <http://di.thingin.com/find?q=xxxxx&i=200&s=100>; rel="next", <http://di.thingin.com/find?q=xxxxx&i=0&s=100>; rel="previous"
To accelerate the execution time of find-requests that reference merely stable avatars, you can cache the result of the requests.
The cached result is isolated by the user (2 different users not access the same cached data). To enable this cache for a find-request, you should use specific headers:
Use-Cache = true : means => you accept data from cache.
Cache-TTL = x (second) : means => you want to cache the result for a duration a x
seconds.
Anonymous_reader_1 : Wow, that looks great ! Can i inverse the object property though ? With something like ''<- http://elite.polito.it/ontologies/dogont.owl#isIn'' ?
Author : yes, the reverse arrow syntax for object properties is correct
Author(After update) : Not available yet...
Anonymous_reader_2 : Do you handle recursivity ? It would be nice to have an operator like
>>http://elite.polito.it/ontologies/dogont.owl#isIn
which does not limit itself to one level of depth
Author : Hm, sorry, not available yet...
Author (After update) : Not available yet... We do have some kind of recursivity and depth handling though ! Go check the
$minDepth
and$maxDepth
operators.
Anonymous_reader_3 : In the case my graph pattern is cyclic, with something like A -> B -> C -> A, do you have a workaround ?
Author : Hee...
Author(After update) : Aha ! Yes we do ! Thanks to the aliases.
Anonymous_reader_4 : I have the same problem as the anonymous_reader_3 but i also want to retrieve A and B, not only A, can we precise which part of the pattern we want to retrieve ?
Author : Hee 2 ...
Author(After update) : Well, as explained above you can either retrieve everything or the single node which you are interested in.
Back to business, there is still a lot of improvements to be made but unless it is really critical (and in that case make sure to contact us ) i will ask you to be patient ... To be fair the Search API was not supposed to be that powerful, it is more the job of SPARQL and we have yet to decided the exact direction we want to take for this API.