---------------------------------------------------------------------------------- See end of this file for licence information.---------------------------------------------------------------------------------- |-- Module : RDFQuery-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke-- License : GPL V2---- Maintainer : Douglas Burke-- Stability : experimental-- Portability : H98---- This module defines functions for querying an RDF graph to obtain-- a set of variable substitutions, and to apply a set of variable-- substitutions to a query pattern to obtain a new graph.---- It also defines a few primitive graph access functions.----------------------------------------------------------------------------------moduleSwish.RDF.RDFQuery(rdfQueryFind,rdfQueryFilter,rdfQueryBack,rdfQueryBackFilter,rdfQueryBackModify,rdfQueryInstance,rdfQuerySubs,rdfQueryBackSubs,rdfQuerySubsAll,rdfQuerySubsBlank,rdfQueryBackSubsBlank,rdfFindArcs,rdfSubjEq,rdfPredEq,rdfObjEq,rdfFindPredVal,rdfFindPredInt,rdfFindValSubj,rdfFindList-- debug,rdfQuerySubs2)whereimportSwish.RDF.RDFVarBinding(RDFVarBinding,nullRDFVarBinding,RDFVarBindingFilter)importSwish.RDF.RDFGraph(Arc(..),LDGraph(..),arcSubj,arcPred,arcObj,RDFLabel(..),isDatatyped,isBlank,isQueryVar,getLiteralText,makeBlank,RDFTriple,RDFGraph,emptyRDFGraph,allLabels,remapLabels,res_rdf_first,res_rdf_rest,res_rdf_nil)importSwish.RDF.MapXsdInteger(mapXsdInteger)importSwish.RDF.Datatype(DatatypeMap(..))importSwish.RDF.VarBinding(VarBinding(..),makeVarBinding,applyVarBinding,joinVarBindings,VarBindingModify(..),VarBindingFilter(..))importSwish.RDF.Vocabulary(xsd_integer,xsd_nonneg_integer)importSwish.Utils.ListHelpers(listProduct,allp,anyp)importqualifiedData.TraversableasTimportControl.Monad.State(State,runState,modify)importData.Maybe(mapMaybe,isJust,fromJust)-------------------------------------------------------------- Primitive RDF graph queries-------------------------------------------------------------- |Basic graph-query function.---- A very basic form of graph query, a query graph and-- a target graph, and returns a list of 'RDFVarBinding'-- values, each of which corresponds to a set of variable-- bindings that make the query graph a subgraph of the-- target graph, or @[]@ if the query cannot be matched.---- The triples of the query graph are matched sequentially-- against the target graph, each taking account of any-- variable bindings that have already been determined,-- and adding new variable bindings as triples containing-- query variables are matched against the graph.--rdfQueryFind::RDFGraph->RDFGraph->[RDFVarBinding]rdfQueryFind=rdfQueryPrim1matchQueryVariablenullRDFVarBinding.getArcs-- Helper function to match query against a graph.-- A node-query function is supplied to determine how query nodes-- are matched against target graph nodes. Also supplied is-- an initial variable binding.--rdfQueryPrim1::NodeQueryRDFLabel->RDFVarBinding->[ArcRDFLabel]->RDFGraph->[RDFVarBinding]rdfQueryPrim1_initv[]_=[initv]rdfQueryPrim1nodeqinitv(qa:qas)tg=letqam=fmap(applyVarBindinginitv)qa-- subst vars already boundnewv=rdfQueryPrim2nodeqqamtg-- new bindings, or nullinconcat[rdfQueryPrim1nodeqv2qastg|v1<-newv,letv2=joinVarBindingsinitvv1]-- Match single query term against graph, and return any new sets-- of variable bindings thus defined, or [] if the query term-- cannot be matched. Each of the RDFVarBinding values returned-- represents an alternative possible match for the query arc.--rdfQueryPrim2::NodeQueryRDFLabel->ArcRDFLabel->RDFGraph->[RDFVarBinding]rdfQueryPrim2nodeqqatg=mapMaybe(getBindingnodeqqa)(getArcstg)-- |RDF query filter.---- This function applies a supplied query binding-- filter to the result from a call of 'rdfQueryFind'.---- If none of the query bindings found satisfy the filter, a null-- list is returned (which is what 'rdfQueryFind' returns if the-- query cannot be satisfied).---- (Because of lazy evaluation, this should be as efficient as-- applying the filter as the search proceeds. I started to build-- the filter logic into the query function itself, with consequent-- increase in complexity, until I remembered lazy evaluation lets-- me keep things separate.)--rdfQueryFilter::RDFVarBindingFilter->[RDFVarBinding]->[RDFVarBinding]rdfQueryFilterqbf=filter(vbfTestqbf)-------------------------------------------------------------- Backward-chaining RDF graph queries-------------------------------------------------------------- |Reverse graph-query function.---- Similar to 'rdfQueryFind', but with different success criteria.-- The query graph is matched against the supplied graph,-- but not every triple of the query is required to be matched.-- Rather, every triple of the target graph must be matched,-- and substitutions for just the variables thus bound are-- returned. In effect, these are subsitutions in the query-- that entail the target graph (where @rdfQueryFind@ returns-- substitutions that are entailed by the target graph).---- Multiple substitutions may be used together, so the result-- returned is a list of lists of query bindings. Each inner-- list contains several variable bindings that must all be applied-- separately to the closure antecendents to obtain a collection of-- expressions that together are antecedent to the supplied-- conclusion. A null list of bindings returned means the-- conclusion can be inferred without any antecedents.---- Note: in back-chaining, the conditions required to prove each-- target triple are derived independently, using the inference rule-- for each such triple, so there are no requirements to check-- consistency with previously determined variable bindings, as-- there are when doing forward chaining. A result of this is that-- there may be redundant triples generated by the back-chaining-- process. Any process using back-chaining should deal with the-- results returned accordingly.---- An empty outer list is returned if no combination of-- substitutions can infer the supplied target.--rdfQueryBack::RDFGraph->RDFGraph->[[RDFVarBinding]]rdfQueryBackqgtg=rdfQueryBack1matchQueryVariable[](getArcsqg)(getArcstg)rdfQueryBack1::NodeQueryRDFLabel->[RDFVarBinding]->[ArcRDFLabel]->[ArcRDFLabel]->[[RDFVarBinding]]rdfQueryBack1_initv_[]=[initv]rdfQueryBack1nodeqinitvqas(ta:tas)=concat[rdfQueryBack1nodeq(nv:initv)qastas|nv<-rdfQueryBack2nodeqqasta]-- Match a query against a single graph term, and return any new sets of-- variable bindings thus defined. Each member of the result is an-- alternative possible set of variable bindings. An empty list returned-- means no match.--rdfQueryBack2::NodeQueryRDFLabel->[ArcRDFLabel]->ArcRDFLabel->[RDFVarBinding]rdfQueryBack2nodeqqasta=[fromJustb|qa<-qas,letb=getBindingnodeqqata,isJustb]-- |RDF back-chaining query filter. This function applies a supplied-- query binding filter to the result from a call of 'rdfQueryBack'.---- Each inner list contains bindings that must all be used to satisfy-- the backchain query, so if any query binding does not satisfy the-- filter, the entire corresponding row is removedrdfQueryBackFilter::RDFVarBindingFilter->[[RDFVarBinding]]->[[RDFVarBinding]]rdfQueryBackFilterqbf=filter(all(vbfTestqbf))-- |RDF back-chaining query modifier. This function applies a supplied-- query binding modifier to the result from a call of 'rdfQueryBack'.---- Each inner list contains bindings that must all be used to satisfy-- a backchaining query, so if any query binding does not satisfy the-- filter, the entire corresponding row is removed--rdfQueryBackModify::VarBindingModifyab->[[VarBindingab]]->[[VarBindingab]]rdfQueryBackModifyqbm=concatMap(rdfQueryBackModify1qbm)-- Auxiliary back-chaining query variable binding modifier function:-- for a supplied list of variable bindings, all of which must be used-- together when backchaining:-- (a) make each list member into a singleton list-- (b) apply the binding modifier to each such list, which may result-- in a list with zero, one or more elements.-- (c) return the listProduct of these, each member of which is-- an alternative list of variable bindings, where the members of-- each alternative must be used together.--rdfQueryBackModify1::VarBindingModifyab->[VarBindingab]->[[VarBindingab]]rdfQueryBackModify1qbmqbs=listProduct$map(vbmApplyqbm.(:[]))qbs-------------------------------------------------------------- Simple entailment graph query-------------------------------------------------------------- |Simple entailment (instance) graph query.---- This function queries a graph to find instances of the-- query graph in the target graph. It is very similar-- to the normal forward chaining query 'rdfQueryFind',-- except that blank nodes rather than query variable nodes-- in the query graph are matched against nodes in the target-- graph. Neither graph should contain query variables.---- An instance is defined by the RDF semantics specification,-- per <http://www.w3.org/TR/rdf-mt/>, and is obtained by replacing-- blank nodes with URIs, literals or other blank nodes. RDF-- simple entailment can be determined in terms of instances.-- This function looks for a subgraph of the target graph that-- is an instance of the query graph, which is a necessary and-- sufficient condition for RDF entailment (see the Interpolation-- Lemma in RDF Semantics, section 1.2).---- It is anticipated that this query function can be used in-- conjunction with backward chaining to determine when the-- search for sufficient antecendents to determine some goal-- has been concluded.rdfQueryInstance::RDFGraph->RDFGraph->[RDFVarBinding]rdfQueryInstance=rdfQueryPrim1matchQueryBnodenullRDFVarBinding.getArcs-------------------------------------------------------------- Primitive RDF graph query support functions-------------------------------------------------------------- |Type of query node testing function. Return value is:---- * @Nothing@ if no match---- * @Just True@ if match with new variable binding---- * @Just False@ if match with new variable binding--typeNodeQuerya=a->a->MaybeBool-- Extract query binding from matching a single query triple with a-- target triple, returning:-- - Nothing if the query is not matched-- - Just nullVarBinding if there are no new variable bindings-- - Just binding is a new query binding for this matchgetBinding::NodeQueryRDFLabel->ArcRDFLabel->ArcRDFLabel->MaybeRDFVarBindinggetBindingnodeq(Arcs1p1o1)(Arcs2p2o2)=makeBinding[(s1,s2),(p1,p2),(o1,o2)][]wheremakeBinding[]bs=Just$makeVarBindingbsmakeBinding(vr@(v,r):bvrs)bs=casenodeqvrofNothing->NothingJustFalse->makeBindingbvrsbsJustTrue->makeBindingbvrs(vr:bs)-- Match variable node against target node, returning-- Nothing if they do not match, Just True if a variable-- node is matched (thereby creating a new variable binding)-- or Just False if a non-blank node is matched.matchQueryVariable::NodeQueryRDFLabelmatchQueryVariable(Var_)_=JustTruematchQueryVariableqt|q==t=JustFalse|otherwise=Nothing-- Match blank query node against target node, returning-- Nothing if they do not match, Just True if a blank node-- is matched (thereby creating a new equivalence) or-- Just False if a non-blank node is matched.matchQueryBnode::NodeQueryRDFLabelmatchQueryBnode(Blank_)_=JustTruematchQueryBnodeqt|q==t=JustFalse|otherwise=Nothing-------------------------------------------------------------- Substitute results from RDF query back into a graph-------------------------------------------------------------- |Graph substitution function.---- Uses the supplied variable bindings to substitute variables in-- a supplied graph, returning a list of result graphs corresponding-- to each set of variable bindings applied to the input graph.-- This function is used for formward chaining substitutions, and-- returns only those result graphs for which all query variables-- are bound.rdfQuerySubs::[RDFVarBinding]->RDFGraph->[RDFGraph]rdfQuerySubsvarsgr=mapfst$filter(null.snd)$rdfQuerySubsAllvarsgr-- |Graph back-substitution function.---- Uses the supplied variable bindings from 'rdfQueryBack' to perform-- a series of variable substitutions in a supplied graph, returning-- a list of lists of result graphs corresponding to each set of variable-- bindings applied to the input graphs.---- The outer list of the result contains alternative antecedent lists-- that satisfy the query goal. Each inner list contains graphs that-- must all be inferred to satisfy the query goal.rdfQueryBackSubs::[[RDFVarBinding]]->RDFGraph->[[(RDFGraph,[RDFLabel])]]rdfQueryBackSubsvarssgr=[rdfQuerySubsAllvgr|v<-varss]-- |Graph substitution function.---- This function performs the substitutions and returns a list of-- result graphs each paired with a list unbound variables in each.rdfQuerySubsAll::[RDFVarBinding]->RDFGraph->[(RDFGraph,[RDFLabel])]rdfQuerySubsAllvarsgr=[rdfQuerySubs2vgr|v<-vars]-- |Graph substitution function.---- This function performs each of the substitutions in 'vars', and-- replaces any nodes corresponding to unbound query variables-- with new blank nodes.rdfQuerySubsBlank::[RDFVarBinding]->RDFGraph->[RDFGraph]rdfQuerySubsBlankvarsgr=[remapLabelsvsbsmakeBlankg|v<-vars,let(g,vs)=rdfQuerySubs2vgr,letbs=allLabelsisBlankg]-- |Graph back-substitution function, replacing variables with bnodes.---- Uses the supplied variable bindings from 'rdfQueryBack' to perform-- a series of variable substitutions in a supplied graph, returning-- a list of lists of result graphs corresponding to each set of variable-- bindings applied to the input graphs.---- The outer list of the result contains alternative antecedent lists-- that satisfy the query goal. Each inner list contains graphs that-- must all be inferred to satisfy the query goal.rdfQueryBackSubsBlank::[[RDFVarBinding]]->RDFGraph->[[RDFGraph]]rdfQueryBackSubsBlankvarssgr=[rdfQuerySubsBlankvgr|v<-varss]-- This function applies a substitution for a single set of variable-- bindings, returning the result and a list of unbound variables.-- It uses a state transformer monad to collect the list of-- unbound variables.---- Adding an empty graph forces elimination of duplicate arcs.rdfQuerySubs2::RDFVarBinding->RDFGraph->(RDFGraph,[RDFLabel])rdfQuerySubs2varbgr=(addemptyRDFGraphg,vs)where(g,vs)=runState(T.traverse(mapNodevarb)gr)[]-- (g,vs) = runState ( fmapM (mapNode varb) gr ) []-- Auxiliary monad function for rdfQuerySubs2.-- This returns a state transformer Monad which in turn returns the-- substituted node value based on the supplied query variable bindings.-- The monad state is a list of labels which accumulates all those-- variables seen for which no substitution was available.mapNode::RDFVarBinding->RDFLabel->State[RDFLabel]RDFLabelmapNodevarblab=casevbMapvarblabofJustv->returnvNothing->ifisQueryVarlabthendo{modify(addVarlab);returnlab}elsereturnlab-- Add variable to list of variables, if not already thereaddVar::RDFLabel->[RDFLabel]->[RDFLabel]addVarvarvars=ifvar`elem`varsthenvarselsevar:vars-------------------------------------------------------------- Simple lightweight query primitives---------------------------------------------------------------- [[[TODO: modify above code to use these for all graph queries]]]-- |rdfFindArcs is the main function here: it takes a predicate on an-- RDF statement and a graph, and returns all statements in the graph-- satisfying that predicate.---- Use combinations of these as follows:---- * find all statements with given subject:-- @rdfQuerySimple (rdfSubjEq s)@---- * find all statements with given property:-- @rdfQuerySimple (rdfPredEq p)@---- * find all statements with given object:-- @rdfQuerySimple (rdfObjEq o)@---- * find all statements matching conjunction of these conditions:-- @rdfQuerySimple ('allp' [...])@---- * find all statements matching disjunction of these conditions:-- @rdfQuerySimple ('anyp' [...])@---- Custom predicates can also be used.--rdfFindArcs::(RDFTriple->Bool)->RDFGraph->[RDFTriple]rdfFindArcsp=filterp.getArcs-- |Test if statement has given subjectrdfSubjEq::RDFLabel->RDFTriple->BoolrdfSubjEqs=(s==).arcSubj-- |Test if statement has given predicaterdfPredEq::RDFLabel->RDFTriple->BoolrdfPredEqp=(p==).arcPred-- |Test if statement has given objectrdfObjEq::RDFLabel->RDFTriple->BoolrdfObjEqo=(o==).arcObj{-
-- |Find statements with given subject
rdfFindSubj :: RDFLabel -> RDFGraph -> [RDFTriple]
rdfFindSubj s = rdfFindArcs (rdfSubjEq s)
-- |Find statements with given predicate
rdfFindPred :: RDFLabel -> RDFGraph -> [RDFTriple]
rdfFindPred p = rdfFindArcs (rdfPredEq p)
-}-- |Find values of given predicate for a given subjectrdfFindPredVal::RDFLabel->RDFLabel->RDFGraph->[RDFLabel]rdfFindPredValsp=maparcObj.rdfFindArcs(allp[rdfSubjEqs,rdfPredEqp])-- |Find integer values of a given predicate for a given subjectrdfFindPredInt::RDFLabel->RDFLabel->RDFGraph->[Integer]rdfFindPredIntsp=mapMaybegetint.filterisint.pvswherepvs=rdfFindPredValspisint=anyp[isDatatypedxsd_integer,isDatatypedxsd_nonneg_integer]getint=mapL2VmapXsdInteger.getLiteralText-- |Find all subjects that have a of given value for for a given predicaterdfFindValSubj::RDFLabel->RDFLabel->RDFGraph->[RDFLabel]rdfFindValSubjpo=maparcSubj.rdfFindArcs(allp[rdfPredEqp,rdfObjEqo])-------------------------------------------------------------- List query-------------------------------------------------------------- |Return a list of nodes that comprise an rdf:collection value,-- given the head element of the collection. If the list is-- ill-formed then some arbitrary value is returned.--rdfFindList::RDFGraph->RDFLabel->[RDFLabel]rdfFindListgrhd=findhead$rdfFindListgrfindrestwherefindhead=headOr(const[])$map(:)(rdfFindPredValhdres_rdf_firstgr)findrest=headOrres_rdf_nil(rdfFindPredValhdres_rdf_restgr){-
findhead = headOr (const [])
[ (ob:) | Arc _ sb ob <- subgr, sb == res_rdf_first ]
findrest = headOr res_rdf_nil
[ ob | Arc _ sb ob <- subgr, sb == res_rdf_rest ]
subgr = filter ((==) hd . arcSubj) $ getArcs gr
-}headOr=foldrconst-- headOr _ (x:_) = x-- headOr x [] = x-------------------------------------------------------------- Interactive tests------------------------------------------------------------{-
s1 = Blank "s1"
p1 = Blank "p1"
o1 = Blank "o1"
s2 = Blank "s2"
p2 = Blank "p2"
o2 = Blank "o2"
qs1 = Var "s1"
qp1 = Var "p1"
qo1 = Var "o1"
qs2 = Var "s2"
qp2 = Var "p2"
qo2 = Var "o2"
qa1 = Arc qs1 qp1 qo1
qa2 = Arc qs2 qp2 qo2
qa3 = Arc qs2 p2 qo2
ta1 = Arc s1 p1 o1
ta2 = Arc s2 p2 o2
g1 = toRDFGraph [ta1,ta2]
g2 = toRDFGraph [qa3]
gb1 = getBinding matchQueryVariable qa1 ta1 -- ?s1=_:s1, ?p1=_:p1, ?o1=_:o1
gvs1 = qbMap (fromJust gb1) qs1 -- _:s1
gvp1 = qbMap (fromJust gb1) qp1 -- _:p1
gvo1 = qbMap (fromJust gb1) qo1 -- _:o1
gvs2 = qbMap (fromJust gb1) qs2 -- Nothing
gb3 = getBinding matchQueryVariable qa3 ta1 -- Nothing
gb4 = getBinding matchQueryVariable qa3 ta2 -- ?s2=_:s1, ?o2=_:o1
mqvs1 = matchQueryVariable qs2 s1
mqvp1 = matchQueryVariable p2 p1
-- rdfQueryFind
qfa = rdfQueryFind g2 g1
qp2a = rdfQueryPrim2 matchQueryVariable qa3 g1
-}{- more tests
qb1a = rdfQueryBack1 [] [qa1] [ta1,ta2]
qb1 = rdfQueryBack1 [] [qa1,qa2] [ta1,ta2]
ql1 = length qb1
qv1 = map (qb1!!0!!0) [qs1,qp1,qo1,qs2,qp2,qo2]
qv2 = map (qb1!!0!!1) [qs1,qp1,qo1,qs2,qp2,qo2]
qv3 = map (qb1!!1!!0) [qs1,qp1,qo1,qs2,qp2,qo2]
qv4 = map (qb1!!1!!1) [qs1,qp1,qo1,qs2,qp2,qo2]
qv5 = map (qb1!!2!!0) [qs1,qp1,qo1,qs2,qp2,qo2]
qv6 = map (qb1!!2!!1) [qs1,qp1,qo1,qs2,qp2,qo2]
qv7 = map (qb1!!3!!0) [qs1,qp1,qo1,qs2,qp2,qo2]
qv8 = map (qb1!!3!!1) [qs1,qp1,qo1,qs2,qp2,qo2]
qb2 = rdfQueryBack2 matchQueryVariable [qa1,qa2] ta1
ql2 = length qb2
qv1 = map (qbMap $ head qb2) [qs1,qp1,qo1,qs2,qp2,qo2]
qv2 = map (qbMap $ head $ tail qb2) [qs1,qp1,qo1,qs2,qp2,qo2]
qb3 = rdfQueryBack2 matchQueryVariable [qa1,qa3] ta1
-}------------------------------------------------------------------------------------ Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke -- All rights reserved.---- This file is part of Swish.---- Swish is free software; you can redistribute it and/or modify-- it under the terms of the GNU General Public License as published by-- the Free Software Foundation; either version 2 of the License, or-- (at your option) any later version.---- Swish is distributed in the hope that it will be useful,-- but WITHOUT ANY WARRANTY; without even the implied warranty of-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the-- GNU General Public License for more details.---- You should have received a copy of the GNU General Public License-- along with Swish; if not, write to:-- The Free Software Foundation, Inc.,-- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA----------------------------------------------------------------------------------