~ generalized _DISJOINT_ partial-bindings
authorEric Prud'hommeaux <bertails@w3.org>
Tue, 05 Jan 2010 12:05:10 -0500
changeset 113 0d6109b5c890
parent 112 42f8fa2dd03d
child 114 84de5e1ea7b4
~ generalized _DISJOINT_ partial-bindings
src/main/scala/RDB2RDFMain.scala
src/test/scala/RDB2RDFTest.scala
--- a/src/main/scala/RDB2RDFMain.scala	Tue Jan 05 12:02:26 2010 -0500
+++ b/src/main/scala/RDB2RDFMain.scala	Tue Jan 05 12:05:10 2010 -0500
@@ -18,12 +18,12 @@
 object RDB2RDF {
   case class R2RState(joins:util.AddOrderedSet[sql.Join], varmap:Map[sparql.Var, SQL2RDFValueMapper], exprs:Set[sql.Expression])
 
-  sealed abstract class SQL2RDFValueMapper(relaliasattr:sql.RelAliasAttribute, disjoints:Set[sql.RelationalExpressionNe])
-  case class IntMapper(relaliasattr:sql.RelAliasAttribute, disjoints:Set[sql.RelationalExpressionNe]) extends SQL2RDFValueMapper(relaliasattr, disjoints)
-  case class StringMapper(relaliasattr:sql.RelAliasAttribute, disjoints:Set[sql.RelationalExpressionNe]) extends SQL2RDFValueMapper(relaliasattr, disjoints)
-  case class DateMapper(relaliasattr:sql.RelAliasAttribute, disjoints:Set[sql.RelationalExpressionNe]) extends SQL2RDFValueMapper(relaliasattr, disjoints)
-  case class RDFNoder(relation:sql.Relation, relaliasattr:sql.RelAliasAttribute, disjoints:Set[sql.RelationalExpressionNe]) extends SQL2RDFValueMapper(relaliasattr, disjoints)
-  case class RDFBNoder(relation:sql.Relation, relaliasattr:sql.RelAliasAttribute, disjoints:Set[sql.RelationalExpressionNe]) extends SQL2RDFValueMapper(relaliasattr, disjoints)
+  sealed abstract class SQL2RDFValueMapper(relaliasattr:sql.RelAliasAttribute, disjoints:Set[sql.RelationalExpression])
+  case class IntMapper(relaliasattr:sql.RelAliasAttribute, disjoints:Set[sql.RelationalExpression]) extends SQL2RDFValueMapper(relaliasattr, disjoints)
+  case class StringMapper(relaliasattr:sql.RelAliasAttribute, disjoints:Set[sql.RelationalExpression]) extends SQL2RDFValueMapper(relaliasattr, disjoints)
+  case class DateMapper(relaliasattr:sql.RelAliasAttribute, disjoints:Set[sql.RelationalExpression]) extends SQL2RDFValueMapper(relaliasattr, disjoints)
+  case class RDFNoder(relation:sql.Relation, relaliasattr:sql.RelAliasAttribute, disjoints:Set[sql.RelationalExpression]) extends SQL2RDFValueMapper(relaliasattr, disjoints)
+  case class RDFBNoder(relation:sql.Relation, relaliasattr:sql.RelAliasAttribute, disjoints:Set[sql.RelationalExpression]) extends SQL2RDFValueMapper(relaliasattr, disjoints)
 
   def relAliasFromS(s:sparql.S):sql.RelAlias = {
     s match {
@@ -92,23 +92,18 @@
     **                                      Employee      _emp.manager       
     */
     val reldesc = db.relationdescs(rel)
-    if (state.varmap.contains(v)) {
-      /* The variable has already been bound. */
-      if (varToAttribute(state.varmap, v) == constrainMe)
-	/* Don't bother stipulating that foo.bar=foo.bar . */
-	state // !!! what about disjoints?
-      else {
-	/* Constraint against the initial binding for this variable. */
-	val constraint = sql.RelationalExpressionEq(sql.PrimaryExpressionAttr(varToAttribute(state.varmap, v)), sql.PrimaryExpressionAttr(constrainMe))
-	R2RState(state.joins, state.varmap, 
-		 if (varToAttributeDisjoints(state.varmap, v).size > 0) {
-		   state.exprs ++ {varToAttributeDisjoints(state.varmap, v) map ((d) => sql.ExprDisjunction(Set(d, constraint)))}
-		 } else
-		   state.exprs + constraint
-	       )
-      }
+    if (state.varmap.contains(v) && varToAttribute(state.varmap, v) != constrainMe) {
+      /* The variable has already been bound to another attribute. */
+      /* Constraint against the initial binding for this variable. */
+      val constraint = sql.RelationalExpressionEq(sql.PrimaryExpressionAttr(varToAttribute(state.varmap, v)), sql.PrimaryExpressionAttr(constrainMe))
+      R2RState(state.joins, state.varmap, 
+	       if (varToAttributeDisjoints(state.varmap, v).size > 0) {
+		 state.exprs ++ {varToAttributeDisjoints(state.varmap, v) map ((d) => sql.ExprDisjunction(Set(d, constraint)))}
+	       } else
+		 state.exprs + constraint
+	     )
     } else {
-      /* This is a new variable. */
+      /* This is a new variable or a replacement bindinig for an old variable. */
       val binding = reldesc.primarykey match {
 	case Some(sql.Attribute(constrainMe.attribute.n)) => 
 	  RDFNoder(rel, constrainMe, Set())
@@ -241,7 +236,7 @@
     }
   }
 
-  def varToAttributeDisjoints(varmap:Map[sparql.Var, SQL2RDFValueMapper], vvar:sparql.Var):Set[sql.RelationalExpressionNe] = {
+  def varToAttributeDisjoints(varmap:Map[sparql.Var, SQL2RDFValueMapper], vvar:sparql.Var):Set[sql.RelationalExpression] = {
     varmap(vvar) match {
       case IntMapper(relalias, disjoints) => disjoints
       case StringMapper(relalias, disjoints) => disjoints
@@ -338,8 +333,6 @@
 	  val disjointState = mapGraphPattern(db, emptyState, disjoint, pk, enforceForeignKeys)
 	  val disjointVars = findVars(disjoint)
 	  val disjointNo = sql.NamedAttribute(sql.PrimaryExpressionTyped(sql.Datatype.INTEGER,sql.Name("" + no)), sql.AttrAlias(sql.Name("_DISJOINT_")))
-	  val disjointNoAliasAttr = sql.RelAliasAttribute(unionAlias, sql.Attribute(sql.Name("_DISJOINT_")))
-	  val disjointCond = sql.RelationalExpressionNe(sql.PrimaryExpressionAttr(disjointNoAliasAttr), sql.PrimaryExpressionTyped(sql.Datatype.INTEGER,sql.Name("" + no)))
 
 	  val attrlist:Set[sql.NamedAttribute] = unionVars.foldLeft(Set(disjointNo))((attrs, v) => {
 	    val attrOrNull = if (disjointState.varmap.contains(v)) varToAttribute(disjointState.varmap, v) else sql.ConstNULL()
@@ -355,6 +348,8 @@
 	      case _ => Some(sql.ExprConjunction(disjointState.exprs))
 	    }
 	  )
+	  val disjointNoAliasAttr = sql.RelAliasAttribute(unionAlias, sql.Attribute(sql.Name("_DISJOINT_")))
+	  val disjointCond = sql.RelationalExpressionNe(sql.PrimaryExpressionAttr(disjointNoAliasAttr), sql.PrimaryExpressionTyped(sql.Datatype.INTEGER,sql.Name("" + no)))
 	  val outerState2 = disjointVars.foldLeft(outerState)((myState, v) => {
 	    val varAliasAttr = sql.RelAliasAttribute(unionAlias, sql.Attribute(sql.Name("A_" + v.s)))
 	    if (myState.varmap.contains(v)) {
@@ -412,9 +407,10 @@
       	)
       	val optionalState = mapGraphPattern(db, emptyState, gp, pk, enforceForeignKeys)
       	val optionalVars = findVars(gp)
+	val disjointNo = sql.NamedAttribute(sql.PrimaryExpressionTyped(sql.Datatype.INTEGER,sql.Name("" + state.joins.size)), sql.AttrAlias(sql.Name("_DISJOINT_")))
 
       	val leftJoinVars = findVars(gp).toList
-      	val attrlist:Set[sql.NamedAttribute] = leftJoinVars.foldLeft(Set[sql.NamedAttribute]())((attrs, v) =>
+      	val attrlist:Set[sql.NamedAttribute] = leftJoinVars.foldLeft(Set(disjointNo))((attrs, v) =>
       	  attrs ++ Set(sql.NamedAttribute(varToAttribute(optionalState.varmap, v), sql.AttrAlias(sql.Name("A_" + v.s))))
       	)
 
@@ -428,37 +424,47 @@
       	  }
       	)
 
-      	val outerState2 = optionalVars.foldLeft(R2RState(state.joins, state.varmap, Set[sql.Expression]()))((myState, v) => {
-      	  val varAliasAttr = sql.RelAliasAttribute(leftJoinAlias, sql.Attribute(sql.Name("A_" + v.s)))
-      	  if (myState.varmap.contains(v)) {
-      	    /* The variable has already been bound. */
-      	    val newMap:Map[sparql.Var, SQL2RDFValueMapper] = if (varToAttribute(myState.varmap, v) == varAliasAttr) {
-      	      /* Same var was already bound. */
-	      error("Variable " + v + " already bound to " + varAliasAttr)
-      	    } else
-      	      Map()
-      	    val newConstraints = {
-      	      /* Constraint against binding from earlier GP. */
-      	      val constraint = sql.RelationalExpressionEq(sql.PrimaryExpressionAttr(varToAttribute(state.varmap, v)), sql.PrimaryExpressionAttr(varAliasAttr))
-      	      if (varToAttributeDisjoints(state.varmap, v).size > 0)
-      		// (leftJoin0._DISJOINT_ != 0 AND leftJoin1._DISJOINT_ != 2) OR leftJoin0.x=leftJoin1.x
-      		varToAttributeDisjoints(state.varmap, v) map ((d) => sql.ExprDisjunction(Set(d, constraint)))
-      	      else
-      		Set(constraint)
-      	    }
-      	    R2RState(myState.joins, myState.varmap ++ newMap, myState.exprs ++ newConstraints)
-      	  } else {
-      	    /* This variable is new to the outer context. */
-      	    val mapper:SQL2RDFValueMapper = optionalState.varmap(v) match {
-      	      case IntMapper(_, _)      => IntMapper(varAliasAttr, Set())
-      	      case StringMapper(_, _)   => StringMapper(varAliasAttr, Set())
-      	      case DateMapper(_, _)   => DateMapper(varAliasAttr, Set())
-      	      case RDFNoder(rel, _, _)  => RDFNoder(rel, varAliasAttr, Set())
-      	      case RDFBNoder(rel, _, _) => RDFBNoder(rel, varAliasAttr, Set())
-      	    }
-      	    R2RState(myState.joins, myState.varmap + (v -> mapper), myState.exprs)
-      	  }
-      	})
+	val optionalNoAliasAttr = sql.RelAliasAttribute(leftJoinAlias, sql.Attribute(sql.Name("_DISJOINT_")))
+	val optionalCond = sql.RelationalExpressionNull(sql.PrimaryExpressionAttr(optionalNoAliasAttr))
+	val outerState2 = optionalVars.foldLeft(R2RState(state.joins, state.varmap, Set[sql.Expression]()))((myState, v) => {
+	  val varAliasAttr = sql.RelAliasAttribute(leftJoinAlias, sql.Attribute(sql.Name("A_" + v.s)))
+	  if (myState.varmap.contains(v)) {
+	    /* The variable has already been bound. */
+	    val newMap:Map[sparql.Var, SQL2RDFValueMapper] = if (varToAttribute(myState.varmap, v) == varAliasAttr) {
+	      /* Same var was bound in an earlier optional. */
+	      val oldDisjoints = varToAttributeDisjoints(myState.varmap, v)
+	      // myState
+	      Map(v -> { optionalState.varmap(v) match {
+		case IntMapper(_, _)      => IntMapper(varAliasAttr, oldDisjoints + optionalCond)
+		case StringMapper(_, _)   => StringMapper(varAliasAttr, oldDisjoints + optionalCond)
+		case DateMapper(_, _)     => DateMapper(varAliasAttr, oldDisjoints + optionalCond)
+		case RDFNoder(rel, _, _)  => RDFNoder(rel, varAliasAttr, oldDisjoints + optionalCond)
+		case RDFBNoder(rel, _, _) => RDFBNoder(rel, varAliasAttr, oldDisjoints + optionalCond)
+	      } } )
+	    } else
+	      Map()
+	    val newConstraints = {
+	      /* Constraint against binding from earlier GP. */
+	      val constraint = sql.RelationalExpressionEq(sql.PrimaryExpressionAttr(varToAttribute(state.varmap, v)), sql.PrimaryExpressionAttr(varAliasAttr))
+	      if (varToAttributeDisjoints(state.varmap, v).size > 0)
+		// (leftJoin0._DISJOINT_ IS NOT NULL AND leftJoin1._DISJOINT_ IS NOT NULL) OR leftJoin0.x=leftJoin1.x
+		varToAttributeDisjoints(state.varmap, v) map ((d) => sql.ExprDisjunction(Set(d, constraint)))
+	      else
+		Set(constraint)
+	    }
+	    R2RState(myState.joins, myState.varmap ++ newMap, myState.exprs ++ newConstraints)
+	  } else {
+	    /* This variable is new to the outer context. */
+	    val mapper:SQL2RDFValueMapper = optionalState.varmap(v) match {
+	      case IntMapper(_, _)      => IntMapper(varAliasAttr, Set(optionalCond))
+	      case StringMapper(_, _)   => StringMapper(varAliasAttr, Set(optionalCond))
+	      case DateMapper(_, _)   => DateMapper(varAliasAttr, Set(optionalCond))
+	      case RDFNoder(rel, _, _)  => RDFNoder(rel, varAliasAttr, Set(optionalCond))
+	      case RDFBNoder(rel, _, _) => RDFBNoder(rel, varAliasAttr, Set(optionalCond))
+	    }
+	    R2RState(myState.joins, myState.varmap + (v -> mapper), myState.exprs)
+	  }
+	})
       	val join = sql.LeftOuterJoin(sql.AliasedResource(sql.Subselect(subselect), leftJoinAlias), 
       	  outerState2.exprs.size match {
       	    case 0 => error ("Nested GP has no variables shared with its context; cowaredly refusing to join ON 1.")
--- a/src/test/scala/RDB2RDFTest.scala	Tue Jan 05 12:02:26 2010 -0500
+++ b/src/test/scala/RDB2RDFTest.scala	Tue Jan 05 12:05:10 2010 -0500
@@ -481,7 +481,7 @@
 SELECT R_emp.lastName AS A_empName, R_opt1.A_managName AS A_managName, R_opt1.A_grandManagName AS A_grandManagName
        FROM Employee AS R_emp
             LEFT OUTER JOIN (
-    SELECT R_grandManager.lastName AS A_grandManagName, R_manager.lastName AS A_managName, R_mang.manages AS A_emp,
+    SELECT 1 AS _DISJOINT_, R_grandManager.lastName AS A_grandManagName, R_manager.lastName AS A_managName, R_mang.manages AS A_emp,
        R_grandMang.manager AS A_grandManager,
        R_mang.id AS A_mang,
        R_mang.manager AS A_manager,
@@ -526,12 +526,12 @@
 SELECT R_emp.lastName AS A_empName, R_opt1.A_managName AS A_managName, R_opt1.A_grandManagName AS A_grandManagName
        FROM Employee AS R_emp
             LEFT OUTER JOIN (
-    SELECT R_opt2.A_grandManagName AS A_grandManagName, R_manager.lastName AS A_managName, R_mang.manages AS A_emp, R_mang.manager AS A_manager,
-           R_mang.id AS A_mang, R_opt2.A_grandMang AS A_grandMang, R_opt2.A_grandManager AS A_grandManager
+    SELECT 1 AS _DISJOINT_, R_opt2.A_grandManagName AS A_grandManagName, R_manager.lastName AS A_managName, R_mang.manages AS A_emp,
+           R_mang.manager AS A_manager, R_mang.id AS A_mang, R_opt2.A_grandMang AS A_grandMang, R_opt2.A_grandManager AS A_grandManager
            FROM Manage AS R_mang
                 INNER JOIN Employee AS R_manager
                 LEFT OUTER JOIN (
-        SELECT R_grandManager.lastName AS A_grandManagName, R_grandMang.manages AS A_manager,
+        SELECT 2 AS _DISJOINT_, R_grandManager.lastName AS A_grandManagName, R_grandMang.manages AS A_manager,
                R_grandMang.id AS A_grandMang, R_grandMang.manager AS A_grandManager
                FROM Manage AS R_grandMang
                     INNER JOIN Employee AS R_grandManager
@@ -545,4 +545,61 @@
 """).get
     assert(RDB2RDF(db2, sparqlSelect, StemURI("http://hr.example/DB/"), PrimaryKey(Attribute(Name("id"))), false, false) === sqlSelect)
   }
+
+  test("transform equivOpt1") {
+    val sparqlParser = Sparql()
+    val sparqlSelect = sparqlParser.parseAll(sparqlParser.select, """
+PREFIX emplP: <http://hr.example/DB/Employee#>
+
+SELECT ?emp1Name ?emp2Name ?emp3Name
+ WHERE { ?emp1     emplP:lastName   ?emp1Name
+         OPTIONAL { ?emp1     emplP:birthday   ?birthday }
+         ?emp2     emplP:lastName   ?emp2Name
+         OPTIONAL { ?emp2     emplP:birthday   ?birthday }
+         ?emp3     emplP:lastName   ?emp3Name .
+         ?emp3     emplP:birthday   ?birthday .
+         ?emp4     emplP:lastName   ?emp4Name .
+         ?emp4     emplP:birthday   ?birthday
+         FILTER ( ?emp1Name < ?emp2Name && ?emp2Name < ?emp3Name && ?emp3Name < ?emp4Name) }
+""").get
+    val sqlParser = Sql()
+    val sqlSelect = sqlParser.parseAll(sqlParser.select, """
+SELECT R_emp1.lastName AS A_emp1Name, R_emp2.lastName AS A_emp2Name, R_emp3.lastName AS A_emp3Name
+  FROM Employee AS R_emp1
+       LEFT OUTER JOIN (
+			SELECT 1 AS _DISJOINT_,
+			       R_emp1.id AS A_emp1,
+			       R_emp1.birthday AS A_birthday
+			  FROM Employee AS R_emp1
+			 WHERE (R_emp1.id IS NOT NULL)
+			       AND (R_emp1.birthday IS NOT NULL)
+			) AS R_opt1 ON R_emp1.id=R_opt1.A_emp1
+       INNER JOIN Employee AS R_emp2 ON (R_emp1.lastName<R_emp2.lastName)
+				    AND (R_emp2.id IS NOT NULL)
+				    AND (R_emp2.lastName IS NOT NULL)
+       LEFT OUTER JOIN (
+			SELECT 3 AS _DISJOINT_,
+			       R_emp2.id AS A_emp2,
+			       R_emp2.birthday AS A_birthday
+			  FROM Employee AS R_emp2
+			 WHERE (R_emp2.id IS NOT NULL)
+			       AND (R_emp2.birthday IS NOT NULL)
+			) AS R_opt3 ON (R_emp2.id=R_opt3.A_emp2)
+				   AND ((R_opt1._DISJOINT_ IS NULL) OR (R_opt1.A_birthday=R_opt3.A_birthday))
+       INNER JOIN Employee AS R_emp3 ON ((R_opt1._DISJOINT_ IS NULL) OR (R_opt1.A_birthday=R_emp3.birthday))
+				    AND (R_emp2.lastName<R_emp3.lastName)
+				    AND (R_emp3.id IS NOT NULL)
+				    AND (R_emp3.lastName IS NOT NULL)
+       INNER JOIN Employee AS R_emp4 ON (R_emp3.lastName<R_emp4.lastName)
+				    AND ((R_opt1._DISJOINT_ IS NULL) OR (R_opt1.A_birthday=R_emp4.birthday))
+				    AND (R_emp4.id IS NOT NULL)
+				    AND (R_emp4.lastName IS NOT NULL)
+ WHERE (R_emp1.id IS NOT NULL)
+   AND (R_emp1.lastName IS NOT NULL)
+   AND (R_opt1.A_birthday IS NOT NULL)
+""").get
+    assert(RDB2RDF(db2, sparqlSelect, StemURI("http://hr.example/DB/"), PrimaryKey(Attribute(Name("id"))), false, false) === sqlSelect)
+  }
+
+
 }