+ commented subselectVars
authorEric Prud'hommeaux <eric@w3.org>
Tue, 18 May 2010 09:48:46 +0200
changeset 192 2773c2007113
parent 191 b402d3939ecb
child 193 4e5e879223fe
+ commented subselectVars
src/main/scala/SparqlToSql.scala
--- a/src/main/scala/SparqlToSql.scala	Thu May 13 14:36:55 2010 -0400
+++ b/src/main/scala/SparqlToSql.scala	Tue May 18 09:48:46 2010 +0200
@@ -79,28 +79,23 @@
    * @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) =>
-	binding
+	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))
     }
   }
-  def toConstraint999(constrainMe:sql.RelVarAttr, against:FullOrPartialBinding):sql.Expression = {
-    against match {
-      case FullBinding(relvarattr) =>
-	sql.RelationalExpressionEq(sql.PrimaryExpressionAttr(constrainMe),
-				   sql.PrimaryExpressionAttr(relvarattr))
-      case PartialBinding(binders) =>
-	sql.ExprConjunction({binders.map(b => {
-	  val BindingConstraint(expr, relvarattr) = b
-	  sql.ExprDisjunction(Set(expr,
-				  sql.RelationalExpressionEq(sql.PrimaryExpressionAttr(constrainMe),
-							     sql.PrimaryExpressionAttr(relvarattr))))})})
-    }
-  }
 
   /**
    * SQL terms representing SPARQL variables and bnodes.
@@ -557,6 +552,15 @@
     
   }
 
+  /**
+   * 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
@@ -575,68 +579,112 @@
     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 = {
-    val (lTerm:sparql.Term, rTerm:sparql.Term, sqlexpr) = f match { // sqlexpr::((sql.RelVarAttr,sql.PrimaryExpressionAttr)=>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(_,_))
     }
-// sql.RelationalExpressionEq(_,_) === (x,y) => PrymaryExpressionEq(x,y)
+
     lTerm match {
-      // does not handle FILTER (<x> = ?v)
+      /** 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)
+      /** 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)
+      /** 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)
     }
   }
 
-  /* subselectVars: Promote variables in OPTIONAL or UNION subselects to the
-   * outer varmap/expressions.
-   * <outerState> could be <myState> -- spliting roles could make proofs easier?
+  /**
+   * Promote a variable in an OPTIONAL or UNION subselect to the outer
+   * varmap/expressions. 
    */
-  def subselectVars(myState:R2RState, v:sparql.Assignable, optionalAlias:sql.RelVar,
+  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.Attribute(attrAliasNameFromVar(v)))
-    if (myState.varmap.contains(v)) {
-      /* The variable has already been bound. */
-      val newMap:Map[sparql.Assignable, SQL2RDFValueMapper] = if (varToAttribute(myState.varmap, v) == varAliasAttr) {
-	/* Same var was bound earlier. */
-	Map(v -> { myState.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
-	Map()
+    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) {
-	  /* Constraint against binding from earlier GP. */
+	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)
-	    // (union0._DISJOINT_ != 0 OR union0.x=union1.x) AND (union1._DISJOINT_ != 2 OR union0.x=union1.x)
+	  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)
-		else Set(sql.ExprConjunction(Set(d, optionalCond)), constraint)
+		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) Set(constraint)
-		  else Set(sql.ExprDisjunction(Set(optionalCond, constraint)))
-		}
-	} else {
-	  Set()
+	  } 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(myState.joins, myState.varmap ++ newMap, myState.exprs ++ newConstraints)
+
+      R2RState(startState.joins, startState.varmap ++ newMap, startState.exprs ++ newConstraints)
     } else {
-      /* This variable is new to the outer context. */
+      /**
+       * 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)
@@ -645,12 +693,13 @@
 	case RDFNoder(rel, _)  => RDFNoder(rel, p)
 	case RDFBNoder(rel, _) => RDFBNoder(rel, p)
       }
-      R2RState(myState.joins, myState.varmap + (v -> mapper), myState.exprs)
+
+      R2RState(startState.joins, startState.varmap + (v -> mapper), startState.exprs)
     }
   }
 
   def synthesizeOuterJoin(initState:R2RState, gp:sparql.GraphPattern, negate:Boolean, db:sql.DatabaseDesc, enforceForeignKeys:Boolean):R2RState = {
-    /* SPARQL OPTIONALs are treated as SQL subselects.
+    /** 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))
@@ -661,7 +710,7 @@
       Set[sql.Expression]()
     )
 
-    /* Create the select for the nested graph pattern.
+    /** Create the select for the nested graph pattern.
      */
     val optionalState = mapGraphPattern(db, emptyState, gp, enforceForeignKeys)
     val optionalVars = gp.findVars
@@ -681,13 +730,13 @@
       }
     )
 
-    /* Create a condition to test if this OPTIONAL was matched (called
+    /** 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.RelVarAttr(leftJoinAlias, sql.Attribute(sql.Name("_DISJOINT_")))))
 
-    /* Bind variables to the attributes projected from the subselect; handle
+    /** Bind variables to the attributes projected from the subselect; handle
      * corefs (equivalence with earlier bindings).
      */
     val outerState2 =
@@ -698,7 +747,7 @@
 		   subselectVars(myState, sparql.VarAssignable(v), leftJoinAlias, optionalCond,
 				 initState.varmap, optionalState.varmap, true))
 
-    /* The final state includes the subselect as a join, the variables bound
+    /** 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.
      */