+ OPTIONALs (last pattern in MappingRules)
authorEric Prud'hommeaux <bertails@w3.org>
Sun, 03 Jan 2010 03:01:12 -0500
changeset 102 2bec0e8a7aec
parent 101 fb5ec48c0c74
child 103 c1d12b781d00
child 105 53c4902744a2
+ OPTIONALs (last pattern in MappingRules)
src/main/scala/RDB2RDFMain.scala
src/main/scala/SQL.scala
src/test/scala/RDB2RDFTest.scala
--- a/src/main/scala/RDB2RDFMain.scala	Sat Jan 02 18:34:46 2010 -0500
+++ b/src/main/scala/RDB2RDFMain.scala	Sun Jan 03 03:01:12 2010 -0500
@@ -216,6 +216,14 @@
 	/* Examine each triple, updating the compilation state. */
 	triplepatterns.foldLeft(Set[Var]())((x, y) => x ++ findVars(y))
 
+      case TableConjunction(list) =>
+	/* Examine each triple, updating the compilation state. */
+	list.foldLeft(Set[Var]())((x, y) => x ++ findVars(y))
+
+      case OptionalGraphPattern(gp2) =>
+	/* Examine each triple, updating the compilation state. */
+	findVars(gp2)
+
       case x => error("no code to handle " + x)
     }
   }
@@ -364,84 +372,73 @@
 	val subselect = Subselect(Union(disjoints))
 	R2RState(state.joins + InnerJoin(AliasedResource(subselect,unionAlias)), state2.varmap, state2.exprs)
       }
-      // case OptionalGraphPattern(gp) => {
-      // 	val leftJoinAlias = RelAlias(Name("R_leftJoin" + state.joins.size))
-      // 	val initDisjoints:Set[Select] = Set()
-      // 	val emptyState = R2RState(
-      // 	  Set[Join](), 
-      // 	  Map[Var, SQL2RDFValueMapper](), 
-      // 	  Set[Expression]()
-      // 	)
-      // 	val leftJoinVars = list.foldLeft(Set[Var]())((mySet,disjoint) => mySet ++ findVars(disjoint)).toList
-      // 	val (state2, disjoints, count) = list.foldLeft((state, initDisjoints, 0))((incPair,disjoint) => {
-      // 	  val (outerState, outerDisjoints, no) = incPair
-      // 	  val disjointState = mapGraphPattern(db, emptyState, disjoint, pk, enforeForeignKeys)
-      // 	  val disjointVars = findVars(disjoint)
-      // 	  val disjointNo = NamedAttribute(ConstInt("" + no), AttrAlias(Name("_DISJOINT_")))
-      // 	  val disjointNoAliasAttr = RelAliasAttribute(leftJoinAlias, Attribute(Name("_DISJOINT_")))
-      // 	  val disjointCond = RelationalExpressionNe(disjointNoAliasAttr, RValueTyped(SQLDatatype.INTEGER,Name("" + no)))
-
-      // 	  val attrlist:Set[NamedAttribute] = leftJoinVars.foldLeft(Set(disjointNo))((attrs, v) => {
-      // 	    val attrOrNull = if (disjointState.varmap.contains(v)) varToAttribute(disjointState.varmap, v) else ConstNULL()
-      // 	    attrs ++ Set(NamedAttribute(attrOrNull, AttrAlias(Name("A_" + v.s))))
-      // 	  })
+      case OptionalGraphPattern(gp) => {
+      	val leftJoinAlias = RelAlias(Name("R_opt" + state.joins.size))
+      	val initDisjoints:Set[Select] = Set()
+      	val emptyState = R2RState(
+      	  Set[Join](), 
+      	  Map[Var, SQL2RDFValueMapper](), 
+      	  Set[Expression]()
+      	)
+      	val optionalState = mapGraphPattern(db, emptyState, gp, pk, enforeForeignKeys)
+      	val optionalVars = findVars(gp)
 
-      // 	  val subselect = Select(
-      // 	    AttributeList(attrlist),
-      // 	    TableList(disjointState.joins),
-      // 	    disjointState.exprs.size match {
-      // 	      case 0 => None
-      // 	      case 1 => Some(disjointState.exprs.toList(0))
-      // 	      case _ => Some(ExprConjunction(disjointState.exprs))
-      // 	    }
-      // 	  )
-      // 	  val outerState2 = disjointVars.foldLeft(outerState)((myState, v) => {
-      // 	    val varAliasAttr = RelAliasAttribute(leftJoinAlias, Attribute(Name("A_" + v.s)))
-      // 	    if (myState.varmap.contains(v)) {
-      // 	      /* The variable has already been bound. */
-      // 	      val newMap:Map[Var, SQL2RDFValueMapper] = if (varToAttribute(myState.varmap, v) == varAliasAttr) {
-      // 		/* Same var was bound in an earlier disjoint. */
-      // 		val oldDisjoints = varToAttributeDisjoints(myState.varmap, v)
-      // 		// myState
-      // 		Map(v -> { disjointState.varmap(v) match {
-      // 		  case IntMapper(_, _)      => IntMapper(varAliasAttr, oldDisjoints + disjointCond)
-      // 		  case StringMapper(_, _)   => StringMapper(varAliasAttr, oldDisjoints + disjointCond)
-      // 		  case DateMapper(_, _)     => DateMapper(varAliasAttr, oldDisjoints + disjointCond)
-      // 		  case RDFNoder(rel, _, _)  => RDFNoder(rel, varAliasAttr, oldDisjoints + disjointCond)
-      // 		  case RDFBNoder(rel, _, _) => RDFBNoder(rel, varAliasAttr, oldDisjoints + disjointCond)
-      // 		} } )
-      // 	      } else
-      // 		Map()
-      // 	      val newConstraints =
-      // 		if (varToAttribute(outerState.varmap, v) != varAliasAttr) {
-      // 		  /* Constraint against binding from earlier GP. */
-      // 		  val constraint = RelationalExpressionEq(varToAttribute(outerState.varmap, v), RValueAttr(varAliasAttr))
-      // 		  if (varToAttributeDisjoints(outerState.varmap, v).size > 0)
-      // 		    // (leftJoin0._DISJOINT_ != 0 AND leftJoin1._DISJOINT_ != 2) OR leftJoin0.x=leftJoin1.x
-      // 		    varToAttributeDisjoints(outerState.varmap, v) map ((d) => ExprDisjunction(Set(ExprConjunction(Set(d, disjointCond)), constraint)))
-      // 		  else
-      // 		    Set(ExprDisjunction(Set(disjointCond, constraint)))
-      // 		} else {
-      // 		  Set()
-      // 		}
-      // 	      R2RState(myState.joins, myState.varmap ++ newMap, myState.exprs ++ newConstraints)
-      // 	    } else {
-      // 	      /* This variable is new to the outer context. */
-      // 	      val mapper:SQL2RDFValueMapper = disjointState.varmap(v) match {
-      // 		case IntMapper(_, _)      => IntMapper(varAliasAttr, Set(disjointCond))
-      // 		case StringMapper(_, _)   => StringMapper(varAliasAttr, Set(disjointCond))
-      // 		case DateMapper(_, _)   => DateMapper(varAliasAttr, Set(disjointCond))
-      // 		case RDFNoder(rel, _, _)  => RDFNoder(rel, varAliasAttr, Set(disjointCond))
-      // 		case RDFBNoder(rel, _, _) => RDFBNoder(rel, varAliasAttr, Set(disjointCond))
-      // 	      }
-      // 	      R2RState(myState.joins, myState.varmap + (v -> mapper), myState.exprs)
-      // 	    }
-      // 	  })
-      // 	  (outerState2, outerDisjoints ++ Set(subselect), no+1)
-      // 	})
-      // 	val subselect = Subselect(Union(disjoints))
-      // 	R2RState(state.joins + InnerJoin(AliasedResource(subselect,unionAlias)), state2.varmap, state2.exprs)
-      // }
+      	val leftJoinVars = findVars(gp).toList
+      	val attrlist:Set[NamedAttribute] = leftJoinVars.foldLeft(Set[NamedAttribute]())((attrs, v) =>
+      	  attrs ++ Set(NamedAttribute(varToAttribute(optionalState.varmap, v), AttrAlias(Name("A_" + v.s))))
+      	)
+
+      	val subselect = Select(
+      	  AttributeList(attrlist),
+      	  TableList(optionalState.joins),
+      	  optionalState.exprs.size match {
+      	    case 0 => None
+      	    case 1 => Some(optionalState.exprs.toList(0))
+      	    case _ => Some(ExprConjunction(optionalState.exprs))
+      	  }
+      	)
+
+      	val outerState2 = optionalVars.foldLeft(R2RState(state.joins, state.varmap, Set[Expression]()))((myState, v) => {
+      	  val varAliasAttr = RelAliasAttribute(leftJoinAlias, Attribute(Name("A_" + v.s)))
+      	  if (myState.varmap.contains(v)) {
+      	    /* The variable has already been bound. */
+      	    val newMap:Map[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 = RelationalExpressionEq(varToAttribute(state.varmap, v), RValueAttr(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) => ExprDisjunction(Set(d, constraint)))
+      	      else
+      		Set(constraint)
+      	    }
+	    println("" + v + " begets " + newMap + " and " + newConstraints)
+      	    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 join = LeftOuterJoin(AliasedResource(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.")
+      	    case 1 => outerState2.exprs.toList(0)
+      	    case _ => ExprConjunction(outerState2.exprs)
+      	  }
+			       )
+      	R2RState(state.joins + join, outerState2.varmap, state.exprs)
+      }
       case x => error("no code to handle " + x)
     }
   }
--- a/src/main/scala/SQL.scala	Sat Jan 02 18:34:46 2010 -0500
+++ b/src/main/scala/SQL.scala	Sun Jan 03 03:01:12 2010 -0500
@@ -75,7 +75,7 @@
   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
+  override def toString = "\n       LEFT OUTER JOIN " + res + " ON " + on
 }
 
 case class AliasedResource(rel:RelationORSubselect, as:RelAlias) {
--- a/src/test/scala/RDB2RDFTest.scala	Sat Jan 02 18:34:46 2010 -0500
+++ b/src/test/scala/RDB2RDFTest.scala	Sun Jan 03 03:01:12 2010 -0500
@@ -417,39 +417,88 @@
     assert(RDB2RDF(db2, sparqlSelect, StemURI("http://hr.example/DB/"), PrimaryKey(Attribute(Name("id"))), false) === sqlSelect)
   }
 
-//   test("transform nestOpt") {
-//     val sparqlParser = Sparql()
-//     val sparqlSelect = sparqlParser.parseAll(sparqlParser.select, """
-// PREFIX emplP: <http://hr.example/DB/Employee#>
-// PREFIX mangP: <http://hr.example/DB/Manage#>
+  test("transform optJoin1") {
+    val sparqlParser = Sparql()
+    val sparqlSelect = sparqlParser.parseAll(sparqlParser.select, """
+PREFIX emplP: <http://hr.example/DB/Employee#>
+PREFIX mangP: <http://hr.example/DB/Manage#>
 
-// SELECT ?empName ?managName ?grandManagName
-//  WHERE {          ?emp            emplP:lastName   ?empName
-//        OPTIONAL { ?mang           mangP:manages    ?emp .
-//                   ?mang           mangP:manager    ?manager .
-//                   ?manager        emplP:lastName   ?managName
-//          OPTIONAL { ?grandMang    mangP:manages    ?manager .
-//                     ?grandMang    mangP:manager    ?grandManager .
-//                     ?grandManager emplP:lastName   ?grandManagName } }
-//        }
-// """).get
-//     val sqlParser = Sql()
-//     val sqlSelect = sqlParser.parseAll(sqlParser.select, """
-// 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_opt1.A_grandManagName AS A_grandManagName, R_manager.lastName AS A_managName, R_mang.manages AS R_emp, R_mang.manager AS A_manager
-//            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
-//                FROM Manage AS R_grandMang
-//                     INNER JOIN Employee AS R_grandManager
-//                  ) AS R_opt1 ON R_opt1.manager=R_mang.manager
-//           WHERE R_manager.id=R_mang.manager AND R_grandManager.id=R_grandMang.manager
-//              ) AS R_opt1 ON opt1.emp=R_emp.id
-//  WHERE R_emp.lastName IS NOT NULL
-// """).get
-//     assert(RDB2RDF(db2, sparqlSelect, StemURI("http://hr.example/DB/"), PrimaryKey(Attribute(Name("id"))), false) === sqlSelect)
-//   }
+SELECT ?empName ?managName ?grandManagName
+ WHERE {          ?emp            emplP:lastName   ?empName
+         OPTIONAL { ?mang         mangP:manages    ?emp .
+                    ?mang         mangP:manager    ?manager .
+                    ?manager      emplP:lastName   ?managName .
+                    ?grandMang    mangP:manages    ?manager .
+                    ?grandMang    mangP:manager    ?grandManager .
+                    ?grandManager emplP:lastName   ?grandManagName } }
+""").get
+    val sqlParser = Sql()
+    val sqlSelect = sqlParser.parseAll(sqlParser.select, """
+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,
+       R_grandMang.manager AS A_grandManager,
+       R_mang.id AS A_mang,
+       R_mang.manager AS A_manager,
+       R_grandMang.id AS A_grandMang
+           FROM Manage AS R_mang
+                INNER JOIN Employee AS R_manager
+                INNER JOIN Manage AS R_grandMang
+                INNER JOIN Employee AS R_grandManager
+     WHERE R_mang.manager=R_manager.id AND R_mang.manager=R_grandMang.manages AND R_grandMang.manager=R_grandManager.id
+       AND (R_grandMang.manager IS NOT NULL)
+       AND (R_mang.manages IS NOT NULL)
+       AND (R_mang.id IS NOT NULL)
+       AND (R_grandManager.lastName IS NOT NULL)
+       AND (R_mang.manager IS NOT NULL)
+       AND (R_manager.lastName IS NOT NULL)
+       AND (R_grandMang.id IS NOT NULL)
+             ) AS R_opt1 ON R_emp.id=R_opt1.A_emp
+ WHERE R_emp.lastName IS NOT NULL
+   AND R_emp.id IS NOT NULL
+""").get
+    assert(RDB2RDF(db2, sparqlSelect, StemURI("http://hr.example/DB/"), PrimaryKey(Attribute(Name("id"))), false) === sqlSelect)
+  }
+
+  test("transform nestOpt") {
+    val sparqlParser = Sparql()
+    val sparqlSelect = sparqlParser.parseAll(sparqlParser.select, """
+PREFIX emplP: <http://hr.example/DB/Employee#>
+PREFIX mangP: <http://hr.example/DB/Manage#>
+
+SELECT ?empName ?managName ?grandManagName
+ WHERE {          ?emp            emplP:lastName   ?empName
+       OPTIONAL { ?mang           mangP:manages    ?emp .
+                  ?mang           mangP:manager    ?manager .
+                  ?manager        emplP:lastName   ?managName
+         OPTIONAL { ?grandMang    mangP:manages    ?manager .
+                    ?grandMang    mangP:manager    ?grandManager .
+                    ?grandManager emplP:lastName   ?grandManagName } }
+       }
+""").get
+    val sqlParser = Sql()
+    val sqlSelect = sqlParser.parseAll(sqlParser.select, """
+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
+           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,
+               R_grandMang.id AS A_grandMang, R_grandMang.manager AS A_grandManager
+               FROM Manage AS R_grandMang
+                    INNER JOIN Employee AS R_grandManager
+         WHERE R_grandMang.manager=R_grandManager.id AND R_grandMang.manages IS NOT NULL AND R_grandMang.manager IS NOT NULL
+           AND R_grandManager.lastName IS NOT NULL AND R_grandMang.id IS NOT NULL
+                 ) AS R_opt2 ON R_mang.manager=R_opt2.A_manager
+          WHERE R_mang.manager=R_manager.id
+            AND R_mang.manages IS NOT NULL AND R_mang.id IS NOT NULL AND R_mang.manager IS NOT NULL AND R_manager.lastName IS NOT NULL
+             ) AS R_opt1 ON R_emp.id=R_opt1.A_emp
+ WHERE R_emp.lastName IS NOT NULL AND R_emp.id IS NOT NULL
+""").get
+    assert(RDB2RDF(db2, sparqlSelect, StemURI("http://hr.example/DB/"), PrimaryKey(Attribute(Name("id"))), false) === sqlSelect)
+  }
 }