--- a/src/main/scala/RDB2RDFMain.scala Fri Jan 29 16:09:58 2010 -0500
+++ b/src/main/scala/RDB2RDFMain.scala Sat Jan 30 13:43:11 2010 -0500
@@ -435,6 +435,79 @@
}
}
+ def synthesizeOuterJoin(initState:R2RState, gp:sparql.GraphPattern, negate:Boolean, db:sql.DatabaseDesc, enforceForeignKeys:Boolean):R2RState = {
+ /* SPARQL OPTIONALs are treated as SQL subselects.
+ * Set up initial state for this subselect.
+ */
+ val leftJoinAlias = sql.RelAlias(sql.Name("G_opt" + initState.joins.size))
+ val initDisjoints:Set[sql.Select] = Set()
+ val emptyState = R2RState(
+ util.AddOrderedSet[sql.Join](),
+ Map[sparql.Var, SQL2RDFValueMapper](),
+ Set[sql.Expression]()
+ )
+
+ /* Create the select for the nested graph pattern.
+ */
+ val optionalState = mapGraphPattern(db, emptyState, gp, enforceForeignKeys)
+ val optionalVars = findVars(gp)
+ val disjointNo = sql.NamedAttribute(sql.PrimaryExpressionTyped(sql.Datatype.INTEGER,sql.Name("" + initState.joins.size)),
+ sql.AttrAlias(sql.Name("_DISJOINT_")))
+ val leftJoinVars = findVars(gp).toList
+ val attrlist:Set[sql.NamedAttribute] = leftJoinVars.foldLeft(Set(disjointNo))((attrs, v) =>
+ attrs ++ Set(sql.NamedAttribute(varToAttribute(optionalState.varmap, v), sql.AttrAlias(attrAliasNameFromVar(v))))
+ )
+ val subselect = sql.Select(
+ sql.AttributeList(attrlist),
+ sql.TableList(optionalState.joins),
+ optionalState.exprs.size match {
+ case 0 => None
+ case 1 => Some(optionalState.exprs.toList(0))
+ case _ => Some(sql.ExprConjunction(optionalState.exprs))
+ }
+ )
+
+ /* Create a condition to test if this OPTIONAL was matched (called
+ * _DISJOINT_ as OPTIONAL behaves pretty much like a disjunction).
+ */
+ val optionalCond = sql.RelationalExpressionNull(sql.PrimaryExpressionAttr(
+ sql.RelAliasAttribute(leftJoinAlias, sql.Attribute(sql.Name("_DISJOINT_")))))
+
+ /* Bind variables to the attributes projected from the subselect; handle
+ * corefs (equivalence with earlier bindings).
+ */
+ val outerState2 =
+ optionalVars.foldLeft(
+ R2RState(initState.joins,
+ initState.varmap,
+ Set[sql.Expression]()))((myState, v) =>
+ subselectVars(myState, v, leftJoinAlias, optionalCond,
+ initState.varmap, optionalState.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.Datatype.INTEGER,sql.Name("1")),
+ sql.PrimaryExpressionTyped(sql.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.RelAliasAttribute(leftJoinAlias, sql.Attribute(sql.Name("_DISJOINT_")))))
+ } else initState.exprs
+ R2RState(initState.joins + join, outerState2.varmap, exprs)
+ }
+
def mapGraphPattern(db:sql.DatabaseDesc, state:R2RState, gp:sparql.GraphPattern, enforceForeignKeys:Boolean):R2RState = {
gp match {
case sparql.TableFilter(gp2:sparql.GraphPattern, expr:sparql.Expression) => {
@@ -538,73 +611,11 @@
)), sql.RelAlias(sql.Name("_EMPTY_"))), None), state.varmap, state.exprs)
else
state
-
- /* SPARQL OPTIONALs are treated as SQL subselects.
- * Set up initial state for this subselect.
- */
- val leftJoinAlias = sql.RelAlias(sql.Name("G_opt" + state_postLeadingTable.joins.size))
- val initDisjoints:Set[sql.Select] = Set()
- val emptyState = R2RState(
- util.AddOrderedSet[sql.Join](),
- Map[sparql.Var, SQL2RDFValueMapper](),
- Set[sql.Expression]()
- )
-
- /* Create the select for the nested graph pattern.
- */
- val optionalState = mapGraphPattern(db, emptyState, gp, enforceForeignKeys)
- val optionalVars = findVars(gp)
- val disjointNo = sql.NamedAttribute(sql.PrimaryExpressionTyped(sql.Datatype.INTEGER,sql.Name("" + state_postLeadingTable.joins.size)),
- sql.AttrAlias(sql.Name("_DISJOINT_")))
- val leftJoinVars = findVars(gp).toList
- val attrlist:Set[sql.NamedAttribute] = leftJoinVars.foldLeft(Set(disjointNo))((attrs, v) =>
- attrs ++ Set(sql.NamedAttribute(varToAttribute(optionalState.varmap, v), sql.AttrAlias(attrAliasNameFromVar(v))))
- )
- val subselect = sql.Select(
- sql.AttributeList(attrlist),
- sql.TableList(optionalState.joins),
- optionalState.exprs.size match {
- case 0 => None
- case 1 => Some(optionalState.exprs.toList(0))
- case _ => Some(sql.ExprConjunction(optionalState.exprs))
- }
- )
-
- /* Create a condition to test if this OPTIONAL was matched (called
- * _DISJOINT_ as OPTIONAL behaves pretty much like a disjunction).
- */
- val optionalCond = sql.RelationalExpressionNull(sql.PrimaryExpressionAttr(
- sql.RelAliasAttribute(leftJoinAlias, sql.Attribute(sql.Name("_DISJOINT_")))))
-
- /* Bind variables to the attributes projected from the subselect; handle
- * corefs (equivalence with earlier bindings).
- */
- val outerState2 =
- optionalVars.foldLeft(
- R2RState(state_postLeadingTable.joins,
- state_postLeadingTable.varmap,
- Set[sql.Expression]()))((myState, v) =>
- subselectVars(myState, v, leftJoinAlias, optionalCond,
- state_postLeadingTable.varmap, optionalState.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 =>
- /* Require corefs unless we have a leading OPTIONAL. */
- if (state.joins.size == 0)
- sql.RelationalExpressionEq(sql.PrimaryExpressionTyped(sql.Datatype.INTEGER,sql.Name("1")),
- sql.PrimaryExpressionTyped(sql.Datatype.INTEGER,sql.Name("1")))
- 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)
- }
- )
- R2RState(state_postLeadingTable.joins + join, outerState2.varmap, state.exprs)
+ synthesizeOuterJoin(state_postLeadingTable, gp, false, db, enforceForeignKeys)
+ }
+ case sparql.MinusGraphPattern(gp) => {
+ if (state.joins.size == 0) state
+ else synthesizeOuterJoin(state, gp, true, db, enforceForeignKeys)
}
case sparql.GraphGraphPattern(gp) => error("no code to handle GraphGraphPatterns (" + gp + ")")
}
@@ -632,7 +643,7 @@
))
/* Construct the generated query as an abstract syntax. */
- sql.Select(
+ val select = sql.Select(
sql.AttributeList(attrlist),
sql.TableList(r2rState.joins),
r2rState.exprs.size match {
@@ -640,7 +651,10 @@
case 1 => Some(r2rState.exprs.toList(0))
case _ => Some(sql.ExprConjunction(r2rState.exprs))
}
- ).makePretty()
+ )
+ // println("r2rState.varmap: " + r2rState.varmap)
+ // println("select.expression: " + select.expression)
+ select.makePretty()
}
}
--- a/src/main/scala/SPARQL.scala Fri Jan 29 16:09:58 2010 -0500
+++ b/src/main/scala/SPARQL.scala Sat Jan 30 13:43:11 2010 -0500
@@ -24,6 +24,7 @@
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 MinusGraphPattern(gp:GraphPattern) extends GraphPattern
case class GraphGraphPattern(gp:GraphPattern) extends GraphPattern
case class TriplePattern(s:S, p:P, o:O)
@@ -161,6 +162,7 @@
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) }
--- a/src/test/scala/RDB2RDFTest.scala Fri Jan 29 16:09:58 2010 -0500
+++ b/src/test/scala/RDB2RDFTest.scala Sat Jan 30 13:43:11 2010 -0500
@@ -862,7 +862,7 @@
Attribute("ingredient") -> Value(Datatype.INTEGER)))
))
- test("swobjects/tests/healthCare/lists-notBound/db.rq") {
+ test("swobjects/tests/healthCare/lists-notBound/db.rq AS OPTIONAL") {
val sparqlParser = Sparql()
val sparqlSelect = sparqlParser.parseAll(sparqlParser.select, """
PREFIX Person: <http://hospital.example/DB/Person#>
@@ -938,4 +938,81 @@
"""
}
+ test("swobjects/tests/healthCare/lists-notBound/db.rq") {
+ val sparqlParser = Sparql()
+ val sparqlSelect = sparqlParser.parseAll(sparqlParser.select, """
+PREFIX Person: <http://hospital.example/DB/Person#>
+PREFIX Sex_DE: <http://hospital.example/DB/Sex_DE#>
+PREFIX Item_Medication: <http://hospital.example/DB/Item_Medication#>
+PREFIX Medication: <http://hospital.example/DB/Medication#>
+PREFIX Medication_DE: <http://hospital.example/DB/Medication_DE#>
+PREFIX NDCcodes: <http://hospital.example/DB/NDCcodes#>
+PREFIX xsd : <http://www.w3.org/2001/XMLSchema#>
+
+SELECT ?patient
+ WHERE {
+ ?patient Person:MiddleName ?middleName .
+ ?patient Person:DateOfBirth ?dob .
+ ?patient Person:SexDE ?sexEntry .
+ ?sexEntry Sex_DE:EntryName ?sex .
+
+ ?indicItem Item_Medication:PatientID ?patient .
+ ?indicItem Item_Medication:PerformedDTTM ?indicDate .
+ ?indicItem Item_Medication:EntryName ?takes .
+ ?indicMed Medication:ItemID ?indicItem .
+ ?indicMed Medication:MedDictDE ?indicDE .
+ ?indicDE Medication_DE:NDC ?indicNDC .
+ ?indicCode NDCcodes:NDC ?indicNDC .
+ ?indicCode NDCcodes:ingredient "6809"^^xsd:integer
+
+ MINUS {
+ ?disqualItem Item_Medication:PatientID ?patient .
+ ?disqualItem Item_Medication:PerformedDTTM ?disqualDate .
+ ?disqualItem Item_Medication:EntryName ?takes .
+ ?disqualMed Medication:ItemID ?disqualItem .
+ ?disqualMed Medication:MedDictDE ?disqualDE .
+ ?disqualDE Medication_DE:NDC ?disqualNDC .
+ ?disqualCode NDCcodes:NDC ?disqualNDC .
+ ?disqualCode NDCcodes:ingredient "11289"^^xsd:integer
+ }
+ }
+""").get
+ val sqlParser = Sql()
+ val parsed = sqlParser.parseAll(sqlParser.select, """
+SELECT R_patient.ID AS patient
+ FROM Person AS R_patient
+ INNER JOIN Sex_DE AS R_sexEntry ON R_sexEntry.ID=R_patient.SexDE
+ INNER JOIN Item_Medication AS R_indicItem ON R_indicItem.PatientID=R_patient.ID
+ INNER JOIN Medication AS R_indicMed ON R_indicMed.ItemID=R_indicItem.ID
+ INNER JOIN Medication_DE AS R_indicDE ON R_indicDE.ID=R_indicMed.MedDictDE
+ INNER JOIN NDCcodes AS R_indicCode ON R_indicCode.NDC=R_indicDE.NDC
+ LEFT OUTER JOIN (
+ SELECT R_disqualCode.ID AS disqualCode, R_disqualMed.MedDictDE AS disqualDE,
+ R_disqualItem.PerformedDTTM AS disqualDate, R_disqualItem.ID AS disqualItem,
+ R_disqualMed.ID AS disqualMed, R_disqualDE.NDC AS disqualNDC,
+ R_disqualItem.PatientID AS patient, R_disqualItem.EntryName AS takes, 6 AS _DISJOINT_
+ FROM Item_Medication AS R_disqualItem
+ INNER JOIN Medication AS R_disqualMed ON R_disqualMed.ItemID=R_disqualItem.ID
+ INNER JOIN Medication_DE AS R_disqualDE ON R_disqualDE.ID=R_disqualMed.MedDictDE
+ INNER JOIN NDCcodes AS R_disqualCode ON R_disqualCode.NDC=R_disqualDE.NDC
+ WHERE R_disqualCode.ingredient=11289
+ AND R_disqualItem.EntryName IS NOT NULL
+ AND R_disqualItem.PatientID IS NOT NULL
+ AND R_disqualItem.PerformedDTTM IS NOT NULL
+ ) AS G_opt6 ON G_opt6.patient=R_patient.ID
+ AND G_opt6.takes=R_indicItem.EntryName
+ WHERE G_opt6._DISJOINT_ IS NULL
+ AND R_indicCode.ingredient=6809
+ AND R_indicItem.EntryName IS NOT NULL
+ AND R_indicItem.PerformedDTTM IS NOT NULL
+ AND R_patient.DateOfBirth IS NOT NULL
+ AND R_patient.MiddleName IS NOT NULL
+ AND R_sexEntry.EntryName IS NOT NULL
+""").get
+ val generated = RDB2RDF(hosp1, sparqlSelect, StemURI("http://hospital.example/DB/"), false, false)
+ assert(generated === parsed)
+ val output = """
+"""
+ }
+
}
--- a/src/test/scala/SparqlTest.scala Fri Jan 29 16:09:58 2010 -0500
+++ b/src/test/scala/SparqlTest.scala Sat Jan 30 13:43:11 2010 -0500
@@ -315,6 +315,31 @@
assert(tps === a.parseAll(a.select, e).get)
}
+ test("parse an minus") {
+ val a = Sparql()
+ val e = """
+SELECT ?x { { ?x <http://hr.example/DB/Employee#manager> ?y} MINUS { ?x <http://hr.example/DB/Employee#manager> ?y} }
+"""
+ val tps =
+ Select(
+ SparqlAttributeList(List(Var("x"))),
+ TableConjunction(List(
+ TriplesBlock(
+ List(
+ TriplePattern(
+ SVar(Var("x")),
+ PUri(Stem("http://hr.example/DB"),Rel("Employee"),Attr("manager")),
+ OVar(Var("y"))))),
+ MinusGraphPattern(
+ TriplesBlock(
+ List(
+ TriplePattern(
+ SVar(Var("x")),
+ PUri(Stem("http://hr.example/DB"),Rel("Employee"),Attr("manager")),
+ OVar(Var("y")))))))))
+ assert(tps === a.parseAll(a.select, e).get)
+ }
+
test("parse disj1") {
val a = Sparql()
val e = """