--- a/src/main/scala/SparqlToSql.scala Wed May 19 15:23:18 2010 +0200
+++ b/src/main/scala/SparqlToSql.scala Wed May 19 21:02:04 2010 +0200
@@ -704,6 +704,7 @@
* 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.DatabaseDesc, enforceForeignKeys:Boolean):R2RState = {
/** SPARQL OPTIONALs and UNIONs are treated as SQL subselects.
@@ -797,12 +798,18 @@
* As { TP1, TP2 } === Join({ TP1 }, { TP2 }), we triple patterns and conjunctions all contribute to the same set of SQL joins, variable bindings and constraints.
*/
case sparql.TriplesBlock(triplepatterns) => {
- /* Examine each triple, updating the compilation state. */
+ /** 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)
@@ -826,6 +833,10 @@
*/
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 table which projects one solution with no selected attributes.
+ */
R2RState(state.joins + sql.InnerJoin(sql.AliasedResource(sql.Subselect(
sql.Select(
sql.AttributeList(Set(sql.NamedAttribute(sql.PrimaryExpressionTyped(sql.Datatype.INTEGER,sql.Name("1")),
@@ -835,6 +846,7 @@
)), 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)
}
@@ -858,17 +870,17 @@
* @param disjoinits list of graph patterns to concatenate.
*/
case sparql.TableDisjunction(disjoints) => {
- /** SPARQL UNIONs are treated as SQL subselects.
+ /** 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))
- val emptyState = R2RState(
+ 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
+ mySet ++ disjoint.findVars).toList // all variables nested in the disjoints.
/** Map the disjoints to subselects.
* <no> is used for uniquely naming flags in the SELECTs used to