Re: Querying collections

Hi Thomas,

On Apr 24, 2014, at 1:14 AM, Thomas Hoppe <thomas.hoppe@n-fuse.de> wrote:

> Hello Gregg,
> 
> just my 50 Cent on this:
> - I don't understand why the {collection} is part of the templated link, I would expect that a client knows this from its current IRI.

As I mentioned elsewhere, depending on the type of IRI you want to mint, joining a template to the resource base can be a problem. For example, if I have a resource I've retrieved from <http://example/giants> and I want to construct a template "interests{?likeKind}", if I do basic URI joining, I would get <http://example/interests?like=fan> , where I probably want it to be relative to the giants.

The problem is easier if all resources are returned with a trailing "/", such as <http://example/giants/>, but this may be too big of an assumption.

> - Apart from that I would model the likeKind filter as you did.
> - The SPARQL queries are just the implementation to perform the actual query behind the API (if you have a SPARQL query at hand) but I don't see how they are related to the API. Of course you could introduce an operation "SPARQLQuery" that exepects a somehow JSON-LD serialized sparql query (what I quite like but as we know is not realistic).

I was using the SPARQL queries to illustrate the kinds of searches we might want to perform, not to bind them that tightly. I suspect this will be out-of-scope at this point, but you could imagine a search with an IRI template such as "interests?group-by=likeKind&count" that might return multiple results with the count of each kind of UserLike.

Gregg

> Greets, Thomas
> 
> 
> On 04/12/2014 11:49 PM, Gregg Kellogg wrote:
>> I've been discussing some issues with Markus off-list related to API issues we're working through in defining the YourSports API using Hydra. One of them deals with how to query a collection.
>> 
>> For an example, I may have a schema:SportsTeam with a collection expressing interests on the team (such as fan, like, dislike, neutral). Some data may look like the following (sorry for the long setup):
>> 
>> </giants> a :SportsTeam;
>>   :name "SF Giants";
>>   :interest [
>>     a :UserLikes;
>>     :linkKind "fan";
>>     :performer </gregg>
>>   ], [
>>     a :UserLikes;
>>     :likKind "foe";
>>     :performer </markus>
>>   ] .
>> 
>> As expressions of interest on a particular team may be quite large, I want to separate these into a PagedCollection. As an interim solution to avoid breaking a specific "interest" property on SportsTeam, I'm using a separate collection property, and referencing it collection property from the SupportedProperty on interest. A part of the API description looks like the following:
>> 
>> ysapi: a hydra:ApiDocumentation;
>>   hydra:supportedClass :SportsTeam .
>> 
>> :SportsTeam a hydra:Class;
>>   hydra:supportedProperty [
>>     a hydra:SupportedProperty;
>>     hydra:description """
>> 	The interest property does not appear directly in an entity description; interests are asserted in a Collection identified as the value of the interestCollection property.
>> 
>> 	Entries are managed through the associated Collection.
>>      """@en;
>>     hydra:property :interest;
>>     hydra:collectionProperty :interestCollection;
>>     hydra:writeOnly true;
>>     hydra:required false
>>   ], [
>>     a hydra:SupportedProperty;
>>     hydra:description """
>> 	Identifies a Collection containing member references to all UserLikes along with the interest relationships from the Thing to each member of that Collection.
>>     """@en;
>>     hydra:property :interestCollection;
>>     hydra:writeOnly true;
>>     hydra:required false
>>   ] .
>> 
>> :interestCollection a hydra:Link .
>> 
>> :InterestCollection a hydra:Class;
>>    hydra:supportedOperation [
>>      a hydra:CreateResourceOperation;
>>      hydra:description "Indicate if you are a fan, like, neutral, dislike or a foe of something."@en;
>>      hydra:expects :UserLikes;
>>      hydra:method "POST";
>>      hydra:returns :UserLikes;
>>      hydra:title "Express interest in something"@en
>>    ];
>>    hydra:supportedProperty [
>>      a hydra:SupportedProperty;
>>      hydra:multiple false;
>>      hydra:property hydra:member;
>>      hydra:required false
>>    ] .
>> 
>> As :interest is not defined as a hydra:Link, but :interestCollection is, an API client will know to retrieve the collection value to get an InterestCollection (defined using rangeIncludes). The two resources that might enable this are the following:
>> 
>> # Giants resource
>> </giants> a :SportsTeam;
>>   :name "SF Giants";
>>   :interestCollection </giants/interests> .
>> 
>> # InterestCollection resource
>> </giants/interests> a :InterestCollection, hydra:Collection;
>>   hydra:member </giants/interests/gregg>, </giants/interests/markus> .
>> </giants> :interest </giants/interests/gregg>, </giants/interests/markus> .
>> 
>> # Interest resources
>> </giants/interests/gregg> a :UserLikes; :likeKind "fan"; :performer </gregg> .
>> 
>> </giants/interests/markus> a :UserLikes :likeKind "foe"; :performer </markus> .
>> 
>> Now, for the question about how to query such a collection.
>> 
>> So, how might I go about finding just the fans of the Giants? Certainly, one way would be to create a different collection property for each variation of the :likeKind property value, and implement server-side logic to perform an appropriate query. However, given the combinatorics of this, it probably makes sense to defer this logic to the API definition. As I understand it, I could create a SupportedProperty in :InterestCollection which is associated with some property with a a domain of :InterestCollection which would allow me to specify such a query. For example:
>> 
>> :InterestCollection a hydra:Class;
>>    hydra:supportedProperty [
>>      a hydra:SupportedProperty;
>>      hydra:multiple false;
>>      hydra:property hydra:member;
>>      hydra:required false
>>    ], [
>>     a hydra:SupportedProperty;
>>     hydra:property :searchInterestCollection
>>   ], .
>> 
>> :searchInterestCollection a hydra:TemplatedLink;
>>   hydra:search [
>>     a hydra:IriTemplate;
>>     hydra:template "{?collection}?likeKind={?kind}";
>>     hydra:mapping [
>>       a hydra:IriTemplateMapping;
>>       hydra:variable "collection";
>>       hydra:property :interestCollection;
>>       hydra:required true
>>     ], [
>>       a hydra:IriTemplateMapping;
>>       hydra:variable "kind";
>>       hydra:property :likeKind
>>       hydra:required true
>>     ] .
>> 
>> The documentation's a bit scant on TemplatedLinks, but I think this says that :searchInterestCollection is a property of an :InterestCollection. To search the collection, you construct an IRI using the template  "{?collection}?likeKind={?kind}". The "collection" variable is bound to the property :interestCollection, although the value of that would be in SportsTeam, not the InterestCollection itself. Perhaps there's a standin for the IRI of the subject. The "kind" variable is bound to the property :likeKind, although that is a property of a :UserLikes (in YS, anyway). Anyway, that  property is defined with a range restriction being one of "fan", "foe", etc., so an API client might have some way of knowing what kind of value to place here.
>> 
>> Another way to look at search is that a Collection represents a Graph composed of the collection resource, and the referenced member resources. Performing a search, could be thought of as defining a SPARQL query against this graph, which returns a CONSTRUCT based on the members of that graph. So, the searchInterestCollection could be thought of as being like the following query:
>> 
>> CONSTRUCT ?member
>> FROM </giants/interests>
>> WHERE {
>>   </giants/interests> hydra:member ?member .
>>   ?member :likeKind ?kind
>>   FILTER ?kind = "fan"
>> }
>> 
>> If we think of it this way, then it might be nice to consider doing aggregations as well, for example:
>> 
>> SELECT ?kind, COUNT(?member) AS ?count
>> FROM </giants/interests>
>> WHERE {
>>   </giants/interests> hydra:member ?member .
>>   ?member :likeKind ?kind
>> }
>> GROUP BY ?kind
>> 
>> Gregg Kellogg
>> gregg@greggkellogg.net
>> 
>> 
> 
> 

Received on Thursday, 24 April 2014 17:58:05 UTC