~ big refactoring: split FeDeRate in small projects, with fine-grained dependencies
only the main code is concerned so far
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/directmapping/src/main/scala/DirectMapping.scala Sun Oct 31 15:17:31 2010 -0400
@@ -0,0 +1,195 @@
+package org.w3.sw.directmapping
+
+import org.w3.sw.rdb
+import org.w3.sw.rdf
+
+// to be adapted to the new RDB model
+
+// object DirectMapping {
+
+// /** A KeyMap associates the candidate key and key values with the
+// * node for any tuple in a unique relation. */
+// case class KeyMap(m:Map[CandidateKey, Map[List[CellValue], Node]]) {
+// //def KeyMap() : KeyMap = KeyMap(Map[CandidateKey, Map[List[CellValue], Node]]())
+// def apply(i:CandidateKey) : Map[List[CellValue], Node] = m(i)
+// def ++(pairs:List[(CandidateKey, List[CellValue])], n:Node):KeyMap = {
+// val m2:Map[CandidateKey, Map[List[CellValue], Node]] =
+// pairs.foldLeft(m)((m, p) => {
+// if (m.get(p._1).isDefined) {
+// val byKey = m(p._1)
+// if (byKey.get(p._2).isDefined) {
+// error("tried to set " + p._1 + p._2 + " = " + n + "(was " + byKey(p._2) + ")")
+// } else {
+// val im1 = byKey ++ Map[List[CellValue], Node](p._2 -> n)
+// m ++ Map[CandidateKey, Map[List[CellValue], Node]](p._1 -> im1)
+// }
+// } else {
+// m ++ Map[CandidateKey, Map[List[CellValue], Node]](p._1 -> Map(p._2 -> n))
+// }
+// })
+// KeyMap(m2)
+// }
+// }
+// case class NodeMap(m:Map[RelName, KeyMap]) {
+// def apply(rn:RelName) = m(rn)
+// def ultimateReferent (rn:RelName, k:CandidateKey, vs:List[LexicalValue], db:Database) : Node = {
+// // Issue: What if fk is a rearrangement of the pk, per issue fk-pk-order?
+// if (db(rn).pk.isDefined && db(rn).fks.contains(db(rn).pk.get.attrs)) {
+// /** Table's primary key is a foreign key. */
+// val target = db(rn).fks(db(rn).pk.get.attrs)
+// ultimateReferent(target.rel, target.key, vs, db)
+// } else
+// m(rn)(k)(vs)
+// }
+// }
+// implicit def list2map (l:Set[(RelName, KeyMap)]):Map[RelName,KeyMap] = l.toMap
+// implicit def list2Nmap (l:Set[(RelName, KeyMap)]):NodeMap = NodeMap(l)
+
+// /** The direct mapping requires one parameter: the StemIRI */
+// case class StemIRI(stem:String) {
+// def +(path:String):IRI = IRI(stem + path)
+// }
+
+// /**
+// * The mapping functions implementing
+// * <http://www.w3.org/2001/sw/rdb2rdf/directGraph/>
+// */
+
+// def references (t:Tuple, r:Relation):Set[List[AttrName]] = {
+// val allFKs:Set[List[AttrName]] = r.fks.keySet
+// val nulllist:Set[AttrName] = t.nullAttributes(r.header)
+// val nullFKs:Set[List[AttrName]] = allFKs.flatMap(a => {
+// val int:Set[AttrName] = nulllist & a.toSet
+// if (int.toList.length == 0) None else List(a)
+// })
+
+// /** Check to see if r's primary key is a hierarchical key.
+// * http://www.w3.org/2001/sw/rdb2rdf/directGraph/#rule3 */
+// if (r.pk.isDefined && r.fks.contains(r.pk.get.attrs))
+// r.fks.keySet -- nullFKs - r.fks(r.pk.get.attrs).key.attrs
+// else
+// r.fks.keySet -- nullFKs
+// }
+
+// def scalars (t:Tuple, r:Relation):Set[AttrName] = {
+// val allAttrs:Set[AttrName] = r.header.keySet
+// val allFKs:Set[List[AttrName]] = r.fks.keySet
+// val unaryFKs:Set[AttrName] = allFKs.flatMap(a => {
+// if (a.length == 1) a else None
+// })
+// val nulllist:Set[AttrName] = t.nullAttributes(r.header)
+
+// /** Check to see if r's primary key is a hierarchical key.
+// * http://www.w3.org/2001/sw/rdb2rdf/directGraph/#rule3 */
+// if (r.pk.isDefined && r.fks.contains(r.pk.get.attrs))
+// allAttrs -- unaryFKs -- nulllist ++ r.fks(r.pk.get.attrs).key.attrs
+// else
+// allAttrs -- unaryFKs -- nulllist
+// }
+
+// /** The NodeMap-generating functions: */
+// def relation2KeyMap (u:StemIRI, r:Relation) : KeyMap = {
+// val m = KeyMap(Map[CandidateKey, Map[List[CellValue], Node]]())
+// r.body.foldLeft(m)((m, t) => {
+// val (pairs, node) = rdfNodeForTuple(u, t, r)
+// m ++ (pairs, node)
+// })
+// }
+
+// def rdfNodeForTuple (u:StemIRI, t:Tuple, r:Relation) : (List[(CandidateKey, List[CellValue])], Node) = {
+// val s:Node =
+// if (r.pk.isDefined) {
+// /** Table has a primkary key. */
+// val vs = t.lexvaluesNoNulls(r.pk.get.attrs)
+// nodemap(u, r.name, r.pk.get.attrs, vs)
+// } else
+// /** Table has no primkary key (but has some candidate keys). */
+// freshbnode()
+// (r.candidates.map(k => {
+// val values:List[CellValue] = k.attrs.map(a => t(a))
+// (k, values)
+// }), s)
+// }
+
+// /** The triples-generating functions start with databasemap: */
+// def directDB (u:StemIRI, db:Database) : RDFGraph = {
+// val idxables = db.keySet filter { rn => !db(rn).candidates.isEmpty }
+// val nodeMap = idxables map {rn => rn -> relation2KeyMap(u, db(rn))}
+// db.keySet.flatMap(rn => directR(u, db(rn), nodeMap, db))
+// }
+
+// def directR (u:StemIRI, r:Relation, nodes:NodeMap, db:Database) : RDFGraph =
+// /* flatMap.toSet assumes that no two triples from directT would be the same.
+// * We know this because relations with candidate keys are mapped to unique
+// * subjects, and potentially redundant rows get unique blank node subjects.
+// */
+// r.body.flatMap(t => directT(u, t, r, nodes, db)).toSet
+
+// def directT (u:StemIRI, t:Tuple, r:Relation, nodes:NodeMap, db:Database) : Set[Triple] = {
+// val s:Node =
+// if (r.candidates.size > 0) {
+// // Known to have at least one key, so take the first one.
+// val k = r.candidates(0)
+// val vs = t.lexvaluesNoNulls(k.attrs)
+// nodes.ultimateReferent(r.name, k, vs, db)
+// } else
+// /** Table has no candidate keys. */
+// freshbnode()
+// directS(u, s, t, r, nodes, db)
+// }
+
+// def directS (u:StemIRI, s:Node, t:Tuple, r:Relation, nodes:NodeMap, db:Database) : Set[Triple] = {
+// references(t, r).map(as => directN(u, s, as, r, t, nodes)) ++
+// scalars(t, r).map(a => directL(u, r.name, s, a, r.header, t))
+// }
+
+// var NextBNode = 97
+// def freshbnode () : BNode = {
+// val ret = NextBNode
+// NextBNode = NextBNode + 1
+// BNode(ret.toChar.toString)
+// }
+
+// def directL (u:StemIRI, rn:RelName, s:Node, a:AttrName, h:Header, t:Tuple) : Triple = {
+// val p = predicatemap (u, rn, List(a))
+// val l = t.lexvalue(a).get
+// val o = literalmap(l, h.sqlDatatype(a))
+// Triple(s, p, o)
+// }
+// def directN (u:StemIRI, s:Node, as:List[AttrName], r:Relation, t:Tuple, nodes:NodeMap) : Triple = {
+// val p = predicatemap (u, r.name, as)
+// val ls:List[LexicalValue] = t.lexvaluesNoNulls(as)
+// val target = r.fks(as)
+// val o:Object = nodes(target.rel)(target.key)(ls)
+// Triple(s, p, o)
+// }
+
+// // These implicits make nodemap and predicatemap functions prettier.
+// implicit def relName2string (rn:RelName) = rn.n
+// implicit def attrName2string (rn:AttrName) = rn.n
+
+// def nodemap (u:StemIRI, rn:RelName, as:List[AttrName], ls:List[LexicalValue]) : IRI = {
+// val pairs:List[String] = as.zip(ls).map(x => UE(x._1) + "." + UE(x._2.s))
+// u + ("/" + UE(rn) + "/" + pairs.mkString("_") + "#_")
+// }
+
+// def predicatemap (u:StemIRI, rn:RelName, as:List[AttrName]) : IRI =
+// u + ("/" + UE(rn) + "#" + as.mkString("_"))
+
+// def XSD (d:Datatype) : IRI =
+// d match {
+// case RDB.Datatype.INTEGER => IRI("http://www.w3.org/2001/XMLSchema#int")
+// case RDB.Datatype.FLOAT => IRI("http://www.w3.org/2001/XMLSchema#float")
+// case RDB.Datatype.DATE => IRI("http://www.w3.org/2001/XMLSchema#date")
+// case RDB.Datatype.TIME => IRI("http://www.w3.org/2001/XMLSchema#time")
+// case RDB.Datatype.TIMESTAMP => IRI("http://www.w3.org/2001/XMLSchema#timestamp")
+// case RDB.Datatype.CHAR => IRI("http://www.w3.org/2001/XMLSchema#char")
+// case RDB.Datatype.VARCHAR => IRI("http://www.w3.org/2001/XMLSchema#varchar")
+// case RDB.Datatype.STRING => IRI("http://www.w3.org/2001/XMLSchema#string")
+// }
+
+// def literalmap (l:LexicalValue, d:Datatype) : TypedLiteral =
+// TypedLiteral(l.s, XSD(d))
+
+// def UE (s:String) : String = s.replaceAll(" ", "+")
+// }
--- a/project/build/RDB2RDF.scala Fri Oct 15 16:42:41 2010 -0400
+++ b/project/build/RDB2RDF.scala Sun Oct 31 15:17:31 2010 -0400
@@ -1,21 +1,41 @@
import sbt._
-class RDB2RDF(info: ProjectInfo) extends DefaultWebProject(info) with BSBMPlugin {
-
- override def compileOptions = super.compileOptions ++ Seq(Unchecked, Deprecation, ExplainTypes)
-
+trait Common extends BasicScalaProject {
val scalatools = "scala-tools" at "http://scala-tools.org/repo-snapshots"
val scalatest = "org.scalatest" % "scalatest" % "1.2-for-scala-2.8.0.final-SNAPSHOT" % "test"
-
- val jetty6 = "org.mortbay.jetty" % "jetty" % "6.1.14" % "test"
- val servlet = "javax.servlet" % "servlet-api" % "2.5" % "provided"
+ override def compileOptions = super.compileOptions ++ Seq(Unchecked, Deprecation, ExplainTypes)
+ override def defaultExcludes = super.defaultExcludes || "*~"
+}
- val mysql = "mysql" % "mysql-connector-java" % "5.1.12"
+class FeDeRate(info: ProjectInfo) extends ParentProject(info) {
- override def defaultExcludes = super.defaultExcludes || "*~"
+ lazy val rdb = project("rdb", "rdb", new RDB(_))
+ lazy val sql = project("sql", "sql", new SQL(_), rdb)
+ lazy val rdf = project("rdf", "rdf", new RDF(_))
+ lazy val directmapping = project("directmapping", "directmapping", new DirectMapping(_), rdb, rdf)
+ lazy val sparql = project("sparql", "sparql", new SPARQL(_), rdf)
+ lazy val sparql2sql = project("sparql2sql", "sparql2sql", new SPARQL2SQL(_), sparql, sql)
+ lazy val sparql2sparql = project("sparql2sparql", "sparql2sparql", new SPARQL2SPARQL(_), sparql)
+ lazy val sql2sql = project("sql2sql", "sql2sql", new SQL2SQL(_), sql)
+ lazy val sparqlendpoint = project("sparqlendpoint", "sparqlendpoint", new SPARQLEndPoint(_), sparql2sql)
- override def webappUnmanaged = super.webappUnmanaged +++ ("src" / "main" / "resources" / "database.properties")
+ class RDB(info: ProjectInfo) extends DefaultProject(info) with Common
+ class SQL(info: ProjectInfo) extends DefaultProject(info) with Common
+ class RDF(info: ProjectInfo) extends DefaultProject(info) with Common
+ class DirectMapping(info: ProjectInfo) extends DefaultProject(info) with Common
+ class SPARQL(info: ProjectInfo) extends DefaultProject(info) with Common
+ class SPARQL2SQL(info: ProjectInfo) extends DefaultProject(info) with Common
+ class SPARQL2SPARQL(info: ProjectInfo) extends DefaultProject(info) with Common
+ class SQL2SQL(info: ProjectInfo) extends DefaultProject(info) with Common
+ class SPARQLEndPoint(info: ProjectInfo) extends DefaultWebProject(info) with Common with BSBMPlugin {
+ val jetty6 = "org.mortbay.jetty" % "jetty" % "6.1.14" % "test"
+ val servlet = "javax.servlet" % "servlet-api" % "2.5" % "provided"
+ val mysql = "mysql" % "mysql-connector-java" % "5.1.12"
+ override def webappUnmanaged = super.webappUnmanaged +++ ("src" / "main" / "resources" / "database.properties")
+ }
- val stemgraph = "w3c" %% "stemgraph" % "1.0"
+
+
+
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/rdb/src/main/scala/RDB.scala Sun Oct 31 15:17:31 2010 -0400
@@ -0,0 +1,95 @@
+package org.w3.sw.rdb
+
+import scala.collection.Set
+
+// Relational structure
+object RDB {
+
+ case class Database (m:Map[RelName, Relation]) {
+ def apply (rn:RelName) = m(rn)
+ def keySet () = m.keySet
+ }
+ object Database {
+ def apply (l:(Relation)*):Database =
+ Database(l.map{r => (r.name -> r)}.toMap)
+ }
+
+ case class Relation (name:RelName, header:Header, body:List[Tuple], candidates:List[CandidateKey], pk:Option[CandidateKey], fks:ForeignKeys)
+
+ case class Header (m:Map[AttrName, Datatype]) {
+ def apply (a:AttrName) = m(a)
+ def keySet () = m.keySet
+ def sqlDatatype (a:AttrName) : Datatype = m(a)
+ def contains (a:AttrName) : Boolean = m.contains(a)
+ }
+ object Header {
+ def apply (s:(String, Datatype)*):Header =
+ Header(s.map{p => (AttrName(p._1), p._2)}.toMap)
+ }
+ case class CandidateKey (attrs:List[AttrName])
+ object CandidateKey {
+ def apply (l:(String)*):CandidateKey =
+ CandidateKey(l.map{s => AttrName(s)}.toList)
+ }
+ implicit def cc2list (cc:CandidateKey) = cc.attrs
+
+ case class ForeignKeys (m:Map[List[AttrName], Target]) {
+ def apply (l:List[AttrName]) = m(l)
+ def keySet () = m.keySet
+ def contains (l:List[AttrName]) = m.contains(l)
+ }
+ object ForeignKeys {
+ def apply (s:(List[String], Target)*):ForeignKeys =
+ ForeignKeys(s.map{p => (p._1.map{s => AttrName(s)}, p._2)}.toMap)
+ }
+
+ case class Target (rel:RelName, key:CandidateKey)
+
+ case class Datatype(name:String) {
+ override def toString = "/* " + name + " */"
+ }
+ object Datatype {
+ val CHAR = Datatype("Char")
+ val VARCHAR = Datatype("Varchar")
+ val STRING = Datatype("String")
+ val INTEGER = Datatype("Int")
+ val FLOAT = Datatype("Float")
+ val DOUBLE = Datatype("Double")
+ val DATE = Datatype("Date")
+ val TIMESTAMP = Datatype("Timestamp")
+ val DATETIME = Datatype("Datetime")
+ }
+
+ case class Tuple (m:Map[AttrName, CellValue]) {
+ def apply (a:AttrName) = m(a)
+ def lexvalue (a:AttrName) : Option[LexicalValue] =
+ m(a) match {
+ case ␀() => None
+ case v:LexicalValue => Some(v)
+ }
+ def lexvaluesNoNulls (as:List[AttrName]) = as.map(a => m(a).asInstanceOf[LexicalValue])
+ def nullAttributes (h:Header) : Set[(AttrName)] = {
+ h.keySet.flatMap(a =>
+ lexvalue(a) match {
+ case None => Some(a)
+ case _ => None
+ })
+ }
+ }
+ object Tuple {
+ def apply (s:(String, CellValue)*):Tuple =
+ Tuple(s.map{p => (AttrName(p._1), p._2)}.toMap)
+ }
+
+ abstract class CellValue
+ case class LexicalValue (s:String) extends CellValue
+ case class ␀ () extends CellValue
+
+ case class RelName(n:String) {
+ override def toString = n
+ }
+ case class AttrName(n:String) {
+ override def toString = n
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/rdf/src/main/scala/RDF.scala Sun Oct 31 15:17:31 2010 -0400
@@ -0,0 +1,31 @@
+package org.w3.sw.rdf
+
+import java.net.URI
+
+case class RDFTriple(s:RDFSubject, p:RDFPredicate, o:RDFObject)
+
+sealed abstract class RDFSubject()
+case class RDFSubjectUri(uri:URI) extends RDFSubject
+case class RDFSubjectBlankNode(b:BlankNode) extends RDFSubject
+
+case class RDFPredicate(uri:URI)
+
+sealed abstract class RDFObject()
+case class RDFObjectUri(uri:URI) extends RDFObject
+case class RDFObjectBlankNode(b:BlankNode) extends RDFObject
+
+case class BlankNode(debugName:String)
+
+case class RDFLiteral(lexicalForm:String, datatype:Datatype) {
+ override def toString = "\"" + lexicalForm + "\"" + datatype
+}
+case class Datatype(uri:URI) {
+ override def toString = "^^" + uri
+}
+
+object RDFLiteral {
+ val StringDatatype = Datatype(new URI("http://www.w3.org/2001/XMLSchema#string"))
+ val IntegerDatatype = Datatype(new URI("http://www.w3.org/2001/XMLSchema#integer"))
+ val DateDatatype = Datatype(new URI("http://www.w3.org/2001/XMLSchema#date"))
+ // val DateTimeDatatype = Datatype(new URI("http://www.w3.org/2001/XMLSchema#dateTime"))
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/sparql/src/main/scala/GraphAnalyser.scala Sun Oct 31 15:17:31 2010 -0400
@@ -0,0 +1,72 @@
+package org.w3.sw.util
+
+import scala.collection.immutable._
+
+class GraphAnalyzer[A](m:Map[A, Set[A]]) {
+
+ def reaches (from:A, to:A) = {
+ if (m.contains(from))
+ new GraphAnalyzer[A](m + (from -> (m(from) + to)))
+ else
+ new GraphAnalyzer[A](m + (from -> Set(to)))
+ }
+ def pair (l:A, r:A) = {
+ reaches(l, r).reaches(r, l)
+ }
+ def neededFor (need:Set[A], t:A, visited:Set[A]):Boolean = {
+ if (!m.contains(t))
+ error("unknown symbol: " + t)
+ var ret:Boolean = false
+ m(t).map((r) => {
+ if (visited.contains(t)) return false
+ if (need.contains(t)) return true
+ ret |= neededFor(need, r, visited + t)
+ })
+ return ret
+ }
+
+}
+
+object GraphAnalyzer {
+ def apply[A]():GraphAnalyzer[A] = GraphAnalyzer() // Map[A, Set[A]]()
+ // def apply[A](list:List[A]):GraphAnalyzer[A] = new GraphAnalyzer(list)
+ // def apply[A](args:A*):GraphAnalyzer[A] = GraphAnalyzer(args.toList)
+}
+
+// object GraphAnalyzer {
+// }
+// //def createEmptyGraphAnalyzer () = GraphAnalyzer(Map[A, Set[A]]())
+
+ // case class Reacher (b:Map[String, Map[String, Set[String]]]) {
+ // def reaches (from:String, to:String) = {
+ // if (b.contains(from)) {
+ // if (b(from).contains(to)) {
+ // val back = b(from)(to) + from
+ // val fore = b(from) + (to -> back)
+ // val ret = Reacher(b + (from -> fore))
+ // println("duplicate path from " + from + " to " + to)
+ // // println("ret: " + ret)
+ // ret
+ // } else {
+ // println(from + "->" + to + " + " + this)
+ // val back = Set[String](from)
+ // val fore = b(from) + (to -> back)
+ // val ret = Reacher(b + (from -> fore))
+ // println("ret: " + ret)
+ // ret
+ // }
+ // } else {
+ // val back = Set[String](from)
+ // val fore = Map[String, Set[String]](to -> back)
+ // val ret = Reacher(b + (from -> fore))
+ // // println("ret: " + ret)
+ // ret
+ // }
+ // }
+ // def pair (l:String, r:String) = {
+ // reaches(l, r).reaches(r, l)
+ // }
+ // override def toString = b.toString
+ // }
+ // def createEmptyReacher () = Reacher(Map[String, Map[String, Set[String]]]())
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/sparql/src/main/scala/SPARQL.scala Sun Oct 31 15:17:31 2010 -0400
@@ -0,0 +1,431 @@
+package org.w3.sw.sparql
+
+import org.w3.sw.rdf._
+import scala.util.parsing.combinator._
+import java.net.URI
+
+object MyParsers extends RegexParsers {
+
+ val uri = """[a-zA-Z0-9:/#_\.\-]+""".r
+ val integer = """[0-9]+""".r
+ val name = """[a-zA-Z][a-zA-Z0-9_-]*|[a-zA-Z_][a-zA-Z0-9_]+""".r
+ var prefixes:Map[String, String] = Map()
+ var bnodes:Map[String, BNode] = Map()
+ var nextBNode = 1
+}
+
+import MyParsers._
+
+case class Select(distinct:Boolean, attrs:SparqlAttributeList, gp:GraphPattern, order:List[OrderElt], offset:Option[Int], limit:Option[Int]) {
+ override def toString =
+ "SELECT "+
+ { if (distinct) "DISTINCT " else "" }+
+ attrs+"\n"+gp+" "+
+ { if (order.size > 0) {order.map(o => o.toString).mkString("ORDER BY ", " ", " ") } else "" }+
+ { if (offset.isDefined) {"OFFSET " + offset.get + " "} else "" }+
+ { if (limit.isDefined) {"LIMIT " + limit.get + " "} else "" }
+}
+case class Construct(head:TriplesBlock, gp:GraphPattern)
+case class SparqlAttributeList(attributelist:List[Var]) {
+ override def toString = attributelist.toList.sortWith((l, r) => l.s < r.s).mkString(" ")
+}
+
+sealed abstract class GraphPattern {
+ def findVars ():Set[Var] = {
+ this match {
+ case TableFilter(gp2:GraphPattern, expr:Expression) =>
+ gp2.findVars
+
+ case TriplesBlock(triplepatterns) =>
+ /* Examine each triple, updating the compilation state. */
+ triplepatterns.foldLeft(Set[Var]())((x, y) => x ++ y.findVars)
+
+ case TableConjunction(list) =>
+ /* Examine each triple, updating the compilation state. */
+ list.foldLeft(Set[Var]())((x, y) => x ++ y.findVars)
+
+ case OptionalGraphPattern(gp2) =>
+ /* Examine each triple, updating the compilation state. */
+ gp2.findVars
+
+ case x => error("no code to handle " + x)
+ }
+ }
+ def findAssignables ():Set[Assignable] = {
+ this match {
+ case TableFilter(gp2:GraphPattern, expr:Expression) =>
+ gp2.findAssignables
+
+ case TriplesBlock(triplepatterns) =>
+ /* Examine each triple, updating the compilation state. */
+ triplepatterns.foldLeft(Set[Assignable]())((x, y) => x ++ y.findAssignables)
+
+ case TableConjunction(list) =>
+ /* Examine each triple, updating the compilation state. */
+ list.foldLeft(Set[Assignable]())((x, y) => x ++ y.findAssignables)
+
+ case OptionalGraphPattern(gp2) =>
+ /* Examine each triple, updating the compilation state. */
+ gp2.findAssignables
+
+ case x => error("no code to handle " + x)
+ }
+ }
+
+ def trim (terms:Set[Term]):GraphPattern = {
+ this match {
+ case TableFilter(gp2:GraphPattern, expr:Expression) =>
+ TableFilter(gp2.trim(terms), expr)
+
+ case TriplesBlock(triplepatterns) => {
+ val r0 = new org.w3.sw.util.GraphAnalyzer[Term](Map[Term, Set[Term]]())
+ /* Examine each triple, updating the compilation state. */
+ val r = triplepatterns.foldLeft(r0)((r, triple) => r.pair(triple.s, triple.o))
+ val useful = triplepatterns.foldLeft(Set[TriplePattern]())((s, t) => {
+ if (r.neededFor(terms, t.s, Set(t.o)) &&
+ r.neededFor(terms, t.o, Set(t.s))) s + t
+ else s
+ })
+ val useful2 =
+ if (useful.size == 0)
+ triplepatterns.foldLeft(Set[TriplePattern]())((s, t) => {
+ if (r.neededFor(terms, t.s, Set(t.o)) ||
+ r.neededFor(terms, t.o, Set(t.s))) s + t
+ else s
+ })
+ else useful
+ TriplesBlock(useful2.toList)
+ }
+
+ case TableConjunction(list) =>
+ /* Examine each triple, updating the compilation state. */
+ TableConjunction(list.map(gp2 => gp2.trim(terms)))
+
+ case OptionalGraphPattern(gp2) =>
+ /* Examine each triple, updating the compilation state. */
+ OptionalGraphPattern(gp2.trim(terms))
+
+ case x => error("no code to handle " + x)
+ }
+ }
+
+ def simplify ():GraphPattern = {
+ this match {
+ case TableFilter(gp2:GraphPattern, expr:Expression) =>
+ TableFilter(gp2.simplify, expr)
+
+ case tb:TriplesBlock => tb
+
+ case TableConjunction(list) => {
+ /* Eliminate series of TriplesBlocks. */
+ val (conjuncts, triples) = list.foldLeft((List[GraphPattern](), List[TriplePattern]()))((pair, gp2) => {
+ val (conj, trip) = pair
+ val gp3 = gp2.simplify
+ gp3 match {
+ case TriplesBlock(triplepatterns) =>
+ (conj, trip ++ triplepatterns)
+ case x => {
+ (conj ++ List(TriplesBlock(trip)), List[TriplePattern]())
+ }
+ }
+ })
+ val conj2 =
+ if (triples.size > 0) conjuncts ++ List(TriplesBlock(triples))
+ else conjuncts
+ if (conj2.size > 1) TableConjunction(conj2)
+ else if (conj2.size == 1) conj2(0)
+ else TriplesBlock(List[TriplePattern]())
+ }
+
+ case OptionalGraphPattern(gp2) =>
+ /* Examine each triple, updating the compilation state. */
+ OptionalGraphPattern(gp2.simplify)
+
+ case x => error("no code to handle " + x)
+ }
+ }
+}
+
+case class TriplesBlock(triplepatterns:List[TriplePattern]) extends GraphPattern {
+ override def toString = "{\n " + (triplepatterns.toList.map(s => s.toString.replace("\n", "\n ")).mkString("", " .\n ", " .\n")) + "}"
+ override def equals (other:Any):Boolean = other match {
+ case that:TriplesBlock => (that canEqual this) && triplepatterns.toSet == that.triplepatterns.toSet
+ case _ => false
+ }
+ override def canEqual(other : Any) : Boolean = other.isInstanceOf[TriplesBlock]
+ override def hashCode:Int = 41*triplepatterns.toSet.hashCode
+}
+case class TableConjunction(gps:List[GraphPattern]) extends GraphPattern {
+ assert (!(gps exists (x => { x match { case TableConjunction(_) => true case _ => false } })))
+ override def toString = "{\n " + (gps.toList.map(s => s.toString.replace("\n", "\n ")).mkString("\n ")) + "\n}\n"
+}
+case class TableDisjunction(gps:List[GraphPattern]) extends GraphPattern {
+ override def toString = "{\n " + (gps.toList.map(s => s.toString.replace("\n", "\n ")).mkString("\nUNION\n ")) + "\n}\n"
+}
+case class TableFilter(gp:GraphPattern, expr:Expression) extends GraphPattern {
+ override def toString = gp.toString + "\nFILTER (" + expr.toString + ")"
+}
+case class OptionalGraphPattern(gp:GraphPattern) extends GraphPattern {
+ override def toString = "OPTIONAL " + gp.toString
+}
+case class MinusGraphPattern(gp:GraphPattern) extends GraphPattern {
+ override def toString = "MINUS " + gp.toString
+}
+case class GraphGraphPattern(gp:GraphPattern) extends GraphPattern
+
+case class TriplePattern(s:Term, p:Term, o:Term) {
+ override def toString = s + " " + p + " " + o
+ def findVars ():Set[Var] = {
+ val varS:Set[Var] = s match {
+ case TermVar(v) => Set(v)
+ case _ => Set()
+ }
+ val varO:Set[Var] = o match {
+ case TermVar(v) => Set(v)
+ case _ => Set()
+ }
+ varS ++ varO
+ }
+ def findAssignables ():Set[Assignable] = {
+ val varS:Set[Assignable] = s match {
+ case TermVar(v) => Set(VarAssignable(v))
+ case TermBNode(v) => Set(BNodeAssignable(v))
+ case _ => Set()
+ }
+ val varO:Set[Assignable] = o match {
+ case TermVar(v) => Set(VarAssignable(v))
+ case TermBNode(b) => Set(BNodeAssignable(b))
+ case _ => Set()
+ }
+ varS ++ varO
+ }
+}
+
+case class Literal(lit:RDFLiteral) {
+ override def toString = lit match {
+ case RDFLiteral(s, RDFLiteral.IntegerDatatype) => s
+ case _ => lit.toString
+ }
+}
+
+sealed abstract class Assignable { val s:String }
+
+case class VarAssignable(v:Var) extends Assignable { val s = v.s }
+case class BNodeAssignable(b:BNode) extends Assignable { val s = b.s }
+
+case class Var(s:String) {
+ override def toString = "?" + s
+}
+
+case class Uri(s:String) {
+ override def toString = "<" + s + ">"
+}
+
+case class BNode(s:String) {
+ override def toString = "_:" + s
+}
+
+case class Expression(conjuncts:List[PrimaryExpression]) {
+ override def toString = conjuncts.map(e => e.toString()).mkString(" && ")
+}
+
+sealed abstract class PrimaryExpression
+case class PrimaryExpressionEq(left:SparqlTermExpression, right:SparqlTermExpression) extends PrimaryExpression {
+ override def toString = left.toString() + " = " + right.toString
+}
+case class PrimaryExpressionLt(left:SparqlTermExpression, right:SparqlTermExpression) extends PrimaryExpression {
+ override def toString = left.toString() + " < " + right.toString
+}
+case class PrimaryExpressionGt(left:SparqlTermExpression, right:SparqlTermExpression) extends PrimaryExpression {
+ override def toString = left.toString() + " > " + right.toString
+}
+case class SparqlTermExpression(term:Term) extends PrimaryExpression {
+ override def toString = term.toString
+}
+
+case class OrderElt(desc:Boolean, expr:Expression) {
+ override def toString = { if (desc) "DESC" else "ASC" } + "(" + expr.toString + ")"
+}
+
+sealed trait Term
+case class TermUri(u:Uri) extends Term {
+ override def toString = u.toString
+}
+case class TermBNode(b:BNode) extends Term {
+ override def toString = b.toString
+}
+case class TermVar(v:Var) extends Term {
+ override def toString = v.toString
+}
+case class TermLit(lit:Literal) extends Term {
+ override def toString = lit.toString
+}
+
+
+case class Sparql() extends JavaTokenParsers {
+
+ def select:Parser[Select] =
+ rep(prefixdecls) ~ "SELECT" ~ opt("DISTINCT") ~ attributelist ~ opt("WHERE") ~ groupgraphpattern ~ opt(order) ~ opt(offset) ~ opt(limit) ^^ {
+ case x~"SELECT"~d~a~w~gp~ord~off~lim => Select(d.isDefined, a, gp, ord.getOrElse(List[OrderElt]()), off, lim)
+ }
+
+ def order:Parser[List[OrderElt]] =
+ "ORDER" ~ "BY" ~ rep(orderelt) ^^ { case o~b~elts => elts }
+
+ def orderelt:Parser[OrderElt] = (
+ "ASC" ~ "(" ~ expression ~ ")" ^^ { case a~o~expr~c => OrderElt(false, expr) }
+ | "DESC" ~ "(" ~ expression ~ ")" ^^ { case a~o~expr~c => OrderElt(true, expr) }
+ | varr ^^ { case v => OrderElt(false, Expression(List(SparqlTermExpression(TermVar(v))))) }
+ )
+
+ def offset:Parser[Int] =
+ "OFFSET" ~ integer ^^ { case o~i => i.toInt }
+
+ def limit:Parser[Int] =
+ "LIMIT" ~ integer ^^ { case o~i => i.toInt }
+
+ def construct:Parser[Construct] =
+ rep(prefixdecls) ~ "CONSTRUCT" ~ constructpattern ~ opt("WHERE") ~ groupgraphpattern ^^ { case x~"CONSTRUCT"~a~w~gp => Construct(a, gp) }
+
+ def constructpattern:Parser[TriplesBlock] =
+ "{" ~ opt(triplesblock) ~ "}" ^^ { case "{"~tbOPT~"}" => tbOPT.getOrElse(TriplesBlock(List[TriplePattern]())) }
+
+ def prefixdecls:Parser[Unit] =
+ "PREFIX" ~ name ~ ":" ~ qnameORuri ^^ { case "PREFIX"~pre~":"~u => prefixes += (pre -> u.s) }
+
+ def filter:Parser[Expression] =
+ "FILTER" ~ "(" ~ expression ~ ")" ^^ { case "FILTER"~"("~expression~")" => expression }
+
+ def expression:Parser[Expression] =
+ repsep(primaryexpression, "&&") ^^
+ { Expression(_) }
+
+ def primaryexpression:Parser[PrimaryExpression] = (
+ value ~ "=" ~ value ^^
+ { case left ~ "=" ~ right => PrimaryExpressionEq(left, right) }
+ | value ~ "<" ~ value ^^
+ { case left ~ "<" ~ right => PrimaryExpressionLt(left, right) }
+ | value ~ ">" ~ value ^^
+ { case left ~ ">" ~ right => PrimaryExpressionGt(left, right) }
+ | value
+ )
+
+ def value:Parser[SparqlTermExpression] = (
+ qnameORuri ^^ { case x => SparqlTermExpression(TermUri(x)) }
+ | varr ^^ { x => SparqlTermExpression(TermVar(x)) }
+ | literal ^^ { x => SparqlTermExpression(TermLit(x)) }
+ )
+
+ def attributelist:Parser[SparqlAttributeList] =
+ rep(varr) ^^ { SparqlAttributeList(_) }
+
+ def groupgraphpattern:Parser[GraphPattern] = (
+ "{" ~ opt(triplesblock) ~ rep(graphpatternnottriplesORfilter ~ opt(triplesblock)) ~ "}" ^^
+ {
+ case "{"~tbOPT~gpntORf_tbOPT~"}" => {
+
+// case class TriplesBlock(triplepatterns:List[TriplePattern]) extends GraphPattern
+// case class TableConjunction(gps:List[GraphPattern]) extends GraphPattern
+// case class TableDisjunction(gps:List[GraphPattern]) extends GraphPattern
+// case class TableFilter(gp:GraphPattern, expr:Expression) extends GraphPattern
+// case class OptionalGraphPattern(gp:GraphPattern) extends GraphPattern
+// case class GraphGraphPattern(gp:GraphPattern) extends GraphPattern
+
+ // println("groupgraphpattern(" + tbOPT + ", " + gpntORf_tbOPT + ")")
+ val init:Option[GraphPattern] = tbOPT
+ gpntORf_tbOPT.foldLeft(init)((gp, lentry) => {//println("match: " + (gp, lentry))
+ // print("case (" + gp + ", " + lentry + ")")
+ (gp, lentry) match {
+ case (Some(TableFilter(TriplesBlock(l), Expression(lexp))), ~(TableFilter(null, Expression(expr)), Some(TriplesBlock(r)))) => Some(TableFilter(TriplesBlock(l ++ r), Expression(lexp ++ expr)))
+ case (Some(TriplesBlock(l)), ~(TableFilter(null, expr), Some(TriplesBlock(r)))) => Some(TableFilter(TriplesBlock(l ++ r), expr))
+ case (Some(gp ), ~(TableFilter(null, expr), None )) => Some(TableFilter(gp, expr))
+ case (None, ~(TableFilter(null, expr), Some(TriplesBlock(r)))) => Some(TableFilter(TriplesBlock(r), expr))
+
+ // case (None, ~(TableConjunction(gps), None )) => TableConjunction(gps)
+ // case (Some(gp), ~(TableConjunction(gps), None )) => TableConjunction(List(List(gp) ++ gps))
+ // case (None, ~(TableConjunction(gps), Some(tb))) => TableConjunction(List(gps ++ List(tb)))
+ // case (Some(gp), ~(TableConjunction(gps), Some(tb))) => TableConjunction(List(List(gp) ++ gps ++ List(tb)))
+
+ case (None , ~(x, None )) => Some(x )
+ case (Some(TableConjunction(l)), ~(x, None )) => Some(TableConjunction(l ++ List( x )))
+ case (Some(gp ), ~(x, None )) => Some(TableConjunction( List(gp, x )))
+ case (None , ~(x, Some(tb))) => Some(TableConjunction( List( x, tb)))
+ case (Some(TableConjunction(l)), ~(x, Some(tb))) => Some(TableConjunction(l ++ List( x, tb)))
+ case (Some(gp ), ~(x, Some(tb))) => Some(TableConjunction( List(gp, x, tb)))
+
+ case x => error("found " + x)
+ }
+ }).get
+ }
+ }
+ )
+
+ def graphpatternnottriplesORfilter:Parser[GraphPattern] = (
+ "OPTIONAL"~groupgraphpattern ^^ { case "OPTIONAL"~ggp => OptionalGraphPattern(ggp) }
+ | "MINUS"~groupgraphpattern ^^ { case "MINUS"~ggp => MinusGraphPattern(ggp) }
+ | rep1sep(groupgraphpattern, "UNION") ^^ { x => if (x.size > 1) TableDisjunction(x) else x(0) }
+ | "GRAPH"~uri~groupgraphpattern ^^ { case "GRAPH"~u~ggp => GraphGraphPattern(ggp) }
+ | filter ^^ { x => TableFilter(null, x) }
+ )
+
+ def triplesblock:Parser[TriplesBlock] =
+ rep1sep(triplepattern, ".") ~ opt(".") ^^ { case pats~x => TriplesBlock(pats) }
+
+ def triplepattern:Parser[TriplePattern] =
+ subject ~ predicate ~ objectt ^^ { case s~p~o => TriplePattern(s, p, o) }
+
+ def subject:Parser[Term] = (
+ qnameORuri ^^ { case x => TermUri(x) }
+ | bnode ^^ { x => TermBNode(x) }
+ | varr ^^ { x => TermVar(x) }
+ )
+
+ def predicate:Parser[Term] = (
+ qnameORuri ^^ { x => TermUri(x) }
+ | "a" ^^ { x => TermUri(Uri("http://www.w3.org/1999/02/22-rdf-syntax-ns#type")) }
+ | varr ^^ { x => TermVar(x) }
+ )
+
+ def objectt:Parser[Term] = (
+ qnameORuri ^^ { case x => TermUri(x) }
+ | bnode ^^ { x => TermBNode(x) }
+ | varr ^^ { x => TermVar(x) }
+ | literal ^^ { x => TermLit(x) }
+ )
+
+ def qnameORuri:Parser[Uri] = (
+ "<"~uri~">" ^^ { case "<"~x~">" => Uri(x) }
+ | name~":"~name ^^ {
+ case prefix~":"~localName => try {
+ Uri(prefixes(prefix) + localName)
+ } catch {
+ case e:java.util.NoSuchElementException =>
+ throw new Exception("unknown prefix " + prefix)
+ }
+ }
+ )
+
+ def bnode:Parser[BNode] =
+ "_:"~name ^^ { case "_:"~name => BNode(name) }
+
+ def literal:Parser[Literal] = (
+ stringLiteral~"^^"~qnameORuri ^^
+ {
+ case lit~"^^"~dt => Literal(RDFLiteral(lit.substring(1,lit.size - 1), dt.s match {
+ case "http://www.w3.org/2001/XMLSchema#string" => RDFLiteral.StringDatatype
+ case "http://www.w3.org/2001/XMLSchema#integer" => RDFLiteral.IntegerDatatype
+ case "http://www.w3.org/2001/XMLSchema#date" => RDFLiteral.DateDatatype
+ // case "http://www.w3.org/2001/XMLSchema#dateTime" => RDFLiteral.DateTimeDatatype
+ case x => error("only programed to deal with string and integer, not " + x)
+ }))
+ }
+ | integer ^^ { l => Literal(RDFLiteral(l, RDFLiteral.IntegerDatatype)) }
+ )
+
+ def varr:Parser[Var] = "?"~ident ^^ { case "?"~x => Var(x) }
+
+}
+
+object Sparql {
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/sparql2sparql/src/main/scala/SparqlToSparql.scala Sun Oct 31 15:17:31 2010 -0400
@@ -0,0 +1,386 @@
+/* SparqlToSparql: convert SPARQL queries to sound SQL queries.
+ *
+
+ *
+ */
+
+package org.w3.sw.sparql2sparql
+
+import scala.util.parsing.combinator._
+import org.w3.sw.sparql
+
+case class MapTarget (term:sparql.Term, nm:Option[NodePattern])
+
+case class NodePatternMap (m:Map[sparql.Var, NodePattern]) {
+ def var2maptarget (t:sparql.Term) : MapTarget = {
+ t match {
+ case sparql.TermVar(v) => MapTarget(t, m.get(v))
+ case _ => MapTarget(t, None)
+ }
+ }
+}
+
+case class NodePattern (iface:String, direct:String)
+
+case class SparqlMap (construct:sparql.Construct, nodemap:NodePatternMap)
+
+object SparqlToSparql {
+
+ /**
+ * Some tools for making pretty output, e.g. shortening strings.
+ */
+ var RuleLabels = scala.collection.mutable.Map[String,String]()
+ var Abbreviations = scala.collection.mutable.Map[String,String]()
+ def _shorten (s:String) = {
+ val s2 = if (RuleLabels.contains(s)) RuleLabels(s)
+ else s
+ val s3 = Abbreviations.foldLeft(s2)((s, ss) => s.replace(ss._1, ss._2))
+ s3
+ }
+
+
+ /**
+ * Substitute the terms in a triple pattern or a graph pattern with other terms.
+ */
+ def substituteTerm (changeMe:sparql.Term, from:sparql.Term, to:sparql.Term, nm:Option[NodePattern]):sparql.Term = {
+ if (changeMe == from) {
+ from match {
+ case sparql.TermVar(v) => {
+ if (nm.isDefined) {
+ val nodeMap = nm.get
+ to match {
+ case sparql.TermVar(v2) => {
+ println("reverse map(" + v2 + ",\n " + nodeMap.direct + ",\n " + nodeMap.iface)
+ to
+ }
+ case sparql.TermBNode(b) => {
+ println("reverse map(" + b + ",\n " + nodeMap.direct + ",\n " + nodeMap.iface)
+ to
+ }
+ case sparql.TermUri(u) => {
+ val s:String = u.s
+ val r = u.s.replaceAll(nodeMap.iface, nodeMap.direct)
+ //println("\"" + u.s + "\".replaceAll("+nodeMap.iface+", "+nodeMap.direct+") = \""+r+"\"")
+ sparql.TermUri(sparql.Uri(u.s.replaceAll(nodeMap.iface, nodeMap.direct))) // !!! failure here should error()
+ }
+ case sparql.TermLit(l) =>
+ error("variable " + v + " bound to literlal " + l + " but should be a node as indicated by " + nodeMap)
+ }
+ } else to
+ }
+ case _ => to
+ }
+ } else changeMe
+ }
+
+ def substitute (gp:sparql.GraphPattern, from:sparql.Term, target:MapTarget) = {
+ gp match {
+ case sparql.TriplesBlock(triplepatterns) => sparql.TriplesBlock(
+ triplepatterns.map((tp) => {
+ // println("sub(" + tp.o + ", " + from + ", " + to + ") => ")
+ // println(substituteTerm(toTerm(tp.o), tp.o, from, target.term))
+ sparql.TriplePattern(substituteTerm(tp.s, from, target.term, target.nm),
+ tp.p,
+ substituteTerm(tp.o, from, target.term, target.nm))
+ }
+ ))
+ case _ => error("not implemented" + gp)
+ }
+ }
+
+ type VarMap = Map[sparql.Var, MapTarget]
+
+ def substituteGraphPattern (gp:sparql.GraphPattern, vartargetmap:VarMap, varPrefix:String):sparql.GraphPattern = {
+ /**
+ * Substitute gp (the rule body) with the variables and constants from the
+ * query which matched variables in the rule head.
+ */
+ val mapped = vartargetmap.foldLeft(gp)((incrementalGP, vartarget) => {
+ val (varr, target) = vartarget
+ val ret = substitute(incrementalGP, sparql.TermVar(varr), target)
+ //println("^^incrementalGP: " + incrementalGP + "\nvartarget: " + vartarget + "\nret: " + ret)
+ ret;
+ })
+
+ /**
+ * "Uniquely" prefix unmapped vars to void conflict with other rules.
+ */
+ // val bound = Set[sparql.Var](vartargetmap.map((vartarget) => vartarget._1))
+ val bound = vartargetmap.foldLeft(Set[sparql.Var]())((s, vartarget) => s + vartarget._1)
+ val mappedTo = vartargetmap.foldLeft(Set[sparql.Term]())((s, vartarget) => {
+ val MapTarget(term, optnp) = vartarget._2
+ (term, optnp) match {
+ case (sparql.TermUri(u), Some(np)) => {
+ s + sparql.TermUri(sparql.Uri(u.s.replaceAll(np.iface, np.direct))) // !!! failure here should error()
+ }
+ case (_, _) => s + vartarget._2.term
+ }
+ })
+ val vars = gp.findVars
+ val diff = vars -- bound
+ diff.foldLeft(mapped)((incrementalGP, varr) => {
+ val full = substitute(incrementalGP, sparql.TermVar(varr),
+ MapTarget(sparql.TermVar(sparql.Var(varPrefix + varr.s)),
+ None// !! vartargetmap(varr).nm
+ ))
+ full.trim(mappedTo)
+ })
+ }
+ case class RuleIndex (trigger:sparql.TriplePattern, smap:SparqlMap) {
+ override def toString = "{ \"" + trigger + "\" } => {\"\n " + _shorten(smap.construct.gp.toString).replace("\n", "\n ") + "\n\"}"
+ def transform (tp:sparql.TriplePattern):sparql.GraphPattern = {
+ substitute(substitute(smap.construct.gp, trigger.s, smap.nodemap.var2maptarget(tp.s)),
+ trigger.o, smap.nodemap.var2maptarget(tp.o))
+ }
+ }
+
+ /**
+ * Bindings keep track of all the ways that a particular rule has been invoked
+ * in order to cover a particular query pattern. It's a mapping from a rule to
+ * the list of "editions" (invocations) of that rule.
+ */
+ case class Bindings (b:Map[sparql.Construct, List[VarMap]]) {
+ def countEditions () = {
+ var count = 0
+ b.map((constructlist) =>
+ constructlist._2.map((_) =>
+ count = count + 1))
+ count
+ }
+ def toGraphPattern (ident:String):sparql.GraphPattern = {
+ var conjNo = 0
+ val conjuncts = b.keySet.toList.flatMap(construct => {
+ val l = b(construct)
+ val patterns:List[sparql.GraphPattern] = l.map((vartermmap) => {
+ val unique = ident + conjNo + "_"
+ conjNo = conjNo + 1
+ substituteGraphPattern(construct.gp, vartermmap, unique)
+ })
+ patterns
+ })
+
+ if (conjuncts.size == 0)
+ sparql.TriplesBlock(List[sparql.TriplePattern]())
+ else if (conjuncts.size > 1)
+ sparql.TableConjunction(conjuncts).simplify
+ else
+ conjuncts(0)
+ }
+ override def toString = "Bindings(Map(" + b.map((constructlist) => {
+ val (construct, l) = constructlist
+ "\"" + _shorten(construct.head.toString) + "\" -> List(" + l.map((vartermmap) => {
+ "Map(" + vartermmap.map((varterm) => {
+ val (varr, term) = varterm
+ varr.toString + ":" + term.toString
+ }) + ")"
+ }).mkString("\n ", ",\n ", "") + ")"
+ }).mkString("\n ", ",\n ", "") + "))"
+ /**
+ * Make sure a particular rule has an entry in the Bindings.
+ */
+ def ensureGraphPattern (construct:sparql.Construct) = {
+ if (b.contains(construct)) this
+ else Bindings(b + (construct -> List[VarMap]()))
+ }
+ // val varsS:Option[Bindings] = vars.maybeRebind(construct, v, tos)
+ // b:Map[sparql.Construct, List[VarMap]]
+ def mustBind (construct:sparql.Construct, nodemap:NodePatternMap, vs:sparql.Term, tos:sparql.Term, vo:sparql.Term, too:sparql.Term):Bindings = {
+ /* ridiculous traversal for the first viably matching rule edition. */
+ var matched = false
+ val existing:List[VarMap] = b(construct).map((map:VarMap) => {
+ def _matches (l:sparql.Term, r:sparql.Term):(Boolean, VarMap) = {
+ val empty:VarMap = Map[sparql.Var, MapTarget]()
+ (l, r) match {
+ case (v:sparql.TermVar, x) =>
+ // println("(v:sparql.TermVar, x)" + v.v + ":" + x)
+ if (map.contains(v.v)) {
+ if (r == map(v.v).term) (true, empty)
+ else (false, empty)
+ } else {
+ (true, Map[sparql.Var, MapTarget](v.v -> MapTarget(r, nodemap.m.get(v.v))))
+ }
+ case (x, v:sparql.TermVar) => {
+ // println("query variable " + v + " known equal to " + x + " at compile time")
+ (true, empty)
+ }
+ case (x, y) =>
+ // println("(x, y)" + x + "?=" + y)
+ if (x == y) (true, empty)
+ else (false, empty)
+ }
+ }
+ val (sMatches, sBindings) = _matches(vs, tos)
+ val (oMatches, oBindings) = _matches(vo, too)
+
+ if (sMatches & oMatches) {
+ matched = true
+ map ++ sBindings ++ oBindings
+ } else
+ map
+ })
+ if (matched)
+ Bindings(b + (construct -> existing))
+ else {
+ Bindings(b.map((constructlist) => {
+ val (oldConstr, l) = constructlist
+ if (oldConstr == construct) {
+ def _newBinding (l:sparql.Term, r:sparql.Term):VarMap = {
+ val empty:VarMap = Map[sparql.Var, MapTarget]()
+ (l, r) match {
+ case (v:sparql.TermVar, _) =>
+ Map[sparql.Var, MapTarget](v.v -> MapTarget(r, nodemap.m.get(v.v)))
+ case (b:sparql.TermBNode, _) => {
+ println(".. synthetic query variable " + b + "")
+ Map[sparql.Var, MapTarget]()
+ // println("@@ mustBind:_newBinding(BNode) + " + b)
+ // Map(sparql.Var("bnode_" + b.b.s) -> r) // !!!
+ }
+ case (_, v:sparql.TermVar) => {
+ println(".. query variable " + v + " known equal to " + l + " at compile time")
+ Map[sparql.Var, MapTarget]()
+ }
+ case (_, _) => Map[sparql.Var, MapTarget]()
+ }
+ }
+ val ent = _newBinding(vs, tos) ++ _newBinding(vo, too)
+ val list = l ++ List(ent)
+ (construct -> list)
+ } else {
+ (oldConstr -> l)
+ }
+ }))
+ }
+ }
+ }
+ def createEmptyBindings () = Bindings(Map[sparql.Construct, List[VarMap]]())
+
+ case class RuleMap (rules:Map[sparql.Uri, List[RuleIndex]]) {
+ /**
+ * Recursively examine the first triple pattern in a graph pattern and add
+ * invocations to the bindings to produce that triple.
+ */
+ def transform (prove:List[sparql.TriplePattern], used:Set[sparql.TriplePattern], varsP:Bindings):Bindings = {
+ val _pad = used.foldLeft("")((s, x) => s + " ")
+ def _deepPrint (s:String):Unit = {
+ //print(used.size + ":" + _pad + s.replace("\n", "\n" + _pad) + "\n\n")
+ }
+ def _deepPrint1 (prefix:String, s:String):Unit = {
+ val p = used.size + ":" + prefix + _pad
+ //print(p + s.replace("\n", "\n" + p) + "\n\n")
+ }
+
+ val car = prove(0)
+ val cdr = prove.filterNot (_ == car)
+ _deepPrint("RuleMap.transform(" + _shorten(car.toString) + ", " + varsP.toString + ")")
+ val xform = car.p match {
+ case sparql.TermUri(u) =>
+ // for each rule that supplies predicate u
+ var _ruleNo = 0;
+ rules(u).foldLeft(createEmptyBindings)((bindings, hornRule) => {
+ val _prefix = "rule:" + _ruleNo
+ _ruleNo = _ruleNo + 1
+ _deepPrint1(_prefix, "trying " + _shorten(hornRule.trigger.toString))
+ val vars = varsP.ensureGraphPattern(hornRule.smap.construct)
+ // try matching the subject
+ val varss:Bindings = vars.mustBind(hornRule.smap.construct, hornRule.smap.nodemap, hornRule.trigger.s, car.s, hornRule.trigger.o, car.o)
+ val ret =
+ if (cdr.size > 0) {
+ transform(cdr, used + car, varss)
+ } else {
+ _deepPrint1("match!", _shorten(hornRule.trigger.toString) + "(" + _shorten(car.toString) + ") matches with " + _shorten(varss.toString))
+ varss
+ }
+ _deepPrint1(_prefix, _shorten(hornRule.trigger.toString) + "(" + _shorten(car.toString) + ") matches ..." + bindings.toString + ret.toString)
+
+ /* Magic Huristics */
+ if (bindings.countEditions == 0) {
+ //print("choose: bindings.countEditions == 0 => " + ret + "\n\n")
+ ret
+ } else if (ret.countEditions == 0) {
+ //print("choose: ret.countEditions == 0 => " + bindings + "\n\n")
+ bindings
+ } else if (ret.countEditions < bindings.countEditions) {
+ //print("choose: "+ret.countEditions+" < "+bindings.countEditions+" => " + ret + "\n\n")
+ ret
+ } else {
+ //print("choose: "+ret.countEditions+" > "+bindings.countEditions+" => " + bindings + "\n\n")
+ bindings
+ }
+ })
+ case _ => error("not implemented: " + car.p)
+ }
+ _deepPrint("RuleMap.transform(" + _shorten(car.toString) + ") => " + _shorten(xform.toString))
+ xform
+ }
+
+ override def toString = "RuleMap(Map(" + rules.map((ent) => {
+ val (u, l) = ent
+ "\"" + _shorten(u.toString) + "\" -> List(" + l.map((hr) => {
+ _shorten(hr.toString).replace("\n", "\n ")
+ }).mkString("\n ", ",\n ", "") + ")"
+ }).mkString("\n ", ",\n ", "") + "))"
+
+ }
+
+ def mapGraphPattern (gp:sparql.GraphPattern, ruleMap:RuleMap, ident:String):sparql.GraphPattern = {
+ gp match {
+ case sparql.TriplesBlock(tps) => {
+ val emptyBindings = createEmptyBindings
+ val b:Bindings = ruleMap.transform(tps, Set[sparql.TriplePattern](), emptyBindings)
+ //print("mapGraphPattern: " + _shorten(gp.toString) + ") => " + _shorten(b.toString) + "\n\n")
+ val g:sparql.GraphPattern = b.toGraphPattern(ident)
+ //print("mapGraphPattern: ... instantiated: " + _shorten(g.toString) + "\n\n")
+ g
+ }
+ case sparql.TableFilter(gp2:sparql.GraphPattern, expr:sparql.Expression) =>
+ sparql.TableFilter(mapGraphPattern(gp2, ruleMap, ident), expr)
+ case sparql.MinusGraphPattern(gp2) =>
+ sparql.MinusGraphPattern(mapGraphPattern(gp2, ruleMap, ident))
+ case sparql.OptionalGraphPattern(gp2) =>
+ sparql.OptionalGraphPattern(mapGraphPattern(gp2, ruleMap, ident))
+ case sparql.GraphGraphPattern(gp2) =>
+ sparql.GraphGraphPattern(mapGraphPattern(gp2, ruleMap, ident))
+ case sparql.TableDisjunction(l) =>
+ sparql.TableDisjunction({l.foldLeft((List[sparql.GraphPattern](), 0))((p, gp2) => {
+ val (l, disjNo) = p
+ (l ++ List(mapGraphPattern(gp2, ruleMap, ident + disjNo + "_")), disjNo + 1)
+ })}._1)
+ case sparql.TableConjunction(l) =>
+ sparql.TableConjunction({l.foldLeft((List[sparql.GraphPattern](), 0))((p, gp2) => {
+ val (l, conjNo) = p
+ (l ++ List(mapGraphPattern(gp2, ruleMap, ident + conjNo + "_")), conjNo + 1)
+ })}._1)
+ }
+ }
+
+ def apply (query:sparql.Select, maps:List[SparqlMap]) : sparql.Select = {
+ var _ruleNo = 0
+ val ruleMap = RuleMap({
+ maps.foldLeft(Map[sparql.Uri, List[RuleIndex]]())((m, sm) => {
+ val SparqlMap(rule, nodemap) = sm
+ // Register abbreviations for debugging output.
+ RuleLabels.update(rule.head.toString, "head" + _ruleNo)
+ RuleLabels.update(rule.gp.toString, "body" + _ruleNo)
+
+ _ruleNo = _ruleNo + 1
+ rule.head.triplepatterns.foldLeft(m)((m, tp) => m + ({
+ tp.p match {
+ case sparql.TermUri(u) => u -> {
+ if (m.contains(u)) m(u) ++ List(RuleIndex(tp, sm))
+ else List(RuleIndex(tp, sm))}
+ case _ => error("not implemented: " + tp.p)
+ }
+ }))
+ })
+ })
+ //println("ruleMap: " + ruleMap)
+ sparql.Select(
+ query.distinct,
+ query.attrs,
+ mapGraphPattern(query.gp, ruleMap, "_"),
+ query.order, query.offset, query.limit
+ )
+ }
+
+}
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/sparql2sql/src/main/scala/SparqlToSql.scala Sun Oct 31 15:17:31 2010 -0400
@@ -0,0 +1,1212 @@
+/** SparqlToSql: convert SPARQL queries to sound SQL queries.
+ * <a href="http://www.w3.org/TR/rdf-sparql-query/#sparqlDefinition">SPARQL semantics</a> defines the evalutation of an abstract query on a dataset.
+ * This object maps a SPARQL abstract query over a <a href="@@">direct graph</a> to an SQL abstract query over the constituant relations.
+ * <p/>
+ * Please read from the bottom -- i.e. apply calls mapGraphPattern with the root
+ * graph pattern. mapGraphPattern handles all the graph pattern types in SPARQL,
+ * effectively peforming the Convert Graph Patterns step in SPARQL 1.0 12.2.1
+ * <a href="http://www.w3.org/TR/rdf-sparql-query/#convertGraphPattern">SPARQL rules for converting graph patterns</a>
+ * with the target semantics in SQL instead of SPARQL.
+ */
+
+package org.w3.sw.sparql2sql
+
+import scala.util.parsing.combinator._
+import java.net.URI
+import org.w3.sw.rdb
+import org.w3.sw.sql
+import org.w3.sw.sql.PrettySql.toPrettySql
+import org.w3.sw.rdf
+import org.w3.sw.sparql
+import org.w3.sw.util
+
+case class StemURI(s:String)
+case class PrimaryKey(attr:rdb.RDB.AttrName)
+
+sealed abstract class Binding
+case class RDFNode(relvarattr:sql.RelVarAttr) extends Binding
+case class Str(relvarattr:sql.RelVarAttr) extends Binding
+case class Int(relvarattr:sql.RelVarAttr) extends Binding
+case class Enum(relvarattr:sql.RelVarAttr) extends Binding
+
+/**
+ * Converts a SPARQL object to an SQL object equivalent over the direct graph.
+ *
+ * @see {@link org.w3.sw.sparql.Sparql Sparql}
+ * @see {@link org.w3.sw.sql.Sql#Select Sql}
+ */
+object SparqlToSql {
+ /**
+ * Accumulated state for generating an Sql object.
+ *
+ * @param joins an AddOrderedSet of SQL joins
+ * @param varmap a map from Sparql.Assignable to SQL terms
+ * @param exprs a set of accumulated SQL expressions
+ */
+ case class R2RState(joins:util.AddOrderedSet[sql.Join], varmap:Map[sparql.Assignable, SQL2RDFValueMapper], exprs:Set[sql.Expression])
+
+ /**
+ * Binding for a sparql.Variable or BNode
+ */
+ sealed abstract class FullOrPartialBinding
+ case class FullBinding(relvarattr:sql.RelVarAttr) extends FullOrPartialBinding
+ case class BindingConstraint(expr:sql.RelationalExpression, relvarattr:sql.RelVarAttr)
+
+ /**
+ * Partial binding, a variable only (so far) found in OPTIONALs or assymetric
+ * UNIONs.
+ */
+ case class PartialBinding(binders:Set[BindingConstraint]) extends FullOrPartialBinding
+
+ /**
+ * Convert a binding to an SQL expression.
+ * <p/>
+ * example return:
+ * <code>if (g_union1._DISJOINT_ != 0, g_union1.who, if (g_union2._DISJOINT_ != 3, g_union2.who, NULL))</code>
+ * @param against binding to be expressed
+ * @return SQL expression representing that binding
+ */
+ def toExpr(against:FullOrPartialBinding):sql.Expression = {
+ against match {
+ case FullBinding(relvarattr) =>
+ sql.PrimaryExpressionAttr(relvarattr)
+ case PartialBinding(binders) =>
+ binders.toList.reverse.foldLeft(sql.ConstNULL():sql.Expression)((exp, binding) => {
+ val BindingConstraint(expr, relvarattr) = binding
+ sql.IfElse(expr, sql.PrimaryExpressionAttr(relvarattr), exp)
+ })
+ }
+ }
+ /**
+ * Accumulate bindings on previously bound sparql variables.
+ * @param binding previous binding
+ * @param relVarAttr SQL relvar attribute to be bound, e.g. <code>G_union1.who</code>
+ * @param expr expr binding constraint, e.g. <code>G_opt6._DISJOINT_ IS NULL</code> or <code>G_union1._DISJOINT_!=0</code>
+ * @return PartialBinding on old constraints plus expr=relVarAttr
+ */
+ def addExpr(binding:FullOrPartialBinding, relVarAttr:sql.RelVarAttr, expr:sql.RelationalExpression):FullOrPartialBinding = {
+ binding match {
+ case FullBinding(relvarattr) =>
+ error("Unexpected FullBinding to " + relvarattr + "\n" +
+ "because subselectVars should only find a pre-existing PartialBindings.")
+ // return binding if this codepath is needed later.
+ case PartialBinding(binders) =>
+ /**
+ * Add a new BindingConstraint to existing one.
+ * e.g. existing constraint: G_union1._DISJOINT_!=0,G_union1.name
+ * add for second side of union: G_union1._DISJOINT_!=1,G_union1.name
+ */
+ PartialBinding(binders + BindingConstraint(expr, relVarAttr))
+ }
+ }
+
+ /**
+ * SQL terms representing SPARQL variables and bnodes.
+ */
+ sealed abstract class SQL2RDFValueMapper(binding:FullOrPartialBinding)
+ case class IntMapper(binding:FullOrPartialBinding) extends SQL2RDFValueMapper(binding)
+ case class StringMapper(binding:FullOrPartialBinding) extends SQL2RDFValueMapper(binding)
+ case class DateMapper(binding:FullOrPartialBinding) extends SQL2RDFValueMapper(binding)
+ /**
+ * map to a URL for a tuple in the database.
+ */
+ case class RDFNoder(relation:rdb.RDB.RelName, binding:FullOrPartialBinding) extends SQL2RDFValueMapper(binding)
+ /**
+ * map to a blank node label for a tuple in the database.
+ */
+ case class RDFBNoder(relation:rdb.RDB.RelName, binding:FullOrPartialBinding) extends SQL2RDFValueMapper(binding)
+
+ /**
+ * a URL representing a tuple in a database.
+ */
+ case class NodeUri(stem:Stem, rel:Rel, attr:Attr, v:CellValue)
+
+ /**
+ * url stem to base the direct graph.
+ * <p/>
+ * e.g. http://mydb.example/theDatabase
+ */
+ case class Stem(s:String) {
+ override def toString = "" + s
+ }
+
+ /**
+ * SQL relation (table) name
+ */
+ case class Rel(s:String) {
+ override def toString = "" + s
+ }
+ /**
+ * SQL attribute (column) name
+ */
+ case class Attr(s:String) {
+ override def toString = "" + s
+ }
+ /**
+ * value in a database
+ */
+ case class CellValue(s:String)
+ /**
+ * a URL representing a predicate in a database.
+ */
+ case class PUri(stem:Stem, rel:Rel, attr:Attr) {
+ override def toString = "<" + stem + "/" + rel + "#" + attr + ">"
+ }
+ /**
+ * parse predicate URL in direct graph into stem, relation and attribute
+ * <pre>stemURI + '/' + (\w+) + '#' (\w+)</pre>
+ */
+ def parsePredicateURI(u:sparql.Uri):PUri = {
+ val x:String = u.s
+ val uri = new URI(x)
+ val path = uri.getPath().split("/").toList.filterNot(_ == "")
+ val subPath = path.slice(0, path.size - 1).mkString("/")
+ val stem = uri.getScheme() + "://" + uri.getAuthority + "/" + subPath
+ PUri(Stem(stem), Rel(path.last), Attr(uri.getFragment))
+ }
+
+ /**
+ * parse node URL in direct graph into stem, relation, attribute and value
+ * <pre>stemURI + '/' (\w+) '/' (\w+) '.' (\w+) '#record'</pre>
+ */
+ def parseObjectURI(u:sparql.Uri):NodeUri = {
+ try {
+ val x:String = u.s
+ val uri = new URI(x)
+ val path = uri.getPath().split("/").toList.filterNot(_ == "")
+ val subPath = path.slice(0, path.size - 2).mkString("/")
+ val rel = path(path.size - 2)
+ val attrPair = path(path.size-1).split("\\.")
+ val stem = uri.getScheme() + "://" + uri.getAuthority + "/" + subPath
+ assert("record" == uri.getFragment)
+ NodeUri(Stem(stem), Rel(rel), Attr(attrPair(0)), CellValue(attrPair(1)))
+ } catch {
+ case e:java.lang.IndexOutOfBoundsException =>
+ throw new Exception("unable to parse " + u + " as an object")
+ }
+ }
+ /**
+ * synthesize a relvar name from a SPARQL term.
+ * <p/>
+ * e.g. <code>?emp</code> =><code>R_emp</code>
+ * e.g. <code><http://hr.example/DB/Employee/empid.253#record></code> =><code>R_empid253</code>
+ * e.g. <code>18</code> =><code>R_18</code>
+ */
+ def relVarFromTerm(s:sparql.Term):sql.RelVar = {
+ s match {
+ case sparql.TermUri(ob) => relVarFromNode(ob)
+ case sparql.TermVar(v) => relVarFromVar(v)
+ case sparql.TermBNode(v) => relVarFromBNode(v)
+ case sparql.TermLit(l) => relVarFromLiteral(l)
+ }
+ }
+
+ def relVarFromNode(u:sparql.Uri):sql.RelVar = {
+ val NodeUri(stem, rel, Attr(a), CellValue(v)) = parseObjectURI(u)
+ sql.RelVar(sql.Name("R_" + a + v))
+ }
+
+ def relVarFromLiteral(l:sparql.Literal):sql.RelVar = {
+ sql.RelVar(sql.Name("R_" + l.lit.lexicalForm))
+ }
+
+ def relVarFromVar(vr:sparql.Var):sql.RelVar = {
+ val sparql.Var(v) = vr
+ sql.RelVar(sql.Name("R_" + v))
+ }
+
+ def relVarFromBNode(vr:sparql.BNode):sql.RelVar = {
+ val sparql.BNode(b) = vr
+ sql.RelVar(sql.Name("B_" + b))
+ }
+
+ /**
+ * synthesize a SQL name from a SPARQL variable or bnode.
+ * <p/>
+ * e.g. <code>?emp</code> =><code>emp</code>
+ */
+ def attrAliasNameFromVar(v:sparql.Assignable):String = "" + v.s
+
+ /**
+ * add constraints implied by a URI
+ * @param state state to be appended
+ * @param constrainMe relvar attribute to constrain, e.g. <code>R_empid18.empid</code>
+ * @param u SparqlToSql URL object, e.g. <code>NodeUri(http://hr.example/DB,Employee,empid,CellValue(18))</code>
+ * @return state + expression for the URI
+ * <p/>
+ * <code>http://hr.example/DB/Employee/empid=18, true</code> =><code>R_emp.manager=18</code>
+ * <code>http://hr.example/DB/Employee/empid=18, false</code> =><code>R_empid18.empid=18</code>
+ * (the latter produces another join on R_empid18 in order to enforce a foreign key.)
+ * http://hr.example/DB/Employee/empid=18 has already been parsed to produce a NodeUri:
+ * NodeUri(http://hr.example/DB,Employee,empid,CellValue(18))
+ */
+ def uriConstraint(state:R2RState, constrainMe:sql.RelVarAttr, u:NodeUri):R2RState = {
+ R2RState(state.joins,
+ state.varmap,
+ state.exprs + sql.RelationalExpressionEq(sql.PrimaryExpressionAttr(constrainMe),
+ sql.PrimaryExpressionTyped(rdb.RDB.Datatype.INTEGER,sql.Name(u.v.s))))
+ }
+
+ /**
+ * add constraints implied by a literal in a SPARQL triple pattern
+ * @param state state to be appended
+ * @param constrainMe relvar attribute to constrain, e.g. <code>R_18.empid</code>
+ * @param lit sparq.Literal, e.g. <code>18</code>
+ * @param dt sparq datatype, e.g. <code>xsd:integer</code>
+ * @return state + expression for the URI, e.g. <code>R_18.empid=18</code>
+ */
+ def literalConstraint(state:R2RState, constrainMe:sql.RelVarAttr, lit:sparql.Literal, dt:rdb.RDB.Datatype):R2RState = {
+ R2RState(state.joins,
+ state.varmap,
+ state.exprs + sql.RelationalExpressionEq(sql.PrimaryExpressionAttr(constrainMe),
+ sql.PrimaryExpressionTyped(dt,sql.Name(lit.lit.lexicalForm))))
+ }
+
+ /** varConstraint
+ * @param state earlier R2RState
+ * @param alias relvar to bind, e.g. Employees AS R_emp
+ * @param optAttr SQL attribute to bind, e.g. Employees.lastName
+ * @param v SPARQL variable or blank node to bind
+ * @param db database description
+ * @param rel SQL relation to bind, e.g. Employee
+ * @return a new R2RState incorporating the new binding
+ * For example, <code>SELECT ?emp WHERE { ?emp emp:lastName ?name }</code> will call varConstraint twice:
+ *
+ * given: alias:=R_emp, optAttr:=lastName, v:=?name, rel:=Employee ->
+ * return: (VarAssignable(?name),StringMapper(FullBinding(R_emp.lastName)))
+ * which maps "Smith" to "Smith"^^xsd:string
+ *
+ * given: alias:=R_emp, optAttr:=empid, v:=?emp, rel:=Employee ->
+ * return: (VarAssignable(?emp),RDFNoder(Employee,FullBinding(R_emp.empid)))
+ * which maps 4 to <http://hr.example/our/favorite/DB/Employee/id.4#record>
+ */
+ def varConstraint(state:R2RState, alias:sql.RelVar, optAttr:Option[rdb.RDB.AttrName], v:sparql.Assignable, db:rdb.RDB.Database, rel:rdb.RDB.RelName):R2RState = {
+ val constrainMe = if (optAttr.isDefined) sql.RelVarAttr(alias, optAttr.get) else sql.RelVarAttr(alias, rdb.RDB.AttrName("_no_such_attribute"))
+ val reldesc = db(rel)
+ val boundTo = FullBinding(constrainMe)
+
+ /**
+ * Bind optAttr to an SQL generator like RDFNoder(Employee,FullBinding(R_emp.empid))
+ */
+ val binding = reldesc.pk match {
+ /** <pre>varConstraint(R_emp, Some(empid), VarAssignable(?emp), Employee) -> RDFNoder(Employee,FullBinding(R_emp.empid))</pre> */
+ case Some(rdb.RDB.CandidateKey(List(rdb.RDB.AttrName(constrainMe.attribute.n)))) => RDFNoder(rel, boundTo)
+ case _ => {
+ val asKey = rdb.RDB.CandidateKey(List(constrainMe.attribute))
+ if (reldesc.fks.contains(asKey)) { // !! (0)
+ /** varConstraint(R_patient, Some(SexDE), VarAssignable(?_0_sexEntry), Person) -> RDFNoder(Person,FullBinding(R_patient.SexDE)) */
+ val rdb.RDB.Target(fkrel, fkattr) = reldesc.fks(asKey)
+ RDFNoder(rel, boundTo)
+ } else if (reldesc.header.contains(constrainMe.attribute)) {
+ reldesc.header(constrainMe.attribute) match {
+ /** varConstraint(R__0_indicDE, Some(NDC), VarAssignable(?_0_indicNDC), Medication_DE) -> IntMapper(FullBinding(R__0_indicDE.NDC)) */
+ case rdb.RDB.Datatype("Int") => IntMapper(boundTo)
+ /** varConstraint(R_emp, Some(lastName), VarAssignable(?name), Employee) -> StringMapper(FullBinding(R_emp.lastName)) */
+ case rdb.RDB.Datatype("String") => StringMapper(boundTo)
+ /** varConstraint(R_patient, Some(DateOfBirth), VarAssignable(?dob), Person) -> DateMapper(FullBinding(R_patient.DateOfBirth)) */
+ case rdb.RDB.Datatype("Date") => DateMapper(boundTo)
+ }
+ } else {
+ /** Default behavior for unknown attributes. */
+ RDFBNoder(rel, boundTo) // @@ untested
+ }
+ }
+ }
+
+ if (state.varmap.contains(v) && state.varmap(v) == constrainMe) {
+ /**
+ * No useful contributions for this variable.
+ * (We could re-add the binding; this case is just for clarity of behavior.)
+ */
+ state
+ } else if (state.varmap.contains(v)) {
+ /**
+ * The variable has already been bound to another attribute.
+ * Constraint against the initial binding for this variable.
+ * e.g. <code>R__0_0_indicCode.ID=R__0_0_indicCode.ID</code>
+ */
+ val constraint = sql.RelationalExpressionEq(sql.PrimaryExpressionAttr(constrainMe),
+ sql.PrimaryExpressionAttr(varToAttribute(state.varmap, v)))
+ R2RState(state.joins, state.varmap,
+ if (varToAttributeDisjoints(state.varmap, v).size > 0)
+ /**
+ * Enumerate the set of constraints capturing all sides of a disjoint or option.
+ * e.g. a UNION where two disjoints constrain R_who.empid=G_union0.who:
+ * Set((G_union0._DISJOINT_!=0) OR (R_who.empid=G_union0.who),
+ * (G_union0._DISJOINT_!=1) OR (R_who.empid=G_union0.who))
+ */
+ state.exprs ++ {varToAttributeDisjoints(state.varmap, v) map ((d) => sql.ExprDisjunction(Set(d, constraint)))}
+ else
+ state.exprs + constraint
+ )
+ } else {
+ /**
+ * Add binding for new variable.
+ */
+ R2RState(state.joins, state.varmap + (v -> binding), state.exprs)
+ }
+ }
+
+ def toString(relvarattr:sql.RelVarAttr) : String = {
+ relvarattr.relvar.n.s + "." + relvarattr.attribute.n
+ }
+ // def toString(mapper:SQL2RDFValueMapper) : String = {
+ // mapper match {
+ // case IntMapper(relvar, disjoints) => "INT: " + toString(relvar)
+ // case StringMapper(relvar, disjoints) => "STRING: " + toString(relvar)
+ // case DateMapper(relvar, disjoints) => "DATE: " + toString(relvar)
+ // case RDFNoder(relation, relvar, disjoints) => "RDFNoder: " + relation.n.s + ", " + toString(relvar)
+ // case RDFBNoder(relation, relvar, disjoints) => "RDFBNoder: " + relation.n.s + ", " + toString(relvar)
+ // }
+ // }
+
+ def __optFirst (k:Option[rdb.RDB.CandidateKey]):Option[rdb.RDB.AttrName] =
+ k match {
+ case Some(ck) => Some(ck.attrs(0))
+ case _ => None
+ }
+
+ /**
+ * map a given triple to one or two joined tables, variable
+ * @param db database description
+ * @param stateP earlier R2RState
+ * @param triple
+ * @param enforceForeignKeys
+ * @return a new R2RState incorporating the new binding
+ * bindings to RelVarAttrs, and constraints if those variables were
+ * already bound. */
+ def bindOnPredicate(db:rdb.RDB.Database, stateP:R2RState, triple:sparql.TriplePattern, enforceForeignKeys:Boolean):R2RState = {
+ val sparql.TriplePattern(s, p, o) = triple
+ p match {
+ /** Don't deal with ?s ?p ?o . We could deal with <s> ?p ?o , but haven't yet. */
+ case sparql.TermVar(v) => error("variable predicates require tedious enumeration; too tedious for me.")
+ case sparql.TermUri(uri) => {
+ val PUri(stem, spRel, spAttr) = parsePredicateURI(uri)
+ /** The relation and attribute come from the predicate, e.g. Employee.fname => Employee and fname. */
+ val rel = rdb.RDB.RelName(spRel.s)
+ val attr = rdb.RDB.AttrName(spAttr.s)
+
+ /**
+ * The particular join for e.g. Employee is controled by the subject.
+ * ?emp => Employee AS emp
+ * <Employee:18> => Employee AS Employee_18 .
+ */
+ val relvar = relVarFromTerm(s)
+ /** Synthesize relvar attribute, e.g. emp.fname. */
+ val objattr = sql.RelVarAttr(relvar, attr)
+
+ /** Accumulate joins and constraints that come from the subject */
+ val state_postSubj = s match {
+ case sparql.TermUri(u) =>
+ /** additional constraint, e.g. R_empid18.empid=18. */
+ uriConstraint(stateP, sql.RelVarAttr(relvar, db(rel).pk.get.attrs(0)), parseObjectURI(u)) // !! (0)
+ case sparql.TermVar(v) =>
+ /** assignable binding for novel vars, new constraint for previously bound vars. */
+ try {
+ varConstraint(stateP, relvar, __optFirst(db(rel).pk), sparql.VarAssignable(v), db, rel) // !! (0)
+ } catch {
+ case e:java.util.NoSuchElementException =>
+ /** Tell user that the relation.attribute encoded in the subject was not found in the database description. */
+ throw new Exception("error processing { " + s + " " + p + " " + o + " } :db(" + rel + ") not found in " + db)
+ }
+ case sparql.TermBNode(b) =>
+ /** assignable binding for novel bnodes, new constraint for previously bound bnodes. */
+ try {
+ varConstraint(stateP, relvar, __optFirst(db(rel).pk), sparql.BNodeAssignable(b), db, rel) // !! (0)
+ } catch {
+ case e:java.util.NoSuchElementException =>
+ throw new Exception("error processing { " + s + " " + p + " " + o + " } :db(" + rel + ") not found in " + db)
+ }
+ case _ => error("illegal SPARQL subject: " + s)
+ }
+
+ /** Join rel (relation dictated by predicate) AS relvar (alias dicated by subject).
+ * may be redundant as R2RState's joins are a set */
+ val state_subjJoin = R2RState(state_postSubj.joins + sql.InnerJoin(sql.AliasedResource(rel,relvar), None), state_postSubj.varmap, state_postSubj.exprs)
+
+ try { db(rel).header(attr) } catch {
+ /** Tell user that the queried attribute was not found in the database description. */
+ case e:java.util.NoSuchElementException =>
+ throw new Exception("error processing { " + s + " " + p + " " + o + " } :db(" + rel + ").header(" + attr + ") not found in " + db)
+ }
+
+ /**
+ * fkrel.fkattr (e.g. Employee.manager) may be a foreign key.
+ * Calculate final relvarattr and relation.
+ */
+ val asKey = rdb.RDB.CandidateKey(List(attr))
+ val (targetattr:sql.RelVarAttr, targetrel, dt, state_fkeys:R2RState) =
+ if (db(rel).fks.contains(asKey)) { // !! (0)
+ val rdb.RDB.Target(fkrel, fkattr) = db(rel).fks(asKey)
+ try { db(fkrel).header(fkattr.attrs(0)) } catch { // !! (0)
+ /** Foreign key relation.attribute was not found in the database description. */
+ case e:java.util.NoSuchElementException =>
+ throw new Exception("db(" + fkrel + ").header(" + fkattr + ") not found in " + db)
+ }
+ val fkdt =
+ if (db(fkrel).fks.contains(fkattr)) {
+ /** Foreign key to something which is a foreign key. May have use
+ * cases, but signal error until we figure out that they are. */
+ val rdb.RDB.Target(dfkrel, dfkattr) = db(fkrel).fks(fkattr)
+ error("foreign key " + rel.n + "." + attr.n +
+ "->" + fkrel.n + "." + fkattr.attrs(0).n + // !! (0)
+ "->" + dfkrel.n + "." + dfkattr.attrs(0).n) // !! (0)
+ } else
+ db(fkrel).header(fkattr(0)) // !! (0)
+ if (enforceForeignKeys) {
+ /**
+ * Create an extra join on the foreign key relvar. For instance,
+ * <code>?task1 <http://hr.example/DB/TaskAssignments#employee> ?who</code>
+ * where TaskAssignments.employee is a foreign key to Employee.empid
+ * will join Employee AS R_who, constrain R_who.empid=R_task1.employee
+ * and bind targetattr:R_who.empid. targetrel:Employee .
+ */
+ val oRelVar = relVarFromTerm(o)
+ val fkaliasattr = sql.RelVarAttr(oRelVar, fkattr.attrs(0)) // !! (0)
+ val state_t = R2RState(state_subjJoin.joins + sql.InnerJoin(sql.AliasedResource(fkrel,oRelVar), None),
+ state_subjJoin.varmap,
+ state_subjJoin.exprs + sql.RelationalExpressionEq(sql.PrimaryExpressionAttr(fkaliasattr),
+ sql.PrimaryExpressionAttr(objattr)))
+ //println("enforceFKs: <code>"+s+" "+p+" "+o+"</code> where "+rel+"."+attr+" is a foreign key to "+fkrel+"."+fkattr+" will join "+fkrel+" AS "+oRelVar+", constrain "+fkaliasattr+"="+objattr+" and bind targetattr:=" + fkaliasattr + ". targetrel:=" + fkrel + " (instead of " + objattr + ", " + rel + ").")
+ (fkaliasattr, fkrel, fkdt, state_t)
+ } else {
+ /**
+ * We're not enforcing foreign keys, so just bind
+ * targetattr:=R_task1.employee, targetrel:=TaskAssignments.
+ */
+ (objattr, rel, fkdt, state_subjJoin)
+ }
+ }
+ else
+ /** not a foreign key, so use the relvarattr and relation calculated
+ * from the predicate. */
+ (objattr, rel, db(rel).header(attr), state_subjJoin)
+
+ o match {
+ case sparql.TermLit(l) => literalConstraint(state_fkeys, targetattr, l, dt)
+ case sparql.TermUri(u) => uriConstraint (state_fkeys, targetattr, parseObjectURI(u))
+ case sparql.TermVar(v) => varConstraint (state_fkeys, targetattr.relvar, Some(targetattr.attribute), sparql.VarAssignable(v), db, targetrel)
+ case sparql.TermBNode(b) => varConstraint (state_fkeys, targetattr.relvar, Some(targetattr.attribute), sparql.BNodeAssignable(b), db, targetrel)
+ }
+ }
+ case _ => error("illegal SPARQL predicate: " + p)
+ }
+ }
+
+ def bindingConstraintToAttribute(constraint:BindingConstraint):sql.RelVarAttr = {
+ val BindingConstraint(expr:sql.RelationalExpression, relvarattr:sql.RelVarAttr) = constraint;
+ relvarattr
+ }
+ def bindingToAttribute(binding:FullOrPartialBinding):sql.RelVarAttr = {
+ binding match {
+ case FullBinding(relvarattr:sql.RelVarAttr) => relvarattr
+ case PartialBinding(binders) => bindingConstraintToAttribute(binders.toList(0))
+ }
+ }
+ def varToAttribute(varmap:Map[sparql.Assignable, SQL2RDFValueMapper], vvar:sparql.Assignable):sql.RelVarAttr = {
+ val mapper = try { varmap(vvar) } catch {
+ case e:java.util.NoSuchElementException =>
+ throw new Exception("mapper for variable " + vvar + " not found in " + varmap)
+ }
+ mapper match {
+ case IntMapper(binding) => bindingToAttribute(binding)
+ case StringMapper(binding) => bindingToAttribute(binding)
+ case DateMapper(binding) => bindingToAttribute(binding)
+ case RDFNoder(relation, binding) => bindingToAttribute(binding)
+ case RDFBNoder(relation, binding) => bindingToAttribute(binding) // error("BNode should not need relvar " + relvar)
+ }
+ }
+
+ def bindingConstraintToExpression(constraint:BindingConstraint):sql.RelationalExpression = {
+ val BindingConstraint(expr:sql.RelationalExpression, relvarattr:sql.RelVarAttr) = constraint;
+ expr
+ }
+ def bindingToDisjoints(binding:FullOrPartialBinding):Set[sql.RelationalExpression] = {
+ binding match {
+ case FullBinding(relvarattr:sql.RelVarAttr) => Set[sql.RelationalExpression]()
+ case PartialBinding(binders) => binders.map({b => bindingConstraintToExpression(b)})
+ }
+ }
+ def varToAttributeDisjoints(varmap:Map[sparql.Assignable, SQL2RDFValueMapper], vvar:sparql.Assignable):Set[sql.RelationalExpression] = {
+ varmap(vvar) match {
+ case IntMapper(binding) => bindingToDisjoints(binding)
+ case StringMapper(binding) => bindingToDisjoints(binding)
+ case DateMapper(binding) => bindingToDisjoints(binding)
+ case RDFNoder(relation, binding) => bindingToDisjoints(binding)
+ case RDFBNoder(relation, binding) => bindingToDisjoints(binding) // error("BNode should not need relvar " + relvar)
+ }
+ }
+
+ /**
+ * Converts a variable bound to a URL to an SQL expression for that URL.
+ *
+ * @param varmap map from variable to SQL2RDFValueMapper
+ * @param vvar the variable to be represented
+ * @return an SQL CONCAT expression
+ * @see VarMap
+ */
+ def varToExpr(varmap:Map[sparql.Assignable, SQL2RDFValueMapper], vvar:sparql.Assignable, stem:StemURI):sql.Expression = {
+ varmap(vvar) match {
+ case IntMapper(binding) => sql.PrimaryExpressionAttr(bindingToAttribute(binding))
+ case StringMapper(binding) =>
+ sql.Concat(List(sql.PrimaryExpressionTyped(rdb.RDB.Datatype("String"),sql.Name("'")),
+ sql.PrimaryExpressionAttr(bindingToAttribute(binding)),
+ sql.PrimaryExpressionTyped(rdb.RDB.Datatype("String"),sql.Name("'^^<http://www.w3.org/2001/XMLSchema#string>"))))
+ case DateMapper(binding) => sql.PrimaryExpressionAttr(bindingToAttribute(binding))
+ case RDFNoder(relation, binding) =>
+ sql.Concat(List(sql.PrimaryExpressionTyped(rdb.RDB.Datatype("String"),sql.Name(stem.s)),
+ sql.PrimaryExpressionTyped(rdb.RDB.Datatype("String"),relation.n),
+ sql.PrimaryExpressionTyped(rdb.RDB.Datatype("String"),sql.Name("/")),
+ sql.PrimaryExpressionTyped(rdb.RDB.Datatype("String"),bindingToAttribute(binding).attribute.n),
+ sql.PrimaryExpressionTyped(rdb.RDB.Datatype("String"),sql.Name(".")),
+ sql.PrimaryExpressionAttr(bindingToAttribute(binding)),
+ sql.PrimaryExpressionTyped(rdb.RDB.Datatype("String"),sql.Name("#record"))))
+ case RDFBNoder(relation, binding) =>
+ sql.Concat(List(sql.PrimaryExpressionTyped(rdb.RDB.Datatype("String"),sql.Name("_:")),
+ sql.PrimaryExpressionTyped(rdb.RDB.Datatype("String"),relation.n),
+ sql.PrimaryExpressionTyped(rdb.RDB.Datatype("String"),sql.Name(".")),
+ sql.PrimaryExpressionTyped(rdb.RDB.Datatype("String"),bindingToAttribute(binding).attribute.n),
+ sql.PrimaryExpressionTyped(rdb.RDB.Datatype("String"),sql.Name(".")),
+ sql.PrimaryExpressionAttr(bindingToAttribute(binding))))
+ }
+
+ }
+
+ /**
+ * return an SQL PrimaryExpression for rTerm.
+ *
+ * @param varmap map from variable to SQL2RDFValueMapper
+ * @param l an SQL relvarattr for a SPARQL Assignable (variable or bnode)
+ * @param rTerm SPARQL term to be converted to a PrimaryExpression
+ * @param sqlexpr an unbound SQL relational expression, e.g. sql.RelationalExpressionEq(_,_)
+ * @return an SQL expression on the relvarattrs to which the variables in f are bound
+ */
+ def assignable2expr(varmap:Map[sparql.Assignable, SQL2RDFValueMapper], l:sql.RelVarAttr, rTerm:sparql.Term, sqlexpr:(sql.PrimaryExpression, sql.PrimaryExpression) => sql.RelationalExpression):sql.RelationalExpression = { // :sparql.Var
+ val r:sql.PrimaryExpression = rTerm match {
+ case sparql.TermUri(u) => error("not implemented: translating RDF URI to SQL: " + u) // :sparql.Uri
+ case sparql.TermVar(v) => sql.PrimaryExpressionAttr(varToAttribute(varmap, sparql.VarAssignable(v)))
+ case sparql.TermBNode(b) => sql.PrimaryExpressionAttr(varToAttribute(varmap, sparql.BNodeAssignable(b)))
+ case sparql.TermLit(sparql.Literal(rdf.RDFLiteral(lit,rdf.Datatype(dt)))) =>
+ sql.PrimaryExpressionTyped({
+ dt.toString match {
+ case "http://www.w3.org/2001/XMLSchema#string" => rdb.RDB.Datatype.STRING
+ case "http://www.w3.org/2001/XMLSchema#integer" => rdb.RDB.Datatype.INTEGER
+ case "http://www.w3.org/2001/XMLSchema#date" => rdb.RDB.Datatype.DATE
+ case _ => error("unable to translate to RDF literal SQL: \"" + lit + "\"^^<" + dt + ">")
+ }
+ }, lit)
+ }
+ sqlexpr(sql.PrimaryExpressionAttr(l), r)
+ }
+
+ /**
+ * create an SQL expression analogous to a SPARQL expression. The variables in
+ * f are expressed as the relvarattrs to which they are mapped. This function
+ * is only minimally implemented here.
+ *
+ * @param varmap map from variable to SQL2RDFValueMapper
+ * @param f SPARQL Expression
+ * @return an SQL expression on the relvarattrs to which the variables in f are bound
+ */
+ def filter2expr(varmap:Map[sparql.Assignable, SQL2RDFValueMapper], f:sparql.PrimaryExpression):sql.RelationalExpression = {
+
+ /** sqlexpr: an unbound RelationalExpression, i.e. a function which takes
+ * (sql.RelVarAttr, sql.PrimaryExpressionAttr) and returns an
+ * sql.RelationalExpression
+ */
+ val (lTerm:sparql.Term, rTerm:sparql.Term, sqlexpr) = f match {
+ case sparql.PrimaryExpressionEq(l, r) => (l.term, r.term, sql.RelationalExpressionEq(_,_))
+ case sparql.PrimaryExpressionLt(l, r) => (l.term, r.term, sql.RelationalExpressionLt(_,_))
+ case sparql.PrimaryExpressionGt(l, r) => (l.term, r.term, sql.RelationalExpressionGt(_,_))
+ case sparql.SparqlTermExpression(t) => error("not implemented") // (l.term, r.term, sql.PrimaryExpressionAttr(_,_))
+ }
+
+ lTerm match {
+ /** does not handle FILTER (<x> = ?v) */
+ case sparql.TermUri(obj) => error("only SPARQL PrimaryExpressions with a variable on the left have been implemented: punting on " + f)
+ /** FILTER (?v = <x> && ?v = ?x && ?v = 7) */
+ case sparql.TermVar(v) => assignable2expr(varmap, varToAttribute(varmap, sparql.VarAssignable(v)), rTerm, sqlexpr)
+ case sparql.TermBNode(b) => assignable2expr(varmap, varToAttribute(varmap, sparql.BNodeAssignable(b)), rTerm, sqlexpr)
+ /** does not handle FILTER (7 = ?v) */
+ case sparql.TermLit(lit) => error("only SPARQL PrimaryExpressions with a variable on the left have been implemented: punting on " + f)
+ }
+ }
+
+ /**
+ * Promote a variable in an OPTIONAL or UNION subselect to the outer
+ * varmap/expressions.
+ */
+ def subselectVars(startState:R2RState, v:sparql.Assignable, optionalAlias:sql.RelVar,
+ optionalCond:sql.RelationalExpression,
+ outerVarmap:Map[sparql.Assignable, SQL2RDFValueMapper],
+ nestedVarmap:Map[sparql.Assignable, SQL2RDFValueMapper],
+ isOpt:Boolean):R2RState = {
+
+ val varAliasAttr = sql.RelVarAttr(optionalAlias, rdb.RDB.AttrName(attrAliasNameFromVar(v)))
+ if (startState.varmap.contains(v)) {
+
+ /** The variable has already been bound. */
+ val newMap:Map[sparql.Assignable, SQL2RDFValueMapper] =
+ /** If var was already bound to the same relvarattr... */
+ if (varToAttribute(startState.varmap, v) == varAliasAttr) {
+ /**
+ * Add new partial constraint to old partial constraints to produce a new binding.
+ * example:
+ * ?name -> StringMapper(PartialBinding(Set(BindingConstraint(G_union1._DISJOINT_!=0,G_union1.name),
+ * BindingConstraint(G_union1._DISJOINT_!=1,G_union1.name))))
+ */
+ Map(v -> {
+ startState.varmap(v) match {
+ case IntMapper(binding) => IntMapper(addExpr(binding, varAliasAttr, optionalCond))
+ case StringMapper(binding) => StringMapper(addExpr(binding, varAliasAttr, optionalCond))
+ case DateMapper(binding) => DateMapper(addExpr(binding, varAliasAttr, optionalCond))
+ case RDFNoder(rel, binding) => RDFNoder(rel, addExpr(binding, varAliasAttr, optionalCond))
+ case RDFBNoder(rel, binding) => RDFBNoder(rel, addExpr(binding, varAliasAttr, optionalCond))
+ } } )
+ } else {
+ /** Var was bound to a different relvarattr so handle as newConstraint below. */
+ Map()
+ }
+
+ val newConstraints =
+ if (varToAttribute(outerVarmap, v) == varAliasAttr) { // also varToAttribute(startState.varmap, v) == varAliasAttr
+ Set()
+ } else {
+ /** Constraint against binding from earlier graph pattern.
+ * e.g. G_union1.who=R_who.empid */
+ val constraint = sql.RelationalExpressionEq(sql.PrimaryExpressionAttr(varAliasAttr),
+ sql.PrimaryExpressionAttr(varToAttribute(outerVarmap, v)))
+ if (varToAttributeDisjoints(outerVarmap, v).size > 0) {
+ /**
+ * Construct a conjunction of disjunctions like:
+ * (union0._DISJOINT_ != 0 OR union0.x=union1.x) AND (union1._DISJOINT_ != 2 OR union0.x=union1.x)
+ */
+ varToAttributeDisjoints(outerVarmap, v) map ((d) =>
+ sql.ExprDisjunction({
+ if (isOpt) Set(d, constraint) // a disjunction like: G_opt1._DISJOINT_ IS NULL OR G_opt3.birthday=G_opt1.birthday
+ else Set(sql.ExprConjunction(Set(d, optionalCond)), constraint) // @@ untested code path
+ }))
+ } else {
+ if (isOpt)
+ /** just the constraint from above */
+ Set(constraint)
+ else
+ /**
+ * Above constraint applied only for this path:
+ * for example: (G_union1._DISJOINT_!=0) OR (G_union1.who=R_who.empid)
+ */
+ Set(sql.ExprDisjunction(Set(optionalCond, constraint)))
+ }
+ }
+
+ R2RState(startState.joins, startState.varmap ++ newMap, startState.exprs ++ newConstraints)
+ } else {
+ /**
+ * This variable is new to the outer context so synthesize a new partial
+ * binding à la:
+ * ?name ->
+ * StringMapper(PartialBinding((G_union1._DISJOINT_!=0,G_union1.name)))
+ */
+ val p = PartialBinding(Set(BindingConstraint(optionalCond, varAliasAttr)))
+ val mapper:SQL2RDFValueMapper = nestedVarmap(v) match {
+ case IntMapper(_) => IntMapper(p)
+ case StringMapper(_) => StringMapper(p)
+ case DateMapper(_) => DateMapper(p)
+ case RDFNoder(rel, _) => RDFNoder(rel, p)
+ case RDFBNoder(rel, _) => RDFBNoder(rel, p)
+ }
+
+ R2RState(startState.joins, startState.varmap + (v -> mapper), startState.exprs)
+ }
+ }
+
+ /**
+ * Recursively add the joins, variable mappings and constraints for an SQL query implementing a graph pattern.
+ * @param db database description.
+ * @return a new state including the subquery representing gp in a join.
+ * This is factored out of mapGraphPattern as it is used for MINUS and probably later for NOT EXISTS.
+ */
+ def synthesizeOuterJoin(initState:R2RState, gp:sparql.GraphPattern, negate:Boolean, db:rdb.RDB.Database, enforceForeignKeys:Boolean):R2RState = {
+ /** SPARQL OPTIONALs and UNIONs are treated as SQL subselects.
+ * Set up initial state for this subselect.
+ */
+ val leftJoinAlias = sql.RelVar(sql.Name("G_opt" + initState.joins.size))
+ val initDisjoints:Set[sql.Select] = Set()
+ val emptyState = R2RState(
+ util.AddOrderedSet[sql.Join](),
+ Map[sparql.Assignable, SQL2RDFValueMapper](),
+ Set[sql.Expression]()
+ )
+
+ /** Create the select for the nested graph pattern.
+ */
+ val nestedState = mapGraphPattern(db, emptyState, gp, enforceForeignKeys)
+ val nestedVars = gp.findVars
+ /**
+ * Select a constant as _DISJOINT_ so later constraints can be
+ * sensitive to whether a variable was bound.
+ * This matters for assymetric UNIONs and, here, OPTIONALs. Given:
+ * Join( LeftJoin( BGP(),
+ * BGP( ?x :p2 ?v2 ) ),
+ * BGP( ?y :p3 ?v2 ) )
+ * coreference constraints against ?v2 should only be enforced for
+ * tuples from the right side of this union.
+ */
+ val pathNo = sql.ProjectAttribute(sql.PrimaryExpressionTyped(rdb.RDB.Datatype.INTEGER,sql.Name("" + initState.joins.size)),
+ sql.AttrAlias(sql.Name("_DISJOINT_")))
+ val leftJoinVars = gp.findVars
+ val attrlist:Set[sql.ProjectAttribute] = leftJoinVars.map(
+ v =>
+ sql.ProjectAttribute(varToAttribute(nestedState.varmap, sparql.VarAssignable(v)),
+ sql.AttrAlias(attrAliasNameFromVar(sparql.VarAssignable(v))))
+ ) + pathNo // add join number to selections
+ val subselect = sql.Select(
+ false,
+ sql.Projection(attrlist),
+ sql.TableList(nestedState.joins),
+ nestedState.exprs.size match {
+ case 0 => None
+ case 1 => Some(nestedState.exprs.toList(0))
+ case _ => Some(sql.ExprConjunction(nestedState.exprs))
+ },
+ List[sql.OrderElt](), None, None
+ )
+
+ /** Create a condition to test if this OPTIONAL was matched (called
+ * _DISJOINT_ as OPTIONAL behaves pretty much like a disjunction).
+ */
+ val nestedCond = sql.RelationalExpressionNull(sql.PrimaryExpressionAttr(
+ sql.RelVarAttr(leftJoinAlias, rdb.RDB.AttrName("_DISJOINT_"))))
+
+ /** Bind variables to the attributes projected from the subselect;
+ * handle corefs (equivalence with earlier bindings).
+ */
+ val outerState2 =
+ nestedVars.foldLeft(
+ R2RState(initState.joins,
+ initState.varmap,
+ Set[sql.Expression]()))((myState, v) =>
+ subselectVars(myState, sparql.VarAssignable(v), leftJoinAlias, nestedCond,
+ initState.varmap, nestedState.varmap, true))
+
+ /** The final state includes the subselect as a join, the variables bound
+ * to subselect projection, and no new expresssions. The expressions
+ * derived from corefs are conditions for the LEFT OUTER JOIN.
+ */
+ val join = sql.LeftOuterJoin(sql.AliasedResource(sql.Subselect(subselect), leftJoinAlias),
+ outerState2.exprs.size match {
+ case 0 =>
+ sql.RelationalExpressionEq(sql.PrimaryExpressionTyped(rdb.RDB.Datatype.INTEGER,sql.Name("1")),
+ sql.PrimaryExpressionTyped(rdb.RDB.Datatype.INTEGER,sql.Name("1")))
+ /** Require corefs unless we have a leading OPTIONAL. */
+ // if (...)
+ // else
+ // error ("Nested GP has no variables shared with its context; cowaredly refusing to join ON 1.")
+ case 1 => outerState2.exprs.toList(0)
+ case _ => sql.ExprConjunction(outerState2.exprs)
+ }
+ )
+ val exprs =
+ if (negate) {
+ initState.exprs + sql.RelationalExpressionNull(sql.PrimaryExpressionAttr(sql.RelVarAttr(leftJoinAlias, rdb.RDB.AttrName("_DISJOINT_"))))
+ } else initState.exprs
+ R2RState(initState.joins + join, outerState2.varmap, exprs)
+ }
+
+ /**
+ * Recursively add the joins, variable mappings and constraints for an SQL query implementing a graph pattern.
+ * @param db database description.
+ * @param state initial set of joins, variable mappings and constraints.
+ * @param gp the SPARQL GraphPattern to represent as SQL.
+ * @param enforceForeignKeys if true, SPARQL triple patterns corresponding to foreign keys, e.g. <code>?who :hasParent ?parent</code>, generate a join on the referenced table.
+ * @return a new set of joins, variable mappings and constraints.
+ * Per <a href="http://www.w3.org/TR/rdf-sparql-query/#sparqlQuery">definition of SPARQL Query</a>, SPARQL Graph Patterns are (Basic Graph Pattern, Join, LeftJoin, Filter, Union, Graph).
+ * mapGraphPattern maps each of these to an SQL abstract query (which can then be serialized as SQL and executed).
+ *
+ */
+ def mapGraphPattern(db:rdb.RDB.Database, state:R2RState, gp:sparql.GraphPattern, enforceForeignKeys:Boolean):R2RState = {
+ gp match {
+
+ /** <a href="http://www.w3.org/TR/rdf-sparql-query/#defn_PatternInstanceMapping">Basic Graph Pattern Matching</a>()
+ * @param triplepatterns set of triple patterns. Premise: all triple patterns must resolve against the direct graph.
+ * As { TP1, TP2 } == Join({ TP1 }, { TP2 }), we can view the bindOnPredicate function as partitioning triple patterns by the relvar they match.
+ * This isn't observable in the SQL query as all the triple patterns in all the conjunctions contribute to the same query.
+ */
+ case sparql.TriplesBlock(triplepatterns) => {
+ /** Examine each triple, updating the compilation state. */
+ val state2 = triplepatterns.foldLeft(state)((incState,s) => bindOnPredicate(db, incState, s, enforceForeignKeys))
+
+ /** NULLs in the database result in no triple in the Direct Graph.
+ * Enforce this by requiring that the SQL expression to which any SPARQL variable (Assignable) is bound is NOT NULL.
+ */
+ val nullExprs = gp.findAssignables.foldLeft(Set[sql.Expression]())((s, vvar) => {
+ if (varToAttributeDisjoints(state2.varmap, vvar).size == 0)
+ /** Create a NOT NULL expression for each fully bound variable. */
+ s ++ Set(sql.RelationalExpressionNotNull(sql.PrimaryExpressionAttr(varToAttribute(state2.varmap, vvar))))
+ else
+ /** Variables in a partial binding can be NULL so the aren't added to the null expressions. */
+ s
+ })
+ R2RState(state2.joins, state2.varmap, state2.exprs ++ nullExprs)
+ }
+
+ /** <a href="http://www.w3.org/TR/rdf-sparql-query/#defn_evalJoin">Join</a>(P1, P2)
+ * Since Join is association, we handle this as an n-ary join.
+ * @param conjoints list of graph patterns to join.
+ */
+ case sparql.TableConjunction(conjoints) => {
+ conjoints.foldLeft(state)((incState,s) => mapGraphPattern(db, incState, s, enforceForeignKeys))
+ }
+
+ /** <a href="http://www.w3.org/TR/rdf-sparql-query/#defn_evalLeftJoin">LeftJoin</a>(P1, P2, F)
+ * The parser providing the SPARQL abstract query turns LeftJoin(P1, P2, F) into Join(P1, Optional(P2)), or Join(P1, Optional(Filter(P2, F))) if there is a FILTER.
+ * @param gp2 nested graph pattern (Ω in algebra)
+ */
+ case sparql.OptionalGraphPattern(gp2) => {
+ /* state_postLeadingTable: create an initial table if the first conjoint is optional.
+ * e.g. ... FROM (SELECT 1 AS _EMPTY_) AS _EMPTY_ LEFT OUTER JOIN ...
+ */
+ val state_postLeadingTable =
+ if (state.joins.size == 0)
+ /**
+ * Leading optionals (ASK WHERE { OPTIONAL { ... } ... }) in SPARQL don't have a counterpart in SQL.
+ * We emulate leading optionals with a leading SQL subselect which projects one solution with no selected attributes.
+ */
+ R2RState(state.joins + sql.InnerJoin(sql.AliasedResource(sql.Subselect(
+ sql.Select(
+ false,
+ sql.Projection(Set(sql.ProjectAttribute(sql.PrimaryExpressionTyped(rdb.RDB.Datatype.INTEGER,sql.Name("1")),
+ sql.AttrAlias(sql.Name("_EMPTY_"))))),
+ sql.TableList(util.AddOrderedSet()),
+ None, List[sql.OrderElt](), None, None
+ )), sql.RelVar(sql.Name("_EMPTY_"))), None), state.varmap, state.exprs)
+ else
+ state
+ /** Create an OUTER JOIN for the nested graph pattern. */
+ synthesizeOuterJoin(state_postLeadingTable, gp2, false, db, enforceForeignKeys)
+ }
+
+ /** <a href="http://www.w3.org/TR/rdf-sparql-query/#defn_algFilter">Filter</a>(expr, Ω)
+ * @param gp2 nested graph pattern (Ω in algebra)
+ * @param expr boolean SPARQL expression (expr in algebra)
+ */
+ case sparql.TableFilter(gp2:sparql.GraphPattern, expr:sparql.Expression) => {
+ /** Calculate state for gp2. */
+ val state2 = mapGraphPattern(db, state, gp2, enforceForeignKeys)
+
+ /** Add constraints for all the FILTERS */
+ val filterExprs:Set[sql.RelationalExpression] =
+ expr.conjuncts.toSet map ((x:sparql.PrimaryExpression) => filter2expr(state2.varmap, x))
+
+ R2RState(state2.joins, state2.varmap, state2.exprs ++ filterExprs)
+ }
+
+ /** <a href="http://www.w3.org/TR/rdf-sparql-query/#defn_evalUnion">Union</a>(P1, P2)
+ * Since Disjunction is associative, we handle this as an n-ary disjunction.
+ * @param disjoinits list of graph patterns to concatenate.
+ */
+ case sparql.TableDisjunction(disjoints) => {
+ /**
+ * SPARQL UNIONs are realized as SQL subselects.
+ * Set up initial state for this subselect.
+ */
+ val unionAlias = sql.RelVar(sql.Name("G_union" + state.joins.size)) // invent a unique name for this union.
+ val emptyState = R2RState( // create an empty state.
+ util.AddOrderedSet[sql.Join](),
+ Map[sparql.Assignable, SQL2RDFValueMapper](),
+ Set[sql.Expression]()
+ )
+ val unionVars = disjoints.foldLeft(Set[sparql.Var]())((mySet,disjoint) =>
+ mySet ++ disjoint.findVars).toList // all variables nested in the disjoints.
+
+ /**
+ * Map the list of disjoints to a list of nested R2RStates, nested variable lists, and unique SQL constants identifying that disjoint.
+ * Non-Functional var <code>number</code> is used for projecting unique
+ * constants to indicate which disjoint produced a tuple.
+ */
+ var number = 0
+ val nestedStates = disjoints.map(disjoint => {
+ val disjointState = mapGraphPattern(db, emptyState, disjoint, enforceForeignKeys)
+ val disjointVars = disjoint.findVars
+ val uniqueConst = sql.PrimaryExpressionTyped(rdb.RDB.Datatype.INTEGER,sql.Name("" + number))
+ number = number + 1 // non-functional, but clearer than wrapping as a parameter in a foldLeft
+ (disjointState, disjointVars, uniqueConst)
+ })
+
+ /**
+ * Map the list of nested R2RStates to a set of subselects.
+ * <code>uniqueConst</code> is used for projecting a value
+ * to indicate which disjoint produced a tuple.
+ */
+ val subselects = nestedStates.foldLeft(Set[sql.Select]())((subselects, state) => {
+ val (disjointState, disjointVars, uniqueConst) = state
+ /**
+ * Select a constant as _DISJOINT_ so later constraints can be
+ * sensitive to whether a variable was bound.
+ * This matters for OPTIONALs and, here, assymetric UNIONs. Given:
+ * Join( Union( BGP( ?x :p1 ?v1 ),
+ * BGP( ?x :p2 ?v1 . ?x :p2 ?v2 ) ),
+ * BGP( ?y :p3 ?v2 ) )
+ * coreference constraints against ?v2 should only be enforced for
+ * tuples from the right side of this union.
+ */
+ val pathNo = sql.ProjectAttribute(uniqueConst,
+ sql.AttrAlias(sql.Name("_DISJOINT_")))
+
+ val attrlist:Set[sql.ProjectAttribute] = unionVars.foldLeft(Set(pathNo))((attrs, v) => {
+ val attrOrNull = if (disjointState.varmap.contains(sparql.VarAssignable(v))) varToAttribute(disjointState.varmap, sparql.VarAssignable(v)) else sql.ConstNULL()
+ attrs ++ Set(sql.ProjectAttribute(attrOrNull, sql.AttrAlias(attrAliasNameFromVar(sparql.VarAssignable(v)))))
+ })
+
+ val subselect = sql.Select(
+ false,
+ sql.Projection(attrlist),
+ sql.TableList(disjointState.joins),
+ disjointState.exprs.size match {
+ case 0 => None
+ case 1 => Some(disjointState.exprs.toList(0))
+ case _ => Some(sql.ExprConjunction(disjointState.exprs))
+ }, List[sql.OrderElt](), None, None
+ )
+ subselects + subselect
+ })
+
+ /**
+ * Connect the variables projected from the nested selects into the outer variable bindings and constraints.
+ * <code>state2</code> will have no additional tables in the TableList.
+ * <code>uniqueConst</code> is used this time to constraint coreferences between the
+ * subselects and the outer context.
+ */
+ val state2 = nestedStates.foldLeft(state)((outerState, state) => {
+ val (disjointState, disjointVars, uniqueConst) = state
+
+ /** Create a condition to test if this disjoint was matched. */
+ val disjointCond = sql.RelationalExpressionNe(sql.PrimaryExpressionAttr(sql.RelVarAttr(unionAlias, rdb.RDB.AttrName("_DISJOINT_"))),
+ uniqueConst)
+ val outerState2 = disjointVars.foldLeft(outerState)((myState, v) =>
+ subselectVars(myState, sparql.VarAssignable(v), unionAlias, disjointCond, outerState.varmap, disjointState.varmap, false))
+ number = number + 1 // non-functional, but clearer than wrapping as a parameter in a foldLeft
+ outerState2
+ })
+ val subselect = sql.Subselect(sql.Union(subselects))
+ R2RState(state.joins + sql.InnerJoin(sql.AliasedResource(subselect,unionAlias), None), state2.varmap, state2.exprs)
+ }
+
+ /** <a href="http://www.w3.org/TR/rdf-sparql-query/#defn_evalGraph">Graph</a>(IRI, P)
+ * I don't know what the parser did with the IRI, but we don't know what to do with GRAPHs anyways.
+ * @param gp2 nested graph pattern (Ω in algebra)
+ */
+ case sparql.GraphGraphPattern(gp2) => error("no code to handle GraphGraphPatterns (" + gp2 + ")")
+
+ /** Minus is from SPARQL 1.1 (in progress). This doesn't need documentation now.
+ * @param gp2 the graph pattern to subtract.
+ */
+ case sparql.MinusGraphPattern(gp2) => {
+ if (state.joins.size == 0) state
+ else synthesizeOuterJoin(state, gp2, true, db, enforceForeignKeys)
+ }
+ }
+ }
+
+ /**
+ * Default interface for SparqlToSql.
+ * @param db database description.
+ * @param sparquery SPARQL compile tree.
+ * @param stem stem URI for all generated RDF URIs.
+ * @param enforceForeignKeys if true, SPARQL triple patterns corresponding to foreign keys, e.g. ?who :hasParent ?parent , generate a join on the referenced table.
+ * @param concat if true, keys will produce SQL functions to generate a URI, e.g. SELECT CONCAT(stemURI, table, "/", pk, ".", R_who.pk) AS who
+ * @return an SQL query corresponding to sparquery
+ */
+ def apply (db:rdb.RDB.Database, sparquery:sparql.Select, stem:StemURI, enforceForeignKeys:Boolean, concat:Boolean) : (sql.Select, Map[sparql.Assignable, SQL2RDFValueMapper]) = {
+
+ /** Create an object to hold our compilation state. */
+ val initState = R2RState(
+ util.AddOrderedSet[sql.Join](),
+ Map[sparql.Assignable, SQL2RDFValueMapper](),
+ Set[sql.Expression]()
+ )
+
+ /**
+ * Generate a new state with the joins, mappings to sql expressions, and
+ * constraints implicit in the SPARQL WHERE pattern.
+ */
+ val r2rState = mapGraphPattern(db, initState, sparquery.gp, enforceForeignKeys)
+
+ /**
+ * Select the attributes corresponding to the variables
+ * in the SPARQL SELECT.
+ */
+ val attrlist:Set[sql.ProjectAttribute] =
+ // This foldLeft could be a map, if i could coerce to a set afterwards.
+ sparquery.attrs.attributelist.foldLeft(Set[sql.ProjectAttribute]())((attrs, v) => {
+ val exp =
+ if (concat)
+ // generate CONCAT expression for keys.
+ varToExpr(r2rState.varmap, sparql.VarAssignable(v), stem)
+ else
+ varToAttribute(r2rState.varmap, sparql.VarAssignable(v))
+ /** Projection alias. */
+ val as = sql.AttrAlias(attrAliasNameFromVar(sparql.VarAssignable(v)))
+ attrs + sql.ProjectAttribute(exp , as)
+ })
+
+ /** Construct the generated query as an abstract syntax. */
+ val select = sql.Select(
+ sparquery.distinct,
+ sql.Projection(attrlist),
+ sql.TableList(r2rState.joins),
+ r2rState.exprs.size match {
+ case 0 => None
+ case 1 => Some(r2rState.exprs.toList(0))
+ case _ => Some(sql.ExprConjunction(r2rState.exprs))
+ },
+ sparquery.order.map((elt:sparql.OrderElt) => {
+ sql.OrderElt(elt.desc, xxx(r2rState.varmap, elt.expr))
+ }), sparquery.offset, sparquery.limit
+ )
+ // println("r2rState.varmap: " + r2rState.varmap)
+ // println("select.expression: " + select.expression)
+ (select.makePretty, r2rState.varmap)
+ }
+
+/*
+ * vvv CLEAN THIS UP!!! vvv
+ */
+
+ def assignable2expr999(varmap:Map[sparql.Assignable, SQL2RDFValueMapper], rTerm:sparql.Term):sql.Expression = { // :sparql.Var
+ val r:sql.PrimaryExpression = rTerm match {
+ case sparql.TermUri(u) => error("not implemented: translating RDF URI to SQL: " + u) // :sparql.Uri
+ case sparql.TermVar(v) => sql.PrimaryExpressionAttr(varToAttribute(varmap, sparql.VarAssignable(v)))
+ case sparql.TermBNode(b) => sql.PrimaryExpressionAttr(varToAttribute(varmap, sparql.BNodeAssignable(b)))
+ case sparql.TermLit(sparql.Literal(rdf.RDFLiteral(lit,rdf.Datatype(dt)))) =>
+ sql.PrimaryExpressionTyped({
+ dt.toString match {
+ case "http://www.w3.org/2001/XMLSchema#string" => rdb.RDB.Datatype.STRING
+ case "http://www.w3.org/2001/XMLSchema#integer" => rdb.RDB.Datatype.INTEGER
+ case "http://www.w3.org/2001/XMLSchema#date" => rdb.RDB.Datatype.DATE
+ case _ => error("unable to translate to RDF literal SQL: \"" + lit + "\"^^<" + dt + ">")
+ }
+ }, lit)
+ }
+ r
+ }
+
+ def xxx(varmap:Map[sparql.Assignable, SQL2RDFValueMapper], from:sparql.Expression) : sql.Expression = {
+ val l = from.conjuncts.map((conj) => {
+ conj match {
+ case sparql.SparqlTermExpression(sparql.TermVar(v:sparql.Var)) =>
+ assignable2expr999(varmap, sparql.TermVar(v))
+ case sparql.SparqlTermExpression(sparql.TermBNode(b:sparql.BNode)) =>
+ assignable2expr999(varmap, sparql.TermBNode(b))
+ case sparql.SparqlTermExpression(sparql.TermLit(l)) =>
+ assignable2expr999(varmap, sparql.TermLit(l))
+ case sparql.SparqlTermExpression(sparql.TermUri(u:sparql.Uri)) =>
+ assignable2expr999(varmap, sparql.TermUri(u))
+ case e:sparql.PrimaryExpression => yyy(varmap, e)
+ }})
+ if (l.size == 1)
+ l(0)
+ else
+ sql.ExprConjunction(l.toSet)
+ }
+
+ def yyy(varmap:Map[sparql.Assignable, SQL2RDFValueMapper], from:sparql.PrimaryExpression) : sql.Expression = {
+ from match {
+ case sparql.SparqlTermExpression(term) => assignable2expr999(varmap, term)
+ case sparql.PrimaryExpressionEq(l, r) => sql.RelationalExpressionEq(yyy(varmap, l), yyy(varmap, r))
+ case sparql.PrimaryExpressionGt(l, r) => sql.RelationalExpressionEq(yyy(varmap, l), yyy(varmap, r))
+ case sparql.PrimaryExpressionLt(l, r) => sql.RelationalExpressionEq(yyy(varmap, l), yyy(varmap, r))
+ }
+ }
+
+ /**
+ * junk that should be elsewhere
+ */
+
+ implicit def relname2relresource (rn:rdb.RDB.RelName) : sql.RelationResource = sql.RelationResource(rn)
+
+}
+
+/**
+ * Support functions to inject SparqlToSql results into an XML Results Set.
+ * @example:
+ * <pre>
+ * val xmlres:String =
+ * head(List[String]("emp", "name")) +
+ * startresult +
+ * binding("emp", "253", rdfmap, stem) +
+ * binding("name", "Bob", rdfmap, stem) +
+ * endresult +
+ * startresult +
+ * binding("emp", "258", rdfmap, stem) +
+ * // employee 258 has no name attribute so omit this binding
+ * endresult +
+ * foot
+ * </pre>
+ *
+ * @see {@link org.w3.sw.sparql2sql.SparqlToSql}
+ * @see {@link http://www.w3.org/TR/2008/REC-rdf-sparql-XMLres-20080115/ XML Results Format}
+ */
+object SqlToXMLRes {
+
+ /**
+ * Create a SPARQL Results format header and begin the body (results).
+ * @param vars list of variable names to insert into the header
+ */
+ def head (vars:List[String]) : String = {
+ "<?xml version=\"1.0\"?>\n<sparql xmlns=\"http://www.w3.org/2005/sparql-results#\">\n <head>\n" +
+ vars.map(varname => " <variable name=\"" + varname + "\"/>\n").mkString +
+ " </head>\n\n <results>\n"
+ }
+
+ /**
+ * Open a result element
+ */
+ def startresult () : String = {
+ " <result> \n"
+ }
+
+ /**
+ * Create a binding value appropriate for <code>name</code>'s datatype.
+ * @param name name of bound variable.
+ * @param value lexical value of bound variable, may need normalization from e.g. SQL.
+ * @param varmap mapping of sparql variables to datatypes, emitted by SparqlToSql._2
+ * @param stem stem URI for all generated RDF URIs.
+ */
+ def binding (name:String, value:String, varmap:Map[sparql.Assignable, SparqlToSql.SQL2RDFValueMapper], stem:StemURI) : String = {
+ def getattr (b:SparqlToSql.FullOrPartialBinding) : rdb.RDB.AttrName = {
+ b match {
+ case SparqlToSql.FullBinding(sql.RelVarAttr(_, attr)) =>
+ attr
+ case SparqlToSql.PartialBinding(binders) => {
+ val SparqlToSql.BindingConstraint(expr, sql.RelVarAttr(rv, attr)) = binders.toList(0)
+ attr
+ }
+ }
+ }
+ val t:String = varmap(sparql.VarAssignable(sparql.Var(name))) match {
+ case SparqlToSql.RDFNoder(rel, b) => "<uri>" + stem.s + rel + "/" + getattr(b) + "." + value + "#record</uri>"
+ case SparqlToSql.RDFBNoder(rel, b) => "<bnode>bnode_" + rel + "/" + getattr(b) + "." + value + "</bnode>"
+ case SparqlToSql.DateMapper(_) => "<literal datatype=\"http://www.w3.org/2001/XMLSchema#date\">" + value + "</literal>"
+ case SparqlToSql.IntMapper(_) => "<literal datatype=\"http://www.w3.org/2001/XMLSchema#integer\">" + value + "</literal>"
+ case SparqlToSql.StringMapper(_) => "<literal datatype=\"http://www.w3.org/2001/XMLSchema#string\">" + value + "</literal>"
+ }
+ " <binding name=\"" + name + "\">\n " + t + "\n </binding>\n"
+ }
+
+ /**
+ * Close a result element.
+ */
+ def endresult () : String = {
+ " </result>\n"
+ }
+
+ /**
+ * End SPARQL Results document.
+ */
+ def foot () : String = {
+ " </results>\n</sparql>\n"
+ }
+
+}
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/sparqlendpoint/src/main/scala/Config.scala Sun Oct 31 15:17:31 2010 -0400
@@ -0,0 +1,43 @@
+package org.w3.sw.sparqlendpoint
+
+import java.util.Properties
+import scala.io.{Source, Codec}
+
+import org.w3.sw.sql.SqlParser
+import org.w3.sw.rdb.RDB.Database
+
+trait ConfigHelper {
+
+ class P(p:Properties) {
+ def getProperty(key:String):Option[String] =
+ try { Some(p.getProperty(key)) } catch { case _ => None }
+ }
+
+ def load(filename:String) = {
+ val prop = new Properties
+ prop.load(this.getClass.getResourceAsStream("/"+filename))
+ new P(prop)
+ }
+
+ def getContent(filename:String):String = {
+ val is = this.getClass.getResourceAsStream("/"+filename)
+ Source.fromInputStream(is)(Codec.UTF8).getLines().mkString("\n")
+ }
+
+}
+
+object Config extends ConfigHelper {
+
+ val rdb2rdfProp = load("rdb2rdf.properties")
+
+ val DDLParser = SqlParser()
+
+ val dbDdl = getContent("ddl.txt")
+
+ val db:org.w3.sw.rdb.RDB.Database = DDLParser.parseAll(DDLParser.ddl, dbDdl).get
+
+ val defaultSparqlQuery = getContent("default-sparql-query.txt")
+
+ val defaultStemURI = rdb2rdfProp.getProperty("default-stemuri").get
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/sparqlendpoint/src/main/scala/Servlet.scala Sun Oct 31 15:17:31 2010 -0400
@@ -0,0 +1,166 @@
+package org.w3.sw.sparqlendpoint
+
+import javax.servlet.http.{HttpServlet, HttpServletRequest, HttpServletResponse}
+import scala.xml.XML
+
+import java.sql.{DriverManager, Connection, Statement, ResultSet, ResultSetMetaData}
+
+import java.sql.{Types => T}
+
+import java.net.URI
+import org.w3.sw.rdb.RDB.{RelName,AttrName}
+import org.w3.sw.sql.SqlParser
+import org.w3.sw.sparql
+import org.w3.sw.sparql.Sparql
+import org.w3.sw.sql
+import org.w3.sw.sparql2sql.{SparqlToSql,StemURI,SqlToXMLRes}
+
+object Control {
+
+ private def using[Closeable <: {def close(): Unit}, B](closeable: Closeable)(getB: Closeable => B): B =
+ try {
+ getB(closeable)
+ } finally {
+ closeable.close()
+ }
+
+ private def bmap[T](test: => Boolean)(block: => T): List[T] = {
+ val ret = new scala.collection.mutable.ListBuffer[T]
+ while(test) ret += block
+ ret.toList
+ }
+
+ /** Executes the SQL and processes the result set using the specified function. */
+ private def query[B](connection: Connection, sql: String)(process: ResultSet => B): B =
+ using (connection) { connection =>
+ using (connection.createStatement) { statement =>
+ using (statement.executeQuery(sql)) { results =>
+ process(results)
+ }
+ }
+ }
+
+ /** Executes the SQL and uses the process function to convert each row into a T. */
+ def queryEach[T](connection: Connection, sql: String)(process: ResultSet => T): List[T] =
+ query(connection, sql) { results =>
+ bmap(results.next) {
+ process(results)
+ }
+ }
+
+ def process(connection: Connection, sql: String,
+ head: List[String] => String,
+ startres: String,
+ binding: (String, String /*, Map[sparql.Assignable, SparqlToSql.SQL2RDFValueMapper], StemURI*/) => String,
+ endres: String, foot: String): String = {
+
+ query(connection, sql) { results =>
+
+ val metadata = results.getMetaData
+
+ val vars:List[String] =
+ (1 to metadata.getColumnCount) map { column => metadata.getColumnLabel(column) } toList
+
+ def processCell(rs:ResultSet, column:Int):Option[String] =
+ try {
+ val name:String = metadata.getColumnLabel(column)
+ val value:String =
+ metadata.getColumnType(column) match {
+ case T.VARCHAR => rs.getString(column)
+ case T.INTEGER => rs.getInt(column).toString
+ case T.DATE => rs.getDate(column).toString
+ case _ => throw new java.lang.Exception("you have to map this type!!!")
+ }
+ Some(binding(name, value))
+ } catch {
+ case _ => None
+ }
+
+ def processRow(rs:ResultSet):String =
+ (1 to metadata.getColumnCount) flatMap { column => processCell(rs, column) } mkString ""
+
+ val rows:List[String] = bmap(results.next) {
+ processRow(results)
+ }
+
+ val body = rows map { startres + _ + endres } mkString ""
+
+ head(vars) + body + foot
+
+ }
+
+ }
+
+}
+
+
+class SparqlEndpoint extends HttpServlet {
+
+ val encoding = "utf-8"
+
+ override def doGet(request:HttpServletRequest, response:HttpServletResponse) {
+
+ request.getParameter("query") match {
+ case null | "" => processIndex(request, response)
+ case query => processSparql(request, response, query)
+ }
+
+ }
+
+ def processSparql(request:HttpServletRequest, response:HttpServletResponse, query:String) {
+
+ val stemURI:StemURI = StemURI(Some(request.getParameter("stemuri")) getOrElse Config.defaultStemURI)
+
+ val sparqlParser = Sparql()
+
+ val sparqlSelect = sparqlParser.parseAll(sparqlParser.select, query).get
+
+ val (generated, rdfmap) = SparqlToSql(Config.db, sparqlSelect, stemURI, true, false)
+
+ Class.forName("com.mysql.jdbc.Driver").newInstance
+ val connection:Connection = DriverManager.getConnection("jdbc:mysql://localhost/rdb2rdf", "root", "")
+
+// if (! connection.isClosed) println("Successfully connected")
+
+ val res = Control.process(connection=connection,
+ sql=generated.toString,
+ head=SqlToXMLRes.head(_),
+ startres=SqlToXMLRes.startresult,
+ binding=SqlToXMLRes.binding(_, _, rdfmap, stemURI),
+ endres=SqlToXMLRes.endresult,
+ foot=SqlToXMLRes.foot)
+
+ response.setContentType("text/plain; charset='" + encoding + "'")
+
+ response.getWriter.write(res)
+
+ }
+
+ def processIndex(request:HttpServletRequest, response:HttpServletResponse) {
+
+ val index =
+ <html xmlns="http://www.w3.org/1999/xhtml">
+ <head><title>RDB2RDF Sparql endpoint</title></head>
+ <body>
+ <h1>RDB2RDF Sparql endpoint</h1>
+ <form action="sparql">
+ <p>
+ StemURI: <input cols="80" name="stemuri" id="stemuri" value={ Config.defaultStemURI } /><br />
+ <textarea rows="10" cols="80" name="query" id="query">{ Config.defaultSparqlQuery }</textarea>
+ <input type="submit" />
+ </p>
+ </form>
+ <hr />
+ <address>
+ <a href="http://www.w3.org/People/Eric/">Eric Prud'hommeaux</a>, <a href="http://www.w3.org/People/Bertails/">Alexandre Bertails</a>, Jun 2010
+ </address>
+ </body>
+ </html>
+
+ response.setContentType("application/xml; charset='" + encoding + "'")
+
+ XML.write(response.getWriter, index, encoding, false, null)
+
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/sql/src/main/scala/AddOrderedSet.scala Sun Oct 31 15:17:31 2010 -0400
@@ -0,0 +1,27 @@
+package org.w3.sw.util
+
+import scala.collection.immutable._
+
+// class AddOrderedSet[A](list:List[A]) extends Set[A] {
+
+// def contains(elem: A): Boolean = list.contains(elem)
+// def iterator: Iterator[A] = list.reverse.iterator
+// def + (elem: A) : AddOrderedSet[A] = if (this contains elem) this else new AddOrderedSet(elem :: list)
+// def - (elem: A) : AddOrderedSet[A] = new AddOrderedSet(list filterNot (_ == elem))
+
+// }
+
+class AddOrderedSet[A](list:List[A]) extends Set[A] {
+
+ def contains(elem: A): Boolean = list.contains(elem)
+ def iterator: Iterator[A] = list.iterator
+ def + (elem: A) : AddOrderedSet[A] = if (this contains elem) this else new AddOrderedSet(list ++ List(elem))
+ def - (elem: A) : AddOrderedSet[A] = new AddOrderedSet(list filterNot (_ == elem))
+
+}
+
+object AddOrderedSet {
+ def apply[A]():AddOrderedSet[A] = AddOrderedSet(List[A]())
+ def apply[A](list:List[A]):AddOrderedSet[A] = new AddOrderedSet(list)
+ def apply[A](args:A*):AddOrderedSet[A] = AddOrderedSet(args.toList)
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/sql/src/main/scala/SQL.scala Sun Oct 31 15:17:31 2010 -0400
@@ -0,0 +1,487 @@
+package org.w3.sw.sql
+
+import org.w3.sw.util._
+import org.w3.sw.rdb.RDB
+
+import scala.util.parsing.combinator._
+
+import scala.collection.Set
+
+object SQLParsers extends RegexParsers {
+
+ val int = """[0-9]+""".r
+ val chars = "\"([^\"\\\\\n\r]|\\\\[tbnrf\\\"'])*\"".r
+}
+
+import SQLParsers._
+
+import scala.util.parsing.combinator._
+import java.net.URI
+
+sealed abstract class RelationORSubselect
+case class Subselect(sel:SelectORUnion) extends RelationORSubselect {
+ override def toString = "(\n " + sel.toString.replace("\n", "\n ") + "\n )"
+}
+sealed abstract class SelectORUnion
+case class Select(distinct:Boolean, projection:Projection, tablelist:TableList, expression:Option[Expression], order:List[OrderElt], offset:Option[Int], limit:Option[Int]) extends SelectORUnion {
+ override def toString =
+ "SELECT "+
+ { if (distinct) "DISTINCT " else "" }+
+ projection+"\n"+
+ tablelist+
+ { if (expression.isDefined) {"\b WHERE " + expression.get + " "} else "" }+
+ { if (order.size > 0) {order.map(o => o.toString).mkString("ORDER BY ", " ", " ") } else "" }+
+ { if (offset.isDefined) {"OFFSET " + offset + " "} else "" }+
+ { if (limit.isDefined) {"LIMIT " + limit + " "} else "" }
+}
+case class RelationResource(rn:RDB.RelName) extends RelationORSubselect {
+ override def toString = rn.toString
+}
+object foo { // doesn't work
+ implicit def relname2relationref (rn:RDB.RelName) = RelationResource(rn)
+}
+case class Union(disjoints:Set[Select]) extends SelectORUnion {
+ override def toString = " " + (disjoints.toList.map(s => s.toString.replace("\n", "\n ")).mkString("\nUNION\n "))
+}
+case class Projection(attributes:Set[ProjectAttribute]) {
+ // foo, bar
+ override def toString = attributes.toList.sortWith((l, r) => l.attralias.toString < r.attralias.toString).mkString(", ")
+}
+case class ProjectAttribute(value:RelVarAttrORExpression, attralias:AttrAlias) {
+ override def toString = value + " AS " + attralias
+}
+//case class RelAttribute(relation:Relation, attribute:RDB.AttrName) c.f. ForeignKey999
+sealed abstract class RelVarAttrORExpression
+case class RelVarAttr(relvar:RelVar, attribute:RDB.AttrName) extends RelVarAttrORExpression {
+ override def toString = relvar + "." + attribute
+}
+
+case class AttrAlias(n:Name) {
+ override def toString = n.s /* "'" + n.s + "'" */
+}
+case class RelVar(n:Name) {
+ override def toString = n.s /* "'" + n.s + "'" */
+}
+case class TableList(joins:AddOrderedSet[Join]) {
+ override def toString =
+ if (joins.size == 0) ""
+ else {
+ " FROM " + joins.foldLeft(("", 0))(
+ (pair, entry) => (pair._1 + {
+ if (pair._2 == 0) entry.toString.substring(19) // !!! shameless!
+ else entry
+ }, pair._2+1))._1
+ }
+}
+
+sealed abstract class Join(res:AliasedResource)
+case class InnerJoin(res:AliasedResource, optOn:Option[Expression]) extends Join(res) {
+ override def toString = "\n INNER JOIN " + res
+}
+case class LeftOuterJoin(res:AliasedResource, on:Expression) extends Join(res) {
+ override def toString = "\n LEFT OUTER JOIN " + res + " ON " + on
+}
+
+case class AliasedResource(rel:RelationORSubselect, as:RelVar) {
+ override def toString = rel + " AS " + as
+}
+sealed abstract class Expression extends RelVarAttrORExpression
+case class ExprConjunction(exprs:Set[Expression]) extends Expression {
+ override def toString = "(" + (exprs.toList.sortWith((l, r) => l.toString < r.toString).mkString (")\n AND (")) + ")"
+}
+case class ExprDisjunction(exprs:Set[Expression]) extends Expression {
+ override def toString = "(" + (exprs mkString (") OR (")) + ")"
+}
+sealed abstract class RelationalExpression extends Expression
+case class RelationalExpressionEq(l:Expression, r:Expression) extends RelationalExpression {
+ override def toString = l + "=" + r
+ /* safer operator== , but doesn't quite work yet. */
+ // override def hashCode = 41 * l.hashCode + r.hashCode
+ // override def equals(other: Any) = other match {
+ // case that: RelationalExpressionEq =>
+ // (that canEqual this) &&
+ // ( ( (this.l == that.l) && (this.r == that.r) ||
+ // (this.l == that.r) && (this.r == that.l) ) )
+ // case _ =>
+ // false
+ // }
+ // override def canEqual(other: Any) =
+ // other.isInstanceOf[RelationalExpressionEq]
+
+ override def equals(that:Any) =
+ that match {
+ case RelationalExpressionEq(l1, r1) => (l == l1 && r == r1) || (r == l1 && l == r1)
+ case _ => false
+ }
+ override def hashCode =
+ if (r.hashCode < l.hashCode)
+ r.hashCode + l.hashCode
+ else
+ l.hashCode + r.hashCode
+}
+case class RelationalExpressionNe(l:Expression, r:Expression) extends RelationalExpression {
+ override def toString = l + "!=" + r
+}
+case class RelationalExpressionLt(l:Expression, r:Expression) extends RelationalExpression {
+ override def toString = l + "<" + r
+}
+case class RelationalExpressionGt(l:Expression, r:Expression) extends RelationalExpression {
+ override def toString = l + ">" + r
+}
+case class RelationalExpressionNull(l:Expression) extends RelationalExpression { // Expression?
+ override def toString = l + " IS NULL"
+}
+case class RelationalExpressionNotNull(l:Expression) extends RelationalExpression { // Expression?
+ override def toString = l + " IS NOT NULL"
+}
+sealed abstract class PrimaryExpression extends Expression
+case class PrimaryExpressionAttr(fqattribute:RelVarAttr) extends PrimaryExpression {
+ override def toString = "" + fqattribute
+}
+case class PrimaryExpressionTyped(datatype:RDB.Datatype, i:Name) extends PrimaryExpression {
+ override def toString = /* "'" + i.s + "'" */ /* + datatype */
+ datatype match {
+ case RDB.Datatype("Int") => i.s
+ case _ => "\"" + i.s + "\""
+ }
+}
+case class OrderElt(desc:Boolean, expr:Expression) {
+ override def toString = { if (desc) "DESC" else "ASC" } + "(" + expr.toString + ")"
+}
+case class ConstNULL() extends PrimaryExpression {
+ override def toString = "NULL"
+}
+case class Concat(args:List[Expression]) extends PrimaryExpression {
+ override def toString = args.mkString("CONCAT(", ", ", ")")
+}
+case class IfElse(cond:Expression, pass:Expression, fail:Expression) extends PrimaryExpression {
+ override def toString = "CONCAT(" + cond + ", " + pass + ", " + fail + ")"
+}
+
+case class Name(s:String)
+
+object Name {
+ implicit def fromStringToName(s:String):Name = Name(s)
+}
+
+sealed abstract class FieldDescOrKeyDeclaration
+case class FieldDesc(attr:RDB.AttrName, datatype:RDB.Datatype, pkness:Boolean) extends FieldDescOrKeyDeclaration
+sealed abstract class KeyDeclaration extends FieldDescOrKeyDeclaration
+case class PrimaryKeyDeclaration(key:RDB.CandidateKey) extends KeyDeclaration
+case class ForeignKeyDeclaration(fk:List[RDB.AttrName], rel:RDB.RelName, pk:RDB.CandidateKey) extends KeyDeclaration
+case class View(rel:RDB.RelName, defn:SelectORUnion) { // sibling of RDB.Relation
+ override def toString = "CREATE VIEW " + rel + " AS\n" + defn
+}
+
+case class SqlParser() extends JavaTokenParsers {
+
+ def createview:Parser[View] = // @@@ could stick under ddl
+ "CREATE" ~ "VIEW" ~ relation ~ "AS" ~ selectORunion ^^
+ { case "CREATE"~"VIEW"~relation~"AS"~defn => View(relation, defn) }
+
+ def ddl:Parser[RDB.Database] =
+ rep1sep(createtable, ";") ~ opt(";") ^^
+ {
+ case l~x => RDB.Database(l.foldLeft(Map[RDB.RelName, RDB.Relation]())((m, p) => {
+ val (rel:RDB.RelName, desc:RDB.Relation) = p
+ m + (rel -> desc)
+ }))
+ }
+
+ def createtable:Parser[(RDB.RelName, RDB.Relation)] =
+ "CREATE" ~ "TABLE" ~ relation ~ "(" ~ rep1sep(fielddescorkeydef, ",") ~ ")" ^^
+ {
+ case "CREATE"~"TABLE"~relation~"("~reldesc~")" => {
+ val pk0:Option[RDB.CandidateKey] = None
+ val attrs0 = Map[RDB.AttrName, RDB.Datatype]()
+ val candidates0 = List[RDB.CandidateKey]()
+ val fks0 = Map[List[RDB.AttrName], RDB.Target]()
+ /* <pk>: (most recently parsed) PRIMARY KEY
+ * <attrs>: map of attribute to type (e.g. INTEGER)
+ * <fks>: map holding FOREIGN KEY relation REFERENCES attr
+ */
+ val (pk, attrs, candidates, fks) =
+ reldesc.foldLeft((pk0, attrs0, candidates0, fks0))((p, rd) => {
+ val (pkopt, attrs, candidates, fks) = p
+ rd match {
+ case FieldDesc(attr, value, pkness) => {
+ val (pkNew, candNew) =
+ if (pkness) (Some(RDB.CandidateKey(List(attr))), candidates ++ List(RDB.CandidateKey(attr.n)))
+ else (pkopt, candidates)
+ (pkNew, attrs + (attr -> value), candNew, fks)
+ }
+ case PrimaryKeyDeclaration(key) =>
+ // @@ why doesn't [[ candidates + RDB.CandidateKey(attr.n) ]] work?
+ (Some(key), attrs, candidates ++ List(RDB.CandidateKey(key map {attr => RDB.AttrName(attr.n)})), fks)
+ case ForeignKeyDeclaration(fk, rel, pk) =>
+ (pkopt, attrs, candidates, fks + (fk -> RDB.Target(rel, pk)))
+ }
+ })
+ val rd = RDB.Relation(relation, RDB.Header(attrs), List(), candidates, pk, RDB.ForeignKeys(fks))
+ (relation -> rd)
+ }
+ }
+
+ def fielddescorkeydef:Parser[FieldDescOrKeyDeclaration] = (
+ attribute ~ typpe ~ opt("PRIMARY" ~ "KEY") ^^
+ { case attribute~typpe~pkness => FieldDesc(attribute, typpe, pkness.isDefined) }
+ | "PRIMARY" ~ "KEY" ~ "(" ~ rep1sep(attribute, ",") ~ ")" ^^
+ { case "PRIMARY"~"KEY"~"("~attributes~")" => PrimaryKeyDeclaration(RDB.CandidateKey(attributes)) }
+ | "FOREIGN" ~ "KEY" ~ "(" ~ rep1sep(attribute, ",") ~ ")" ~ "REFERENCES" ~ relation ~ "(" ~ rep1sep(attribute, ",") ~ ")" ^^
+ { case "FOREIGN"~"KEY"~"("~fk~")"~"REFERENCES"~relation~"("~pk~")" => ForeignKeyDeclaration(fk, relation, RDB.CandidateKey(pk)) }
+ )
+
+ def typpe:Parser[RDB.Datatype] = (
+ "INT" ^^ { case _ => RDB.Datatype.INTEGER }
+ | "DOUBLE" ^^ { case _ => RDB.Datatype.DOUBLE }
+ | "STRING" ^^ { case _ => RDB.Datatype.STRING }
+ | "DATETIME" ^^ { case _ => RDB.Datatype.DATETIME }
+ | "DATE" ^^ { case _ => RDB.Datatype.DATE }
+ )
+
+ def selectORunion:Parser[SelectORUnion] =
+ rep1sep(select, "UNION") ^^ { l => if (l.size == 1) l(0) else Union(l.toSet) }
+
+ def select:Parser[Select] =
+ "SELECT" ~ opt("DISTINCT") ~ projection ~ opt(tablelist) ~ opt(where) ~ opt(order) ~ opt(offset) ~ opt(limit) ^^
+ {
+ case "SELECT" ~ distinct ~ attributes ~ tablesANDons ~ whereexpr ~ order ~ offset ~ limit => {
+ val (tables, onExpressions) =
+ tablesANDons.getOrElse((TableList(AddOrderedSet[Join]()), Set[Expression]()))
+ val t:Set[Expression] = onExpressions
+ val onConjoints:Set[Expression] =
+ onExpressions.foldLeft(Set[Expression]())((s, ent) =>
+ s ++ {ent match {
+ case ExprConjunction(l) => l
+ case _ => Set(ent)
+ }})
+ val conjoints = whereexpr match {
+ case Some(ExprConjunction(l)) => onConjoints ++ l
+ case Some(x) => onConjoints + x
+ case _ => onConjoints
+ }
+ val expr:Option[Expression] = conjoints.size match {
+ case 0 => None
+ case 1 => Some(conjoints.toList(0))
+ case _ => Some(ExprConjunction(conjoints))
+ }
+ Select(distinct.isDefined, attributes, tables, expr, order.getOrElse(List[OrderElt]()), offset, limit)
+ }
+ }
+
+ def order:Parser[List[OrderElt]] =
+ "ORDER" ~ "BY" ~ rep(orderelt) ^^ { case o~b~elts => elts }
+
+ def orderelt:Parser[OrderElt] = (
+ "ASC" ~ "(" ~ expression ~ ")" ^^ { case a~o~expr~c => OrderElt(false, expr) }
+ | "DESC" ~ "(" ~ expression ~ ")" ^^ { case a~o~expr~c => OrderElt(true, expr) }
+ | fqattribute ^^ { case v => OrderElt(false, PrimaryExpressionAttr(v)) }
+ )
+
+ def offset:Parser[Int] =
+ "OFFSET" ~ int ^^ { case o~i => i.toInt }
+
+ def limit:Parser[Int] =
+ "LIMIT" ~ int ^^ { case o~i => i.toInt }
+
+ def where:Parser[Expression] =
+ "WHERE" ~ expression ^^ { case "WHERE" ~ expression => expression }
+
+ def projection:Parser[Projection] =
+ repsep(namedattribute, ",") ^^ { l => Projection(l.toSet) }
+
+ def namedattribute:Parser[ProjectAttribute] =
+ fqattributeORprimaryexpression ~ "AS" ~ attralias ^^
+ { case fqattributeORprimaryexpression ~ "AS" ~ attralias =>
+ ProjectAttribute(fqattributeORprimaryexpression, attralias) }
+
+ def fqattributeORprimaryexpression:Parser[RelVarAttrORExpression] = (
+ fqattribute ^^ { case fqattribute => fqattribute }
+ | primaryexpression ^^ { case const => const }
+ )
+
+ def fqattribute:Parser[RelVarAttr] =
+ relvar ~ "." ~ attribute ^^
+ { case relvar ~ "." ~ attribute => RelVarAttr(relvar, attribute) }
+
+ def attribute:Parser[RDB.AttrName] =
+ """[a-zA-Z_]\w*""".r ^^ { x => RDB.AttrName(x) }
+
+ def attralias:Parser[AttrAlias] =
+ """[a-zA-Z_]\w*""".r ^^ { x => AttrAlias(Name(x)) }
+
+ def relationORsubselect:Parser[RelationORSubselect] = (
+ relation ^^ { x => RelationResource(x) }
+ | "(" ~ selectORunion ~ ")" ^^ { case "("~s~")" => Subselect(s) }
+ )
+
+ def relation:Parser[RDB.RelName] =
+ """[a-zA-Z_]\w*""".r ^^ { x => RDB.RelName(x) }
+
+ def relvar:Parser[RelVar] =
+ """[a-zA-Z_]\w*""".r ^^ { x => RelVar(Name(x)) }
+
+ def tablelist:Parser[(TableList, Set[Expression])] =
+ "FROM" ~ aliasedjoin ~ rep(innerORouter) ^^
+ { case "FROM"~aj~l => (TableList(AddOrderedSet(InnerJoin(aj, None) :: l.map((one) => one._1))),
+ l.foldLeft(Set[Expression]())((all, one) => all ++ one._2)) }
+
+ def innerORouter:Parser[(Join, Set[Expression])] = (
+ "INNER" ~ "JOIN" ~ aliasedjoin ~ opt("ON" ~ expression) ^^
+ { case "INNER"~"JOIN"~a~o => (InnerJoin(a, None), { if (o.isDefined) Set(o.get._2) else Set[Expression]() } ) }
+ | "LEFT" ~ "OUTER" ~ "JOIN" ~ aliasedjoin ~ "ON" ~ expression ^^
+ { case l~o~j~alijoin~on~expr => (LeftOuterJoin(alijoin, expr), Set[Expression]()) }
+ )
+
+ def aliasedjoin:Parser[AliasedResource] =
+ relationORsubselect ~ "AS" ~ relvar ^^
+ { case rel1 ~ "AS" ~ rel2 => AliasedResource(rel1, rel2) }
+
+ def expression:Parser[Expression] =
+ ORexpression ^^ { x => x }
+
+ def ORexpression:Parser[Expression] =
+ rep1sep (ANDexpression, "OR") ^^
+ { xs => if (xs.size > 1) ExprDisjunction(xs.toSet) else xs(0) }
+
+ def ANDexpression:Parser[Expression] =
+ rep1sep (relationalexpression, "AND") ^^
+ { xs => if (xs.size > 1) ExprConjunction(xs.toSet) else xs(0) }
+
+ def relationalexpression:Parser[Expression] = (
+ primaryexpression ~ "=" ~ primaryexpression ^^
+ { case primaryexpression ~ "=" ~ rvalue => RelationalExpressionEq(primaryexpression, rvalue) }
+ | primaryexpression ~ "!=" ~ primaryexpression ^^
+ { case primaryexpression ~ "!=" ~ rvalue => RelationalExpressionNe(primaryexpression, rvalue) }
+ | primaryexpression ~ "<" ~ primaryexpression ^^
+ { case primaryexpression ~ "<" ~ rvalue => RelationalExpressionLt(primaryexpression, rvalue) }
+ | primaryexpression ~ ">" ~ primaryexpression ^^
+ { case primaryexpression ~ ">" ~ rvalue => RelationalExpressionGt(primaryexpression, rvalue) }
+ | primaryexpression ~ "IS" ~ "NULL" ^^
+ { case primaryexpression ~ "IS" ~ "NULL" => RelationalExpressionNull(primaryexpression) }
+ | primaryexpression ~ "IS" ~ "NOT" ~ "NULL" ^^
+ { case primaryexpression ~ "IS" ~ "NOT" ~ "NULL" => RelationalExpressionNotNull(primaryexpression) }
+ | primaryexpression ^^
+ { case primaryexpression => primaryexpression }
+ )
+
+ def primaryexpression:Parser[Expression] = (
+ fqattribute ^^ { PrimaryExpressionAttr(_) }
+ | int ^^ { i => PrimaryExpressionTyped(RDB.Datatype.INTEGER, Name(i)) }
+ | chars ^^ { x => PrimaryExpressionTyped(RDB.Datatype.STRING, Name(x.substring(1, x.size - 1))) }
+ | "NULL" ^^ { case "NULL" => ConstNULL() }
+ | "CONCAT" ~ "(" ~ rep1sep(expression, ",") ~ ")" ^^ { case "CONCAT"~"("~expressions~")" => Concat(expressions) }
+ | "IF" ~ "(" ~ expression ~ "," ~ expression ~ "," ~ expression ~ ")" ^^ { case "IF"~"("~c~","~p~","~f~")" => IfElse(c, p, f) }
+ | "(" ~ expression ~ ")" ^^ { case "("~x~")" => x }
+ )
+
+}
+
+case class PrettySql(select:Select) {
+ def makePretty():Select = {
+ val Select(distinct, projection, tablelist, expression, order, offset, limit) = select
+ val nullStripped =
+ if (expression.isDefined) {
+ val nonNullAttrs = PrettySql.findNonNullRelVarAttrs(expression.get)
+ PrettySql.stripNotNulls(expression.get, nonNullAttrs)
+ }
+ else None
+ val XeqXStripped =
+ if (nullStripped.isDefined) {
+ PrettySql.stripXeqX(nullStripped.get)
+ }
+ else None
+ val strippedTables = tablelist.joins.foldLeft(AddOrderedSet[Join]())((set, join) => set + {
+ join match {
+ case InnerJoin(AliasedResource(rel, as), optOn) => InnerJoin(AliasedResource({ rel match {
+ case Subselect(s:Select) => Subselect(PrettySql(s).makePretty())
+ case Subselect(Union(disjoints)) => Subselect(Union(disjoints.map(s => PrettySql(s).makePretty())))
+ case r:RelationResource => r
+ }}, as), None)
+ case LeftOuterJoin(AliasedResource(rel, as), on:Expression) => LeftOuterJoin(AliasedResource({ rel match {
+ case Subselect(s:Select) => Subselect(PrettySql(s).makePretty())
+ case Subselect(Union(disjoints)) => Subselect(Union(disjoints.map(s => PrettySql(s).makePretty())))
+ case r:RelationResource => r
+ }}, as), on)
+ }})
+ Select(distinct, projection, TableList(strippedTables), XeqXStripped, order, offset, limit)
+ }
+}
+
+object PrettySql {
+ def findNonNullRelVarAttrs(expr:Expression):Set[RelVarAttr] = {
+ expr match {
+ case ExprConjunction(s) => s.foldLeft(Set[RelVarAttr]())((s, e) => s ++ findNonNullRelVarAttrs(e))
+ case ExprDisjunction(s) => {
+ val l = s.toList
+ l.slice(1, l.size).foldLeft(findNonNullRelVarAttrs(l(0)))((s, e) => s & findNonNullRelVarAttrs(e))
+ }
+ case RelationalExpressionEq(l, r) => findNonNullRelVarAttrs(l) ++ findNonNullRelVarAttrs(r)
+ case RelationalExpressionNe(l, r) => findNonNullRelVarAttrs(l) ++ findNonNullRelVarAttrs(r)
+ case RelationalExpressionLt(l, r) => findNonNullRelVarAttrs(l) ++ findNonNullRelVarAttrs(r)
+ case RelationalExpressionGt(l, r) => findNonNullRelVarAttrs(l) ++ findNonNullRelVarAttrs(r)
+ case e:PrimaryExpressionTyped => Set()
+ case PrimaryExpressionAttr(a) => Set(a)
+ case e:ConstNULL => Set()
+ case e:Concat => Set()
+ case RelationalExpressionNull(a) => findNonNullRelVarAttrs(a)
+ case RelationalExpressionNotNull(a) => Set()
+ case IfElse(eef, den, els) => Set()
+ }
+ }
+ def stripNotNulls(expr:Expression, stripMe:Set[RelVarAttr]):Option[Expression] = {
+ expr match {
+ case ExprConjunction(l) => Some(ExprConjunction({l.foldLeft(Set[Expression]())((s, e) => {
+ val e2 = stripNotNulls(e,stripMe)
+ if (e2.isDefined) s + e2.get
+ else s})}))
+ case ExprDisjunction(l) => Some(ExprDisjunction({l.foldLeft(Set[Expression]())((s, e) => {
+ val e2 = stripNotNulls(e,stripMe)
+ if (e2.isDefined) s + e2.get
+ else s})}))
+ case e:RelationalExpressionEq => Some(e)
+ case e:RelationalExpressionNe => Some(e)
+ case e:RelationalExpressionLt => Some(e)
+ case e:RelationalExpressionGt => Some(e)
+ case e:PrimaryExpressionTyped => Some(e)
+ case e:PrimaryExpressionAttr => Some(e)
+ case e:ConstNULL => Some(e)
+ case e:Concat => Some(e)
+ case e:RelationalExpressionNull => Some(e)
+ case RelationalExpressionNotNull(PrimaryExpressionAttr(a)) =>
+ if (stripMe.contains(a)) None
+ else Some(RelationalExpressionNotNull(PrimaryExpressionAttr(a)))
+ case e:RelationalExpressionNotNull => Some(e)
+ case e:IfElse => Some(e)
+ }
+ }
+ def stripXeqX(expr:Expression):Option[Expression] = {
+ expr match {
+ case ExprConjunction(l) => Some(ExprConjunction({l.foldLeft(Set[Expression]())((s, e) => {
+ val e2 = stripXeqX(e)
+ if (e2.isDefined) s + e2.get
+ else s})}))
+ case ExprDisjunction(l) => Some(ExprDisjunction({l.foldLeft(Set[Expression]())((s, e) => {
+ val e2 = stripXeqX(e)
+ if (e2.isDefined) s + e2.get
+ else s})}))
+ case e:RelationalExpressionEq => {
+ val RelationalExpressionEq(l, r) = e
+ if (l == r) None
+ else Some(e)
+ }
+ case e:RelationalExpressionNe => Some(e)
+ case e:RelationalExpressionLt => Some(e)
+ case e:RelationalExpressionGt => Some(e)
+ case e:PrimaryExpressionTyped => Some(e)
+ case e:PrimaryExpressionAttr => Some(e)
+ case e:ConstNULL => Some(e)
+ case e:Concat => Some(e)
+ case e:RelationalExpressionNull => Some(e)
+ case e:RelationalExpressionNotNull => Some(e)
+ case e:IfElse => Some(e)
+ }
+ }
+ implicit def toPrettySql(select:Select) = PrettySql(select)
+}
+
--- a/src/main/scala/AddOrderedSet.scala Fri Oct 15 16:42:41 2010 -0400
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,26 +0,0 @@
-package w3c.sw.util
-import scala.collection.immutable._
-
-// class AddOrderedSet[A](list:List[A]) extends Set[A] {
-
-// def contains(elem: A): Boolean = list.contains(elem)
-// def iterator: Iterator[A] = list.reverse.iterator
-// def + (elem: A) : AddOrderedSet[A] = if (this contains elem) this else new AddOrderedSet(elem :: list)
-// def - (elem: A) : AddOrderedSet[A] = new AddOrderedSet(list filterNot (_ == elem))
-
-// }
-
-class AddOrderedSet[A](list:List[A]) extends Set[A] {
-
- def contains(elem: A): Boolean = list.contains(elem)
- def iterator: Iterator[A] = list.iterator
- def + (elem: A) : AddOrderedSet[A] = if (this contains elem) this else new AddOrderedSet(list ++ List(elem))
- def - (elem: A) : AddOrderedSet[A] = new AddOrderedSet(list filterNot (_ == elem))
-
-}
-
-object AddOrderedSet {
- def apply[A]():AddOrderedSet[A] = AddOrderedSet(List[A]())
- def apply[A](list:List[A]):AddOrderedSet[A] = new AddOrderedSet(list)
- def apply[A](args:A*):AddOrderedSet[A] = AddOrderedSet(args.toList)
-}
--- a/src/main/scala/Config.scala Fri Oct 15 16:42:41 2010 -0400
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,43 +0,0 @@
-package org.w3.sparql2sql.servlet
-
-import java.util.Properties
-import scala.io.{Source, Codec}
-
-import w3c.sw.sql.SqlParser
-import w3c.sw.sql.RDB.Database
-
-trait ConfigHelper {
-
- class P(p:Properties) {
- def getProperty(key:String):Option[String] =
- try { Some(p.getProperty(key)) } catch { case _ => None }
- }
-
- def load(filename:String) = {
- val prop = new Properties
- prop.load(this.getClass.getResourceAsStream("/"+filename))
- new P(prop)
- }
-
- def getContent(filename:String):String = {
- val is = this.getClass.getResourceAsStream("/"+filename)
- Source.fromInputStream(is)(Codec.UTF8).getLines().mkString("\n")
- }
-
-}
-
-object Config extends ConfigHelper {
-
- val rdb2rdfProp = load("rdb2rdf.properties")
-
- val DDLParser = SqlParser()
-
- val dbDdl = getContent("ddl.txt")
-
- val db:w3c.sw.sql.RDB.Database = DDLParser.parseAll(DDLParser.ddl, dbDdl).get
-
- val defaultSparqlQuery = getContent("default-sparql-query.txt")
-
- val defaultStemURI = rdb2rdfProp.getProperty("default-stemuri").get
-
-}
--- a/src/main/scala/GraphAnalyzer.scala Fri Oct 15 16:42:41 2010 -0400
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,71 +0,0 @@
-package w3c.sw.util
-import scala.collection.immutable._
-
-class GraphAnalyzer[A](m:Map[A, Set[A]]) {
-
- def reaches (from:A, to:A) = {
- if (m.contains(from))
- new GraphAnalyzer[A](m + (from -> (m(from) + to)))
- else
- new GraphAnalyzer[A](m + (from -> Set(to)))
- }
- def pair (l:A, r:A) = {
- reaches(l, r).reaches(r, l)
- }
- def neededFor (need:Set[A], t:A, visited:Set[A]):Boolean = {
- if (!m.contains(t))
- error("unknown symbol: " + t)
- var ret:Boolean = false
- m(t).map((r) => {
- if (visited.contains(t)) return false
- if (need.contains(t)) return true
- ret |= neededFor(need, r, visited + t)
- })
- return ret
- }
-
-}
-
-object GraphAnalyzer {
- def apply[A]():GraphAnalyzer[A] = GraphAnalyzer() // Map[A, Set[A]]()
- // def apply[A](list:List[A]):GraphAnalyzer[A] = new GraphAnalyzer(list)
- // def apply[A](args:A*):GraphAnalyzer[A] = GraphAnalyzer(args.toList)
-}
-
-// object GraphAnalyzer {
-// }
-// //def createEmptyGraphAnalyzer () = GraphAnalyzer(Map[A, Set[A]]())
-
- // case class Reacher (b:Map[String, Map[String, Set[String]]]) {
- // def reaches (from:String, to:String) = {
- // if (b.contains(from)) {
- // if (b(from).contains(to)) {
- // val back = b(from)(to) + from
- // val fore = b(from) + (to -> back)
- // val ret = Reacher(b + (from -> fore))
- // println("duplicate path from " + from + " to " + to)
- // // println("ret: " + ret)
- // ret
- // } else {
- // println(from + "->" + to + " + " + this)
- // val back = Set[String](from)
- // val fore = b(from) + (to -> back)
- // val ret = Reacher(b + (from -> fore))
- // println("ret: " + ret)
- // ret
- // }
- // } else {
- // val back = Set[String](from)
- // val fore = Map[String, Set[String]](to -> back)
- // val ret = Reacher(b + (from -> fore))
- // // println("ret: " + ret)
- // ret
- // }
- // }
- // def pair (l:String, r:String) = {
- // reaches(l, r).reaches(r, l)
- // }
- // override def toString = b.toString
- // }
- // def createEmptyReacher () = Reacher(Map[String, Map[String, Set[String]]]())
-
--- a/src/main/scala/RDF.scala Fri Oct 15 16:42:41 2010 -0400
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,31 +0,0 @@
-package w3c.sw.rdf
-
-import java.net.URI
-
-case class RDFTriple(s:RDFSubject, p:RDFPredicate, o:RDFObject)
-
-sealed abstract class RDFSubject()
-case class RDFSubjectUri(uri:URI) extends RDFSubject
-case class RDFSubjectBlankNode(b:BlankNode) extends RDFSubject
-
-case class RDFPredicate(uri:URI)
-
-sealed abstract class RDFObject()
-case class RDFObjectUri(uri:URI) extends RDFObject
-case class RDFObjectBlankNode(b:BlankNode) extends RDFObject
-
-case class BlankNode(debugName:String)
-
-case class RDFLiteral(lexicalForm:String, datatype:Datatype) {
- override def toString = "\"" + lexicalForm + "\"" + datatype
-}
-case class Datatype(uri:URI) {
- override def toString = "^^" + uri
-}
-
-object RDFLiteral {
- val StringDatatype = Datatype(new URI("http://www.w3.org/2001/XMLSchema#string"))
- val IntegerDatatype = Datatype(new URI("http://www.w3.org/2001/XMLSchema#integer"))
- val DateDatatype = Datatype(new URI("http://www.w3.org/2001/XMLSchema#date"))
- // val DateTimeDatatype = Datatype(new URI("http://www.w3.org/2001/XMLSchema#dateTime"))
-}
--- a/src/main/scala/SPARQL.scala Fri Oct 15 16:42:41 2010 -0400
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,431 +0,0 @@
-package w3c.sw.sparql
-
-import w3c.sw.rdf._
-import scala.util.parsing.combinator._
-import java.net.URI
-
-object MyParsers extends RegexParsers {
-
- val uri = """[a-zA-Z0-9:/#_\.\-]+""".r
- val integer = """[0-9]+""".r
- val name = """[a-zA-Z][a-zA-Z0-9_-]*|[a-zA-Z_][a-zA-Z0-9_]+""".r
- var prefixes:Map[String, String] = Map()
- var bnodes:Map[String, BNode] = Map()
- var nextBNode = 1
-}
-
-import MyParsers._
-
-case class Select(distinct:Boolean, attrs:SparqlAttributeList, gp:GraphPattern, order:List[OrderElt], offset:Option[Int], limit:Option[Int]) {
- override def toString =
- "SELECT "+
- { if (distinct) "DISTINCT " else "" }+
- attrs+"\n"+gp+" "+
- { if (order.size > 0) {order.map(o => o.toString).mkString("ORDER BY ", " ", " ") } else "" }+
- { if (offset.isDefined) {"OFFSET " + offset.get + " "} else "" }+
- { if (limit.isDefined) {"LIMIT " + limit.get + " "} else "" }
-}
-case class Construct(head:TriplesBlock, gp:GraphPattern)
-case class SparqlAttributeList(attributelist:List[Var]) {
- override def toString = attributelist.toList.sortWith((l, r) => l.s < r.s).mkString(" ")
-}
-
-sealed abstract class GraphPattern {
- def findVars ():Set[Var] = {
- this match {
- case TableFilter(gp2:GraphPattern, expr:Expression) =>
- gp2.findVars
-
- case TriplesBlock(triplepatterns) =>
- /* Examine each triple, updating the compilation state. */
- triplepatterns.foldLeft(Set[Var]())((x, y) => x ++ y.findVars)
-
- case TableConjunction(list) =>
- /* Examine each triple, updating the compilation state. */
- list.foldLeft(Set[Var]())((x, y) => x ++ y.findVars)
-
- case OptionalGraphPattern(gp2) =>
- /* Examine each triple, updating the compilation state. */
- gp2.findVars
-
- case x => error("no code to handle " + x)
- }
- }
- def findAssignables ():Set[Assignable] = {
- this match {
- case TableFilter(gp2:GraphPattern, expr:Expression) =>
- gp2.findAssignables
-
- case TriplesBlock(triplepatterns) =>
- /* Examine each triple, updating the compilation state. */
- triplepatterns.foldLeft(Set[Assignable]())((x, y) => x ++ y.findAssignables)
-
- case TableConjunction(list) =>
- /* Examine each triple, updating the compilation state. */
- list.foldLeft(Set[Assignable]())((x, y) => x ++ y.findAssignables)
-
- case OptionalGraphPattern(gp2) =>
- /* Examine each triple, updating the compilation state. */
- gp2.findAssignables
-
- case x => error("no code to handle " + x)
- }
- }
-
- def trim (terms:Set[Term]):GraphPattern = {
- this match {
- case TableFilter(gp2:GraphPattern, expr:Expression) =>
- TableFilter(gp2.trim(terms), expr)
-
- case TriplesBlock(triplepatterns) => {
- val r0 = new w3c.sw.util.GraphAnalyzer[Term](Map[Term, Set[Term]]())
- /* Examine each triple, updating the compilation state. */
- val r = triplepatterns.foldLeft(r0)((r, triple) => r.pair(triple.s, triple.o))
- val useful = triplepatterns.foldLeft(Set[TriplePattern]())((s, t) => {
- if (r.neededFor(terms, t.s, Set(t.o)) &&
- r.neededFor(terms, t.o, Set(t.s))) s + t
- else s
- })
- val useful2 =
- if (useful.size == 0)
- triplepatterns.foldLeft(Set[TriplePattern]())((s, t) => {
- if (r.neededFor(terms, t.s, Set(t.o)) ||
- r.neededFor(terms, t.o, Set(t.s))) s + t
- else s
- })
- else useful
- TriplesBlock(useful2.toList)
- }
-
- case TableConjunction(list) =>
- /* Examine each triple, updating the compilation state. */
- TableConjunction(list.map(gp2 => gp2.trim(terms)))
-
- case OptionalGraphPattern(gp2) =>
- /* Examine each triple, updating the compilation state. */
- OptionalGraphPattern(gp2.trim(terms))
-
- case x => error("no code to handle " + x)
- }
- }
-
- def simplify ():GraphPattern = {
- this match {
- case TableFilter(gp2:GraphPattern, expr:Expression) =>
- TableFilter(gp2.simplify, expr)
-
- case tb:TriplesBlock => tb
-
- case TableConjunction(list) => {
- /* Eliminate series of TriplesBlocks. */
- val (conjuncts, triples) = list.foldLeft((List[GraphPattern](), List[TriplePattern]()))((pair, gp2) => {
- val (conj, trip) = pair
- val gp3 = gp2.simplify
- gp3 match {
- case TriplesBlock(triplepatterns) =>
- (conj, trip ++ triplepatterns)
- case x => {
- (conj ++ List(TriplesBlock(trip)), List[TriplePattern]())
- }
- }
- })
- val conj2 =
- if (triples.size > 0) conjuncts ++ List(TriplesBlock(triples))
- else conjuncts
- if (conj2.size > 1) TableConjunction(conj2)
- else if (conj2.size == 1) conj2(0)
- else TriplesBlock(List[TriplePattern]())
- }
-
- case OptionalGraphPattern(gp2) =>
- /* Examine each triple, updating the compilation state. */
- OptionalGraphPattern(gp2.simplify)
-
- case x => error("no code to handle " + x)
- }
- }
-}
-
-case class TriplesBlock(triplepatterns:List[TriplePattern]) extends GraphPattern {
- override def toString = "{\n " + (triplepatterns.toList.map(s => s.toString.replace("\n", "\n ")).mkString("", " .\n ", " .\n")) + "}"
- override def equals (other:Any):Boolean = other match {
- case that:TriplesBlock => (that canEqual this) && triplepatterns.toSet == that.triplepatterns.toSet
- case _ => false
- }
- override def canEqual(other : Any) : Boolean = other.isInstanceOf[TriplesBlock]
- override def hashCode:Int = 41*triplepatterns.toSet.hashCode
-}
-case class TableConjunction(gps:List[GraphPattern]) extends GraphPattern {
- assert (!(gps exists (x => { x match { case TableConjunction(_) => true case _ => false } })))
- override def toString = "{\n " + (gps.toList.map(s => s.toString.replace("\n", "\n ")).mkString("\n ")) + "\n}\n"
-}
-case class TableDisjunction(gps:List[GraphPattern]) extends GraphPattern {
- override def toString = "{\n " + (gps.toList.map(s => s.toString.replace("\n", "\n ")).mkString("\nUNION\n ")) + "\n}\n"
-}
-case class TableFilter(gp:GraphPattern, expr:Expression) extends GraphPattern {
- override def toString = gp.toString + "\nFILTER (" + expr.toString + ")"
-}
-case class OptionalGraphPattern(gp:GraphPattern) extends GraphPattern {
- override def toString = "OPTIONAL " + gp.toString
-}
-case class MinusGraphPattern(gp:GraphPattern) extends GraphPattern {
- override def toString = "MINUS " + gp.toString
-}
-case class GraphGraphPattern(gp:GraphPattern) extends GraphPattern
-
-case class TriplePattern(s:Term, p:Term, o:Term) {
- override def toString = s + " " + p + " " + o
- def findVars ():Set[Var] = {
- val varS:Set[Var] = s match {
- case TermVar(v) => Set(v)
- case _ => Set()
- }
- val varO:Set[Var] = o match {
- case TermVar(v) => Set(v)
- case _ => Set()
- }
- varS ++ varO
- }
- def findAssignables ():Set[Assignable] = {
- val varS:Set[Assignable] = s match {
- case TermVar(v) => Set(VarAssignable(v))
- case TermBNode(v) => Set(BNodeAssignable(v))
- case _ => Set()
- }
- val varO:Set[Assignable] = o match {
- case TermVar(v) => Set(VarAssignable(v))
- case TermBNode(b) => Set(BNodeAssignable(b))
- case _ => Set()
- }
- varS ++ varO
- }
-}
-
-case class Literal(lit:RDFLiteral) {
- override def toString = lit match {
- case RDFLiteral(s, RDFLiteral.IntegerDatatype) => s
- case _ => lit.toString
- }
-}
-
-sealed abstract class Assignable { val s:String }
-
-case class VarAssignable(v:Var) extends Assignable { val s = v.s }
-case class BNodeAssignable(b:BNode) extends Assignable { val s = b.s }
-
-case class Var(s:String) {
- override def toString = "?" + s
-}
-
-case class Uri(s:String) {
- override def toString = "<" + s + ">"
-}
-
-case class BNode(s:String) {
- override def toString = "_:" + s
-}
-
-case class Expression(conjuncts:List[PrimaryExpression]) {
- override def toString = conjuncts.map(e => e.toString()).mkString(" && ")
-}
-
-sealed abstract class PrimaryExpression
-case class PrimaryExpressionEq(left:SparqlTermExpression, right:SparqlTermExpression) extends PrimaryExpression {
- override def toString = left.toString() + " = " + right.toString
-}
-case class PrimaryExpressionLt(left:SparqlTermExpression, right:SparqlTermExpression) extends PrimaryExpression {
- override def toString = left.toString() + " < " + right.toString
-}
-case class PrimaryExpressionGt(left:SparqlTermExpression, right:SparqlTermExpression) extends PrimaryExpression {
- override def toString = left.toString() + " > " + right.toString
-}
-case class SparqlTermExpression(term:Term) extends PrimaryExpression {
- override def toString = term.toString
-}
-
-case class OrderElt(desc:Boolean, expr:Expression) {
- override def toString = { if (desc) "DESC" else "ASC" } + "(" + expr.toString + ")"
-}
-
-sealed trait Term
-case class TermUri(u:Uri) extends Term {
- override def toString = u.toString
-}
-case class TermBNode(b:BNode) extends Term {
- override def toString = b.toString
-}
-case class TermVar(v:Var) extends Term {
- override def toString = v.toString
-}
-case class TermLit(lit:Literal) extends Term {
- override def toString = lit.toString
-}
-
-
-case class Sparql() extends JavaTokenParsers {
-
- def select:Parser[Select] =
- rep(prefixdecls) ~ "SELECT" ~ opt("DISTINCT") ~ attributelist ~ opt("WHERE") ~ groupgraphpattern ~ opt(order) ~ opt(offset) ~ opt(limit) ^^ {
- case x~"SELECT"~d~a~w~gp~ord~off~lim => Select(d.isDefined, a, gp, ord.getOrElse(List[OrderElt]()), off, lim)
- }
-
- def order:Parser[List[OrderElt]] =
- "ORDER" ~ "BY" ~ rep(orderelt) ^^ { case o~b~elts => elts }
-
- def orderelt:Parser[OrderElt] = (
- "ASC" ~ "(" ~ expression ~ ")" ^^ { case a~o~expr~c => OrderElt(false, expr) }
- | "DESC" ~ "(" ~ expression ~ ")" ^^ { case a~o~expr~c => OrderElt(true, expr) }
- | varr ^^ { case v => OrderElt(false, Expression(List(SparqlTermExpression(TermVar(v))))) }
- )
-
- def offset:Parser[Int] =
- "OFFSET" ~ integer ^^ { case o~i => i.toInt }
-
- def limit:Parser[Int] =
- "LIMIT" ~ integer ^^ { case o~i => i.toInt }
-
- def construct:Parser[Construct] =
- rep(prefixdecls) ~ "CONSTRUCT" ~ constructpattern ~ opt("WHERE") ~ groupgraphpattern ^^ { case x~"CONSTRUCT"~a~w~gp => Construct(a, gp) }
-
- def constructpattern:Parser[TriplesBlock] =
- "{" ~ opt(triplesblock) ~ "}" ^^ { case "{"~tbOPT~"}" => tbOPT.getOrElse(TriplesBlock(List[TriplePattern]())) }
-
- def prefixdecls:Parser[Unit] =
- "PREFIX" ~ name ~ ":" ~ qnameORuri ^^ { case "PREFIX"~pre~":"~u => prefixes += (pre -> u.s) }
-
- def filter:Parser[Expression] =
- "FILTER" ~ "(" ~ expression ~ ")" ^^ { case "FILTER"~"("~expression~")" => expression }
-
- def expression:Parser[Expression] =
- repsep(primaryexpression, "&&") ^^
- { Expression(_) }
-
- def primaryexpression:Parser[PrimaryExpression] = (
- value ~ "=" ~ value ^^
- { case left ~ "=" ~ right => PrimaryExpressionEq(left, right) }
- | value ~ "<" ~ value ^^
- { case left ~ "<" ~ right => PrimaryExpressionLt(left, right) }
- | value ~ ">" ~ value ^^
- { case left ~ ">" ~ right => PrimaryExpressionGt(left, right) }
- | value
- )
-
- def value:Parser[SparqlTermExpression] = (
- qnameORuri ^^ { case x => SparqlTermExpression(TermUri(x)) }
- | varr ^^ { x => SparqlTermExpression(TermVar(x)) }
- | literal ^^ { x => SparqlTermExpression(TermLit(x)) }
- )
-
- def attributelist:Parser[SparqlAttributeList] =
- rep(varr) ^^ { SparqlAttributeList(_) }
-
- def groupgraphpattern:Parser[GraphPattern] = (
- "{" ~ opt(triplesblock) ~ rep(graphpatternnottriplesORfilter ~ opt(triplesblock)) ~ "}" ^^
- {
- case "{"~tbOPT~gpntORf_tbOPT~"}" => {
-
-// case class TriplesBlock(triplepatterns:List[TriplePattern]) extends GraphPattern
-// case class TableConjunction(gps:List[GraphPattern]) extends GraphPattern
-// case class TableDisjunction(gps:List[GraphPattern]) extends GraphPattern
-// case class TableFilter(gp:GraphPattern, expr:Expression) extends GraphPattern
-// case class OptionalGraphPattern(gp:GraphPattern) extends GraphPattern
-// case class GraphGraphPattern(gp:GraphPattern) extends GraphPattern
-
- // println("groupgraphpattern(" + tbOPT + ", " + gpntORf_tbOPT + ")")
- val init:Option[GraphPattern] = tbOPT
- gpntORf_tbOPT.foldLeft(init)((gp, lentry) => {//println("match: " + (gp, lentry))
- // print("case (" + gp + ", " + lentry + ")")
- (gp, lentry) match {
- case (Some(TableFilter(TriplesBlock(l), Expression(lexp))), ~(TableFilter(null, Expression(expr)), Some(TriplesBlock(r)))) => Some(TableFilter(TriplesBlock(l ++ r), Expression(lexp ++ expr)))
- case (Some(TriplesBlock(l)), ~(TableFilter(null, expr), Some(TriplesBlock(r)))) => Some(TableFilter(TriplesBlock(l ++ r), expr))
- case (Some(gp ), ~(TableFilter(null, expr), None )) => Some(TableFilter(gp, expr))
- case (None, ~(TableFilter(null, expr), Some(TriplesBlock(r)))) => Some(TableFilter(TriplesBlock(r), expr))
-
- // case (None, ~(TableConjunction(gps), None )) => TableConjunction(gps)
- // case (Some(gp), ~(TableConjunction(gps), None )) => TableConjunction(List(List(gp) ++ gps))
- // case (None, ~(TableConjunction(gps), Some(tb))) => TableConjunction(List(gps ++ List(tb)))
- // case (Some(gp), ~(TableConjunction(gps), Some(tb))) => TableConjunction(List(List(gp) ++ gps ++ List(tb)))
-
- case (None , ~(x, None )) => Some(x )
- case (Some(TableConjunction(l)), ~(x, None )) => Some(TableConjunction(l ++ List( x )))
- case (Some(gp ), ~(x, None )) => Some(TableConjunction( List(gp, x )))
- case (None , ~(x, Some(tb))) => Some(TableConjunction( List( x, tb)))
- case (Some(TableConjunction(l)), ~(x, Some(tb))) => Some(TableConjunction(l ++ List( x, tb)))
- case (Some(gp ), ~(x, Some(tb))) => Some(TableConjunction( List(gp, x, tb)))
-
- case x => error("found " + x)
- }
- }).get
- }
- }
- )
-
- def graphpatternnottriplesORfilter:Parser[GraphPattern] = (
- "OPTIONAL"~groupgraphpattern ^^ { case "OPTIONAL"~ggp => OptionalGraphPattern(ggp) }
- | "MINUS"~groupgraphpattern ^^ { case "MINUS"~ggp => MinusGraphPattern(ggp) }
- | rep1sep(groupgraphpattern, "UNION") ^^ { x => if (x.size > 1) TableDisjunction(x) else x(0) }
- | "GRAPH"~uri~groupgraphpattern ^^ { case "GRAPH"~u~ggp => GraphGraphPattern(ggp) }
- | filter ^^ { x => TableFilter(null, x) }
- )
-
- def triplesblock:Parser[TriplesBlock] =
- rep1sep(triplepattern, ".") ~ opt(".") ^^ { case pats~x => TriplesBlock(pats) }
-
- def triplepattern:Parser[TriplePattern] =
- subject ~ predicate ~ objectt ^^ { case s~p~o => TriplePattern(s, p, o) }
-
- def subject:Parser[Term] = (
- qnameORuri ^^ { case x => TermUri(x) }
- | bnode ^^ { x => TermBNode(x) }
- | varr ^^ { x => TermVar(x) }
- )
-
- def predicate:Parser[Term] = (
- qnameORuri ^^ { x => TermUri(x) }
- | "a" ^^ { x => TermUri(Uri("http://www.w3.org/1999/02/22-rdf-syntax-ns#type")) }
- | varr ^^ { x => TermVar(x) }
- )
-
- def objectt:Parser[Term] = (
- qnameORuri ^^ { case x => TermUri(x) }
- | bnode ^^ { x => TermBNode(x) }
- | varr ^^ { x => TermVar(x) }
- | literal ^^ { x => TermLit(x) }
- )
-
- def qnameORuri:Parser[Uri] = (
- "<"~uri~">" ^^ { case "<"~x~">" => Uri(x) }
- | name~":"~name ^^ {
- case prefix~":"~localName => try {
- Uri(prefixes(prefix) + localName)
- } catch {
- case e:java.util.NoSuchElementException =>
- throw new Exception("unknown prefix " + prefix)
- }
- }
- )
-
- def bnode:Parser[BNode] =
- "_:"~name ^^ { case "_:"~name => BNode(name) }
-
- def literal:Parser[Literal] = (
- stringLiteral~"^^"~qnameORuri ^^
- {
- case lit~"^^"~dt => Literal(RDFLiteral(lit.substring(1,lit.size - 1), dt.s match {
- case "http://www.w3.org/2001/XMLSchema#string" => RDFLiteral.StringDatatype
- case "http://www.w3.org/2001/XMLSchema#integer" => RDFLiteral.IntegerDatatype
- case "http://www.w3.org/2001/XMLSchema#date" => RDFLiteral.DateDatatype
- // case "http://www.w3.org/2001/XMLSchema#dateTime" => RDFLiteral.DateTimeDatatype
- case x => error("only programed to deal with string and integer, not " + x)
- }))
- }
- | integer ^^ { l => Literal(RDFLiteral(l, RDFLiteral.IntegerDatatype)) }
- )
-
- def varr:Parser[Var] = "?"~ident ^^ { case "?"~x => Var(x) }
-
-}
-
-object Sparql {
-
-}
--- a/src/main/scala/SQL.scala Fri Oct 15 16:42:41 2010 -0400
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,577 +0,0 @@
-package w3c.sw.sql
-import w3c.sw.util._
-
-import scala.util.parsing.combinator._
-
-import scala.collection.Set
-
-// Relational structure
-object RDB {
-
- case class Database (m:Map[RelName, Relation]) {
- def apply (rn:RelName) = m(rn)
- def keySet () = m.keySet
- }
- object Database {
- def apply (l:(Relation)*):Database =
- Database(l.map{r => (r.name -> r)}.toMap)
- }
-
- case class Relation (name:RelName, header:Header, body:List[Tuple], candidates:List[CandidateKey], pk:Option[CandidateKey], fks:ForeignKeys)
-
- case class Header (m:Map[AttrName, Datatype]) {
- def apply (a:AttrName) = m(a)
- def keySet () = m.keySet
- def sqlDatatype (a:AttrName) : Datatype = m(a)
- def contains (a:AttrName) : Boolean = m.contains(a)
- }
- object Header {
- def apply (s:(String, Datatype)*):Header =
- Header(s.map{p => (AttrName(p._1), p._2)}.toMap)
- }
- case class CandidateKey (attrs:List[AttrName])
- object CandidateKey {
- def apply (l:(String)*):CandidateKey =
- CandidateKey(l.map{s => AttrName(s)}.toList)
- }
- implicit def cc2list (cc:CandidateKey) = cc.attrs
-
- case class ForeignKeys (m:Map[List[AttrName], Target]) {
- def apply (l:List[AttrName]) = m(l)
- def keySet () = m.keySet
- def contains (l:List[AttrName]) = m.contains(l)
- }
- object ForeignKeys {
- def apply (s:(List[String], Target)*):ForeignKeys =
- ForeignKeys(s.map{p => (p._1.map{s => AttrName(s)}, p._2)}.toMap)
- }
-
- case class Target (rel:RelName, key:CandidateKey)
-
- case class Datatype(name:String) {
- override def toString = "/* " + name + " */"
- }
- object Datatype {
- val CHAR = Datatype("Char")
- val VARCHAR = Datatype("Varchar")
- val STRING = Datatype("String")
- val INTEGER = Datatype("Int")
- val FLOAT = Datatype("Float")
- val DOUBLE = Datatype("Double")
- val DATE = Datatype("Date")
- val TIMESTAMP = Datatype("Timestamp")
- val DATETIME = Datatype("Datetime")
- }
-
- case class Tuple (m:Map[AttrName, CellValue]) {
- def apply (a:AttrName) = m(a)
- def lexvalue (a:AttrName) : Option[LexicalValue] =
- m(a) match {
- case ␀() => None
- case v:LexicalValue => Some(v)
- }
- def lexvaluesNoNulls (as:List[AttrName]) = as.map(a => m(a).asInstanceOf[LexicalValue])
- def nullAttributes (h:Header) : Set[(AttrName)] = {
- h.keySet.flatMap(a =>
- lexvalue(a) match {
- case None => Some(a)
- case _ => None
- })
- }
- }
- object Tuple {
- def apply (s:(String, CellValue)*):Tuple =
- Tuple(s.map{p => (AttrName(p._1), p._2)}.toMap)
- }
-
- abstract class CellValue
- case class LexicalValue (s:String) extends CellValue
- case class ␀ () extends CellValue
-
- case class RelName(n:String) {
- override def toString = n
- }
- case class AttrName(n:String) {
- override def toString = n
- }
-
-}
-
-object SQLParsers extends RegexParsers {
-
- val int = """[0-9]+""".r
- val chars = "\"([^\"\\\\\n\r]|\\\\[tbnrf\\\"'])*\"".r
-}
-
-import SQLParsers._
-
-import scala.util.parsing.combinator._
-import java.net.URI
-
-sealed abstract class RelationORSubselect
-case class Subselect(sel:SelectORUnion) extends RelationORSubselect {
- override def toString = "(\n " + sel.toString.replace("\n", "\n ") + "\n )"
-}
-sealed abstract class SelectORUnion
-case class Select(distinct:Boolean, projection:Projection, tablelist:TableList, expression:Option[Expression], order:List[OrderElt], offset:Option[Int], limit:Option[Int]) extends SelectORUnion {
- override def toString =
- "SELECT "+
- { if (distinct) "DISTINCT " else "" }+
- projection+"\n"+
- tablelist+
- { if (expression.isDefined) {"\b WHERE " + expression.get + " "} else "" }+
- { if (order.size > 0) {order.map(o => o.toString).mkString("ORDER BY ", " ", " ") } else "" }+
- { if (offset.isDefined) {"OFFSET " + offset + " "} else "" }+
- { if (limit.isDefined) {"LIMIT " + limit + " "} else "" }
-}
-case class RelationResource(rn:RDB.RelName) extends RelationORSubselect {
- override def toString = rn.toString
-}
-object foo { // doesn't work
- implicit def relname2relationref (rn:RDB.RelName) = RelationResource(rn)
-}
-case class Union(disjoints:Set[Select]) extends SelectORUnion {
- override def toString = " " + (disjoints.toList.map(s => s.toString.replace("\n", "\n ")).mkString("\nUNION\n "))
-}
-case class Projection(attributes:Set[ProjectAttribute]) {
- // foo, bar
- override def toString = attributes.toList.sortWith((l, r) => l.attralias.toString < r.attralias.toString).mkString(", ")
-}
-case class ProjectAttribute(value:RelVarAttrORExpression, attralias:AttrAlias) {
- override def toString = value + " AS " + attralias
-}
-//case class RelAttribute(relation:Relation, attribute:RDB.AttrName) c.f. ForeignKey999
-sealed abstract class RelVarAttrORExpression
-case class RelVarAttr(relvar:RelVar, attribute:RDB.AttrName) extends RelVarAttrORExpression {
- override def toString = relvar + "." + attribute
-}
-
-case class AttrAlias(n:Name) {
- override def toString = n.s /* "'" + n.s + "'" */
-}
-case class RelVar(n:Name) {
- override def toString = n.s /* "'" + n.s + "'" */
-}
-case class TableList(joins:AddOrderedSet[Join]) {
- override def toString =
- if (joins.size == 0) ""
- else {
- " FROM " + joins.foldLeft(("", 0))(
- (pair, entry) => (pair._1 + {
- if (pair._2 == 0) entry.toString.substring(19) // !!! shameless!
- else entry
- }, pair._2+1))._1
- }
-}
-
-sealed abstract class Join(res:AliasedResource)
-case class InnerJoin(res:AliasedResource, optOn:Option[Expression]) extends Join(res) {
- override def toString = "\n INNER JOIN " + res
-}
-case class LeftOuterJoin(res:AliasedResource, on:Expression) extends Join(res) {
- override def toString = "\n LEFT OUTER JOIN " + res + " ON " + on
-}
-
-case class AliasedResource(rel:RelationORSubselect, as:RelVar) {
- override def toString = rel + " AS " + as
-}
-sealed abstract class Expression extends RelVarAttrORExpression
-case class ExprConjunction(exprs:Set[Expression]) extends Expression {
- override def toString = "(" + (exprs.toList.sortWith((l, r) => l.toString < r.toString).mkString (")\n AND (")) + ")"
-}
-case class ExprDisjunction(exprs:Set[Expression]) extends Expression {
- override def toString = "(" + (exprs mkString (") OR (")) + ")"
-}
-sealed abstract class RelationalExpression extends Expression
-case class RelationalExpressionEq(l:Expression, r:Expression) extends RelationalExpression {
- override def toString = l + "=" + r
- /* safer operator== , but doesn't quite work yet. */
- // override def hashCode = 41 * l.hashCode + r.hashCode
- // override def equals(other: Any) = other match {
- // case that: RelationalExpressionEq =>
- // (that canEqual this) &&
- // ( ( (this.l == that.l) && (this.r == that.r) ||
- // (this.l == that.r) && (this.r == that.l) ) )
- // case _ =>
- // false
- // }
- // override def canEqual(other: Any) =
- // other.isInstanceOf[RelationalExpressionEq]
-
- override def equals(that:Any) =
- that match {
- case RelationalExpressionEq(l1, r1) => (l == l1 && r == r1) || (r == l1 && l == r1)
- case _ => false
- }
- override def hashCode =
- if (r.hashCode < l.hashCode)
- r.hashCode + l.hashCode
- else
- l.hashCode + r.hashCode
-}
-case class RelationalExpressionNe(l:Expression, r:Expression) extends RelationalExpression {
- override def toString = l + "!=" + r
-}
-case class RelationalExpressionLt(l:Expression, r:Expression) extends RelationalExpression {
- override def toString = l + "<" + r
-}
-case class RelationalExpressionGt(l:Expression, r:Expression) extends RelationalExpression {
- override def toString = l + ">" + r
-}
-case class RelationalExpressionNull(l:Expression) extends RelationalExpression { // Expression?
- override def toString = l + " IS NULL"
-}
-case class RelationalExpressionNotNull(l:Expression) extends RelationalExpression { // Expression?
- override def toString = l + " IS NOT NULL"
-}
-sealed abstract class PrimaryExpression extends Expression
-case class PrimaryExpressionAttr(fqattribute:RelVarAttr) extends PrimaryExpression {
- override def toString = "" + fqattribute
-}
-case class PrimaryExpressionTyped(datatype:RDB.Datatype, i:Name) extends PrimaryExpression {
- override def toString = /* "'" + i.s + "'" */ /* + datatype */
- datatype match {
- case RDB.Datatype("Int") => i.s
- case _ => "\"" + i.s + "\""
- }
-}
-case class OrderElt(desc:Boolean, expr:Expression) {
- override def toString = { if (desc) "DESC" else "ASC" } + "(" + expr.toString + ")"
-}
-case class ConstNULL() extends PrimaryExpression {
- override def toString = "NULL"
-}
-case class Concat(args:List[Expression]) extends PrimaryExpression {
- override def toString = args.mkString("CONCAT(", ", ", ")")
-}
-case class IfElse(cond:Expression, pass:Expression, fail:Expression) extends PrimaryExpression {
- override def toString = "CONCAT(" + cond + ", " + pass + ", " + fail + ")"
-}
-
-case class Name(s:String)
-
-object Name {
- implicit def fromStringToName(s:String):Name = Name(s)
-}
-
-sealed abstract class FieldDescOrKeyDeclaration
-case class FieldDesc(attr:RDB.AttrName, datatype:RDB.Datatype, pkness:Boolean) extends FieldDescOrKeyDeclaration
-sealed abstract class KeyDeclaration extends FieldDescOrKeyDeclaration
-case class PrimaryKeyDeclaration(key:RDB.CandidateKey) extends KeyDeclaration
-case class ForeignKeyDeclaration(fk:List[RDB.AttrName], rel:RDB.RelName, pk:RDB.CandidateKey) extends KeyDeclaration
-case class View(rel:RDB.RelName, defn:SelectORUnion) { // sibling of RDB.Relation
- override def toString = "CREATE VIEW " + rel + " AS\n" + defn
-}
-
-case class SqlParser() extends JavaTokenParsers {
-
- def createview:Parser[View] = // @@@ could stick under ddl
- "CREATE" ~ "VIEW" ~ relation ~ "AS" ~ selectORunion ^^
- { case "CREATE"~"VIEW"~relation~"AS"~defn => View(relation, defn) }
-
- def ddl:Parser[RDB.Database] =
- rep1sep(createtable, ";") ~ opt(";") ^^
- {
- case l~x => RDB.Database(l.foldLeft(Map[RDB.RelName, RDB.Relation]())((m, p) => {
- val (rel:RDB.RelName, desc:RDB.Relation) = p
- m + (rel -> desc)
- }))
- }
-
- def createtable:Parser[(RDB.RelName, RDB.Relation)] =
- "CREATE" ~ "TABLE" ~ relation ~ "(" ~ rep1sep(fielddescorkeydef, ",") ~ ")" ^^
- {
- case "CREATE"~"TABLE"~relation~"("~reldesc~")" => {
- val pk0:Option[RDB.CandidateKey] = None
- val attrs0 = Map[RDB.AttrName, RDB.Datatype]()
- val candidates0 = List[RDB.CandidateKey]()
- val fks0 = Map[List[RDB.AttrName], RDB.Target]()
- /* <pk>: (most recently parsed) PRIMARY KEY
- * <attrs>: map of attribute to type (e.g. INTEGER)
- * <fks>: map holding FOREIGN KEY relation REFERENCES attr
- */
- val (pk, attrs, candidates, fks) =
- reldesc.foldLeft((pk0, attrs0, candidates0, fks0))((p, rd) => {
- val (pkopt, attrs, candidates, fks) = p
- rd match {
- case FieldDesc(attr, value, pkness) => {
- val (pkNew, candNew) =
- if (pkness) (Some(RDB.CandidateKey(List(attr))), candidates ++ List(RDB.CandidateKey(attr.n)))
- else (pkopt, candidates)
- (pkNew, attrs + (attr -> value), candNew, fks)
- }
- case PrimaryKeyDeclaration(key) =>
- // @@ why doesn't [[ candidates + RDB.CandidateKey(attr.n) ]] work?
- (Some(key), attrs, candidates ++ List(RDB.CandidateKey(key map {attr => RDB.AttrName(attr.n)})), fks)
- case ForeignKeyDeclaration(fk, rel, pk) =>
- (pkopt, attrs, candidates, fks + (fk -> RDB.Target(rel, pk)))
- }
- })
- val rd = RDB.Relation(relation, RDB.Header(attrs), List(), candidates, pk, RDB.ForeignKeys(fks))
- (relation -> rd)
- }
- }
-
- def fielddescorkeydef:Parser[FieldDescOrKeyDeclaration] = (
- attribute ~ typpe ~ opt("PRIMARY" ~ "KEY") ^^
- { case attribute~typpe~pkness => FieldDesc(attribute, typpe, pkness.isDefined) }
- | "PRIMARY" ~ "KEY" ~ "(" ~ rep1sep(attribute, ",") ~ ")" ^^
- { case "PRIMARY"~"KEY"~"("~attributes~")" => PrimaryKeyDeclaration(RDB.CandidateKey(attributes)) }
- | "FOREIGN" ~ "KEY" ~ "(" ~ rep1sep(attribute, ",") ~ ")" ~ "REFERENCES" ~ relation ~ "(" ~ rep1sep(attribute, ",") ~ ")" ^^
- { case "FOREIGN"~"KEY"~"("~fk~")"~"REFERENCES"~relation~"("~pk~")" => ForeignKeyDeclaration(fk, relation, RDB.CandidateKey(pk)) }
- )
-
- def typpe:Parser[RDB.Datatype] = (
- "INT" ^^ { case _ => RDB.Datatype.INTEGER }
- | "DOUBLE" ^^ { case _ => RDB.Datatype.DOUBLE }
- | "STRING" ^^ { case _ => RDB.Datatype.STRING }
- | "DATETIME" ^^ { case _ => RDB.Datatype.DATETIME }
- | "DATE" ^^ { case _ => RDB.Datatype.DATE }
- )
-
- def selectORunion:Parser[SelectORUnion] =
- rep1sep(select, "UNION") ^^ { l => if (l.size == 1) l(0) else Union(l.toSet) }
-
- def select:Parser[Select] =
- "SELECT" ~ opt("DISTINCT") ~ projection ~ opt(tablelist) ~ opt(where) ~ opt(order) ~ opt(offset) ~ opt(limit) ^^
- {
- case "SELECT" ~ distinct ~ attributes ~ tablesANDons ~ whereexpr ~ order ~ offset ~ limit => {
- val (tables, onExpressions) =
- tablesANDons.getOrElse((TableList(AddOrderedSet[Join]()), Set[Expression]()))
- val t:Set[Expression] = onExpressions
- val onConjoints:Set[Expression] =
- onExpressions.foldLeft(Set[Expression]())((s, ent) =>
- s ++ {ent match {
- case ExprConjunction(l) => l
- case _ => Set(ent)
- }})
- val conjoints = whereexpr match {
- case Some(ExprConjunction(l)) => onConjoints ++ l
- case Some(x) => onConjoints + x
- case _ => onConjoints
- }
- val expr:Option[Expression] = conjoints.size match {
- case 0 => None
- case 1 => Some(conjoints.toList(0))
- case _ => Some(ExprConjunction(conjoints))
- }
- Select(distinct.isDefined, attributes, tables, expr, order.getOrElse(List[OrderElt]()), offset, limit)
- }
- }
-
- def order:Parser[List[OrderElt]] =
- "ORDER" ~ "BY" ~ rep(orderelt) ^^ { case o~b~elts => elts }
-
- def orderelt:Parser[OrderElt] = (
- "ASC" ~ "(" ~ expression ~ ")" ^^ { case a~o~expr~c => OrderElt(false, expr) }
- | "DESC" ~ "(" ~ expression ~ ")" ^^ { case a~o~expr~c => OrderElt(true, expr) }
- | fqattribute ^^ { case v => OrderElt(false, PrimaryExpressionAttr(v)) }
- )
-
- def offset:Parser[Int] =
- "OFFSET" ~ int ^^ { case o~i => i.toInt }
-
- def limit:Parser[Int] =
- "LIMIT" ~ int ^^ { case o~i => i.toInt }
-
- def where:Parser[Expression] =
- "WHERE" ~ expression ^^ { case "WHERE" ~ expression => expression }
-
- def projection:Parser[Projection] =
- repsep(namedattribute, ",") ^^ { l => Projection(l.toSet) }
-
- def namedattribute:Parser[ProjectAttribute] =
- fqattributeORprimaryexpression ~ "AS" ~ attralias ^^
- { case fqattributeORprimaryexpression ~ "AS" ~ attralias =>
- ProjectAttribute(fqattributeORprimaryexpression, attralias) }
-
- def fqattributeORprimaryexpression:Parser[RelVarAttrORExpression] = (
- fqattribute ^^ { case fqattribute => fqattribute }
- | primaryexpression ^^ { case const => const }
- )
-
- def fqattribute:Parser[RelVarAttr] =
- relvar ~ "." ~ attribute ^^
- { case relvar ~ "." ~ attribute => RelVarAttr(relvar, attribute) }
-
- def attribute:Parser[RDB.AttrName] =
- """[a-zA-Z_]\w*""".r ^^ { x => RDB.AttrName(x) }
-
- def attralias:Parser[AttrAlias] =
- """[a-zA-Z_]\w*""".r ^^ { x => AttrAlias(Name(x)) }
-
- def relationORsubselect:Parser[RelationORSubselect] = (
- relation ^^ { x => RelationResource(x) }
- | "(" ~ selectORunion ~ ")" ^^ { case "("~s~")" => Subselect(s) }
- )
-
- def relation:Parser[RDB.RelName] =
- """[a-zA-Z_]\w*""".r ^^ { x => RDB.RelName(x) }
-
- def relvar:Parser[RelVar] =
- """[a-zA-Z_]\w*""".r ^^ { x => RelVar(Name(x)) }
-
- def tablelist:Parser[(TableList, Set[Expression])] =
- "FROM" ~ aliasedjoin ~ rep(innerORouter) ^^
- { case "FROM"~aj~l => (TableList(AddOrderedSet(InnerJoin(aj, None) :: l.map((one) => one._1))),
- l.foldLeft(Set[Expression]())((all, one) => all ++ one._2)) }
-
- def innerORouter:Parser[(Join, Set[Expression])] = (
- "INNER" ~ "JOIN" ~ aliasedjoin ~ opt("ON" ~ expression) ^^
- { case "INNER"~"JOIN"~a~o => (InnerJoin(a, None), { if (o.isDefined) Set(o.get._2) else Set[Expression]() } ) }
- | "LEFT" ~ "OUTER" ~ "JOIN" ~ aliasedjoin ~ "ON" ~ expression ^^
- { case l~o~j~alijoin~on~expr => (LeftOuterJoin(alijoin, expr), Set[Expression]()) }
- )
-
- def aliasedjoin:Parser[AliasedResource] =
- relationORsubselect ~ "AS" ~ relvar ^^
- { case rel1 ~ "AS" ~ rel2 => AliasedResource(rel1, rel2) }
-
- def expression:Parser[Expression] =
- ORexpression ^^ { x => x }
-
- def ORexpression:Parser[Expression] =
- rep1sep (ANDexpression, "OR") ^^
- { xs => if (xs.size > 1) ExprDisjunction(xs.toSet) else xs(0) }
-
- def ANDexpression:Parser[Expression] =
- rep1sep (relationalexpression, "AND") ^^
- { xs => if (xs.size > 1) ExprConjunction(xs.toSet) else xs(0) }
-
- def relationalexpression:Parser[Expression] = (
- primaryexpression ~ "=" ~ primaryexpression ^^
- { case primaryexpression ~ "=" ~ rvalue => RelationalExpressionEq(primaryexpression, rvalue) }
- | primaryexpression ~ "!=" ~ primaryexpression ^^
- { case primaryexpression ~ "!=" ~ rvalue => RelationalExpressionNe(primaryexpression, rvalue) }
- | primaryexpression ~ "<" ~ primaryexpression ^^
- { case primaryexpression ~ "<" ~ rvalue => RelationalExpressionLt(primaryexpression, rvalue) }
- | primaryexpression ~ ">" ~ primaryexpression ^^
- { case primaryexpression ~ ">" ~ rvalue => RelationalExpressionGt(primaryexpression, rvalue) }
- | primaryexpression ~ "IS" ~ "NULL" ^^
- { case primaryexpression ~ "IS" ~ "NULL" => RelationalExpressionNull(primaryexpression) }
- | primaryexpression ~ "IS" ~ "NOT" ~ "NULL" ^^
- { case primaryexpression ~ "IS" ~ "NOT" ~ "NULL" => RelationalExpressionNotNull(primaryexpression) }
- | primaryexpression ^^
- { case primaryexpression => primaryexpression }
- )
-
- def primaryexpression:Parser[Expression] = (
- fqattribute ^^ { PrimaryExpressionAttr(_) }
- | int ^^ { i => PrimaryExpressionTyped(RDB.Datatype.INTEGER, Name(i)) }
- | chars ^^ { x => PrimaryExpressionTyped(RDB.Datatype.STRING, Name(x.substring(1, x.size - 1))) }
- | "NULL" ^^ { case "NULL" => ConstNULL() }
- | "CONCAT" ~ "(" ~ rep1sep(expression, ",") ~ ")" ^^ { case "CONCAT"~"("~expressions~")" => Concat(expressions) }
- | "IF" ~ "(" ~ expression ~ "," ~ expression ~ "," ~ expression ~ ")" ^^ { case "IF"~"("~c~","~p~","~f~")" => IfElse(c, p, f) }
- | "(" ~ expression ~ ")" ^^ { case "("~x~")" => x }
- )
-
-}
-
-case class PrettySql(select:Select) {
- def makePretty():Select = {
- val Select(distinct, projection, tablelist, expression, order, offset, limit) = select
- val nullStripped =
- if (expression.isDefined) {
- val nonNullAttrs = PrettySql.findNonNullRelVarAttrs(expression.get)
- PrettySql.stripNotNulls(expression.get, nonNullAttrs)
- }
- else None
- val XeqXStripped =
- if (nullStripped.isDefined) {
- PrettySql.stripXeqX(nullStripped.get)
- }
- else None
- val strippedTables = tablelist.joins.foldLeft(AddOrderedSet[Join]())((set, join) => set + {
- join match {
- case InnerJoin(AliasedResource(rel, as), optOn) => InnerJoin(AliasedResource({ rel match {
- case Subselect(s:Select) => Subselect(PrettySql(s).makePretty())
- case Subselect(Union(disjoints)) => Subselect(Union(disjoints.map(s => PrettySql(s).makePretty())))
- case r:RelationResource => r
- }}, as), None)
- case LeftOuterJoin(AliasedResource(rel, as), on:Expression) => LeftOuterJoin(AliasedResource({ rel match {
- case Subselect(s:Select) => Subselect(PrettySql(s).makePretty())
- case Subselect(Union(disjoints)) => Subselect(Union(disjoints.map(s => PrettySql(s).makePretty())))
- case r:RelationResource => r
- }}, as), on)
- }})
- Select(distinct, projection, TableList(strippedTables), XeqXStripped, order, offset, limit)
- }
-}
-
-object PrettySql {
- def findNonNullRelVarAttrs(expr:Expression):Set[RelVarAttr] = {
- expr match {
- case ExprConjunction(s) => s.foldLeft(Set[RelVarAttr]())((s, e) => s ++ findNonNullRelVarAttrs(e))
- case ExprDisjunction(s) => {
- val l = s.toList
- l.slice(1, l.size).foldLeft(findNonNullRelVarAttrs(l(0)))((s, e) => s & findNonNullRelVarAttrs(e))
- }
- case RelationalExpressionEq(l, r) => findNonNullRelVarAttrs(l) ++ findNonNullRelVarAttrs(r)
- case RelationalExpressionNe(l, r) => findNonNullRelVarAttrs(l) ++ findNonNullRelVarAttrs(r)
- case RelationalExpressionLt(l, r) => findNonNullRelVarAttrs(l) ++ findNonNullRelVarAttrs(r)
- case RelationalExpressionGt(l, r) => findNonNullRelVarAttrs(l) ++ findNonNullRelVarAttrs(r)
- case e:PrimaryExpressionTyped => Set()
- case PrimaryExpressionAttr(a) => Set(a)
- case e:ConstNULL => Set()
- case e:Concat => Set()
- case RelationalExpressionNull(a) => findNonNullRelVarAttrs(a)
- case RelationalExpressionNotNull(a) => Set()
- case IfElse(eef, den, els) => Set()
- }
- }
- def stripNotNulls(expr:Expression, stripMe:Set[RelVarAttr]):Option[Expression] = {
- expr match {
- case ExprConjunction(l) => Some(ExprConjunction({l.foldLeft(Set[Expression]())((s, e) => {
- val e2 = stripNotNulls(e,stripMe)
- if (e2.isDefined) s + e2.get
- else s})}))
- case ExprDisjunction(l) => Some(ExprDisjunction({l.foldLeft(Set[Expression]())((s, e) => {
- val e2 = stripNotNulls(e,stripMe)
- if (e2.isDefined) s + e2.get
- else s})}))
- case e:RelationalExpressionEq => Some(e)
- case e:RelationalExpressionNe => Some(e)
- case e:RelationalExpressionLt => Some(e)
- case e:RelationalExpressionGt => Some(e)
- case e:PrimaryExpressionTyped => Some(e)
- case e:PrimaryExpressionAttr => Some(e)
- case e:ConstNULL => Some(e)
- case e:Concat => Some(e)
- case e:RelationalExpressionNull => Some(e)
- case RelationalExpressionNotNull(PrimaryExpressionAttr(a)) =>
- if (stripMe.contains(a)) None
- else Some(RelationalExpressionNotNull(PrimaryExpressionAttr(a)))
- case e:RelationalExpressionNotNull => Some(e)
- case e:IfElse => Some(e)
- }
- }
- def stripXeqX(expr:Expression):Option[Expression] = {
- expr match {
- case ExprConjunction(l) => Some(ExprConjunction({l.foldLeft(Set[Expression]())((s, e) => {
- val e2 = stripXeqX(e)
- if (e2.isDefined) s + e2.get
- else s})}))
- case ExprDisjunction(l) => Some(ExprDisjunction({l.foldLeft(Set[Expression]())((s, e) => {
- val e2 = stripXeqX(e)
- if (e2.isDefined) s + e2.get
- else s})}))
- case e:RelationalExpressionEq => {
- val RelationalExpressionEq(l, r) = e
- if (l == r) None
- else Some(e)
- }
- case e:RelationalExpressionNe => Some(e)
- case e:RelationalExpressionLt => Some(e)
- case e:RelationalExpressionGt => Some(e)
- case e:PrimaryExpressionTyped => Some(e)
- case e:PrimaryExpressionAttr => Some(e)
- case e:ConstNULL => Some(e)
- case e:Concat => Some(e)
- case e:RelationalExpressionNull => Some(e)
- case e:RelationalExpressionNotNull => Some(e)
- case e:IfElse => Some(e)
- }
- }
- implicit def toPrettySql(select:Select) = PrettySql(select)
-}
-
--- a/src/main/scala/Servlet.scala Fri Oct 15 16:42:41 2010 -0400
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,166 +0,0 @@
-package org.w3.sparql2sql.servlet
-
-import javax.servlet.http.{HttpServlet, HttpServletRequest, HttpServletResponse}
-import scala.xml.XML
-
-import java.sql.{DriverManager, Connection, Statement, ResultSet, ResultSetMetaData}
-
-import java.sql.{Types => T}
-
-import java.net.URI
-import w3c.sw.sql.RDB.{RelName,AttrName}
-import w3c.sw.sql.SqlParser
-import w3c.sw.sparql
-import w3c.sw.sparql.Sparql
-import w3c.sw.sql
-import w3c.sw.sparql2sql.{SparqlToSql,StemURI,SqlToXMLRes}
-
-object Control {
-
- private def using[Closeable <: {def close(): Unit}, B](closeable: Closeable)(getB: Closeable => B): B =
- try {
- getB(closeable)
- } finally {
- closeable.close()
- }
-
- private def bmap[T](test: => Boolean)(block: => T): List[T] = {
- val ret = new scala.collection.mutable.ListBuffer[T]
- while(test) ret += block
- ret.toList
- }
-
- /** Executes the SQL and processes the result set using the specified function. */
- private def query[B](connection: Connection, sql: String)(process: ResultSet => B): B =
- using (connection) { connection =>
- using (connection.createStatement) { statement =>
- using (statement.executeQuery(sql)) { results =>
- process(results)
- }
- }
- }
-
- /** Executes the SQL and uses the process function to convert each row into a T. */
- def queryEach[T](connection: Connection, sql: String)(process: ResultSet => T): List[T] =
- query(connection, sql) { results =>
- bmap(results.next) {
- process(results)
- }
- }
-
- def process(connection: Connection, sql: String,
- head: List[String] => String,
- startres: String,
- binding: (String, String /*, Map[sparql.Assignable, SparqlToSql.SQL2RDFValueMapper], StemURI*/) => String,
- endres: String, foot: String): String = {
-
- query(connection, sql) { results =>
-
- val metadata = results.getMetaData
-
- val vars:List[String] =
- (1 to metadata.getColumnCount) map { column => metadata.getColumnLabel(column) } toList
-
- def processCell(rs:ResultSet, column:Int):Option[String] =
- try {
- val name:String = metadata.getColumnLabel(column)
- val value:String =
- metadata.getColumnType(column) match {
- case T.VARCHAR => rs.getString(column)
- case T.INTEGER => rs.getInt(column).toString
- case T.DATE => rs.getDate(column).toString
- case _ => throw new java.lang.Exception("you have to map this type!!!")
- }
- Some(binding(name, value))
- } catch {
- case _ => None
- }
-
- def processRow(rs:ResultSet):String =
- (1 to metadata.getColumnCount) flatMap { column => processCell(rs, column) } mkString ""
-
- val rows:List[String] = bmap(results.next) {
- processRow(results)
- }
-
- val body = rows map { startres + _ + endres } mkString ""
-
- head(vars) + body + foot
-
- }
-
- }
-
-}
-
-
-class SparqlEndpoint extends HttpServlet {
-
- val encoding = "utf-8"
-
- override def doGet(request:HttpServletRequest, response:HttpServletResponse) {
-
- request.getParameter("query") match {
- case null | "" => processIndex(request, response)
- case query => processSparql(request, response, query)
- }
-
- }
-
- def processSparql(request:HttpServletRequest, response:HttpServletResponse, query:String) {
-
- val stemURI:StemURI = StemURI(Some(request.getParameter("stemuri")) getOrElse Config.defaultStemURI)
-
- val sparqlParser = Sparql()
-
- val sparqlSelect = sparqlParser.parseAll(sparqlParser.select, query).get
-
- val (generated, rdfmap) = SparqlToSql(Config.db, sparqlSelect, stemURI, true, false)
-
- Class.forName("com.mysql.jdbc.Driver").newInstance
- val connection:Connection = DriverManager.getConnection("jdbc:mysql://localhost/rdb2rdf", "root", "")
-
-// if (! connection.isClosed) println("Successfully connected")
-
- val res = Control.process(connection=connection,
- sql=generated.toString,
- head=SqlToXMLRes.head(_),
- startres=SqlToXMLRes.startresult,
- binding=SqlToXMLRes.binding(_, _, rdfmap, stemURI),
- endres=SqlToXMLRes.endresult,
- foot=SqlToXMLRes.foot)
-
- response.setContentType("text/plain; charset='" + encoding + "'")
-
- response.getWriter.write(res)
-
- }
-
- def processIndex(request:HttpServletRequest, response:HttpServletResponse) {
-
- val index =
- <html xmlns="http://www.w3.org/1999/xhtml">
- <head><title>RDB2RDF Sparql endpoint</title></head>
- <body>
- <h1>RDB2RDF Sparql endpoint</h1>
- <form action="sparql">
- <p>
- StemURI: <input cols="80" name="stemuri" id="stemuri" value={ Config.defaultStemURI } /><br />
- <textarea rows="10" cols="80" name="query" id="query">{ Config.defaultSparqlQuery }</textarea>
- <input type="submit" />
- </p>
- </form>
- <hr />
- <address>
- <a href="http://www.w3.org/People/Eric/">Eric Prud'hommeaux</a>, <a href="http://www.w3.org/People/Bertails/">Alexandre Bertails</a>, Jun 2010
- </address>
- </body>
- </html>
-
- response.setContentType("application/xml; charset='" + encoding + "'")
-
- XML.write(response.getWriter, index, encoding, false, null)
-
- }
-
-}
--- a/src/main/scala/SparqlToSparql.scala Fri Oct 15 16:42:41 2010 -0400
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,386 +0,0 @@
-/* SparqlToSparql: convert SPARQL queries to sound SQL queries.
- *
-
- *
- */
-
-package w3c.sw.sparql2sparql
-
-import scala.util.parsing.combinator._
-import w3c.sw.sparql
-
-case class MapTarget (term:sparql.Term, nm:Option[NodePattern])
-
-case class NodePatternMap (m:Map[sparql.Var, NodePattern]) {
- def var2maptarget (t:sparql.Term) : MapTarget = {
- t match {
- case sparql.TermVar(v) => MapTarget(t, m.get(v))
- case _ => MapTarget(t, None)
- }
- }
-}
-
-case class NodePattern (iface:String, direct:String)
-
-case class SparqlMap (construct:sparql.Construct, nodemap:NodePatternMap)
-
-object SparqlToSparql {
-
- /**
- * Some tools for making pretty output, e.g. shortening strings.
- */
- var RuleLabels = scala.collection.mutable.Map[String,String]()
- var Abbreviations = scala.collection.mutable.Map[String,String]()
- def _shorten (s:String) = {
- val s2 = if (RuleLabels.contains(s)) RuleLabels(s)
- else s
- val s3 = Abbreviations.foldLeft(s2)((s, ss) => s.replace(ss._1, ss._2))
- s3
- }
-
-
- /**
- * Substitute the terms in a triple pattern or a graph pattern with other terms.
- */
- def substituteTerm (changeMe:sparql.Term, from:sparql.Term, to:sparql.Term, nm:Option[NodePattern]):sparql.Term = {
- if (changeMe == from) {
- from match {
- case sparql.TermVar(v) => {
- if (nm.isDefined) {
- val nodeMap = nm.get
- to match {
- case sparql.TermVar(v2) => {
- println("reverse map(" + v2 + ",\n " + nodeMap.direct + ",\n " + nodeMap.iface)
- to
- }
- case sparql.TermBNode(b) => {
- println("reverse map(" + b + ",\n " + nodeMap.direct + ",\n " + nodeMap.iface)
- to
- }
- case sparql.TermUri(u) => {
- val s:String = u.s
- val r = u.s.replaceAll(nodeMap.iface, nodeMap.direct)
- //println("\"" + u.s + "\".replaceAll("+nodeMap.iface+", "+nodeMap.direct+") = \""+r+"\"")
- sparql.TermUri(sparql.Uri(u.s.replaceAll(nodeMap.iface, nodeMap.direct))) // !!! failure here should error()
- }
- case sparql.TermLit(l) =>
- error("variable " + v + " bound to literlal " + l + " but should be a node as indicated by " + nodeMap)
- }
- } else to
- }
- case _ => to
- }
- } else changeMe
- }
-
- def substitute (gp:sparql.GraphPattern, from:sparql.Term, target:MapTarget) = {
- gp match {
- case sparql.TriplesBlock(triplepatterns) => sparql.TriplesBlock(
- triplepatterns.map((tp) => {
- // println("sub(" + tp.o + ", " + from + ", " + to + ") => ")
- // println(substituteTerm(toTerm(tp.o), tp.o, from, target.term))
- sparql.TriplePattern(substituteTerm(tp.s, from, target.term, target.nm),
- tp.p,
- substituteTerm(tp.o, from, target.term, target.nm))
- }
- ))
- case _ => error("not implemented" + gp)
- }
- }
-
- type VarMap = Map[sparql.Var, MapTarget]
-
- def substituteGraphPattern (gp:sparql.GraphPattern, vartargetmap:VarMap, varPrefix:String):sparql.GraphPattern = {
- /**
- * Substitute gp (the rule body) with the variables and constants from the
- * query which matched variables in the rule head.
- */
- val mapped = vartargetmap.foldLeft(gp)((incrementalGP, vartarget) => {
- val (varr, target) = vartarget
- val ret = substitute(incrementalGP, sparql.TermVar(varr), target)
- //println("^^incrementalGP: " + incrementalGP + "\nvartarget: " + vartarget + "\nret: " + ret)
- ret;
- })
-
- /**
- * "Uniquely" prefix unmapped vars to void conflict with other rules.
- */
- // val bound = Set[sparql.Var](vartargetmap.map((vartarget) => vartarget._1))
- val bound = vartargetmap.foldLeft(Set[sparql.Var]())((s, vartarget) => s + vartarget._1)
- val mappedTo = vartargetmap.foldLeft(Set[sparql.Term]())((s, vartarget) => {
- val MapTarget(term, optnp) = vartarget._2
- (term, optnp) match {
- case (sparql.TermUri(u), Some(np)) => {
- s + sparql.TermUri(sparql.Uri(u.s.replaceAll(np.iface, np.direct))) // !!! failure here should error()
- }
- case (_, _) => s + vartarget._2.term
- }
- })
- val vars = gp.findVars
- val diff = vars -- bound
- diff.foldLeft(mapped)((incrementalGP, varr) => {
- val full = substitute(incrementalGP, sparql.TermVar(varr),
- MapTarget(sparql.TermVar(sparql.Var(varPrefix + varr.s)),
- None// !! vartargetmap(varr).nm
- ))
- full.trim(mappedTo)
- })
- }
- case class RuleIndex (trigger:sparql.TriplePattern, smap:SparqlMap) {
- override def toString = "{ \"" + trigger + "\" } => {\"\n " + _shorten(smap.construct.gp.toString).replace("\n", "\n ") + "\n\"}"
- def transform (tp:sparql.TriplePattern):sparql.GraphPattern = {
- substitute(substitute(smap.construct.gp, trigger.s, smap.nodemap.var2maptarget(tp.s)),
- trigger.o, smap.nodemap.var2maptarget(tp.o))
- }
- }
-
- /**
- * Bindings keep track of all the ways that a particular rule has been invoked
- * in order to cover a particular query pattern. It's a mapping from a rule to
- * the list of "editions" (invocations) of that rule.
- */
- case class Bindings (b:Map[sparql.Construct, List[VarMap]]) {
- def countEditions () = {
- var count = 0
- b.map((constructlist) =>
- constructlist._2.map((_) =>
- count = count + 1))
- count
- }
- def toGraphPattern (ident:String):sparql.GraphPattern = {
- var conjNo = 0
- val conjuncts = b.keySet.toList.flatMap(construct => {
- val l = b(construct)
- val patterns:List[sparql.GraphPattern] = l.map((vartermmap) => {
- val unique = ident + conjNo + "_"
- conjNo = conjNo + 1
- substituteGraphPattern(construct.gp, vartermmap, unique)
- })
- patterns
- })
-
- if (conjuncts.size == 0)
- sparql.TriplesBlock(List[sparql.TriplePattern]())
- else if (conjuncts.size > 1)
- sparql.TableConjunction(conjuncts).simplify
- else
- conjuncts(0)
- }
- override def toString = "Bindings(Map(" + b.map((constructlist) => {
- val (construct, l) = constructlist
- "\"" + _shorten(construct.head.toString) + "\" -> List(" + l.map((vartermmap) => {
- "Map(" + vartermmap.map((varterm) => {
- val (varr, term) = varterm
- varr.toString + ":" + term.toString
- }) + ")"
- }).mkString("\n ", ",\n ", "") + ")"
- }).mkString("\n ", ",\n ", "") + "))"
- /**
- * Make sure a particular rule has an entry in the Bindings.
- */
- def ensureGraphPattern (construct:sparql.Construct) = {
- if (b.contains(construct)) this
- else Bindings(b + (construct -> List[VarMap]()))
- }
- // val varsS:Option[Bindings] = vars.maybeRebind(construct, v, tos)
- // b:Map[sparql.Construct, List[VarMap]]
- def mustBind (construct:sparql.Construct, nodemap:NodePatternMap, vs:sparql.Term, tos:sparql.Term, vo:sparql.Term, too:sparql.Term):Bindings = {
- /* ridiculous traversal for the first viably matching rule edition. */
- var matched = false
- val existing:List[VarMap] = b(construct).map((map:VarMap) => {
- def _matches (l:sparql.Term, r:sparql.Term):(Boolean, VarMap) = {
- val empty:VarMap = Map[sparql.Var, MapTarget]()
- (l, r) match {
- case (v:sparql.TermVar, x) =>
- // println("(v:sparql.TermVar, x)" + v.v + ":" + x)
- if (map.contains(v.v)) {
- if (r == map(v.v).term) (true, empty)
- else (false, empty)
- } else {
- (true, Map[sparql.Var, MapTarget](v.v -> MapTarget(r, nodemap.m.get(v.v))))
- }
- case (x, v:sparql.TermVar) => {
- // println("query variable " + v + " known equal to " + x + " at compile time")
- (true, empty)
- }
- case (x, y) =>
- // println("(x, y)" + x + "?=" + y)
- if (x == y) (true, empty)
- else (false, empty)
- }
- }
- val (sMatches, sBindings) = _matches(vs, tos)
- val (oMatches, oBindings) = _matches(vo, too)
-
- if (sMatches & oMatches) {
- matched = true
- map ++ sBindings ++ oBindings
- } else
- map
- })
- if (matched)
- Bindings(b + (construct -> existing))
- else {
- Bindings(b.map((constructlist) => {
- val (oldConstr, l) = constructlist
- if (oldConstr == construct) {
- def _newBinding (l:sparql.Term, r:sparql.Term):VarMap = {
- val empty:VarMap = Map[sparql.Var, MapTarget]()
- (l, r) match {
- case (v:sparql.TermVar, _) =>
- Map[sparql.Var, MapTarget](v.v -> MapTarget(r, nodemap.m.get(v.v)))
- case (b:sparql.TermBNode, _) => {
- println(".. synthetic query variable " + b + "")
- Map[sparql.Var, MapTarget]()
- // println("@@ mustBind:_newBinding(BNode) + " + b)
- // Map(sparql.Var("bnode_" + b.b.s) -> r) // !!!
- }
- case (_, v:sparql.TermVar) => {
- println(".. query variable " + v + " known equal to " + l + " at compile time")
- Map[sparql.Var, MapTarget]()
- }
- case (_, _) => Map[sparql.Var, MapTarget]()
- }
- }
- val ent = _newBinding(vs, tos) ++ _newBinding(vo, too)
- val list = l ++ List(ent)
- (construct -> list)
- } else {
- (oldConstr -> l)
- }
- }))
- }
- }
- }
- def createEmptyBindings () = Bindings(Map[sparql.Construct, List[VarMap]]())
-
- case class RuleMap (rules:Map[sparql.Uri, List[RuleIndex]]) {
- /**
- * Recursively examine the first triple pattern in a graph pattern and add
- * invocations to the bindings to produce that triple.
- */
- def transform (prove:List[sparql.TriplePattern], used:Set[sparql.TriplePattern], varsP:Bindings):Bindings = {
- val _pad = used.foldLeft("")((s, x) => s + " ")
- def _deepPrint (s:String):Unit = {
- //print(used.size + ":" + _pad + s.replace("\n", "\n" + _pad) + "\n\n")
- }
- def _deepPrint1 (prefix:String, s:String):Unit = {
- val p = used.size + ":" + prefix + _pad
- //print(p + s.replace("\n", "\n" + p) + "\n\n")
- }
-
- val car = prove(0)
- val cdr = prove.filterNot (_ == car)
- _deepPrint("RuleMap.transform(" + _shorten(car.toString) + ", " + varsP.toString + ")")
- val xform = car.p match {
- case sparql.TermUri(u) =>
- // for each rule that supplies predicate u
- var _ruleNo = 0;
- rules(u).foldLeft(createEmptyBindings)((bindings, hornRule) => {
- val _prefix = "rule:" + _ruleNo
- _ruleNo = _ruleNo + 1
- _deepPrint1(_prefix, "trying " + _shorten(hornRule.trigger.toString))
- val vars = varsP.ensureGraphPattern(hornRule.smap.construct)
- // try matching the subject
- val varss:Bindings = vars.mustBind(hornRule.smap.construct, hornRule.smap.nodemap, hornRule.trigger.s, car.s, hornRule.trigger.o, car.o)
- val ret =
- if (cdr.size > 0) {
- transform(cdr, used + car, varss)
- } else {
- _deepPrint1("match!", _shorten(hornRule.trigger.toString) + "(" + _shorten(car.toString) + ") matches with " + _shorten(varss.toString))
- varss
- }
- _deepPrint1(_prefix, _shorten(hornRule.trigger.toString) + "(" + _shorten(car.toString) + ") matches ..." + bindings.toString + ret.toString)
-
- /* Magic Huristics */
- if (bindings.countEditions == 0) {
- //print("choose: bindings.countEditions == 0 => " + ret + "\n\n")
- ret
- } else if (ret.countEditions == 0) {
- //print("choose: ret.countEditions == 0 => " + bindings + "\n\n")
- bindings
- } else if (ret.countEditions < bindings.countEditions) {
- //print("choose: "+ret.countEditions+" < "+bindings.countEditions+" => " + ret + "\n\n")
- ret
- } else {
- //print("choose: "+ret.countEditions+" > "+bindings.countEditions+" => " + bindings + "\n\n")
- bindings
- }
- })
- case _ => error("not implemented: " + car.p)
- }
- _deepPrint("RuleMap.transform(" + _shorten(car.toString) + ") => " + _shorten(xform.toString))
- xform
- }
-
- override def toString = "RuleMap(Map(" + rules.map((ent) => {
- val (u, l) = ent
- "\"" + _shorten(u.toString) + "\" -> List(" + l.map((hr) => {
- _shorten(hr.toString).replace("\n", "\n ")
- }).mkString("\n ", ",\n ", "") + ")"
- }).mkString("\n ", ",\n ", "") + "))"
-
- }
-
- def mapGraphPattern (gp:sparql.GraphPattern, ruleMap:RuleMap, ident:String):sparql.GraphPattern = {
- gp match {
- case sparql.TriplesBlock(tps) => {
- val emptyBindings = createEmptyBindings
- val b:Bindings = ruleMap.transform(tps, Set[sparql.TriplePattern](), emptyBindings)
- //print("mapGraphPattern: " + _shorten(gp.toString) + ") => " + _shorten(b.toString) + "\n\n")
- val g:sparql.GraphPattern = b.toGraphPattern(ident)
- //print("mapGraphPattern: ... instantiated: " + _shorten(g.toString) + "\n\n")
- g
- }
- case sparql.TableFilter(gp2:sparql.GraphPattern, expr:sparql.Expression) =>
- sparql.TableFilter(mapGraphPattern(gp2, ruleMap, ident), expr)
- case sparql.MinusGraphPattern(gp2) =>
- sparql.MinusGraphPattern(mapGraphPattern(gp2, ruleMap, ident))
- case sparql.OptionalGraphPattern(gp2) =>
- sparql.OptionalGraphPattern(mapGraphPattern(gp2, ruleMap, ident))
- case sparql.GraphGraphPattern(gp2) =>
- sparql.GraphGraphPattern(mapGraphPattern(gp2, ruleMap, ident))
- case sparql.TableDisjunction(l) =>
- sparql.TableDisjunction({l.foldLeft((List[sparql.GraphPattern](), 0))((p, gp2) => {
- val (l, disjNo) = p
- (l ++ List(mapGraphPattern(gp2, ruleMap, ident + disjNo + "_")), disjNo + 1)
- })}._1)
- case sparql.TableConjunction(l) =>
- sparql.TableConjunction({l.foldLeft((List[sparql.GraphPattern](), 0))((p, gp2) => {
- val (l, conjNo) = p
- (l ++ List(mapGraphPattern(gp2, ruleMap, ident + conjNo + "_")), conjNo + 1)
- })}._1)
- }
- }
-
- def apply (query:sparql.Select, maps:List[SparqlMap]) : sparql.Select = {
- var _ruleNo = 0
- val ruleMap = RuleMap({
- maps.foldLeft(Map[sparql.Uri, List[RuleIndex]]())((m, sm) => {
- val SparqlMap(rule, nodemap) = sm
- // Register abbreviations for debugging output.
- RuleLabels.update(rule.head.toString, "head" + _ruleNo)
- RuleLabels.update(rule.gp.toString, "body" + _ruleNo)
-
- _ruleNo = _ruleNo + 1
- rule.head.triplepatterns.foldLeft(m)((m, tp) => m + ({
- tp.p match {
- case sparql.TermUri(u) => u -> {
- if (m.contains(u)) m(u) ++ List(RuleIndex(tp, sm))
- else List(RuleIndex(tp, sm))}
- case _ => error("not implemented: " + tp.p)
- }
- }))
- })
- })
- //println("ruleMap: " + ruleMap)
- sparql.Select(
- query.distinct,
- query.attrs,
- mapGraphPattern(query.gp, ruleMap, "_"),
- query.order, query.offset, query.limit
- )
- }
-
-}
-
--- a/src/main/scala/SparqlToSql.scala Fri Oct 15 16:42:41 2010 -0400
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,1211 +0,0 @@
-/** SparqlToSql: convert SPARQL queries to sound SQL queries.
- * <a href="http://www.w3.org/TR/rdf-sparql-query/#sparqlDefinition">SPARQL semantics</a> defines the evalutation of an abstract query on a dataset.
- * This object maps a SPARQL abstract query over a <a href="@@">direct graph</a> to an SQL abstract query over the constituant relations.
- * <p/>
- * Please read from the bottom -- i.e. apply calls mapGraphPattern with the root
- * graph pattern. mapGraphPattern handles all the graph pattern types in SPARQL,
- * effectively peforming the Convert Graph Patterns step in SPARQL 1.0 12.2.1
- * <a href="http://www.w3.org/TR/rdf-sparql-query/#convertGraphPattern">SPARQL rules for converting graph patterns</a>
- * with the target semantics in SQL instead of SPARQL.
- */
-
-package w3c.sw.sparql2sql
-
-import scala.util.parsing.combinator._
-import java.net.URI
-import w3c.sw.sql
-import w3c.sw.sql.PrettySql.toPrettySql
-import w3c.sw.rdf
-import w3c.sw.sparql
-import w3c.sw.util
-
-case class StemURI(s:String)
-case class PrimaryKey(attr:sql.RDB.AttrName)
-
-sealed abstract class Binding
-case class RDFNode(relvarattr:sql.RelVarAttr) extends Binding
-case class Str(relvarattr:sql.RelVarAttr) extends Binding
-case class Int(relvarattr:sql.RelVarAttr) extends Binding
-case class Enum(relvarattr:sql.RelVarAttr) extends Binding
-
-/**
- * Converts a SPARQL object to an SQL object equivalent over the direct graph.
- *
- * @see {@link w3c.sw.sparql.Sparql Sparql}
- * @see {@link w3c.sw.sql.Sql#Select Sql}
- */
-object SparqlToSql {
- /**
- * Accumulated state for generating an Sql object.
- *
- * @param joins an AddOrderedSet of SQL joins
- * @param varmap a map from Sparql.Assignable to SQL terms
- * @param exprs a set of accumulated SQL expressions
- */
- case class R2RState(joins:util.AddOrderedSet[sql.Join], varmap:Map[sparql.Assignable, SQL2RDFValueMapper], exprs:Set[sql.Expression])
-
- /**
- * Binding for a sparql.Variable or BNode
- */
- sealed abstract class FullOrPartialBinding
- case class FullBinding(relvarattr:sql.RelVarAttr) extends FullOrPartialBinding
- case class BindingConstraint(expr:sql.RelationalExpression, relvarattr:sql.RelVarAttr)
-
- /**
- * Partial binding, a variable only (so far) found in OPTIONALs or assymetric
- * UNIONs.
- */
- case class PartialBinding(binders:Set[BindingConstraint]) extends FullOrPartialBinding
-
- /**
- * Convert a binding to an SQL expression.
- * <p/>
- * example return:
- * <code>if (g_union1._DISJOINT_ != 0, g_union1.who, if (g_union2._DISJOINT_ != 3, g_union2.who, NULL))</code>
- * @param against binding to be expressed
- * @return SQL expression representing that binding
- */
- def toExpr(against:FullOrPartialBinding):sql.Expression = {
- against match {
- case FullBinding(relvarattr) =>
- sql.PrimaryExpressionAttr(relvarattr)
- case PartialBinding(binders) =>
- binders.toList.reverse.foldLeft(sql.ConstNULL():sql.Expression)((exp, binding) => {
- val BindingConstraint(expr, relvarattr) = binding
- sql.IfElse(expr, sql.PrimaryExpressionAttr(relvarattr), exp)
- })
- }
- }
- /**
- * Accumulate bindings on previously bound sparql variables.
- * @param binding previous binding
- * @param relVarAttr SQL relvar attribute to be bound, e.g. <code>G_union1.who</code>
- * @param expr expr binding constraint, e.g. <code>G_opt6._DISJOINT_ IS NULL</code> or <code>G_union1._DISJOINT_!=0</code>
- * @return PartialBinding on old constraints plus expr=relVarAttr
- */
- def addExpr(binding:FullOrPartialBinding, relVarAttr:sql.RelVarAttr, expr:sql.RelationalExpression):FullOrPartialBinding = {
- binding match {
- case FullBinding(relvarattr) =>
- error("Unexpected FullBinding to " + relvarattr + "\n" +
- "because subselectVars should only find a pre-existing PartialBindings.")
- // return binding if this codepath is needed later.
- case PartialBinding(binders) =>
- /**
- * Add a new BindingConstraint to existing one.
- * e.g. existing constraint: G_union1._DISJOINT_!=0,G_union1.name
- * add for second side of union: G_union1._DISJOINT_!=1,G_union1.name
- */
- PartialBinding(binders + BindingConstraint(expr, relVarAttr))
- }
- }
-
- /**
- * SQL terms representing SPARQL variables and bnodes.
- */
- sealed abstract class SQL2RDFValueMapper(binding:FullOrPartialBinding)
- case class IntMapper(binding:FullOrPartialBinding) extends SQL2RDFValueMapper(binding)
- case class StringMapper(binding:FullOrPartialBinding) extends SQL2RDFValueMapper(binding)
- case class DateMapper(binding:FullOrPartialBinding) extends SQL2RDFValueMapper(binding)
- /**
- * map to a URL for a tuple in the database.
- */
- case class RDFNoder(relation:sql.RDB.RelName, binding:FullOrPartialBinding) extends SQL2RDFValueMapper(binding)
- /**
- * map to a blank node label for a tuple in the database.
- */
- case class RDFBNoder(relation:sql.RDB.RelName, binding:FullOrPartialBinding) extends SQL2RDFValueMapper(binding)
-
- /**
- * a URL representing a tuple in a database.
- */
- case class NodeUri(stem:Stem, rel:Rel, attr:Attr, v:CellValue)
-
- /**
- * url stem to base the direct graph.
- * <p/>
- * e.g. http://mydb.example/theDatabase
- */
- case class Stem(s:String) {
- override def toString = "" + s
- }
-
- /**
- * SQL relation (table) name
- */
- case class Rel(s:String) {
- override def toString = "" + s
- }
- /**
- * SQL attribute (column) name
- */
- case class Attr(s:String) {
- override def toString = "" + s
- }
- /**
- * value in a database
- */
- case class CellValue(s:String)
- /**
- * a URL representing a predicate in a database.
- */
- case class PUri(stem:Stem, rel:Rel, attr:Attr) {
- override def toString = "<" + stem + "/" + rel + "#" + attr + ">"
- }
- /**
- * parse predicate URL in direct graph into stem, relation and attribute
- * <pre>stemURI + '/' + (\w+) + '#' (\w+)</pre>
- */
- def parsePredicateURI(u:sparql.Uri):PUri = {
- val x:String = u.s
- val uri = new URI(x)
- val path = uri.getPath().split("/").toList.filterNot(_ == "")
- val subPath = path.slice(0, path.size - 1).mkString("/")
- val stem = uri.getScheme() + "://" + uri.getAuthority + "/" + subPath
- PUri(Stem(stem), Rel(path.last), Attr(uri.getFragment))
- }
-
- /**
- * parse node URL in direct graph into stem, relation, attribute and value
- * <pre>stemURI + '/' (\w+) '/' (\w+) '.' (\w+) '#record'</pre>
- */
- def parseObjectURI(u:sparql.Uri):NodeUri = {
- try {
- val x:String = u.s
- val uri = new URI(x)
- val path = uri.getPath().split("/").toList.filterNot(_ == "")
- val subPath = path.slice(0, path.size - 2).mkString("/")
- val rel = path(path.size - 2)
- val attrPair = path(path.size-1).split("\\.")
- val stem = uri.getScheme() + "://" + uri.getAuthority + "/" + subPath
- assert("record" == uri.getFragment)
- NodeUri(Stem(stem), Rel(rel), Attr(attrPair(0)), CellValue(attrPair(1)))
- } catch {
- case e:java.lang.IndexOutOfBoundsException =>
- throw new Exception("unable to parse " + u + " as an object")
- }
- }
- /**
- * synthesize a relvar name from a SPARQL term.
- * <p/>
- * e.g. <code>?emp</code> =><code>R_emp</code>
- * e.g. <code><http://hr.example/DB/Employee/empid.253#record></code> =><code>R_empid253</code>
- * e.g. <code>18</code> =><code>R_18</code>
- */
- def relVarFromTerm(s:sparql.Term):sql.RelVar = {
- s match {
- case sparql.TermUri(ob) => relVarFromNode(ob)
- case sparql.TermVar(v) => relVarFromVar(v)
- case sparql.TermBNode(v) => relVarFromBNode(v)
- case sparql.TermLit(l) => relVarFromLiteral(l)
- }
- }
-
- def relVarFromNode(u:sparql.Uri):sql.RelVar = {
- val NodeUri(stem, rel, Attr(a), CellValue(v)) = parseObjectURI(u)
- sql.RelVar(sql.Name("R_" + a + v))
- }
-
- def relVarFromLiteral(l:sparql.Literal):sql.RelVar = {
- sql.RelVar(sql.Name("R_" + l.lit.lexicalForm))
- }
-
- def relVarFromVar(vr:sparql.Var):sql.RelVar = {
- val sparql.Var(v) = vr
- sql.RelVar(sql.Name("R_" + v))
- }
-
- def relVarFromBNode(vr:sparql.BNode):sql.RelVar = {
- val sparql.BNode(b) = vr
- sql.RelVar(sql.Name("B_" + b))
- }
-
- /**
- * synthesize a SQL name from a SPARQL variable or bnode.
- * <p/>
- * e.g. <code>?emp</code> =><code>emp</code>
- */
- def attrAliasNameFromVar(v:sparql.Assignable):String = "" + v.s
-
- /**
- * add constraints implied by a URI
- * @param state state to be appended
- * @param constrainMe relvar attribute to constrain, e.g. <code>R_empid18.empid</code>
- * @param u SparqlToSql URL object, e.g. <code>NodeUri(http://hr.example/DB,Employee,empid,CellValue(18))</code>
- * @return state + expression for the URI
- * <p/>
- * <code>http://hr.example/DB/Employee/empid=18, true</code> =><code>R_emp.manager=18</code>
- * <code>http://hr.example/DB/Employee/empid=18, false</code> =><code>R_empid18.empid=18</code>
- * (the latter produces another join on R_empid18 in order to enforce a foreign key.)
- * http://hr.example/DB/Employee/empid=18 has already been parsed to produce a NodeUri:
- * NodeUri(http://hr.example/DB,Employee,empid,CellValue(18))
- */
- def uriConstraint(state:R2RState, constrainMe:sql.RelVarAttr, u:NodeUri):R2RState = {
- R2RState(state.joins,
- state.varmap,
- state.exprs + sql.RelationalExpressionEq(sql.PrimaryExpressionAttr(constrainMe),
- sql.PrimaryExpressionTyped(sql.RDB.Datatype.INTEGER,sql.Name(u.v.s))))
- }
-
- /**
- * add constraints implied by a literal in a SPARQL triple pattern
- * @param state state to be appended
- * @param constrainMe relvar attribute to constrain, e.g. <code>R_18.empid</code>
- * @param lit sparq.Literal, e.g. <code>18</code>
- * @param dt sparq datatype, e.g. <code>xsd:integer</code>
- * @return state + expression for the URI, e.g. <code>R_18.empid=18</code>
- */
- def literalConstraint(state:R2RState, constrainMe:sql.RelVarAttr, lit:sparql.Literal, dt:sql.RDB.Datatype):R2RState = {
- R2RState(state.joins,
- state.varmap,
- state.exprs + sql.RelationalExpressionEq(sql.PrimaryExpressionAttr(constrainMe),
- sql.PrimaryExpressionTyped(dt,sql.Name(lit.lit.lexicalForm))))
- }
-
- /** varConstraint
- * @param state earlier R2RState
- * @param alias relvar to bind, e.g. Employees AS R_emp
- * @param optAttr SQL attribute to bind, e.g. Employees.lastName
- * @param v SPARQL variable or blank node to bind
- * @param db database description
- * @param rel SQL relation to bind, e.g. Employee
- * @return a new R2RState incorporating the new binding
- * For example, <code>SELECT ?emp WHERE { ?emp emp:lastName ?name }</code> will call varConstraint twice:
- *
- * given: alias:=R_emp, optAttr:=lastName, v:=?name, rel:=Employee ->
- * return: (VarAssignable(?name),StringMapper(FullBinding(R_emp.lastName)))
- * which maps "Smith" to "Smith"^^xsd:string
- *
- * given: alias:=R_emp, optAttr:=empid, v:=?emp, rel:=Employee ->
- * return: (VarAssignable(?emp),RDFNoder(Employee,FullBinding(R_emp.empid)))
- * which maps 4 to <http://hr.example/our/favorite/DB/Employee/id.4#record>
- */
- def varConstraint(state:R2RState, alias:sql.RelVar, optAttr:Option[sql.RDB.AttrName], v:sparql.Assignable, db:sql.RDB.Database, rel:sql.RDB.RelName):R2RState = {
- val constrainMe = if (optAttr.isDefined) sql.RelVarAttr(alias, optAttr.get) else sql.RelVarAttr(alias, sql.RDB.AttrName("_no_such_attribute"))
- val reldesc = db(rel)
- val boundTo = FullBinding(constrainMe)
-
- /**
- * Bind optAttr to an SQL generator like RDFNoder(Employee,FullBinding(R_emp.empid))
- */
- val binding = reldesc.pk match {
- /** <pre>varConstraint(R_emp, Some(empid), VarAssignable(?emp), Employee) -> RDFNoder(Employee,FullBinding(R_emp.empid))</pre> */
- case Some(sql.RDB.CandidateKey(List(sql.RDB.AttrName(constrainMe.attribute.n)))) => RDFNoder(rel, boundTo)
- case _ => {
- val asKey = sql.RDB.CandidateKey(List(constrainMe.attribute))
- if (reldesc.fks.contains(asKey)) { // !! (0)
- /** varConstraint(R_patient, Some(SexDE), VarAssignable(?_0_sexEntry), Person) -> RDFNoder(Person,FullBinding(R_patient.SexDE)) */
- val sql.RDB.Target(fkrel, fkattr) = reldesc.fks(asKey)
- RDFNoder(rel, boundTo)
- } else if (reldesc.header.contains(constrainMe.attribute)) {
- reldesc.header(constrainMe.attribute) match {
- /** varConstraint(R__0_indicDE, Some(NDC), VarAssignable(?_0_indicNDC), Medication_DE) -> IntMapper(FullBinding(R__0_indicDE.NDC)) */
- case sql.RDB.Datatype("Int") => IntMapper(boundTo)
- /** varConstraint(R_emp, Some(lastName), VarAssignable(?name), Employee) -> StringMapper(FullBinding(R_emp.lastName)) */
- case sql.RDB.Datatype("String") => StringMapper(boundTo)
- /** varConstraint(R_patient, Some(DateOfBirth), VarAssignable(?dob), Person) -> DateMapper(FullBinding(R_patient.DateOfBirth)) */
- case sql.RDB.Datatype("Date") => DateMapper(boundTo)
- }
- } else {
- /** Default behavior for unknown attributes. */
- RDFBNoder(rel, boundTo) // @@ untested
- }
- }
- }
-
- if (state.varmap.contains(v) && state.varmap(v) == constrainMe) {
- /**
- * No useful contributions for this variable.
- * (We could re-add the binding; this case is just for clarity of behavior.)
- */
- state
- } else if (state.varmap.contains(v)) {
- /**
- * The variable has already been bound to another attribute.
- * Constraint against the initial binding for this variable.
- * e.g. <code>R__0_0_indicCode.ID=R__0_0_indicCode.ID</code>
- */
- val constraint = sql.RelationalExpressionEq(sql.PrimaryExpressionAttr(constrainMe),
- sql.PrimaryExpressionAttr(varToAttribute(state.varmap, v)))
- R2RState(state.joins, state.varmap,
- if (varToAttributeDisjoints(state.varmap, v).size > 0)
- /**
- * Enumerate the set of constraints capturing all sides of a disjoint or option.
- * e.g. a UNION where two disjoints constrain R_who.empid=G_union0.who:
- * Set((G_union0._DISJOINT_!=0) OR (R_who.empid=G_union0.who),
- * (G_union0._DISJOINT_!=1) OR (R_who.empid=G_union0.who))
- */
- state.exprs ++ {varToAttributeDisjoints(state.varmap, v) map ((d) => sql.ExprDisjunction(Set(d, constraint)))}
- else
- state.exprs + constraint
- )
- } else {
- /**
- * Add binding for new variable.
- */
- R2RState(state.joins, state.varmap + (v -> binding), state.exprs)
- }
- }
-
- def toString(relvarattr:sql.RelVarAttr) : String = {
- relvarattr.relvar.n.s + "." + relvarattr.attribute.n
- }
- // def toString(mapper:SQL2RDFValueMapper) : String = {
- // mapper match {
- // case IntMapper(relvar, disjoints) => "INT: " + toString(relvar)
- // case StringMapper(relvar, disjoints) => "STRING: " + toString(relvar)
- // case DateMapper(relvar, disjoints) => "DATE: " + toString(relvar)
- // case RDFNoder(relation, relvar, disjoints) => "RDFNoder: " + relation.n.s + ", " + toString(relvar)
- // case RDFBNoder(relation, relvar, disjoints) => "RDFBNoder: " + relation.n.s + ", " + toString(relvar)
- // }
- // }
-
- def __optFirst (k:Option[sql.RDB.CandidateKey]):Option[sql.RDB.AttrName] =
- k match {
- case Some(ck) => Some(ck.attrs(0))
- case _ => None
- }
-
- /**
- * map a given triple to one or two joined tables, variable
- * @param db database description
- * @param stateP earlier R2RState
- * @param triple
- * @param enforceForeignKeys
- * @return a new R2RState incorporating the new binding
- * bindings to RelVarAttrs, and constraints if those variables were
- * already bound. */
- def bindOnPredicate(db:sql.RDB.Database, stateP:R2RState, triple:sparql.TriplePattern, enforceForeignKeys:Boolean):R2RState = {
- val sparql.TriplePattern(s, p, o) = triple
- p match {
- /** Don't deal with ?s ?p ?o . We could deal with <s> ?p ?o , but haven't yet. */
- case sparql.TermVar(v) => error("variable predicates require tedious enumeration; too tedious for me.")
- case sparql.TermUri(uri) => {
- val PUri(stem, spRel, spAttr) = parsePredicateURI(uri)
- /** The relation and attribute come from the predicate, e.g. Employee.fname => Employee and fname. */
- val rel = sql.RDB.RelName(spRel.s)
- val attr = sql.RDB.AttrName(spAttr.s)
-
- /**
- * The particular join for e.g. Employee is controled by the subject.
- * ?emp => Employee AS emp
- * <Employee:18> => Employee AS Employee_18 .
- */
- val relvar = relVarFromTerm(s)
- /** Synthesize relvar attribute, e.g. emp.fname. */
- val objattr = sql.RelVarAttr(relvar, attr)
-
- /** Accumulate joins and constraints that come from the subject */
- val state_postSubj = s match {
- case sparql.TermUri(u) =>
- /** additional constraint, e.g. R_empid18.empid=18. */
- uriConstraint(stateP, sql.RelVarAttr(relvar, db(rel).pk.get.attrs(0)), parseObjectURI(u)) // !! (0)
- case sparql.TermVar(v) =>
- /** assignable binding for novel vars, new constraint for previously bound vars. */
- try {
- varConstraint(stateP, relvar, __optFirst(db(rel).pk), sparql.VarAssignable(v), db, rel) // !! (0)
- } catch {
- case e:java.util.NoSuchElementException =>
- /** Tell user that the relation.attribute encoded in the subject was not found in the database description. */
- throw new Exception("error processing { " + s + " " + p + " " + o + " } :db(" + rel + ") not found in " + db)
- }
- case sparql.TermBNode(b) =>
- /** assignable binding for novel bnodes, new constraint for previously bound bnodes. */
- try {
- varConstraint(stateP, relvar, __optFirst(db(rel).pk), sparql.BNodeAssignable(b), db, rel) // !! (0)
- } catch {
- case e:java.util.NoSuchElementException =>
- throw new Exception("error processing { " + s + " " + p + " " + o + " } :db(" + rel + ") not found in " + db)
- }
- case _ => error("illegal SPARQL subject: " + s)
- }
-
- /** Join rel (relation dictated by predicate) AS relvar (alias dicated by subject).
- * may be redundant as R2RState's joins are a set */
- val state_subjJoin = R2RState(state_postSubj.joins + sql.InnerJoin(sql.AliasedResource(rel,relvar), None), state_postSubj.varmap, state_postSubj.exprs)
-
- try { db(rel).header(attr) } catch {
- /** Tell user that the queried attribute was not found in the database description. */
- case e:java.util.NoSuchElementException =>
- throw new Exception("error processing { " + s + " " + p + " " + o + " } :db(" + rel + ").header(" + attr + ") not found in " + db)
- }
-
- /**
- * fkrel.fkattr (e.g. Employee.manager) may be a foreign key.
- * Calculate final relvarattr and relation.
- */
- val asKey = sql.RDB.CandidateKey(List(attr))
- val (targetattr:sql.RelVarAttr, targetrel, dt, state_fkeys:R2RState) =
- if (db(rel).fks.contains(asKey)) { // !! (0)
- val sql.RDB.Target(fkrel, fkattr) = db(rel).fks(asKey)
- try { db(fkrel).header(fkattr.attrs(0)) } catch { // !! (0)
- /** Foreign key relation.attribute was not found in the database description. */
- case e:java.util.NoSuchElementException =>
- throw new Exception("db(" + fkrel + ").header(" + fkattr + ") not found in " + db)
- }
- val fkdt =
- if (db(fkrel).fks.contains(fkattr)) {
- /** Foreign key to something which is a foreign key. May have use
- * cases, but signal error until we figure out that they are. */
- val sql.RDB.Target(dfkrel, dfkattr) = db(fkrel).fks(fkattr)
- error("foreign key " + rel.n + "." + attr.n +
- "->" + fkrel.n + "." + fkattr.attrs(0).n + // !! (0)
- "->" + dfkrel.n + "." + dfkattr.attrs(0).n) // !! (0)
- } else
- db(fkrel).header(fkattr(0)) // !! (0)
- if (enforceForeignKeys) {
- /**
- * Create an extra join on the foreign key relvar. For instance,
- * <code>?task1 <http://hr.example/DB/TaskAssignments#employee> ?who</code>
- * where TaskAssignments.employee is a foreign key to Employee.empid
- * will join Employee AS R_who, constrain R_who.empid=R_task1.employee
- * and bind targetattr:R_who.empid. targetrel:Employee .
- */
- val oRelVar = relVarFromTerm(o)
- val fkaliasattr = sql.RelVarAttr(oRelVar, fkattr.attrs(0)) // !! (0)
- val state_t = R2RState(state_subjJoin.joins + sql.InnerJoin(sql.AliasedResource(fkrel,oRelVar), None),
- state_subjJoin.varmap,
- state_subjJoin.exprs + sql.RelationalExpressionEq(sql.PrimaryExpressionAttr(fkaliasattr),
- sql.PrimaryExpressionAttr(objattr)))
- //println("enforceFKs: <code>"+s+" "+p+" "+o+"</code> where "+rel+"."+attr+" is a foreign key to "+fkrel+"."+fkattr+" will join "+fkrel+" AS "+oRelVar+", constrain "+fkaliasattr+"="+objattr+" and bind targetattr:=" + fkaliasattr + ". targetrel:=" + fkrel + " (instead of " + objattr + ", " + rel + ").")
- (fkaliasattr, fkrel, fkdt, state_t)
- } else {
- /**
- * We're not enforcing foreign keys, so just bind
- * targetattr:=R_task1.employee, targetrel:=TaskAssignments.
- */
- (objattr, rel, fkdt, state_subjJoin)
- }
- }
- else
- /** not a foreign key, so use the relvarattr and relation calculated
- * from the predicate. */
- (objattr, rel, db(rel).header(attr), state_subjJoin)
-
- o match {
- case sparql.TermLit(l) => literalConstraint(state_fkeys, targetattr, l, dt)
- case sparql.TermUri(u) => uriConstraint (state_fkeys, targetattr, parseObjectURI(u))
- case sparql.TermVar(v) => varConstraint (state_fkeys, targetattr.relvar, Some(targetattr.attribute), sparql.VarAssignable(v), db, targetrel)
- case sparql.TermBNode(b) => varConstraint (state_fkeys, targetattr.relvar, Some(targetattr.attribute), sparql.BNodeAssignable(b), db, targetrel)
- }
- }
- case _ => error("illegal SPARQL predicate: " + p)
- }
- }
-
- def bindingConstraintToAttribute(constraint:BindingConstraint):sql.RelVarAttr = {
- val BindingConstraint(expr:sql.RelationalExpression, relvarattr:sql.RelVarAttr) = constraint;
- relvarattr
- }
- def bindingToAttribute(binding:FullOrPartialBinding):sql.RelVarAttr = {
- binding match {
- case FullBinding(relvarattr:sql.RelVarAttr) => relvarattr
- case PartialBinding(binders) => bindingConstraintToAttribute(binders.toList(0))
- }
- }
- def varToAttribute(varmap:Map[sparql.Assignable, SQL2RDFValueMapper], vvar:sparql.Assignable):sql.RelVarAttr = {
- val mapper = try { varmap(vvar) } catch {
- case e:java.util.NoSuchElementException =>
- throw new Exception("mapper for variable " + vvar + " not found in " + varmap)
- }
- mapper match {
- case IntMapper(binding) => bindingToAttribute(binding)
- case StringMapper(binding) => bindingToAttribute(binding)
- case DateMapper(binding) => bindingToAttribute(binding)
- case RDFNoder(relation, binding) => bindingToAttribute(binding)
- case RDFBNoder(relation, binding) => bindingToAttribute(binding) // error("BNode should not need relvar " + relvar)
- }
- }
-
- def bindingConstraintToExpression(constraint:BindingConstraint):sql.RelationalExpression = {
- val BindingConstraint(expr:sql.RelationalExpression, relvarattr:sql.RelVarAttr) = constraint;
- expr
- }
- def bindingToDisjoints(binding:FullOrPartialBinding):Set[sql.RelationalExpression] = {
- binding match {
- case FullBinding(relvarattr:sql.RelVarAttr) => Set[sql.RelationalExpression]()
- case PartialBinding(binders) => binders.map({b => bindingConstraintToExpression(b)})
- }
- }
- def varToAttributeDisjoints(varmap:Map[sparql.Assignable, SQL2RDFValueMapper], vvar:sparql.Assignable):Set[sql.RelationalExpression] = {
- varmap(vvar) match {
- case IntMapper(binding) => bindingToDisjoints(binding)
- case StringMapper(binding) => bindingToDisjoints(binding)
- case DateMapper(binding) => bindingToDisjoints(binding)
- case RDFNoder(relation, binding) => bindingToDisjoints(binding)
- case RDFBNoder(relation, binding) => bindingToDisjoints(binding) // error("BNode should not need relvar " + relvar)
- }
- }
-
- /**
- * Converts a variable bound to a URL to an SQL expression for that URL.
- *
- * @param varmap map from variable to SQL2RDFValueMapper
- * @param vvar the variable to be represented
- * @return an SQL CONCAT expression
- * @see VarMap
- */
- def varToExpr(varmap:Map[sparql.Assignable, SQL2RDFValueMapper], vvar:sparql.Assignable, stem:StemURI):sql.Expression = {
- varmap(vvar) match {
- case IntMapper(binding) => sql.PrimaryExpressionAttr(bindingToAttribute(binding))
- case StringMapper(binding) =>
- sql.Concat(List(sql.PrimaryExpressionTyped(sql.RDB.Datatype("String"),sql.Name("'")),
- sql.PrimaryExpressionAttr(bindingToAttribute(binding)),
- sql.PrimaryExpressionTyped(sql.RDB.Datatype("String"),sql.Name("'^^<http://www.w3.org/2001/XMLSchema#string>"))))
- case DateMapper(binding) => sql.PrimaryExpressionAttr(bindingToAttribute(binding))
- case RDFNoder(relation, binding) =>
- sql.Concat(List(sql.PrimaryExpressionTyped(sql.RDB.Datatype("String"),sql.Name(stem.s)),
- sql.PrimaryExpressionTyped(sql.RDB.Datatype("String"),relation.n),
- sql.PrimaryExpressionTyped(sql.RDB.Datatype("String"),sql.Name("/")),
- sql.PrimaryExpressionTyped(sql.RDB.Datatype("String"),bindingToAttribute(binding).attribute.n),
- sql.PrimaryExpressionTyped(sql.RDB.Datatype("String"),sql.Name(".")),
- sql.PrimaryExpressionAttr(bindingToAttribute(binding)),
- sql.PrimaryExpressionTyped(sql.RDB.Datatype("String"),sql.Name("#record"))))
- case RDFBNoder(relation, binding) =>
- sql.Concat(List(sql.PrimaryExpressionTyped(sql.RDB.Datatype("String"),sql.Name("_:")),
- sql.PrimaryExpressionTyped(sql.RDB.Datatype("String"),relation.n),
- sql.PrimaryExpressionTyped(sql.RDB.Datatype("String"),sql.Name(".")),
- sql.PrimaryExpressionTyped(sql.RDB.Datatype("String"),bindingToAttribute(binding).attribute.n),
- sql.PrimaryExpressionTyped(sql.RDB.Datatype("String"),sql.Name(".")),
- sql.PrimaryExpressionAttr(bindingToAttribute(binding))))
- }
-
- }
-
- /**
- * return an SQL PrimaryExpression for rTerm.
- *
- * @param varmap map from variable to SQL2RDFValueMapper
- * @param l an SQL relvarattr for a SPARQL Assignable (variable or bnode)
- * @param rTerm SPARQL term to be converted to a PrimaryExpression
- * @param sqlexpr an unbound SQL relational expression, e.g. sql.RelationalExpressionEq(_,_)
- * @return an SQL expression on the relvarattrs to which the variables in f are bound
- */
- def assignable2expr(varmap:Map[sparql.Assignable, SQL2RDFValueMapper], l:sql.RelVarAttr, rTerm:sparql.Term, sqlexpr:(sql.PrimaryExpression, sql.PrimaryExpression) => sql.RelationalExpression):sql.RelationalExpression = { // :sparql.Var
- val r:sql.PrimaryExpression = rTerm match {
- case sparql.TermUri(u) => error("not implemented: translating RDF URI to SQL: " + u) // :sparql.Uri
- case sparql.TermVar(v) => sql.PrimaryExpressionAttr(varToAttribute(varmap, sparql.VarAssignable(v)))
- case sparql.TermBNode(b) => sql.PrimaryExpressionAttr(varToAttribute(varmap, sparql.BNodeAssignable(b)))
- case sparql.TermLit(sparql.Literal(rdf.RDFLiteral(lit,rdf.Datatype(dt)))) =>
- sql.PrimaryExpressionTyped({
- dt.toString match {
- case "http://www.w3.org/2001/XMLSchema#string" => sql.RDB.Datatype.STRING
- case "http://www.w3.org/2001/XMLSchema#integer" => sql.RDB.Datatype.INTEGER
- case "http://www.w3.org/2001/XMLSchema#date" => sql.RDB.Datatype.DATE
- case _ => error("unable to translate to RDF literal SQL: \"" + lit + "\"^^<" + dt + ">")
- }
- }, lit)
- }
- sqlexpr(sql.PrimaryExpressionAttr(l), r)
- }
-
- /**
- * create an SQL expression analogous to a SPARQL expression. The variables in
- * f are expressed as the relvarattrs to which they are mapped. This function
- * is only minimally implemented here.
- *
- * @param varmap map from variable to SQL2RDFValueMapper
- * @param f SPARQL Expression
- * @return an SQL expression on the relvarattrs to which the variables in f are bound
- */
- def filter2expr(varmap:Map[sparql.Assignable, SQL2RDFValueMapper], f:sparql.PrimaryExpression):sql.RelationalExpression = {
-
- /** sqlexpr: an unbound RelationalExpression, i.e. a function which takes
- * (sql.RelVarAttr, sql.PrimaryExpressionAttr) and returns an
- * sql.RelationalExpression
- */
- val (lTerm:sparql.Term, rTerm:sparql.Term, sqlexpr) = f match {
- case sparql.PrimaryExpressionEq(l, r) => (l.term, r.term, sql.RelationalExpressionEq(_,_))
- case sparql.PrimaryExpressionLt(l, r) => (l.term, r.term, sql.RelationalExpressionLt(_,_))
- case sparql.PrimaryExpressionGt(l, r) => (l.term, r.term, sql.RelationalExpressionGt(_,_))
- case sparql.SparqlTermExpression(t) => error("not implemented") // (l.term, r.term, sql.PrimaryExpressionAttr(_,_))
- }
-
- lTerm match {
- /** does not handle FILTER (<x> = ?v) */
- case sparql.TermUri(obj) => error("only SPARQL PrimaryExpressions with a variable on the left have been implemented: punting on " + f)
- /** FILTER (?v = <x> && ?v = ?x && ?v = 7) */
- case sparql.TermVar(v) => assignable2expr(varmap, varToAttribute(varmap, sparql.VarAssignable(v)), rTerm, sqlexpr)
- case sparql.TermBNode(b) => assignable2expr(varmap, varToAttribute(varmap, sparql.BNodeAssignable(b)), rTerm, sqlexpr)
- /** does not handle FILTER (7 = ?v) */
- case sparql.TermLit(lit) => error("only SPARQL PrimaryExpressions with a variable on the left have been implemented: punting on " + f)
- }
- }
-
- /**
- * Promote a variable in an OPTIONAL or UNION subselect to the outer
- * varmap/expressions.
- */
- def subselectVars(startState:R2RState, v:sparql.Assignable, optionalAlias:sql.RelVar,
- optionalCond:sql.RelationalExpression,
- outerVarmap:Map[sparql.Assignable, SQL2RDFValueMapper],
- nestedVarmap:Map[sparql.Assignable, SQL2RDFValueMapper],
- isOpt:Boolean):R2RState = {
-
- val varAliasAttr = sql.RelVarAttr(optionalAlias, sql.RDB.AttrName(attrAliasNameFromVar(v)))
- if (startState.varmap.contains(v)) {
-
- /** The variable has already been bound. */
- val newMap:Map[sparql.Assignable, SQL2RDFValueMapper] =
- /** If var was already bound to the same relvarattr... */
- if (varToAttribute(startState.varmap, v) == varAliasAttr) {
- /**
- * Add new partial constraint to old partial constraints to produce a new binding.
- * example:
- * ?name -> StringMapper(PartialBinding(Set(BindingConstraint(G_union1._DISJOINT_!=0,G_union1.name),
- * BindingConstraint(G_union1._DISJOINT_!=1,G_union1.name))))
- */
- Map(v -> {
- startState.varmap(v) match {
- case IntMapper(binding) => IntMapper(addExpr(binding, varAliasAttr, optionalCond))
- case StringMapper(binding) => StringMapper(addExpr(binding, varAliasAttr, optionalCond))
- case DateMapper(binding) => DateMapper(addExpr(binding, varAliasAttr, optionalCond))
- case RDFNoder(rel, binding) => RDFNoder(rel, addExpr(binding, varAliasAttr, optionalCond))
- case RDFBNoder(rel, binding) => RDFBNoder(rel, addExpr(binding, varAliasAttr, optionalCond))
- } } )
- } else {
- /** Var was bound to a different relvarattr so handle as newConstraint below. */
- Map()
- }
-
- val newConstraints =
- if (varToAttribute(outerVarmap, v) == varAliasAttr) { // also varToAttribute(startState.varmap, v) == varAliasAttr
- Set()
- } else {
- /** Constraint against binding from earlier graph pattern.
- * e.g. G_union1.who=R_who.empid */
- val constraint = sql.RelationalExpressionEq(sql.PrimaryExpressionAttr(varAliasAttr),
- sql.PrimaryExpressionAttr(varToAttribute(outerVarmap, v)))
- if (varToAttributeDisjoints(outerVarmap, v).size > 0) {
- /**
- * Construct a conjunction of disjunctions like:
- * (union0._DISJOINT_ != 0 OR union0.x=union1.x) AND (union1._DISJOINT_ != 2 OR union0.x=union1.x)
- */
- varToAttributeDisjoints(outerVarmap, v) map ((d) =>
- sql.ExprDisjunction({
- if (isOpt) Set(d, constraint) // a disjunction like: G_opt1._DISJOINT_ IS NULL OR G_opt3.birthday=G_opt1.birthday
- else Set(sql.ExprConjunction(Set(d, optionalCond)), constraint) // @@ untested code path
- }))
- } else {
- if (isOpt)
- /** just the constraint from above */
- Set(constraint)
- else
- /**
- * Above constraint applied only for this path:
- * for example: (G_union1._DISJOINT_!=0) OR (G_union1.who=R_who.empid)
- */
- Set(sql.ExprDisjunction(Set(optionalCond, constraint)))
- }
- }
-
- R2RState(startState.joins, startState.varmap ++ newMap, startState.exprs ++ newConstraints)
- } else {
- /**
- * This variable is new to the outer context so synthesize a new partial
- * binding à la:
- * ?name ->
- * StringMapper(PartialBinding((G_union1._DISJOINT_!=0,G_union1.name)))
- */
- val p = PartialBinding(Set(BindingConstraint(optionalCond, varAliasAttr)))
- val mapper:SQL2RDFValueMapper = nestedVarmap(v) match {
- case IntMapper(_) => IntMapper(p)
- case StringMapper(_) => StringMapper(p)
- case DateMapper(_) => DateMapper(p)
- case RDFNoder(rel, _) => RDFNoder(rel, p)
- case RDFBNoder(rel, _) => RDFBNoder(rel, p)
- }
-
- R2RState(startState.joins, startState.varmap + (v -> mapper), startState.exprs)
- }
- }
-
- /**
- * Recursively add the joins, variable mappings and constraints for an SQL query implementing a graph pattern.
- * @param db database description.
- * @return a new state including the subquery representing gp in a join.
- * This is factored out of mapGraphPattern as it is used for MINUS and probably later for NOT EXISTS.
- */
- def synthesizeOuterJoin(initState:R2RState, gp:sparql.GraphPattern, negate:Boolean, db:sql.RDB.Database, enforceForeignKeys:Boolean):R2RState = {
- /** SPARQL OPTIONALs and UNIONs are treated as SQL subselects.
- * Set up initial state for this subselect.
- */
- val leftJoinAlias = sql.RelVar(sql.Name("G_opt" + initState.joins.size))
- val initDisjoints:Set[sql.Select] = Set()
- val emptyState = R2RState(
- util.AddOrderedSet[sql.Join](),
- Map[sparql.Assignable, SQL2RDFValueMapper](),
- Set[sql.Expression]()
- )
-
- /** Create the select for the nested graph pattern.
- */
- val nestedState = mapGraphPattern(db, emptyState, gp, enforceForeignKeys)
- val nestedVars = gp.findVars
- /**
- * Select a constant as _DISJOINT_ so later constraints can be
- * sensitive to whether a variable was bound.
- * This matters for assymetric UNIONs and, here, OPTIONALs. Given:
- * Join( LeftJoin( BGP(),
- * BGP( ?x :p2 ?v2 ) ),
- * BGP( ?y :p3 ?v2 ) )
- * coreference constraints against ?v2 should only be enforced for
- * tuples from the right side of this union.
- */
- val pathNo = sql.ProjectAttribute(sql.PrimaryExpressionTyped(sql.RDB.Datatype.INTEGER,sql.Name("" + initState.joins.size)),
- sql.AttrAlias(sql.Name("_DISJOINT_")))
- val leftJoinVars = gp.findVars
- val attrlist:Set[sql.ProjectAttribute] = leftJoinVars.map(
- v =>
- sql.ProjectAttribute(varToAttribute(nestedState.varmap, sparql.VarAssignable(v)),
- sql.AttrAlias(attrAliasNameFromVar(sparql.VarAssignable(v))))
- ) + pathNo // add join number to selections
- val subselect = sql.Select(
- false,
- sql.Projection(attrlist),
- sql.TableList(nestedState.joins),
- nestedState.exprs.size match {
- case 0 => None
- case 1 => Some(nestedState.exprs.toList(0))
- case _ => Some(sql.ExprConjunction(nestedState.exprs))
- },
- List[sql.OrderElt](), None, None
- )
-
- /** Create a condition to test if this OPTIONAL was matched (called
- * _DISJOINT_ as OPTIONAL behaves pretty much like a disjunction).
- */
- val nestedCond = sql.RelationalExpressionNull(sql.PrimaryExpressionAttr(
- sql.RelVarAttr(leftJoinAlias, sql.RDB.AttrName("_DISJOINT_"))))
-
- /** Bind variables to the attributes projected from the subselect;
- * handle corefs (equivalence with earlier bindings).
- */
- val outerState2 =
- nestedVars.foldLeft(
- R2RState(initState.joins,
- initState.varmap,
- Set[sql.Expression]()))((myState, v) =>
- subselectVars(myState, sparql.VarAssignable(v), leftJoinAlias, nestedCond,
- initState.varmap, nestedState.varmap, true))
-
- /** The final state includes the subselect as a join, the variables bound
- * to subselect projection, and no new expresssions. The expressions
- * derived from corefs are conditions for the LEFT OUTER JOIN.
- */
- val join = sql.LeftOuterJoin(sql.AliasedResource(sql.Subselect(subselect), leftJoinAlias),
- outerState2.exprs.size match {
- case 0 =>
- sql.RelationalExpressionEq(sql.PrimaryExpressionTyped(sql.RDB.Datatype.INTEGER,sql.Name("1")),
- sql.PrimaryExpressionTyped(sql.RDB.Datatype.INTEGER,sql.Name("1")))
- /** Require corefs unless we have a leading OPTIONAL. */
- // if (...)
- // else
- // error ("Nested GP has no variables shared with its context; cowaredly refusing to join ON 1.")
- case 1 => outerState2.exprs.toList(0)
- case _ => sql.ExprConjunction(outerState2.exprs)
- }
- )
- val exprs =
- if (negate) {
- initState.exprs + sql.RelationalExpressionNull(sql.PrimaryExpressionAttr(sql.RelVarAttr(leftJoinAlias, sql.RDB.AttrName("_DISJOINT_"))))
- } else initState.exprs
- R2RState(initState.joins + join, outerState2.varmap, exprs)
- }
-
- /**
- * Recursively add the joins, variable mappings and constraints for an SQL query implementing a graph pattern.
- * @param db database description.
- * @param state initial set of joins, variable mappings and constraints.
- * @param gp the SPARQL GraphPattern to represent as SQL.
- * @param enforceForeignKeys if true, SPARQL triple patterns corresponding to foreign keys, e.g. <code>?who :hasParent ?parent</code>, generate a join on the referenced table.
- * @return a new set of joins, variable mappings and constraints.
- * Per <a href="http://www.w3.org/TR/rdf-sparql-query/#sparqlQuery">definition of SPARQL Query</a>, SPARQL Graph Patterns are (Basic Graph Pattern, Join, LeftJoin, Filter, Union, Graph).
- * mapGraphPattern maps each of these to an SQL abstract query (which can then be serialized as SQL and executed).
- *
- */
- def mapGraphPattern(db:sql.RDB.Database, state:R2RState, gp:sparql.GraphPattern, enforceForeignKeys:Boolean):R2RState = {
- gp match {
-
- /** <a href="http://www.w3.org/TR/rdf-sparql-query/#defn_PatternInstanceMapping">Basic Graph Pattern Matching</a>()
- * @param triplepatterns set of triple patterns. Premise: all triple patterns must resolve against the direct graph.
- * As { TP1, TP2 } == Join({ TP1 }, { TP2 }), we can view the bindOnPredicate function as partitioning triple patterns by the relvar they match.
- * This isn't observable in the SQL query as all the triple patterns in all the conjunctions contribute to the same query.
- */
- case sparql.TriplesBlock(triplepatterns) => {
- /** Examine each triple, updating the compilation state. */
- val state2 = triplepatterns.foldLeft(state)((incState,s) => bindOnPredicate(db, incState, s, enforceForeignKeys))
-
- /** NULLs in the database result in no triple in the Direct Graph.
- * Enforce this by requiring that the SQL expression to which any SPARQL variable (Assignable) is bound is NOT NULL.
- */
- val nullExprs = gp.findAssignables.foldLeft(Set[sql.Expression]())((s, vvar) => {
- if (varToAttributeDisjoints(state2.varmap, vvar).size == 0)
- /** Create a NOT NULL expression for each fully bound variable. */
- s ++ Set(sql.RelationalExpressionNotNull(sql.PrimaryExpressionAttr(varToAttribute(state2.varmap, vvar))))
- else
- /** Variables in a partial binding can be NULL so the aren't added to the null expressions. */
- s
- })
- R2RState(state2.joins, state2.varmap, state2.exprs ++ nullExprs)
- }
-
- /** <a href="http://www.w3.org/TR/rdf-sparql-query/#defn_evalJoin">Join</a>(P1, P2)
- * Since Join is association, we handle this as an n-ary join.
- * @param conjoints list of graph patterns to join.
- */
- case sparql.TableConjunction(conjoints) => {
- conjoints.foldLeft(state)((incState,s) => mapGraphPattern(db, incState, s, enforceForeignKeys))
- }
-
- /** <a href="http://www.w3.org/TR/rdf-sparql-query/#defn_evalLeftJoin">LeftJoin</a>(P1, P2, F)
- * The parser providing the SPARQL abstract query turns LeftJoin(P1, P2, F) into Join(P1, Optional(P2)), or Join(P1, Optional(Filter(P2, F))) if there is a FILTER.
- * @param gp2 nested graph pattern (Ω in algebra)
- */
- case sparql.OptionalGraphPattern(gp2) => {
- /* state_postLeadingTable: create an initial table if the first conjoint is optional.
- * e.g. ... FROM (SELECT 1 AS _EMPTY_) AS _EMPTY_ LEFT OUTER JOIN ...
- */
- val state_postLeadingTable =
- if (state.joins.size == 0)
- /**
- * Leading optionals (ASK WHERE { OPTIONAL { ... } ... }) in SPARQL don't have a counterpart in SQL.
- * We emulate leading optionals with a leading SQL subselect which projects one solution with no selected attributes.
- */
- R2RState(state.joins + sql.InnerJoin(sql.AliasedResource(sql.Subselect(
- sql.Select(
- false,
- sql.Projection(Set(sql.ProjectAttribute(sql.PrimaryExpressionTyped(sql.RDB.Datatype.INTEGER,sql.Name("1")),
- sql.AttrAlias(sql.Name("_EMPTY_"))))),
- sql.TableList(util.AddOrderedSet()),
- None, List[sql.OrderElt](), None, None
- )), sql.RelVar(sql.Name("_EMPTY_"))), None), state.varmap, state.exprs)
- else
- state
- /** Create an OUTER JOIN for the nested graph pattern. */
- synthesizeOuterJoin(state_postLeadingTable, gp2, false, db, enforceForeignKeys)
- }
-
- /** <a href="http://www.w3.org/TR/rdf-sparql-query/#defn_algFilter">Filter</a>(expr, Ω)
- * @param gp2 nested graph pattern (Ω in algebra)
- * @param expr boolean SPARQL expression (expr in algebra)
- */
- case sparql.TableFilter(gp2:sparql.GraphPattern, expr:sparql.Expression) => {
- /** Calculate state for gp2. */
- val state2 = mapGraphPattern(db, state, gp2, enforceForeignKeys)
-
- /** Add constraints for all the FILTERS */
- val filterExprs:Set[sql.RelationalExpression] =
- expr.conjuncts.toSet map ((x:sparql.PrimaryExpression) => filter2expr(state2.varmap, x))
-
- R2RState(state2.joins, state2.varmap, state2.exprs ++ filterExprs)
- }
-
- /** <a href="http://www.w3.org/TR/rdf-sparql-query/#defn_evalUnion">Union</a>(P1, P2)
- * Since Disjunction is associative, we handle this as an n-ary disjunction.
- * @param disjoinits list of graph patterns to concatenate.
- */
- case sparql.TableDisjunction(disjoints) => {
- /**
- * SPARQL UNIONs are realized as SQL subselects.
- * Set up initial state for this subselect.
- */
- val unionAlias = sql.RelVar(sql.Name("G_union" + state.joins.size)) // invent a unique name for this union.
- val emptyState = R2RState( // create an empty state.
- util.AddOrderedSet[sql.Join](),
- Map[sparql.Assignable, SQL2RDFValueMapper](),
- Set[sql.Expression]()
- )
- val unionVars = disjoints.foldLeft(Set[sparql.Var]())((mySet,disjoint) =>
- mySet ++ disjoint.findVars).toList // all variables nested in the disjoints.
-
- /**
- * Map the list of disjoints to a list of nested R2RStates, nested variable lists, and unique SQL constants identifying that disjoint.
- * Non-Functional var <code>number</code> is used for projecting unique
- * constants to indicate which disjoint produced a tuple.
- */
- var number = 0
- val nestedStates = disjoints.map(disjoint => {
- val disjointState = mapGraphPattern(db, emptyState, disjoint, enforceForeignKeys)
- val disjointVars = disjoint.findVars
- val uniqueConst = sql.PrimaryExpressionTyped(sql.RDB.Datatype.INTEGER,sql.Name("" + number))
- number = number + 1 // non-functional, but clearer than wrapping as a parameter in a foldLeft
- (disjointState, disjointVars, uniqueConst)
- })
-
- /**
- * Map the list of nested R2RStates to a set of subselects.
- * <code>uniqueConst</code> is used for projecting a value
- * to indicate which disjoint produced a tuple.
- */
- val subselects = nestedStates.foldLeft(Set[sql.Select]())((subselects, state) => {
- val (disjointState, disjointVars, uniqueConst) = state
- /**
- * Select a constant as _DISJOINT_ so later constraints can be
- * sensitive to whether a variable was bound.
- * This matters for OPTIONALs and, here, assymetric UNIONs. Given:
- * Join( Union( BGP( ?x :p1 ?v1 ),
- * BGP( ?x :p2 ?v1 . ?x :p2 ?v2 ) ),
- * BGP( ?y :p3 ?v2 ) )
- * coreference constraints against ?v2 should only be enforced for
- * tuples from the right side of this union.
- */
- val pathNo = sql.ProjectAttribute(uniqueConst,
- sql.AttrAlias(sql.Name("_DISJOINT_")))
-
- val attrlist:Set[sql.ProjectAttribute] = unionVars.foldLeft(Set(pathNo))((attrs, v) => {
- val attrOrNull = if (disjointState.varmap.contains(sparql.VarAssignable(v))) varToAttribute(disjointState.varmap, sparql.VarAssignable(v)) else sql.ConstNULL()
- attrs ++ Set(sql.ProjectAttribute(attrOrNull, sql.AttrAlias(attrAliasNameFromVar(sparql.VarAssignable(v)))))
- })
-
- val subselect = sql.Select(
- false,
- sql.Projection(attrlist),
- sql.TableList(disjointState.joins),
- disjointState.exprs.size match {
- case 0 => None
- case 1 => Some(disjointState.exprs.toList(0))
- case _ => Some(sql.ExprConjunction(disjointState.exprs))
- }, List[sql.OrderElt](), None, None
- )
- subselects + subselect
- })
-
- /**
- * Connect the variables projected from the nested selects into the outer variable bindings and constraints.
- * <code>state2</code> will have no additional tables in the TableList.
- * <code>uniqueConst</code> is used this time to constraint coreferences between the
- * subselects and the outer context.
- */
- val state2 = nestedStates.foldLeft(state)((outerState, state) => {
- val (disjointState, disjointVars, uniqueConst) = state
-
- /** Create a condition to test if this disjoint was matched. */
- val disjointCond = sql.RelationalExpressionNe(sql.PrimaryExpressionAttr(sql.RelVarAttr(unionAlias, sql.RDB.AttrName("_DISJOINT_"))),
- uniqueConst)
- val outerState2 = disjointVars.foldLeft(outerState)((myState, v) =>
- subselectVars(myState, sparql.VarAssignable(v), unionAlias, disjointCond, outerState.varmap, disjointState.varmap, false))
- number = number + 1 // non-functional, but clearer than wrapping as a parameter in a foldLeft
- outerState2
- })
- val subselect = sql.Subselect(sql.Union(subselects))
- R2RState(state.joins + sql.InnerJoin(sql.AliasedResource(subselect,unionAlias), None), state2.varmap, state2.exprs)
- }
-
- /** <a href="http://www.w3.org/TR/rdf-sparql-query/#defn_evalGraph">Graph</a>(IRI, P)
- * I don't know what the parser did with the IRI, but we don't know what to do with GRAPHs anyways.
- * @param gp2 nested graph pattern (Ω in algebra)
- */
- case sparql.GraphGraphPattern(gp2) => error("no code to handle GraphGraphPatterns (" + gp2 + ")")
-
- /** Minus is from SPARQL 1.1 (in progress). This doesn't need documentation now.
- * @param gp2 the graph pattern to subtract.
- */
- case sparql.MinusGraphPattern(gp2) => {
- if (state.joins.size == 0) state
- else synthesizeOuterJoin(state, gp2, true, db, enforceForeignKeys)
- }
- }
- }
-
- /**
- * Default interface for SparqlToSql.
- * @param db database description.
- * @param sparquery SPARQL compile tree.
- * @param stem stem URI for all generated RDF URIs.
- * @param enforceForeignKeys if true, SPARQL triple patterns corresponding to foreign keys, e.g. ?who :hasParent ?parent , generate a join on the referenced table.
- * @param concat if true, keys will produce SQL functions to generate a URI, e.g. SELECT CONCAT(stemURI, table, "/", pk, ".", R_who.pk) AS who
- * @return an SQL query corresponding to sparquery
- */
- def apply (db:sql.RDB.Database, sparquery:sparql.Select, stem:StemURI, enforceForeignKeys:Boolean, concat:Boolean) : (sql.Select, Map[sparql.Assignable, SQL2RDFValueMapper]) = {
-
- /** Create an object to hold our compilation state. */
- val initState = R2RState(
- util.AddOrderedSet[sql.Join](),
- Map[sparql.Assignable, SQL2RDFValueMapper](),
- Set[sql.Expression]()
- )
-
- /**
- * Generate a new state with the joins, mappings to sql expressions, and
- * constraints implicit in the SPARQL WHERE pattern.
- */
- val r2rState = mapGraphPattern(db, initState, sparquery.gp, enforceForeignKeys)
-
- /**
- * Select the attributes corresponding to the variables
- * in the SPARQL SELECT.
- */
- val attrlist:Set[sql.ProjectAttribute] =
- // This foldLeft could be a map, if i could coerce to a set afterwards.
- sparquery.attrs.attributelist.foldLeft(Set[sql.ProjectAttribute]())((attrs, v) => {
- val exp =
- if (concat)
- // generate CONCAT expression for keys.
- varToExpr(r2rState.varmap, sparql.VarAssignable(v), stem)
- else
- varToAttribute(r2rState.varmap, sparql.VarAssignable(v))
- /** Projection alias. */
- val as = sql.AttrAlias(attrAliasNameFromVar(sparql.VarAssignable(v)))
- attrs + sql.ProjectAttribute(exp , as)
- })
-
- /** Construct the generated query as an abstract syntax. */
- val select = sql.Select(
- sparquery.distinct,
- sql.Projection(attrlist),
- sql.TableList(r2rState.joins),
- r2rState.exprs.size match {
- case 0 => None
- case 1 => Some(r2rState.exprs.toList(0))
- case _ => Some(sql.ExprConjunction(r2rState.exprs))
- },
- sparquery.order.map((elt:sparql.OrderElt) => {
- sql.OrderElt(elt.desc, xxx(r2rState.varmap, elt.expr))
- }), sparquery.offset, sparquery.limit
- )
- // println("r2rState.varmap: " + r2rState.varmap)
- // println("select.expression: " + select.expression)
- (select.makePretty, r2rState.varmap)
- }
-
-/*
- * vvv CLEAN THIS UP!!! vvv
- */
-
- def assignable2expr999(varmap:Map[sparql.Assignable, SQL2RDFValueMapper], rTerm:sparql.Term):sql.Expression = { // :sparql.Var
- val r:sql.PrimaryExpression = rTerm match {
- case sparql.TermUri(u) => error("not implemented: translating RDF URI to SQL: " + u) // :sparql.Uri
- case sparql.TermVar(v) => sql.PrimaryExpressionAttr(varToAttribute(varmap, sparql.VarAssignable(v)))
- case sparql.TermBNode(b) => sql.PrimaryExpressionAttr(varToAttribute(varmap, sparql.BNodeAssignable(b)))
- case sparql.TermLit(sparql.Literal(rdf.RDFLiteral(lit,rdf.Datatype(dt)))) =>
- sql.PrimaryExpressionTyped({
- dt.toString match {
- case "http://www.w3.org/2001/XMLSchema#string" => sql.RDB.Datatype.STRING
- case "http://www.w3.org/2001/XMLSchema#integer" => sql.RDB.Datatype.INTEGER
- case "http://www.w3.org/2001/XMLSchema#date" => sql.RDB.Datatype.DATE
- case _ => error("unable to translate to RDF literal SQL: \"" + lit + "\"^^<" + dt + ">")
- }
- }, lit)
- }
- r
- }
-
- def xxx(varmap:Map[sparql.Assignable, SQL2RDFValueMapper], from:sparql.Expression) : sql.Expression = {
- val l = from.conjuncts.map((conj) => {
- conj match {
- case sparql.SparqlTermExpression(sparql.TermVar(v:sparql.Var)) =>
- assignable2expr999(varmap, sparql.TermVar(v))
- case sparql.SparqlTermExpression(sparql.TermBNode(b:sparql.BNode)) =>
- assignable2expr999(varmap, sparql.TermBNode(b))
- case sparql.SparqlTermExpression(sparql.TermLit(l)) =>
- assignable2expr999(varmap, sparql.TermLit(l))
- case sparql.SparqlTermExpression(sparql.TermUri(u:sparql.Uri)) =>
- assignable2expr999(varmap, sparql.TermUri(u))
- case e:sparql.PrimaryExpression => yyy(varmap, e)
- }})
- if (l.size == 1)
- l(0)
- else
- sql.ExprConjunction(l.toSet)
- }
-
- def yyy(varmap:Map[sparql.Assignable, SQL2RDFValueMapper], from:sparql.PrimaryExpression) : sql.Expression = {
- from match {
- case sparql.SparqlTermExpression(term) => assignable2expr999(varmap, term)
- case sparql.PrimaryExpressionEq(l, r) => sql.RelationalExpressionEq(yyy(varmap, l), yyy(varmap, r))
- case sparql.PrimaryExpressionGt(l, r) => sql.RelationalExpressionEq(yyy(varmap, l), yyy(varmap, r))
- case sparql.PrimaryExpressionLt(l, r) => sql.RelationalExpressionEq(yyy(varmap, l), yyy(varmap, r))
- }
- }
-
- /**
- * junk that should be elsewhere
- */
-
- implicit def relname2relresource (rn:sql.RDB.RelName) : sql.RelationResource = sql.RelationResource(rn)
-
-}
-
-/**
- * Support functions to inject SparqlToSql results into an XML Results Set.
- * @example:
- * <pre>
- * val xmlres:String =
- * head(List[String]("emp", "name")) +
- * startresult +
- * binding("emp", "253", rdfmap, stem) +
- * binding("name", "Bob", rdfmap, stem) +
- * endresult +
- * startresult +
- * binding("emp", "258", rdfmap, stem) +
- * // employee 258 has no name attribute so omit this binding
- * endresult +
- * foot
- * </pre>
- *
- * @see {@link w3c.sw.sparql2sql.SparqlToSql}
- * @see {@link http://www.w3.org/TR/2008/REC-rdf-sparql-XMLres-20080115/ XML Results Format}
- */
-object SqlToXMLRes {
-
- /**
- * Create a SPARQL Results format header and begin the body (results).
- * @param vars list of variable names to insert into the header
- */
- def head (vars:List[String]) : String = {
- "<?xml version=\"1.0\"?>\n<sparql xmlns=\"http://www.w3.org/2005/sparql-results#\">\n <head>\n" +
- vars.map(varname => " <variable name=\"" + varname + "\"/>\n").mkString +
- " </head>\n\n <results>\n"
- }
-
- /**
- * Open a result element
- */
- def startresult () : String = {
- " <result> \n"
- }
-
- /**
- * Create a binding value appropriate for <code>name</code>'s datatype.
- * @param name name of bound variable.
- * @param value lexical value of bound variable, may need normalization from e.g. SQL.
- * @param varmap mapping of sparql variables to datatypes, emitted by SparqlToSql._2
- * @param stem stem URI for all generated RDF URIs.
- */
- def binding (name:String, value:String, varmap:Map[sparql.Assignable, SparqlToSql.SQL2RDFValueMapper], stem:StemURI) : String = {
- def getattr (b:SparqlToSql.FullOrPartialBinding) : sql.RDB.AttrName = {
- b match {
- case SparqlToSql.FullBinding(sql.RelVarAttr(_, attr)) =>
- attr
- case SparqlToSql.PartialBinding(binders) => {
- val SparqlToSql.BindingConstraint(expr, sql.RelVarAttr(rv, attr)) = binders.toList(0)
- attr
- }
- }
- }
- val t:String = varmap(sparql.VarAssignable(sparql.Var(name))) match {
- case SparqlToSql.RDFNoder(rel, b) => "<uri>" + stem.s + rel + "/" + getattr(b) + "." + value + "#record</uri>"
- case SparqlToSql.RDFBNoder(rel, b) => "<bnode>bnode_" + rel + "/" + getattr(b) + "." + value + "</bnode>"
- case SparqlToSql.DateMapper(_) => "<literal datatype=\"http://www.w3.org/2001/XMLSchema#date\">" + value + "</literal>"
- case SparqlToSql.IntMapper(_) => "<literal datatype=\"http://www.w3.org/2001/XMLSchema#integer\">" + value + "</literal>"
- case SparqlToSql.StringMapper(_) => "<literal datatype=\"http://www.w3.org/2001/XMLSchema#string\">" + value + "</literal>"
- }
- " <binding name=\"" + name + "\">\n " + t + "\n </binding>\n"
- }
-
- /**
- * Close a result element.
- */
- def endresult () : String = {
- " </result>\n"
- }
-
- /**
- * End SPARQL Results document.
- */
- def foot () : String = {
- " </results>\n</sparql>\n"
- }
-
-}
-