2019, April 8th
This document presents Method, Event and Datatype extensions to SPARQL Function, a simple programming that enables users to define end execute extension functions with SPARQL. We propose a mechanism to define events that are associated to query processing. We also propose mechanisms that enables us to overload SPARQL operators and to define methods attached to classes of the ontology.
We have defined SPARQL Function which enables users to define extension functions for SPARQL. We now define SPARQL Event which models events occcurring during query processing. We associate SPARQL function definitions to SPARQL events. When an event occurs the associated function is executed. Furthermore, we define SPARQL Datatype which are extension datatypes to represent and manipulate objects that participates to SPARQL query processing such as: statement, expression, triple, solution, error, etc. This generic design pattern, SPARQL Function, Event and Datatype enables us to provide error overloading, extension datatype operator overloading and SPARQL statement overloading. In addition, it enables us to provide a generic "eval" function that enables us to call the SPARQL filter interpreter within SPARQL Function.
In the document, we use prefix and namespaces shown below:
prefix rq: <http://ns.inria.fr/sparql-function/> prefix dt: <http://ns.inria.fr/sparql-datatype/> prefix st: <http://ns.inria.fr/sparql-template/> prefix xt: <http://ns.inria.fr/sparql-extension/> prefix us: <http://ns.inria.fr/sparql-extension/user/>
SPARQL Function is a simple programming language that enables users to define and execute functions within SPARQL. The body of a function is based on SPARQL filter expressions. We show an example of SPARQL Function.
select * where { ?x rdf:value ?v filter us:test(?x) } function us:test(?x) { strstarts(str(?x), rdf:) || strstarts(str(?x), rdfs:) }
SPARQL Function enables users to execute SPARQL queries within functions. It provides users with additional statements such as "let" and "for", second order functions such as "apply" and "map", pattern matching statements and extension datatypes to represent RDF and SPARQL objects. The function below returns the type of an individual.
function us:type(?x) { let (select * where { ?x a ?t }) { ?t } }
We define SPARQL Event as events occurring during query processing such as: start, finish, etc. For each SPARQL Event we define an event annotation: @start, @finish, etc. An event annotation can be associated to a function in such a way that the function is associated to the event.
In the example below, we associate a function to the ''before'' event by means of the ''@before'' annotation. The "before" function is called with the query as argument.
@event select * where { ?x rdf:value ?v } @before function us:before(?q) { xt:print('before', ?q) }
We define the ''@type'' annotation which enables us to specify that a function is associated to a specific extension datatype.
@type dt:triple function us:eq(?t1, ?t2) { mapevery(rq:eq, ?t1, ?t2) }
We define the ''@type dt:error'' annotation which enables us to specify that a function is associated to the occurrence of an error.
@type dt:error function us:eq(?e, ?t1, ?t2) { xt:print('error', ?e, ?t1, ?t2) }
We define a set of extension datatypes to represent SPARQL query processing objects.
The list datatype is dt:list
dt:list
SPARQL select and construct, as well as update, query is represented as dt:query datatype value. SPARQL statement such as union or optional is represented as dt:statement, filter expression is represented as dt:expression.
dt:query dt:statement dt:expression
The dt:expression datatype represents expressions such as: "?x + ?y" of "us:fun(?x)". The content of an expression can be accessed by pattern matching in SPARQL Function, considering the expression in prefix (functional) notation.
# ?e = "?x + ?y"^^dt:expression let ((?name | ?arg) = ?e) { # ?name = "+" # ?arg = "(?x ?y)"^^dt:list }
SPARQL Query solution sequence is represented as dt:mappings and solution as dt:mapping. SPARQL construct query solution is represented as dt:graph and dt:triple. A solution to a Property Path is a dt:path which contains the list of triples of the path.
dt:mappings dt:mapping dt:graph dt:triple dt:path
Query solution sequence is iterable.
# ?s : dt:mappings # ?r : dt:mapping for (?r in ?s) { }
Graph and triple are iterable.
# ?graph : dt:graph # ?triple : dt:triple for (?triple in ?graph) { let ((?s ?p ?o) = ?triple) { } }
An error is represented by the abstract datatype dt:error.
dt:error
The first event that we define is the ''error'' event wich represents the occurrence of an error in SPARQL filter evaluation. An error may occur when a variable is unbound or when the arguments of an operator do not have the expected type. We distinguish local errors from top level errors. A local error is detected at the moment where it is throwned. A top level error is detected in the SPARQL clause that started the evaluation of the expression: filter, select, having, etc.
If an error occurs during the evaluation of an expression, an error is thrown. Error processing for binary operators is done by method annotated with @type dt:error, whose name is the name of the operator in the us: namespace, e.g. us:eq for "=". There is also a default method with name us:error, that traps errors for all binary operators. The arguments of the method are the expression and the values of the arguments of the operator. The result of the method is returned as the result of the evaluation of the expression, hence it is no more an error. However, it is possible to return an error from the method by calling the specific function error().
@type dt:error function us:eq(?e, ?a, ?b) { } @type dt:error function us:error(?e, ?a, ?b) { }
Below is the list of error functions.
function us:eq(dt:expression ?e, term ?a, term ?b) function us:ne(dt:expression ?e, term ?a, term ?b) function us:le(dt:expression ?e, term ?a, term ?b) function us:lt(dt:expression ?e, term ?a, term ?b) function us:ge(dt:expression ?e, term ?a, term ?b) function us:gt(dt:expression ?e, term ?a, term ?b) function us:plus(dt:expression ?e, term ?a, term ?b) function us:mult(dt:expression ?e, term ?a, term ?b) function us:minus(dt:expression ?e, term ?a, term ?b) function us:divis(dt:expression ?e, term ?a, term ?b) function us:error(dt:expression ?e, term ?a, term ?b)
Errors trapped at toplevel of evaluation (filter, select, bind, etc.) call a method with same name as above but with expression as solely argument. In addition to binary operators, it is generalized to every SPARQL predefined function with a method whose name is the function name in the us: namespace, for example "us:if" for "if". The generic method us:error can also be used to trap errors on any expression. The result of the method overloads the error. However, it is possible to return an error using the error() specific function.
@type dt:error function us:plus(?e) { } @type dt:error function us:if(?e) { } @type dt:error function us:error(?e) { }
function us:eq(dt:expression ?e) function us:ne(dt:expression ?e) function us:le(dt:expression ?e) function us:lt(dt:expression ?e) function us:ge(dt:expression ?e) function us:gt(dt:expression ?e) function us:plus(dt:expression ?e) function us:mult(dt:expression ?e) function us:minus(dt:expression ?e) function us:divis(dt:expression ?e) function us:error(dt:expression ?e)
The error function has for effect to throw an error, in the same way as an exception. Evaluation of the current expression resumes to the top level SPARQL clause that executes the expression, e.g. filter. An error that is throwned by the error function can be trapped by the mechanism presented in this document.
function error()
The ''eval'' function evaluates an argument that is an expression of type dt:expression.
It returns the result that would be returned by the SPARQL filter interpreter when evaluating the expression. Hence, the "eval" function can be seen as a call to the SPARQL filter interpreter given a representation of an expression in the form as a dt:expression datatype value.
Use case: error @select @filter, etc., which call the event function with an argument which is a Pointer on Expression.
@select function us:select(?e, ?v) { xt:print('select', ?e, eval(?e), ?v) }
The code below emulates the SPARQL filter interpreter.
@filter function us:filter(?g, ?e, ?v) { us:eval(?e) }
Variable ?name is bound to the name of the function or operation and ?arg is bound to the list of arguments.
function us:eval(?e) { let ((?name | ?arg) = ?e) { if (bound(?name)) { apply(?name, maplist(us:eval, ?arg)) } else { eval(?e) } } }
function eval(dt:expression ?e)
Extension datatypes deserve extension operators.
function us:eq(datatype ?a, datatype ?b) function us:ne(datatype ?a, datatype ?b) function us:le(datatype ?a, datatype ?b) function us:lt(datatype ?a, datatype ?b) function us:ge(datatype ?a, datatype ?b) function us:gt(datatype ?a, datatype ?b) function us:plus(datatype ?a, datatype ?b) function us:mult(datatype ?a, datatype ?b) function us:minus(datatype ?a, datatype ?b) function us:divis(datatype ?a, datatype ?b)
Operator overloading for ''km'' datatype.
insert data { us:t1 us:length '1'^^us:km . us:t2 us:length '2'^^us:km . }
@type us:km function us:eq(?a, ?b) { us:convert(?a) = us:convert(?b) } function us:convert(?a) { if (datatype(?a) = us:km, us:value(?a) * 1000, if (datatype(?a) = us:m, us:value(?a), if (datatype(?a) = us:length, us:valueunit(?a), us:value(?a)))) } function us:value(?a) { if (contains(?a, ' '), xsd:integer(strbefore(?a, ' ')), xsd:integer(?a)) } function us:valueunit(?a) { if (strafter(?a, ' ') = 'km', 1000 * us:value(?a), us:value(?a)) }
Possibility to define datatypes as specializing supertype.
If two different datatypes specialize the same supertype, overloading is performed.
Overloaded operator may be defined on type or supertype
us:km and us:m can be compared because they specialize us:length, operator can be defined on us:km, us:m and us:length.
Suitable for "1"^^us:km and for "1 km"^^us:length
xt:datatype(us:km, us:length) xt:datatype(us:m, us:length)
filter ('1 km'^^us:length = '1000 m'^^us:length) @type us:length function us:eq(?a, ?b) { us:convert(?a) = us:concert(?b) }
Possible to mix '1'^^us:km with '1000 m'^^us:length.
insert data { us:t1 us:length '1'^^us:km . us:t2 us:length '1000'^^us:m . us:t3 us:length '1000 m'^^us:length }
@event select * where { ?x ?p ?v . ?y ?p ?w filter (?v = ?w) } @init function us:init(?q){ xt:datatype(us:km, us:length) ; xt:datatype(us:m, us:length) }
@type us:length us:km us:m function us:eq(?a, ?b) { us:convert(?a) = us:convert(?b) }
Overload us:compare for order by with extension datatypes.
@event select where {} order by ?x @type us:length function us:compare(?a, ?b) { if (?a < ?b, -1, if (?a = ?b, 0, 1)) }
Overload bnode comparison.
@type dt:bnode function us:eq(?a, ?b) { ?a = ?b }
Romain numbers with datatype us:romain.
prefix rm: <http://ns.inria.fr/sparql-extension/spqr/> @event select (rm:digit(?res) as ?ope) (rm:digit(?val) as ?dig) where { bind ('II'^^us:romain * 'X'^^us:romain + 'V'^^us:romain as ?res) bind (maplist(us:romain, xt:iota(7)) as ?list) bind (reduce (lambda(?x, ?y) { ?x + ?y }, ?list) as ?val) } function us:rom(?x) { strdt(rm:romain(?x), us:romain) } @type us:romain { function us:compare(?x, ?y) { if (?x < ?y, -1, if (?x = ?y, 0, 1)) } function us:eq(?x, ?y) { (rm:digit(?x) = rm:digit(?y)) } function us:ne(?x, ?y) { (rm:digit(?x) != rm:digit(?y)) } function us:lt(?x, ?y) { (rm:digit(?x) < rm:digit(?y)) } function us:le(?x, ?y) { (rm:digit(?x) <= rm:digit(?y)) } function us:gt(?x, ?y) { (rm:digit(?x) > rm:digit(?y)) } function us:ge(?x, ?y) { (rm:digit(?x) >= rm:digit(?y)) } function us:plus(?x, ?y) { us:rom(rm:digit(?x) + rm:digit(?y)) } function us:mult(?x, ?y) { us:rom(rm:digit(?x) * rm:digit(?y)) } function us:minus(?x, ?y) { us:rom(rm:digit(?x) - rm:digit(?y)) } function us:divis(?x, ?y) { us:rom(rm:digit(?x) / rm:digit(?y)) } }
LDScript functions annotated with @before, @after are executed by QuerySolverVisitor during query processing.
@before function us:before(?q) { xt:print('start', ?q) } @after function us:after(?m) { xt:print('result', ?m) }
Annotation @event enables use of event functions
Annotation @recursion enables operator recursive overloading: dt:graph = dt:graph can recursively overload dt:triple = dt:triple
@event @recursion
@timeout function us:timeout(?serv) timeout for service
@limit function us:limit(?map) return true if under limit
@start @finish for SPARQL subquery, LDScript subquery and subtemplate.
@event select * where { ?a owl:sameAs ?b {?a foaf:knows ?x} union {?b foaf:knows ?x} } @distinct function us:distinct(?m) { let ((?a ?b ?x) = ?m) { us:key(xt:add(xt:sort(xt:list(?a, ?b)), ?x)) } } function us:key(?l) { reduce(rq:concat, maplist(lambda(?e) { concat(xt:index(?e), ".") }, ?l)) }
@event select * where { ?a foaf:knows ?y optional {?y foaf:name ?n} } @orderby function us:comparemap(?m1, ?m2) { us:revcompare(xt:size(?m1) , xt:size(?m2)) } function us:revcompare(?x, ?y) { if (?x < ?y, 1, if (?x = ?y, 0, -1)) }
Path where all edges verify a constraint, e.g. subject.age <= object.age.
@event select * where { ?x foaf:knows+ ?y } @step function us:step(?g, ?q, ?p, ?s, ?o) { let ((?f . ?l) = ?p, (?a ?q ?b) = ?l) { return(exists { ?a us:age ?aa . ?b us:age ?bb filter (?aa <= ?bb) }) } }
Focus on path with size less than threshold
@step function us:step(?g, ?q, ?p, ?s, ?o) { return(xt:size(?p) <= 2) }
We present the list of SPARQL events and the associated functions with their signature. RDF terms, i.e. URI, bnode and literal, have term type.
@init function us:init(dt:query ?q) @before function us:before(dt:query ?q) @after function us:after(dt:mappings ?map) @start function us:start(dt:query ?q) @finish function us:finish(dt:mappings ?map) @statement function us:statement(URI ?g, dt:statement ?e) @function function us:function(dt:expression ?call, dt:expression ?fun) @produce function us:produce(URI ?g, dt:triple ?q) @candidate function us:candidate(URI ?g, dt:triple ?q, dt:triple ?t) @result function us:result(dt:mappings ?map, dt:mapping ?m) @distinct function us:key(dt:mapping ?m) @orderby function us:compare(dt:mapping ?m1, dt:mapping ?m2) @limit function us:limit(dt:mappings ?map) @timeout function us:timeout(URI ?serv) @slice function us:slice(URI ?serv, dt:mappings ?map) @join function us:join (URI ?g, dt:statement ?e, dt:mappings ?m1, dt:mappings ?m2) @minus function us:minus (URI ?g, dt:statement ?e, dt:mappings ?m1, dt:mappings ?m2) @union function us:union (URI ?g, dt:statement ?e, dt:mappings ?m1, dt:mappings ?m2) @optional function us:optional(URI ?g, dt:statement ?e, dt:mappings ?m1, dt:mappings ?m2) @bgp function us:bgp (URI ?g, dt:statement ?e, dt:mappings ?m) @graph function us:graph (URI ?g, dt:statement ?e, dt:mappings ?m) @service function us:service(URI ?s, dt:statement ?e, dt:mappings ?m) @query function us:query (URI ?g, dt:statement ?e, dt:mappings ?m) @values function us:values (URI ?g, dt:statement ?e, dt:mappings ?m) @path function us:path(URI ?g, dt:triple ?q, dt:path ?p, term ?s, term ?o) @step function us:step(URI ?g, dt:triple ?q, dt:path ?p, term ?s, term ?o) @select function us:select(dt:expression ?e, term ?v) @aggregate function us:agg(dt:expression ?e, term ?v) @bind function us:bind(URI ?g, dt:expression ?e, term ?v) @filter function us:filter(URI ?g, dt:expression ?e, xsd:boolean ?b) @having function us:having(dt:expression ?e, xsd:boolean ?b)
We propose a design pattern to associate methods (functions) to classes of the ontology. We associate a method to a class on the ontology by specifying the @type annotation followed by the name of the class. This enables us to call a method on an individual in such a way that the method that is executed is retrieved in the list of the classes (using the rdf:type property) and superclasses (using rdf:type/rdfs:subClassOf*) of the individual. A method is called using the "method" function. The @method annotation enables method call.
@method select (method(us:area, ?x) as ?a) where { ?x a us:Figure }
@type us:Rectangle function us:area(?x) { let (select * where { ?x us:width ?w ; us:length ?l } ) { ?w * ?l } } @type us:Circle function us:area(?x) { let (select * where { ?x us:radius ?r } ) { 3.1415 * ?r * ?r } }
The "xt:method" function enables users to specify the type of the individual to be considered for the method call, independently of its rdf:type.
@method select (xt:method(us:area, ?x, us:Circle) as ?a) where { ?x a us:Circle }
SPARQL Function Method, Event and Datatype are implemented is the Corese Semantic Web factory as open source software. It is available on github. A demo server is available online.
Olivier Corby, Catherine Faron-Zucker and Fabien Gandon, LDScript: a Linked Data Script Language, International Semantic Web Conference, ISWC, 2017 October, Vienna, Austria.
RDF 1.1 Concepts and Abstract Syntax, Graham Klyne, Jeremy J. Carroll, Brian McBride. W3C Recommendation, February 2014
SPARQL 1.1 Query Language, Steve Harris, Andy Seaborne. W3C Recommendation, March 2013